大厂面试官必问的mysql锁机制(十分钟搞懂MYSQL锁机制)
大厂面试官必问的mysql锁机制(十分钟搞懂MYSQL锁机制)表级锁在下列几种情况下比行级锁更优越当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。而意向锁的作用就是当一个事务在需要获取资源锁定的时候 如果遇到自己需要的资源已经被排他锁占用的时候 该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。所以,可以说Innodb的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),我们可以通过以下表格
什么是锁数据库的锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性,这样才能保证在高并发的情况下,访问数据库的时候,数据不会出现问题。
数据库并发(锁)策略并发控制一般采用三种方法,分别是乐观锁和悲观锁以及时间戳。
乐观锁- 乐观锁认为一个用户读数据的时候,别人不会去写自己所读的数据;悲观锁就刚好相反,觉得自己读数据库的时候,别人可能刚好在写自己刚读的数据,其实就是持一种比较保守的态度;时间戳就是不加锁,通过时间戳来控制并发出现的问题。
- 悲观锁就是在读取数据的时候,为了不让别人修改自己读取的数据,就会先对自己读取的数据加锁,只有自己把数据读完了,才允许别人修改那部分数据,或者反过来说,就是自己修改某条数据的时候,不允许别人读取该数据,只有等自己的整个事务提交了,才释放自己加上的锁,才允许其他用户访问那部分数据。
- 时间戳就是在数据库表中单独加一列时间戳,比如“TimeStamp”,每次读出来的时候,把该字段也读出来,当写回去的时候,把该字段加1,提交之前 ,跟数据库的该字段比较一次,如果比数据库的值大的话,就允许保存,否则不允许保存,这种处理方法虽然不使用数据库系统提供的锁机制,但是这种方法可以大大提高数据库处理的并发量。 以上悲观锁所说的加“锁”,其实分为几种锁,分别是:排它锁(写锁)和共享锁(读锁)。
行级锁,页级锁和表级锁。下面我们先分析一下Mysql这三种锁定的特点和各自的优劣所在。
行级锁(row-level)- 行级锁最大的特点就是锁对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁颗粒度最小的。由于锁颗粒度很小,所以发生锁资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
- 虽然能够在并发处理能力上面有较大的优势,但是行级定也因此带来了不少弊端。由于锁资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁也最容易发生死锁
- 和行级锁相反,表级别的锁是MySQL各存储引擎中最大颗粒度的锁机制。该锁机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
- 当然,锁颗粒度大所带来最大的负面影响就是出现锁资源争用的概率也会最高,致使并发度大打折扣。
- 页级锁是MySQL中比较独特的一种锁级别,在其他数据库管理软件中也并不是太常见。页级锁的特点是锁颗粒度介于行级锁定与表级锁之间,所以获取锁所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁和行级锁一样,会发生死锁。
- 在数据库实现资源锁定的过程中,随着锁资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂。不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。
- 在MySQL数据库中,使用表级锁的主要是MyISAM,Memory,CSV等一些非事务性存储引擎,而使用行级锁的主要是InnoDB存储引擎和NDBCluster存储引擎,页级锁主要是BerkeleyDB存储引擎的锁方式。
表级锁和行级锁可以进一步划分为共享锁(s)和排他锁(X)。
共享锁(s)共享锁(Share Locks,简记为S)又被称为读锁,其他用户可以并发读取数据,但任何事务都不能获取数据上的排他锁,直到已释放所有共享锁。若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
排他锁(X)排它锁((Exclusive lock 简记为X锁))又称为写锁,若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。
两者之间的区别- 共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不 能加排他锁。获取共享锁的事务只能读数据,不能修改数据。
- 排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获取排他锁的事务既能读数据,又能修改数据。
InnoDB在锁机制实现过程中为了让行级锁和表级锁共存,Innodb使用了意向锁(表级锁)的概念,也就有了意向共享锁和意向排他锁这两种。
意向共享锁(IS)- 表示事务准备给数据行记入共享锁,事务在一个数据行加共享锁前必须先取得该表的IS锁。
- 表示事务准备给数据行加入排他锁,事务在一个数据行加排他锁前必须先取得该表的IX锁。
当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。而意向锁的作用就是当一个事务在需要获取资源锁定的时候 如果遇到自己需要的资源已经被排他锁占用的时候 该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。所以,可以说Innodb的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),我们可以通过以下表格来总结上面这四种所的共存逻辑关系:
- 对索引项加锁,而非数据本身,如果innodb表中没有索引,那么会自动创建一个隐藏的聚集索引,锁住的就是这个聚集索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚集索引后面加X锁,这个类似于表锁,但原理上和表锁应该是完全不同的。
- 对索引项之间的“间隙”加锁,锁定记录的范围(对第一条记录前的间隙或最后一条将记录后的间隙加锁),不包含索引项本身。其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行。
- 锁定索引项本身和索引范围。即Record Lock和Gap Lock的结合。可解决幻读问题。
- 注意:InnoDB使用Next-key Lock执行行级锁定。当它搜索或扫描表的索引之时,它对遇到的索引记录设置共享或独占锁定。因此,行级锁定事实上是索引记录锁定。InnoDB对索引记录设置的锁定也影响索引记录之前的“间隙”。如果一个用户对一个索引上的记录R有共享或独占的锁定,另一个用户不能紧接在R之前以索引的顺序插入一个新索引记录。这个间隙的锁定被执行来防止所谓的“幽灵问题”。可以用next-key锁定在你的应用程序上实现一个唯一性检查:如果你以共享模式读数据,并且没有看到你将要插入的行的重复,则你可以安全地插入你的行,并且知道在读过程中对你的行的继承者设置的Next-key锁定与此同时阻止任何人对你的行插入一个重复。因此,Next-key锁定允许你锁住在你的表中并不存在的一些东西。
表级锁在下列几种情况下比行级锁更优越
- 很多操作都是读表。
- 在严格条件的索引上读取和更新,当更新或者删除可以用单独的索引来读取得到时: UPDATE tbl_name SET column=value WHERE unique_key_col=key_value; DELETEF ROMt bl_name WHERE unique_key_col=key_value;
- SELECT和INSERT语句并发的执行,但是只有很少的UPDATE和DELETE语句。
- 很多的扫描表和对全表的GROUPBY操作,但是没有任何写表。
- 当在许多线程中访问不同的行时只存在少量锁定冲突。
- 回滚时只有少量的更改。
- 可以长时间锁定单一的行。
多数情况下,可以认为如果一个资源被锁定,它总会在以后某个时间被释放。而死锁发生在当多个进程访问同一数据库时,其中每个进程拥有的锁都是其他进程所需的,由此造成每个进程都无法继续下去。简单的说,进程 A 等待进程 B 释放他的资源,B 又等待 A 释放他的资源,这样就互相等待就形成死锁。 虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件:
- 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合 {P0,P1,P2,•••,Pn} 中的 P0 正在等待一个 P1 占用的资源;P1 正在等待 P2 占用的资源,……,Pn 正在等待已被 P0 占用的资源
- 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
- 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率。
- 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率。
- 设置获得锁的超时时间。通过超时,至少保证最差情况下,可以有退出的口子。
- MyISAM类型的表可以考虑通过改成Innodb类型的表来减少锁冲突。
- 根据应用的情况,尝试横向拆分成多个表或者改成MyISAM分区对减少锁冲突也会有一定的帮助。
- 分离能并行的操作 MyISAM存储引擎有一个控制是否打开ConcurrentInsert.
- 合理利用读写优先级 MySQL的表级锁定对于读和写是有不同优先级设定的,默认情况下是写优先级要大于读优先级。
- 首先要确认,在对表获取行锁的时候,要尽量的使用索引检索记录,如果没有使用索引访问,那么即便你只是要更新其中的一行记录,也是全表锁定的。要确保sql是使用索引来访问记录的,必要的时候,请使用explain检查sql的执行计划,判断是否按照预期使用了索引。
- 由于mysql的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是相同的索引键,是会被加锁的。
- 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,当表有主键或者唯一索引的时候,不是必须使用主键或者唯一索引锁定记录,其他普通索引同样可以用来检索记录,并只锁定符合条件的行。
- 确定更合理的事务大小,小事务更少地倾向于冲突。
- 如果你正使用锁定读,(SELECT...FORUPDATE或...LOCKINSHAREMODE),试着用更低的隔离级别,比如READCOMMITTED。
- 以固定的顺序访问你的表和行。则事务形成良好定义的查询并且没有死锁。