锁的概念
@toc:
- 全局锁/表级锁/行锁;
- 共享锁/排他锁;
- 意向锁;
- 两段锁协议;
➤ 按锁的范围分: 全局锁(数据库锁), 表级锁(表锁, meta锁), 行锁;
- 全局锁: 锁住整个数据库实例, 例如
Flush tables with read lock
整个数据库处于只读状态, 常用于全库备份; - 表级锁: 分为 表锁 和 元数据锁
- 表锁需要显式加锁: 例如
lock table t1 write
即给表t1加写锁, 允许读不允许写; - 元数据锁(MDL) 不需要显式加,
- 当增删改查时, 数据库自动给表加MDL读锁, 可以增删改查数据, 但对修改表结构互斥;
- 当修改表结构时, 数据库自动给表加MDL写锁;
- 表锁需要显式加锁: 例如
- 行锁: 行锁分为S锁/X锁 (类似读写锁), 根据数据库当前隔离级别的不同, 以及sql语句的不同, 加的锁也不同;
- Serializable级别: 读加S锁, 写加X锁
- RR/RC级别: 读不加锁(MVCC), 写可能加 X锁 和 间隙锁
MyISAM 不支持行锁, 不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。InnoDB 是支持行锁的,这也是 MyISAM 被 InnoDB 替代的重要原因之一。
表锁的使用:
lock table xxx read; // 读锁 |
参考:
- 表锁/元数据锁 @link:: [[../49.Course/course.MySQL实战45讲]] 第6节;
- 行锁 @link:: [[../49.Course/course.MySQL实战45讲]] 第7节;
➤ 按锁的特性分:
- 共享锁(S锁): S锁类似读锁, 当线程1持有读锁, 不会排斥其他线程加读锁, 但排斥其他线程加写锁
- 手动加 S 锁:
select * from tableName where … lock in share mode
- 自动加 S 锁: 串行隔离下,
select
语句加S锁
- 手动加 S 锁:
- 排他锁(X锁): X锁类似写锁, 当线程1持有写锁, 排斥其他线程加读锁 or 写锁
- 自动加 X 锁: 执行
update
,select .. for update
,
- 自动加 X 锁: 执行
- 意向锁:
- IS锁: 意向共享锁, 事务加”S行锁”前, 必须取得该表的 IS锁;
- IX锁: 意向独占锁, 事务加”X行锁”前, 必须取得该表的 IX锁; // IS和IX都是表级锁
- 「IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突。」
@ref:
- MySQL 8.0 Reference Manual :: InnoDB Locking
- MySQL 8.0 Reference Manual :: LOCK TABLES and UNLOCK TABLES Statements
什么是两段锁协议
- 两段锁协议( Two-Phase Locking, 2PL): 事务中, 可以分为加锁阶段 和放锁阶段, 所以叫两段锁协议;
- 加锁阶段按照语句顺序进行加锁(例如事务中的 insert, update语句 到执行时才加相应的锁);
- 放锁阶段: 解开所有的锁;
- 也即行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。
使用MySQL实现乐观锁&悲观锁
- 悲观锁: 假定会出现冲突, 首先尝试锁定数据(获得锁), 然后修改数据, 通常使用
select .. for update
进行加锁, 注意, 加锁是执行加锁语句的时候才加锁, 放锁需要等到事务结束后才能放锁. - 乐观锁: 假定不会出现冲突, 直接更新数据, 在更新数据的同时做判断是否冲突, 常见的MySQL乐观锁方式:
- 使用version或timestamp, 先读取当前数据version/timestamp, 然后
update .. where version=x
的方式进行更新.
- 使用version或timestamp, 先读取当前数据version/timestamp, 然后
以更新商品库存为例( itemId primary key, count )说明乐观锁和悲观锁:
悲观锁更新库存:
select * from T where itemId=1 for update # 锁定 |
乐观锁更新库存: 通常使用version 或时间戳
select itemId, count from T where itemId = 1 |
@ref MySQL 乐观锁与悲观锁 - 简书