在一个高并发系统中,会出现多会话同时访问同一资源的情况,此时即产生了竞争。为了保证数据的一致性,必须要用锁机制来控制资源的并发访问。
InnoDB采用的行锁的设计(MyISAM只支持表锁),行锁带来更高的并发性,但管理复杂度也要比表锁更高。InnoDB共实现了2种标准的行级锁:
- 共享锁(S Lock),允许持有锁的事务读取数据。
- 排他锁(X Lock),允许持有锁的事务修改和删除数据。
其中仅S锁和S锁兼容,X锁与任何锁都不兼容。
意向锁(Intention locks):
InnoDB存储引擎支持多粒度的锁定,即允许行级锁和表级锁同时存在。意向锁共有两种
- 意向共享锁(IS lock),表示事务想要获得表中某几行的共享锁。
- 意向排他锁(IX lock),表示事务想要获得表中某几行的排他锁。
意向锁是一种表级锁,表示事务稍后希望在更细的粒度上(行级别)加锁。
- select ... lock in share mode; 会对表施加IS锁。
- select ... for update; 会对表施加IX锁。
表级别锁的兼容性如下图:
X | IX | S | IS | |
---|---|---|---|---|
X | Conflict | Conflict | Conflict | Conflict |
IX | Conflict | Compatible | Conflict | Compatible |
S | Conflict | Conflict | Compatible | Compatible |
IS | Conflict | Compatible | Compatible | Compatible |
例如,如果请求在记录r上加X锁,则需要先在表级别上加意向排他锁(IX),如果此时表上存在其他锁,则意向锁需要等待表级别锁的释放,待表级意向锁(IX)获得成功后,才可以对行级别加X锁。
意向锁只会阻塞表级别的请求(如全表扫描、lock tables ... write;),除此之外不会阻塞任何操作。
记录锁(Record locks):
记录锁是对单一索引记录加的锁,即使表没有定义索引,InnoDB会为其建立一个隐藏的聚簇索引,并对此索引加记录锁。例如:select col from t where col=1 for update; 即对col为1的记录添加记录锁,阻止其他事务对此记录的操作。
间隙锁(Gap locks):
间隙锁是用来锁住一段索引区间的。例如:select col from t where col between 1 and 10 for update;会锁住1~10之间的间隙,而不管这段间隙内是否存在记录,因此间隙锁可能只锁住了一段空气。此时如果想插入为col为5的记录会被阻塞,即使5的记录不存在。间隙锁的唯一目的即是阻止其他的事务往间隙中插入记录,因此不同的事务可以对同样的间隙重复加锁,没用共享和排他类型之分。
间隙锁只在特性的隔离级别下使用,关闭间隙锁可以用2种方式:
- 将事务的隔离级别设置为read committed
- 将参数innodb_locks_unsafe_for_binlog(过时参数)设置为1
间隙锁是性能和并发之前的一个权衡,关闭间隙锁将破坏事务的隔离性。
临键锁(next-key locks):
Next-key lock 是记录锁和间隙锁的结合,next-key lock会对记录本身和记录之前间隙加锁。例如:假设表中存在索引记录1,10。则next-key lock可能锁住的范围是:
(-∞,1]
(1,10]
(10,+∞)
对于最后一个区间,next-key lock也会锁住最大记录之后的间隙。
Next-key lock只在MySQL的repeatable read隔离级别下使用,主要是用来解决幻读(phantom read)的问题。幻读即在同一个事务中,同样的查询读到了之前不存在的记录(由其他事务提交)。由于读到的是已提交的数据,因此很多DB厂商认为这不是问题,例如Oracle/SQLserver的默认隔离级别是read committed。是允许幻读和不可重复读存在的。而MySQL可以repeatable read隔离级别可以使用next-key lock实现事务的完全隔离。
当对唯一键值进行锁定时,next-key lock将会降级为record lock,即仅锁住唯一记录。而如果唯一键由多个列组成,而查询仅使用其中一列,则其实是range查询,InnoDB会依然使用next-key lock进行锁定。
插入意向锁(Insert intention locks):
插入意向锁是间隙锁的一种,其由inert语句在插入记录前获取,代表将在间隙中插入记录的意向。多个事务可以对同一个间隙重复加insert intention lock,只要插入的记录值不同,事务就不会冲突。例如表中已存在记录1和10,两个事务分别想插入5和6。两个事务都会对1和10记录之间的间隙(2,9)加insert intention lock,但由于插入的记录值不同,因此后续对要插入的记录获取X锁的时候并不会冲突。
自增锁(auto-inc locks):
自增锁是一种特殊类型的表锁,只要在事务对auto_increment类型的列插入数据时,才会施加auto-inc lock,此时其他想插入的事务都需要等待该锁的释放,而持有该锁的事务可以获得连续的primary key值。
在InnoDB存储引擎中,参数innodb_lock_wait_timeout用来控制锁的等待时间,默认值是50(秒)。
innodb_rollback_on_timeout控制超时时是否对事务进行回滚(默认是off,代表不回滚)。