MySQL锁总体结构
MySQL 的锁可以分成三类:总体、类型、粒度。
- 总体上分成两种:乐观锁和悲观锁
- 类型上也是两种:读锁和写锁
- 锁的粒度上可以分成五种:表锁,行锁,页面锁,间隙锁,临键锁
下面我们就来详细讲一下这些锁
1. 悲观锁
悲观锁对于数据库中数据的读写持悲观态度,即在整个数据处理的过程中,他会悲观认为数据不会保持一致性,所以是会将相应的数据锁定。在数据库中,悲观锁的实现是依赖数据库提供的锁机制。
如果加上了悲观锁,那么就无法对这些数据进行读取操作。
2. 乐观锁
乐观锁对于数据库的数据的读写持乐观态度,即在整个数据处理的过程中,他会很乐观的认为数据会保持一致性,所以不加锁,而是通过数据版本记录机制实现。
MySQL中的MVCC多版本控制就是乐观锁的一种实现方式。
- 往往会在数据表中增加一个类型version的版本号字段。
- 在查询数据库中的数据时,会将版本号字段的值一起读取出来。
- 当更新数据时,会令版本号字段的值加1。将提交数据的版本与数据库表对应记录的版本进行对比。
- 如果提交的数据版本号大于数据表中当前要修改的数据的版本号,则数据进行修改操作。
- 否则不修改数据库表中的数据。
3. 读锁
读写又称为共享锁或者S锁(Shared Lock),针对同一份数据,可以加多个读锁而互不影响。
4. 写锁
写锁又称为排他锁或者X锁(Exclusive Lock),如果当前写锁未释放,他会阻塞其他的写锁和读锁。
5. 表锁
表锁也称为表级锁,就是在整个数据表上对数据进行加锁和释放锁。特点:开销小,加速快,粒度大,并发度最低,发生锁冲突概率高。
在MySQL中,有两种表锁模式:一种是表共享锁(Table Shard Lock),另一种是表独占写锁(Table Write Lock)。
当一个线程获取到一个表的读锁后,其他线程仍然可以进行读操作,但不能对表进行写操作。那么对应的如果一个线程获取到一个表的写锁后,只有这个线程可以进行读写操作,其他线程无法对表进行读写操作,直到写锁被释放为止。
在mysql中可以通过以下命令手动添加表锁
代码语言:txt复制LOCKTABLE 表名称 read(write);
eg: 添加读表锁
代码语言:txt复制LOCKTABLE user_table read;
eg: 添加写表锁
代码语言:txt复制LOCKTABLE user_table write;
使用如下命令可以查看数据表上增加的锁
代码语言:txt复制SHOWOPENTABLES;
删除表锁:
代码语言:txt复制UNLOCKTABLES;
6. 行锁
行锁也称为行级别,就是在数据行上对数据进行加锁和释放锁。特点:开销大,加锁慢,粒度小,并发度高,锁冲突概率最小。
在mysql的InnoDB存储引擎中有两种行锁,排他锁和共享锁。
- 共享锁:允许一个事务读取一行数据,但不允许一个事务对加了共享锁的当前行增加排他锁。
- 排他锁:允许当前事务对数据行进行增删改查操作,不允许其他事务对增加了排他锁的数据行增加共享锁和排他锁。
注意:
- 行锁主要加在索引上,如果对非索引字段设置条件进行更新,行锁可能会变成表锁。例如
UPDATE user_table SET number = 2 WHERE name = 'fanone'
如果name没有加索引,那么可能会进行表锁。所以我们一般建议使用主键id作为更新数据的查询条件。 - InnoDB的行锁是针对索引加锁,不是针对记录加锁,并且加锁的索引不能失效,否则行锁也有可能变成表锁。而导致索引失效的有很多,比如联合索引不遵循最左匹配原则会失效、OR会失效等等...
- 锁定某一行时,可以使用
lock in share mode
命令来指定共享锁,使用for update
命令来指定排他锁。
UPDATE user_table SETnumber = 2WHEREname = 'fanone'LOCKINSHAREMODE;
UPDATE user_table SETnumber = 2WHEREname = 'fanone'FORUPDATE;
7. 页面锁
页级锁定是 MySQL 中比较独特的一种锁定级别。特点:锁定颗粒度介于行级锁定与表级锁之间,锁开销和加锁时间界于表锁和行锁之间,并发处理能力也同样是介于上面二者之间,并发度一般。
不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。
使用页级锁定的主要是 BerkeleyDB 存储引擎。
8. 间隙锁
在mysql中使用范围查询的时,如果请求共享锁或者排他锁,InnoDB会给符合条件的已有数据的索引项加锁。如果键值在条件范围内,而这个范围内并不存在记录,而认为此时出现了间隙,InnoDB会对这个间隙进行加锁,这也称为间隙锁。
eg:
代码语言:txt复制SELECT * FROM user_user;
---- ------- -------
|id | name | sex |
---- ------- -------
| 1 |zhangsan| 1 |
| 2 |lisi | 2 |
| 3 |lisi2 | 2 |
| 7 |lisi3 | 2 |
| 10 |lisi4 | 2 |
| 21 |lisi5 | 2 |
---- ------- -------
上面出现了间隙有 (3,7], (7,10], (10,21],(21, ∞] 的三个区间。
如果执行以下sql
代码语言:txt复制UPDATE user_user SET sex = 1WHEREid > 8ANDid < 18;
那么其他事务就无法在 (7,21] 这个区间内插入或者修改任何数据。间隙锁会锁住 (7,10], (10,21] 这两个间隙。不过间隙锁只会在 可重复读事务隔离级别 下才会生效。
9. 临键锁
临键锁就是行锁和间隙锁的组合,也可以理解为一种特殊的间隙锁。通过临建锁可以解决幻读的问题。
每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据 。
需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关 ,在唯一索引列(包括主键列)上不存在临键锁。
上面的(7,21]就是临键锁。