两种锁概念:
lock
- lock的对象是事务
- 锁定数据中的对象:
- 表
- 页
- 行
- 一般lock的对象仅在事务commit或rollback后进行释放
- 不同的隔离级别,情况不同
- 有死锁机制
SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
latch
- 闩锁(轻量级的锁)
- 持续时间长,则性能非常差
- 分为:
- mutex(互斥)
- rwlock(读写锁)
- 没有死锁检测机制
SHOW ENGINE INNODB MUTEX;
SHOW ENGINE INNODB STATUS;
对比
类别 | lock | latch |
---|---|---|
对象 | 事务 | 线程 |
保护 | 数据库内容 | 内存数据结构 |
持续时间 | 整个事务过程 | 临界资源 |
模式 | 行锁、表锁、意向锁 | 读写锁、互斥量 |
死锁 | 通过waits-for graph、time out等机制进行死锁检测处理 | 无死锁检测与处理机制。仅通过应用程序加锁的顺序(lock leveling)保证无死锁的情况发生 |
存在于 | Lock Manager 的哈希表中 | 每个数据结构的对象中 |
锁类型
锁的类型:
- 共享锁(S Lock)
- 允许事务读一行数据
- 锁兼容(Lock Compatible)
- 排他锁(X Lock)
- 允许事务删除或更新一行数据
- 锁不兼容
类型 | X | S |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
多粒度(granular)锁定
- 允许事务在行级上的锁和表级上的锁同时存在
- InnoDB存储引擎支持:意向锁(Intention Lock)
- 如需对最下层的记录加X锁,则需要对上层的数据库、表、页、记录加IX锁
- MySQL中意向锁即为:表级别的锁
- 设计目的:
- 为了在一个事务中揭示下一行将被请求的锁类型
- 支持两种意向锁:
- 意向共享锁(IS Lock)
- 事务想要获得一张表中某几行的共享锁
- 意向排他锁(IX Lock)
- 事务想要获得一张表中某几行的排他锁
- 意向共享锁(IS Lock)
- 意向锁不会阻塞除全表扫以外的任何请求
graph TB
DB[数据库A]
T[表]
P[页]
R[记录]
DB-->T
T-->P
P-->R
锁类型 | IS | IX | S | X |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
一致性读
- 一致性非锁定读
- MVCC
- 多版本控制
- 如果读取的行,正在执行DELETE/UPDATE操作,则读操作不会等待行上锁的释放,直接读取快照
- 直接读取undo段
- 用于事务回滚
- 不需要额外的快照开销
- 直接读取undo段
- 提高并发
- MVCC
- 一致性锁定读
- 显式的对数据库读取操作加锁
- 两种锁定读(locking read)操作:
SELECT ... FOR UPDATE
- 对读取行加X锁
- 其他事务不能对已锁定的行加上任何锁
- 其他事务还是可以继续读取
SELECT ... LOCK IN SHARE MODE
- 对读取行加S锁
- 其他事务可以再次加S锁
- 如果其他事务加X锁,则阻塞
自增字段锁问题
InnoDB内存结构钟,对每个自增长值的表,都有一个自增长计数器(auto-increment counter)。
- 当插入时,计数器初始化
SELECT MAX(auto_inc_col) FROM t FOR UPDATE
- AUTO-INC Locking
- 一种特殊的表锁机制
- 不在事务中控制
- 自增值插入后,立即释放
INSERT ... SELECT
- 大数据量的插入会影响插入的性能
- 轻量级互斥量
innodb_autoinc_lock_mode
- 0
- 5.1.22之前
- 通过表锁的AUTO-INC Locking方式
- 1
- 默认值
simple inserts
- 会用互斥量(mutex)去对内存中的计数器进行累加的操作
bluk inserts
- 使用传统表锁的AUTO-INC Locking方式
- 如果不考虑回滚操作,自增长的值还是连续增长的
- 2
insert-like
- 自增长都是通过互斥量
- 性能最高
- 问题
- 每次插入时,自增长值不连续
- 基于Statement-Base Replication会出现问题
- 应该配合row-base replication一起使用
- 0
- InnoDB存储引擎中,自增长值的列必须是索引,同时必须是索引的第一个列
自增长类型:
insert-like
- 指所有的插入语句
simple inserts
- 指可以在插入前就确定插入行数的语句
bulk inserts
- 指在插入前不可确定行数的语句
mixed-mode inserts
- 插入中有一部分的值是自增长的,有一部分是确定的
InnoDB会自动对外键加一个索引
- 避免表锁
算法
锁的算法:
- Record Lock
- 单行记录上的锁
- 锁索引
- Gap Lock
- 间隙锁
- 锁定一个范围
- 不包含记录本身
- Next-Key Lock
- Gap Lock + Record Lock
- 锁定一个范围
- 并且锁定记录本身
- 查询时,也是用此算法
- 当查询时,发现索引唯一,则降级为 Record Lock
Phantom Problem问题
- 是指,在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行。
- InnoDB使用Next-Key Lock算法,避免此问题
- 间隙锁(X锁)
锁问题
1. 脏读
脏读(Dirty Read)
- 脏数据
- 指,事务对缓冲池中的行记录进行了修改,但是还没有提交(commit)
- 在不同的事务下,当前事务可以读到另外事务未提交的数据(脏数据)
发生于:READ UNCOMMITTED
- 常用于:replication环境中的slave节点
2. 不可重复读
指,在一个事务内多次读取同一数据集合
- 两次读到的数据不一致
- 读到了其他事务已经提交的数据
- 违反了数据库事务一致性
READ COMMITTED
可解决- Next-Key Lock 的Gap锁,避免其他事务对范围内数据修改
- InnoDB 的
READ REPEATABLE
采用Next-Key Lock也可以避免
- InnoDB 的
- Next-Key Lock 的Gap锁,避免其他事务对范围内数据修改
3. 丢失更新
更粗粒度锁,避免此问题
后续修改,覆盖前一次更新
FOR UPDATE
阻塞
InnoDB中,
innodb_lock_wait_timeout
用来控制等待的时间- 默认50秒
innodb_rollback_on_timeout
用来设定是否在等待超时时回滚事务- 默认OFF,不回滚
- 需要注意
- 特别是在间隙锁的超时情况
死锁
死锁
- 指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。
解决方案:
- 超时
- 如果操作更新了很多行,占用了较多的
undo log
- 普遍采用
wait-for graph
- 等待图
- 节点:事务
- 边:
- 事务T1等待事务T2所占用的资源
- 事务T1最终等待T2所占用的资源
- 事务之间在等待相同的资源
- 事务T1发生在事务T2的后面
- 进行:死锁检测(主动)
- 判断是否存在回路
- 如果存在,则死锁,则回滚undo量最小的事务
- 存储:
- 锁的信息链表
- 事务等待链表
- 等待图
- 如果操作更新了很多行,占用了较多的
死锁因素:
- 系统中事务的数量,数量越多发生死锁的概率越大
- 每个事务操作的数量越多,发生死锁的概率越大
- 操作数据的集合,越小则发生死锁的概率越大
锁升级
锁升级(Lock Escalation)
InnoDB,根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式
- 不论事务锁住一个记录还是多个记录,其开销是一致的