这篇文章来浅谈一下什么是Mysql 行锁,以及产生行锁的原因。
锁的分类
MySQL有三种锁的级别:页级、表级、行级。
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
因为这篇笔记只介绍Mysql 行锁,所以这里不对其他类型的锁做介绍了。
行锁
InnoDB实现了两种类型的行锁:
- 共享锁【S锁】又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
- 排他锁【X锁】又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
所谓X锁,是事务T对数据A加上X锁时,只允许事务T读取和修改数据A; 所谓S锁,是事务T对数据A加上S锁时,其他事务只能再对数据A加S锁,而不能加X锁,直到T释放A上的S锁
场景重现
- 首先创建一个
InnoDB
类型的数据表,SQL 如下:
1 | CREATE TABLE `gap` ( |
- 创建会话1,开启事务A并执行update 语句
1 | start transaction; |
- 创建会话2,开启事务B并执行另一个update 语句
1 | start transaction; |
在会话2中 插入20 > id < 39
范围外的值时 可以执行成功,而当要插入 [20,39)
范围内的值时 会遇到gap lock 。
- 用会话1 查看当前正在进行中的事务
1
SELECT * FROM information_schema.INNODB_TRX;
不会意外,能看到下面两条记录:
可以看到 进程id为3175 的事务在锁住了,而另一个id为3173的事务正在执行,但是没有提交事务。
这是因为执行update 语句之后,mysql 会执行索引扫描并在该表上施加一个 next-key lock
,向左扫描到20,向右扫描到39 ,锁定区间左闭右开,所以lock的范围是 [20,39)
。
解决办法
根据实际情况的不同,有不同的方式可以避免死锁,这里介绍常用的几种:
- 改变数据库操作逻辑,尽量避免在不同的事务中,对同一条记录进行更改。
- 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。