锁是数据库系统区别于文件系统的一个关键特性。
数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。
lock与latch
latch称为闩锁,操作对象是线程,在innodb中,latch又可以分为mutex(互斥量)和rwlock(读写锁)。用来保证并发线程操作临界资源的正确性,通常没有死锁检测的机制。
lock的对象是事务,用来锁定数据库中的对象,例如表、页、行。lock的对象仅在事务commit或rollback后进行释放。不同事务隔离级别释放的时间可能不同。lock有死锁机制。
InnoDB存储引擎中的锁
两种行级锁
共享锁(S Lock),允许事务读一行数据。
排他锁(X Lock),允许事务删除或更新一行数据。
共享锁之间可以兼容,如果一个事务已经获得行r上的共享锁了,那么另一个事务想要获得行r上的排他锁时,必须等待行r上的共享锁释放。
表级锁
InnoDB存储引擎支持多粒度锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。这种额外的加锁方式称为意向锁,意向锁即为表级锁。
意向共享锁(IS Lock),事务想要获得一张表中某几行的共享锁。
意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁。
因为InnoDB支持的是行级别的锁,所以意向锁其实不会阻塞除全表扫描以外的任何请求。
一致性非锁定读
一致性非锁定读是指InnoDB通过行多版本控制(MVCC)的方式来读取当前执行时间数据库中的数据。即,去读快照数据。
快照数据是指该行的之前版本的数据,通过undo段完成,所以快照数据本身是没有额外的开销,且不用上锁(因为没有事务需要对历史数据进行修改操作)。
在RC和RR隔离级别下,InnoDB存储引擎使用非锁定一致性读。RC级别下,在事务中,每次select总是读取被锁定行的最新一份快照数据;RR级别下,读取事务开始时的行数据版本。
一致性锁定读(也称为当前读)
在某些情况下,用户需要显示地对数据库读取操作进行加锁以保证数据逻辑的一致性。
两种一致性读的操作:
select … for update 加X锁,其他事务不能对已锁定的行加上任何锁。
select … lock in share mode 对读取的行加一个S锁,其他事务可以加S锁,加X锁会被阻塞。
不管在RC还是RR隔离级别下,一致性锁定读都会读取被锁定行的最新一份快照数据。
自增长与锁
每个含有自增长值的表都有一个自增长计数器。
在mysql 5.1.22版本之前,执行如下语句可以得到计数器的值:
select max(auto_inc_col) from t for update;
插入操作会依据获得的计数器值加一赋予自增长列。这种方式称为AUTO_INC Locking.这种锁其实是采用一种特殊的表锁机制。这种方式在并发插入时的性能较差,事务必须等待前一个插入的完成。
5.1.22版本之后,innoDB提供了一种轻量级互斥量的自增长的实现机制,这种机制大大提高了自增长值插入的性能。
可以使用innodb_autoinc_lock_mode字段设置增长机制。
0 通过表锁auto-inc locking的方式。
1 默认。对于simple inserts使用互斥量。对于bulk inserts使用传统的表锁。在已经使用auto-inc locking 方式时,还是需要等待表锁的释放。
2 对于insert-like都是使用互斥量。
InnoDB中,字增长值的列必须是索引,且必须是索引的第一个列,否则会报错。
锁
行锁的3种算法
Record Lock:单个行记录上的锁。默认锁住索引记录,如果没有设置任何一个索引,InnoDB会使用隐式的主键来进行锁定。
Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。为了阻止多个事务将记录插入到同一个范围内。
Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,且锁定记录本身。为了解决Phantom Problem问题。
对于Next-Key Lock,如果一个索引有10,11,13和20这四个值,那么该索引可能被Next-Key Locking的区间为:(-∞,10],(10,11],(11,13],(13,20],(20, +∞).
当索引含有唯一属性时,Next-Key Lock会降级为Record Lock,对于辅助索引,还是会锁住一个范围,且会对下一个键值加上gap锁。例如辅助索引next-key locking的区间为:(-∞,10],(10,11],(11,13],(13,20],(20, +∞),如果此时查询键值为11的行记录,则会锁住两个区间(10,11)(11,13)。
如果唯一索引列有多个列组成,查询仅仅是查找多个唯一索引列中的其中一个,那么查询是range类型查询,不是point类型查询,依然会使用next-key lock进行锁定。
解决Phantom Problem
Phantom Problem 幻象问题,指在同一事务下,连续执行两次同样的sql语句可能导致不同的结果,第二次的sql语句可能会返回之前不存在的行。InnoDB采用Next-Key Locking算法避免幻象。因为在查询时会锁住一个范围,在这个范围内的所有插入都是不允许的。
使用next-key locking做唯一性检查的时候,如果有多个事务并发操作,最终只有一个事务能插入成功,其余事务会抛出死锁错误。
锁问题
脏读
一个事务可以读到另外一个事务中未提交的数据,违反了数据库的隔离性。
不可重复读
一个事务在未提交时读到了另一个事务刚刚提交的数据。违反了事务的一致性。
丢失更新
一个事务的更新操作会被另一个事务的更新操作覆盖,导致数据不一致。但是在当前数据库的任何隔离级别下,都不会导致数据库理论意义上的丢失更新问题。
阻塞
默认情况下InnoDB不会回滚超时引发的错误异常。在大部分情况下,都不会对异常进行回滚。用户必须判断是否需要commit还是rollback。
死锁
死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象。
解决死锁最简单的一种方式是超时。超时机制根据FIFO的顺序选择回滚对象。但是如果需要回滚的时候占权重比较大,占用了较多的undo log,这时采用FIFO的方式就不太合适。
目前数据库普遍采用wait-for graph(等待图)的方式来进行死锁检测。wait-for graph要求数据库保存以下两种信息:锁的信息链表,事务等待链表。通过这两种链表可以构造出一张图,如果图中存在回路,代表存在死锁。若存在死锁,InnoDB通常选择回滚undo量最小的事务,也有例外。
wait-for graph死锁检测通常采用深度优先的算法实现。
死锁概率
发生死锁的概率与以下几点因素有关:
系统中事务的数量越多,发生死锁的概率越大。
每个事务操作的数量越多,发生死锁的概率越大。
操作数据的集合(数据行)越小,发生死锁的概率越大。
锁升级
锁升级是指将当前锁的粒度降低。
InnoDB不存在锁升级的问题,因为锁是由事务访问的页来管理的,采用位图的方式。相较于根据每个记录产生行锁,InnoDB中锁机制带来的开销就相对于少很多。
