一、mysql锁机制概述
二、InnoDB的锁机制
2.1、并发事务的锁形式
针对并发事务问题,数据库重点是实现事务隔离,基本方式:
(A)、不加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(snapshot),利用这个快照提供一定级别的一致性读取。-----【快照读,普通的select查询才用到快照读,才会用到MVCC的读写非阻塞】。
简单的select操作,属于快照读,不加锁 ------快照读
select * from table where ?
(B)、在读取数据前,加锁,阻止其他事务对数据进行修改;—【当前读,又分为共享锁和排它锁两种,特别的select、insert、delete、update会对行进行锁定,查询的肯定是行记录的最新版本】
特殊的读操作、插入、更新、删除操作,需要加锁------当前读
select * from table where ? lock in share mode; //共享锁【其他事务只能select操作】
【其他事务可以查询该记录,并可以对该记录加共享锁,若当前事务对该记录进行更新操作,可能会死锁】
select * from table where ? for update; //排它锁
insert into table values (…);
update table set ? where ?;
delete from table where ?;
【保证查到的数据是最新的,并保证在自己事务中,只允许自己来修改。其他事务可以查询该记录【简单的select】,但不能对该记录加共享锁或排它锁,而是等待获取锁】
2.1.1、select… for update(排它锁)
为了避免自己看到的数据并不是数据库存储的最新数据并且看到的数据只能由自己修改,需要用 for update 来限制。【保证最新数据且只能自己修改】
2.1.2、select * from table where ? lock in share mode (共享锁)
将查询到的数据加上S锁,其他事务只能对这些数据进行简单的select操作。本事务也是只读。【保证最新数据,且不允许其他事务修改,自己不一定能够修改,除非只有自己加了S锁,不然会无法提交直到超时】
2.2、行级锁
行级锁是基于索引来锁定指定行,如果查询语句没有用到索引的话,会锁定整个表的,使用表级锁。
行级锁并不是直接锁记录,而是锁索引。
如果一条 SQL 语句操作了主键索引,MySQL 就会锁定这条主键索引;如果一条SQL语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
在update、delete时,MySQL 不仅锁定 WHERE 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key lock。
2.2.1、记录锁(record lock)
单条索引记录上加锁,record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚集索引后面加X锁。
2.2.2、间隙锁(gap lock)
只有在RR或串行隔离级别以上,进行特殊select、update和delete时,除了主键索引或唯一索引外,才会取得gap lock或next-key lock。
gap lock的机制主要是解决可重复读模式下的幻读问题【防止幻读】。主要目的是防止其他事务在间隔中插入数据。
例如 select * from table1 where id > 100 for update;
对于101记录加锁,同时也对大于101的间隙加锁。
使用间隙锁目的是防止幻读。如果不使用间隙锁,有其他事务插入>100的记录,那么本事务再次执行上述语句会发生幻读。
gap lock的前置条件:
事务隔离级别为REPEATABLE-READ,innodb_locks_unsafe_for_binlog参数为0,且sql走的索引为非唯一索引。
事务隔离级别为REPEATABLE-READ,innodb_locks_unsafe_for_binlog参数为0,且sql是一个范围的当前读操作,这时即使不是非唯一索引也会加gap lock。
2.2.3、临键锁(next-key lock)
是记录锁和间隙锁的组合。
默认情况下,mysql的事务隔离级别是可重复读,并且innodb_locks_unsafe_for_binlog参数为0,这时默认采用next-key locks。
2.2.4、插入意向锁—间隙锁特例
提高并发插入性能。【备注:插入意向锁是不按照自增列方式插入的场景,区别自增锁】
专门针对数据行的insert操作,插入意向锁是间隙锁,多个事务插入相同的数据间隙时,只要不是相同的位置,都不需要进行锁等待【多个事务持有的插入意向锁锁定同一个间隙,但锁直接不会阻塞的】。
多个事务在同一区间(gap)插入位置不同的多条数据时,事务之间不需要互相等待。对于某个区间,两个事务都需要在这个区间插入不同行的记录,这时候两个事务其实都获取到这个插入意向锁,只要不是相同行位置,就不会阻塞互斥。
2.2.5、隐式锁—record lock类别
Innodb 实现了一个延迟加锁的机制,在可能发生冲突时才加锁,来减少加锁的数量。
聚簇索引的每行记录都有隐含的trx_id字段【该字段作用:它是最近一次更新或者插入或者删除该行数据的事务ID】。
隐式锁逻辑过程:
A:操作一条记录前,首先根据记录中trx_id检查该事务是否活动的事务(未提交或回滚),如果是活动事务,先将隐式锁转换为显式锁【为该事务添加一个锁】;
B:检查是否有锁冲突,若有,在创建锁,并设置为waiting状态,如果没冲突,调转到D;
C:等待加锁成功,被唤醒或超时;
D:写数据,并加自己事务id写入trx_id字段;
2.2.6、2PL约束(two-phase locking)
锁操作分为两个阶段:加锁阶段与解锁阶段,并且保证加锁阶段与解锁阶段不相交。加锁阶段:只加锁,不放锁。解锁阶段:只放锁,不加锁。
在RC隔离级别,如果是聚簇索引的全表扫描,所有行都会加X锁,但Mysql service层做了优化,对不满足条件的行记录,加锁后释放锁。违背2PL约束。
2.2.7、semi-consistent read
semi-consistent read开启的情况下,对于不满足查询条件的记录,MySQL会提前放锁,释放的是X锁和对应的gap锁。
semi-consistent read如何触发: read committed隔离级别; Repeatable Read隔离级别,同时设置了innodb_locks_unsafe_for_binlog 参数。
2.3、表级锁
2.3.1、 InnoDB什么时候使用表级锁
对于InnoDB表,在绝大部分情况下都应该使用行级锁。而使用表级锁主要是下面两种情况:
(A):事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
(B):事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
使用表锁要注意以下两点:
(1).使用LOCK TALBES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层MySQL Server负责的,仅当autocommit=0、innodb_table_lock=1(默认设置)时,InnoDB层才能知道MySQL加的表锁