1 锁的类型
1.1 行级锁
MySQL提供两种标准级别的行级锁
- 共享锁 (S Lock):允许事务读取一行数据
- 排他锁 (X Lock):允许事务删除或更新一行数据
排他锁与共享锁的兼容性
1.2 意图锁
InnoDB支持多细粒度锁定,允许在行级上的锁和表级上的锁同时存在
InnoDB还支持额外的锁方式,称之意向锁,是表级别的锁,用于在一个事务中揭示下一行将被请求的锁的类型
- 意向共享锁(IS Lock):事务想获取表中某几行的共享锁
- 意向排他锁(IX Lock):事务想获取表中某几行的排他锁
意图锁需遵循以下协议:
- 在事务获取某行的共享锁前,首先需获取IS锁或更强的锁
- 在事务获取某行的独占锁前,首先在表上获取IX锁
IS | IX | S | X | |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
X锁与任何的锁都不兼容,S锁与IX锁不兼容,剩下的均兼容
意向锁只会阻塞表级别的锁,并不会阻塞行级别的锁
InnoDB存储引擎的两种加锁操作:
- select … for update:对读取的行加一个X锁,其他事务加锁将会被阻塞
- select … lock in share mode:对读取的行记录加一个S锁,其他事务可以加S锁,但对于X锁将会被阻塞
1.3 自增锁
事务插入到具有AUTO-INC列的一种特殊表级锁,当事务插入时,必须获取自增锁以获取自增列的值
innodb_autoinc_lock_mode参数
- 值0:在5.1版本之前的自增长实现方式,通过表锁的AUTO-INC Locking方式
- 值1(默认):对于Simple Inserts,使用互斥量对内存的计数器进行累加,对于Bulk Inserts,使用传统表锁的AUTO-INC Locking方式
- 值2:此模式任何时候都可以使用Row-Base Replication,保证了最大并发性能和Replication数据的同步
1.4 外键的锁机制
外键的插入更新需先查询父表记录,若该行记录被另一个事务加X锁未提交,则后续插入操作首先会加入S锁,则插入操作会被阻塞,提交后也会产生数据不一致的情况
2 锁算法
InnoDB三个行锁算法:
- Record Lock: 单行记录的锁
- Gap Lock:间隙锁,锁定一个范围,不包含记录本身
- Next-Key Lock:锁定一个范围,包含记录本身,在可重复读隔离级别下的默认行记录锁定算法
3 锁问题
-
丢失更新
多个事务同时修改某个行数据,导致某个事务的修改被覆盖
-
脏读
脏数据概念:缓冲区已被修改的页,尚未刷写会磁盘的数据
脏读:不同的事务下,读取到其他事务未提交的数据,存在于读未提交隔离级别下
-
不可重复读
同一个事内,若数据被另一个事务修改提交,会导致两次读取的数据不一致,违反了事务一致性的原则
在InnoDB里,通过Next-Key Lock算法避免了不可重复读的问题,在mysql里定义为Phantom Problem(幻读),Next-key算法会锁定扫描到的以及间隙,避免了范围内的插入操作,避免了不可重复读的问题
4 阻塞
一个事务中的锁需要等待另一个事务中的锁释放占用的资源,使用mutex数据结构实现阻塞机制
InnoDB使用参数innodb_lock_wait_timeout控制锁的等待时间,参数innodb_rollback_on_timeout设定是否进行回滚,默认OFF表示不回滚,lock参数可在数据库运行时动态调整,而rollback参数是静态的,仅可以在启动时调整
5 死锁
数据库在并行情况下可能会发生死锁,存在于两种资源之间的相互等待
InnoDB在侦测到死锁后,会回滚事务。如oracle,死锁多发生于未对外键添加索引,而InnoDB引擎会自动进行添加,不可人为删除外键索引,因此较好的解决了这种问题
6 锁升级
定义:将当前锁的粒度降低,例如把一个表的100个行锁升级成一个页锁,或页锁升级成表锁,为避免锁的开销,会频繁出现锁升级的现象
mssql在05版之后支持行锁,当一条SQL在一个对象的锁数量超过阈值5000或锁资源占用的内存超过40%会发生锁升级
InnoDB中不存在锁升级的问题,MySQL里锁的开销与数量无关,类似Oracle的设计