为保证并发访问数据的一致性,需要使用锁机制。可以参考操作系统进程同步相关的知识。
MySQL锁的类型:全局锁,表级锁,行级锁。
1. 全局锁
保证备份数据库期间数据不被业务修改,避免数据不一致。给数据库加上全局锁后,只允许读操作,不允许更新和删除操作(阻塞直到全局锁被释放,必须由加锁的客户端释放才行)。
1.1 全局锁的约束
-- client1:加全局锁
use test;
flush tables with read lock;
select * from Customers; -- 查询操作正常
update Customers set cust_email='222@xxx.com' where cust_city = 'Chicago'; -- 报错:操作和读锁冲突
-- client2:查询正常
use test;
select * from Customers; -- 查询操作正常
update Customers set cust_email='222@xxx.com' where cust_city = 'Chicago'; -- 阻塞:等待全局锁被释放
-- client1:释放锁
unlock tables; -- 释放后,client2的阻塞更新操作顺利执行。
1.2 使用全局锁进行备份
-- mysql client session1:加全局锁
use test;
flush tables with read lock;
-- 备份,注意:要退出mysql cli,在容器的命令行中执行
mysqldump -uroot -p test > test.sql
-- mysql client session1: 备份完毕释放锁
unlock tablesl;
1.2 在不加锁同时保证数据一致性的情况下进行备份
# innodb提供
mysqldump -uroot -p --single-transaction test > test.sql
当使用 --single-transaction 参数时,mysqldump 会在开始备份之前启动一个事务。在 InnoDB 存储引擎中,事务具有隔离性,通过设置合适的隔离级别(通常是可重复读隔离级别),可以保证在事务执行期间,读取的数据是事务开始时的快照。·
1.3 补充
1.3.1 导入备份数据
# 命令行导入,注意要先创建数据库test
mysql -u 用户名 -p test < test.sql
# mysql cli中导入
use test;
.\ /path/to/test.sql;
source /path/to/test.sql;
2. 表级锁
2.1 表锁
2.1.1 表共享锁(read lock)
对表添加读锁,多个客户端只读不能修改和删除表数据。
-- client1: 添加读锁
lock tables Customers read;
select * from Customers; -- 查询操作正常
update Customers set cust_email='222@xxx.com' where cust_city = 'Chicago'; -- 报错:操作和读锁冲突
-- client2: 其他客户端读正常,写受限
select * from Customers; -- 查询操作正常
update Customers set cust_email='222@xxx.com' where cust_city = 'Chicago'; -- 阻塞,直到读锁被client1释放
-- client1:释放读锁:执行unlock tables;断开加锁的连接
unlock tables; -- 释放后,阻塞的更新操作继续执行。
2.1.2 表独占锁(write lock)
客户端A加写锁,则本客户端可以读写表数据,其他客户端不能读,也不能写。表独占锁只能被一个客户端持有,可以看成一个互斥访问的资源。
-- cleint1: 加写速
lock tables users Customers write;
select * from Customers; -- 查询操作正常
update Customers set cust_email='222@xxx.com' where cust_city = 'Chicago'; -- 更新操作正常
-- client2: 其他客户端读写受限
select * from Customers; -- 阻塞:直到写锁被持有者释放才能执行
update Customers set cust_email='222@xxx.com' where cust_city = 'Chicago'; -- 阻塞:直到写锁被持有者释放才能执行
-- client1: 释放写锁
unlock tables; -- 释放后,client2中阻塞的操作顺利执行。
2.2 元数据锁
- Metadata Data Lock(MDL),主要用于限制事务之间DML和DDL之间的冲突来维护数据一致性(对表进行操作的同时不允许修改表结构)。
- 当一个事务需要访问某个数据库对象(如表、视图等)时,会自动获取该对象的 MDL。根据操作类型,会获取不同类型的锁,即共享锁(SHARED_READ,SHARED_WRITE)和独占锁(EXCLUSIVE)。
- 共享锁(SHARED_READ,SHARED_WRITE)之间是兼容的,允许多个事务同时获取共享锁(SHARED_READ,SHARED_WRITE)来读取数据,因为数据读写不会影响表的元数据(表结构)。
- 独占锁(EXCLUSIVE)与其他任何类型的MDL都不兼容,同一时间只能有一个事务获取EXCLUSIVE锁来对元数据(表结构)进行修改操作。
2.2.1 SQL语句及其对应的MDL
| SQL Statement | MDL Type | Compatibility |
|---|---|---|
| Lock tables xxx with read/write lock | SHARED_READ_ONLY/SHARED_NOT_READ_WRITE | |
| select, select … lock in share mode | SHARED_READ | 和SHARED_WRITE, SHARED_READ兼容,与EXCLUSIVE互斥 |
| update, insert, delete, select … for update | SHARED_WRITE | 和SHARED_WRITE, SHARED_READ兼容,与EXCLUSIVE互斥 |
| alter table … | EXCLUSIVE | 和其他MDL不兼容 |
2.2.2 MDL在不同客户端中的可见性
- 客户端在事务中获得的锁对其他客户端可见。各个客户端在开启事务之前持有关于performance_schema.metadata_locks的共享读锁。但是各个客户端只能看见自己持有这个共享读锁,看不到其他客户端持的共享读锁。
- 客户端开启事务后则其对performance_schema.metadata_locks的共享读锁以及事务内创建的共享锁对其他客户端可见。相当于客户端开启事务后自己持有的MDL就公开了,无论它们是否在事务中创建的。
-- client1: OWNER_THREAD_ID=61,只能看见自己持有的对metadata_locks的共享读锁
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID from performance_schema.metadata_locks;
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| performance_schema | metadata_locks | SHARED_READ | 61 |
+--------------------+----------------+-------------+-----------------+
-- client2: OWNER_THREAD_ID=62,只能看见自己持有的对metadata_locks的共享读锁
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID from performance_schema.metadata_locks;
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| performance_schema | metadata_locks | SHARED_READ | 62 |
+--------------------+----------------+-------------+-----------------+
-- client1: 开启事务
use test;
start transaction;
select * from customers;
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 两个锁:开启事务之前自己持有的共享读锁(1,metadata_locks);事务内创建的共享读锁(1,customers)
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| test | customers | SHARED_READ | 61 |
| performance_schema | metadata_locks | SHARED_READ | 61 |
+--------------------+----------------+-------------+-----------------+
-- client2: client1在事务中创建的MDL对其他客户端可见
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 3个锁:开启事务之前自己持有的共享读锁(1,metadata_locks);client1持有的全部锁(2,metadata_locks,customers)
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| test | customers | SHARED_READ | 61 |
| performance_schema | metadata_locks | SHARED_READ | 61 |
| performance_schema | metadata_locks | SHARED_READ | 62 |
+--------------------+----------------+-------------+-----------------+
-- client2
start transaction;
select * from users;
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 4个锁:自己开启事务前持有的锁(1,metadata_locks);自己开启事务后的锁(1,customers);client1持有的全部锁(2,metadata_locks,customers)
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| test | customers | SHARED_READ | 61 |
| performance_schema | metadata_locks | SHARED_READ | 61 |
| test | customers | SHARED_READ | 62 |
| performance_schema | metadata_locks | SHARED_READ | 62 |
+--------------------+----------------+-------------+-----------------+
-- client1
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 4个锁:自己持有的全部锁(2,metadata_locks,customers);client2持有的全部锁(2,metadata_locks,customers)
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| performance_schema | metadata_locks | SHARED_READ | 61 |
| test | customers | SHARED_READ | 62 |
| performance_schema | metadata_locks | SHARED_READ | 62 |
| test | customers | SHARED_READ | 61 |
+--------------------+----------------+-------------+-----------------+
commit; -- 释放事务中创建的共享锁(customers)。不释放开启事务前创建的共享锁(metadata_locks)
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 3个锁:自己持有的锁(1,metadata_locks),client2持有的全部锁(2,metadata_locks,customers)
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| performance_schema | metadata_locks | SHARED_READ | 61 |
| test | customers | SHARED_READ | 62 |
| performance_schema | metadata_locks | SHARED_READ | 62 |
+--------------------+----------------+-------------+-----------------+
-- client2:client1提交了事务,client2只能看见自己持有的锁(2,metadata_locks,customers)
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 2个锁:client2自己的2个锁(metadata_locks,customers)
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| test | customers | SHARED_READ | 62 |
| performance_schema | metadata_locks | SHARED_READ | 62 |
+--------------------+----------------+-------------+-----------------+
commit; -- 释放事务中创建的共享锁(customers)。不释放开启事务前创建的共享锁(metadata_locks)
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 1个锁:client2自己的1个锁(metadata_locks)
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| performance_schema | metadata_locks | SHARED_READ | 62 |
+--------------------+----------------+-------------+-----------------+
2.2.3 MDL限制DDL和DML
同一客户端内的共享锁和独占锁
-- client1
use test;
start transaction;
select * from users where uid = 0; -- 获取users的SHARED_READ锁
update users set name='U0' where uid = 0; -- 获取users的SHARED_WRITE锁
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 查看client1拥有的锁
+--------------------+----------------+--------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+--------------+-----------------+
| performance_schema | metadata_locks | SHARED_READ | 66 |
| test | users | SHARED_READ | 66 |
| test | users | SHARED_WRITE | 66 |
+--------------------+----------------+--------------+-----------------+
-- 由于共享锁和独占锁互斥,此时独占锁空闲(没有其他客户端使用独占锁),在获取独占锁前要释放client1持有的所有共享锁。
alter table users add column new_col varchar(20) default ''; -- 获取EXCLUSIVE锁,执行成功
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 查看client11拥有的锁
+--------------------+----------------+-------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+-------------+-----------------+
| performance_schema | metadata_locks | SHARED_READ | 66 |
+--------------------+----------------+-------------+-----------------+
不同客户端内独占锁和共享锁
-- client1
use test;
start transaction;
select * from users where uid = 0; -- 获取users的SHARED_READ锁
update users set name='U0' where uid = 0; -- 获取users的SHARED_WRITE锁
select OBJECT_SCHEMA,OBJECT_NAME, LOCK_TYPE,OWNER_THREAD_ID
from performance_schema.metadata_locks; -- 查看client1拥有的锁
+--------------------+----------------+--------------+-----------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | OWNER_THREAD_ID |
+--------------------+----------------+--------------+-----------------+
| performance_schema | metadata_locks | SHARED_READ | 66 |
| test | users | SHARED_READ | 66 |
| test | users | SHARED_WRITE | 66 |
+--------------------+----------------+--------------+-----------------+
-- client2
alter table users add column new_col varchar(20) default ''; -- 获取EXCLUSIVE锁失败,阻塞执行
-- client1
commit; -- client1事务提交后,client2中阻塞的DML语句顺利执行
2.2.4 补充
-
客户端在获取关于某表的独占锁之前要确认没有关于这表的共享锁被任何客户端持有。如果关于此表的共享锁被自己持有,那么自己可以释放这些共享锁再获取独占锁来进行操作。如果关于此表的共享锁被其他客户端持有,则获取独占锁会阻塞,直到其他客户端释放共享锁(提交事务,断开连接等)。
-
如果在use db后开启事务,则事务涉及哪些表,就对那些表加对应的MDL。如果开启事务后再use db,则该db的所有表都会被加MDL。即在事务中执行use db将导致对db内所有表加MDL。
-
对比元数据锁和表锁,它们都有各自的共享锁和排他锁,但是针对的是不同场景。元数据锁主要用于限制并发事务之间变动表结构的操作,元数据要在事务中创建;表锁主要用于限制不同客户端变更表数据的操作,不要求在事务中创建表锁。
2.3 意向锁
- 客户端A给表加了行锁,客户端B需要给统一表加表锁。客户端需要逐行检查是否有表锁,这个检查是低效的。意向锁主要用来快速判断一个表是否有行锁。具体地,如果客户端A执行更新操作,它会先给这行加上行锁,同时再对这张表加上意向锁。客户端B要给这张表加表锁,客户端B将直接检查意向锁,如果表锁和意向锁类型兼容,则客户端B可以添加表锁。
- 意向锁类型:意向共享锁(IS),与表读锁兼容,与表写锁互斥;意向排他锁(IX),与表读锁和表写锁互斥。
- 在事务中添加行锁和表意向锁。
2.3.1 IS和表锁的兼容与互斥
-- client1:在事务中添加行锁和IS
start transaction;
select * from users where uid=0 lock in share mode; -- 添加行锁和IS
select OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE from performance_schema.data_locks; -- 查看IS和行锁
+---------------+-------------+-----------+---------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_MODE |
+---------------+-------------+-----------+---------------+
| test | users | TABLE | IS |
| test | users | RECORD | S,REC_NOT_GAP |
+---------------+-------------+-----------+---------------+
-- client2: 添加表锁
lock tables users read; -- 表读锁和IS兼容,正常执行
select OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE from performance_schema.data_locks;
unlock tables;
lock tables users write; -- 表写锁和IS互斥,阻塞直到client1事务提交
-- client1: 提交事务,client2阻塞的操作顺利执行
commit;
2.3.2 IX与表锁互斥
-- client1:在事务中添加行锁和IX
start transaction;
update users set name = 'U0' where uid = 0; -- 更新行将加行锁和IX
select OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE from performance_schema.data_locks;
+---------------+-------------+-----------+---------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_MODE |
+---------------+-------------+-----------+---------------+
| test | users | TABLE | IX |
| test | users | RECORD | X,REC_NOT_GAP |
+---------------+-------------+-----------+---------------+
-- client2
lock tables users read; -- 阻塞,IX与S互斥
lock tables users write; -- 阻塞,IX与X互斥
-- client1
commit; -- client2的阻塞操作顺利执行
3. 行级锁
3.1 行锁
行锁主要用于限制并发事务对同一行数据的更改操作。行锁有共享锁(S)和独占锁(X)。并发事务之间的行共享锁兼容,并发事务之间的独占锁与共享锁互斥,且一个时刻只能有一个事务持有独占锁。
3.1.1 SQL语句机器对应的行锁
| SQL Statement | 行锁 | 说明 |
|---|---|---|
| insert | 排他锁 | 自动添加 |
| update | 排他锁 | 自动添加 |
| delete | 排他锁 | 自动添加 |
| select … for update | 排他锁 | 结尾:for update |
| select … lock in share mode | 共享锁 | 结尾:lock in share mode |
| select | 不加锁 |
3.1.2 行锁兼容和互斥
只有一个事务
一个事务可以同时持有共享锁和独占锁。
start transaction;
select * from users where uid=0 lock in share mode; -- 添加共享锁
select OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE
from performance_schema.data_locks;
+---------------+-------------+-----------+---------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_MODE |
+---------------+-------------+-----------+---------------+
| test | users | TABLE | IS |
| test | users | RECORD | S,REC_NOT_GAP |
+---------------+-------------+-----------+---------------+
select * from users where uid=0 for update; -- 添加独占锁
+---------------+-------------+-----------+---------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_MODE |
+---------------+-------------+-----------+---------------+
| test | users | TABLE | IX |
| test | users | TABLE | IS |
| test | users | RECORD | S,REC_NOT_GAP |
| test | users | RECORD | X,REC_NOT_GAP |
+---------------+-------------+-----------+---------------+
commit; -- 提交事务,释放共享锁和独占锁
select OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE
from performance_schema.data_locks; -- empty set
多个事务
独占锁只能被一个事务持有,如果一个事务已经持有独占锁,则其他事务申请独占锁需要阻塞直到持有它的事务提交释放独占锁。
-- client1
start transaction;
select * from users where uid=0; -- 系统不会为select语句自动添加行锁
select * from users where uid=0 lock in share mode; -- 手动为select语句添加行锁
select OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE
from performance_schema.data_locks; -- 查看行锁和表意向锁:REC_NOT_GAP表示为行锁
+---------------+-------------+-----------+---------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_MODE |
+---------------+-------------+-----------+---------------+
| test | users | TABLE | IS |
| test | users | RECORD | S,REC_NOT_GAP |
+---------------+-------------+-----------+---------------+
-- client2:申请共享锁
select * from users where uid=0 lock in share mode; -- client2申请行锁成功,因为client1对同一行持有的共享锁兼容client2正申请的共享锁
select OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE
from performance_schema.data_locks;
+---------------+-------------+-----------+---------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_MODE |
+---------------+-------------+-----------+---------------+
| test | users | TABLE | IS |
| test | users | RECORD | S,REC_NOT_GAP |
| test | users | TABLE | IS |
| test | users | RECORD | S,REC_NOT_GAP |
+---------------+-------------+-----------+---------------+
-- client2:申请独占锁
select * from users where uid=0 for update; -- 阻塞,因为client2申请的独占锁与client1持有的共享锁互斥
-- client1
commit; -- client1的事务提交,释放独占锁,client2不再阻塞而获得独占锁
3.2 间隙锁和临键锁
3.2.1 什么是间隙锁和临键锁
间隙锁(Gap Locks)
MySQL每行表数据是按照B+tree索引结构组织的,聚集索引中每个B+Tree叶子结点存储一行数据,所有叶子结点用指针链接起来,形成链表,且该链表是有序的。相邻记录之间存在主键值上间隙。间隙锁主要用于限制往这些间隙中插入数据。多个事务可以对同一间隙获取间隙锁。
临键锁(Next-Key Locks)
对某行的临键锁等价于该行的行锁加上该行之前的间隙锁。InnoDB默认在可重复度隔离等级下使临键锁进行行搜索和索引扫描来防止幻读。
-- 临界锁例子
+-----------+---------------+-------------+---------------+-----------+---------------+-----------+
| THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_DATA |
+-----------+---------------+-------------+---------------+-----------+---------------+-----------+
| 82 | test | users | NULL | TABLE | IS | NULL |
| 82 | test | users | idx_users_age | RECORD | S | 3, 3 | -- 临键锁
| 82 | test | users | PRIMARY | RECORD | S,REC_NOT_GAP | 3 |
| 82 | test | users | idx_users_age | RECORD | S,GAP | 7, 7 |
+-----------+---------------+-------------+---------------+-----------+---------------+-----------+
/*
INDEX_NAME=idx_users_age, LOCK_MODE=S, LOCK_DATA=3,3 表明这个临键锁中的行锁是共享锁,且行锁锁定的是age=3的一行,间隙锁锁的是 x<age<3 的间隙,x为在索引 idx_users_age中 age=3 的前驱。3,3分别指示age=3这一项的索引值和主键值。
*/
3.2.2 情形一
使用唯一索对不存在的行加入行锁,行锁转化为间隙锁。
-- 索引上的等值查询(唯一索引):更新不存在的记录,添加行独占锁转化为添加间隙锁
-- client1: 假设记录uid=[0,4,5,6...]
start transaction;
select * from users where uid = 2 lock in share mode; -- 对uid=[1,2,3]的间隙加上间隙锁(共享锁)
update users set name='u-1' where uid = 3; -- 对uid=[1,2,3]的间隙加上间隙锁(独占锁)
select OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE
from performance_schema.data_locks;
+-----------+---------------+-------------+-----------+-----------+-----------+
| THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_MODE | LOCK_DATA |
+-----------+---------------+-------------+-----------+-----------+-----------+
| 82 | test | users | TABLE | IX | NULL |
| 82 | test | users | TABLE | IS | NULL |
| 82 | test | users | RECORD | S,GAP | 4 |
| 82 | test | users | RECORD | X,GAP | 4 |
+-----------+---------------+-------------+-----------+-----------+-----------+
-- client2:其他客户端对相同的间隙也可以获取锁,无论是共享锁还是独占锁
start transaction;
select * from users where uid = 2 lock in share mode;
update users set name='u3' where uid = 3;
select THREAD_ID, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA
from performance_schema.data_locks;
+-----------+---------------+-------------+-----------+-----------+-----------+
| THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_MODE | LOCK_DATA |
+-----------+---------------+-------------+-----------+-----------+-----------+
| 81 | test | users | TABLE | IX | NULL |
| 81 | test | users | TABLE | IS | NULL |
| 81 | test | users | RECORD | S,GAP | 4 |
| 81 | test | users | RECORD | X,GAP | 4 |
| 82 | test | users | TABLE | IX | NULL |
| 82 | test | users | TABLE | IS | NULL |
| 82 | test | users | RECORD | S,GAP | 4 |
| 82 | test | users | RECORD | X,GAP | 4 |
+-----------+---------------+-------------+-----------+-----------+-----------+
-- client2:间隙锁限制插入
commit; -- 先释放client2持有的锁
select THREAD_ID, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA
from performance_schema.data_locks;
+-----------+---------------+-------------+-----------+-----------+-----------+
| THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_MODE | LOCK_DATA |
+-----------+---------------+-------------+-----------+-----------+-----------+
| 82 | test | users | TABLE | IX | NULL |
| 82 | test | users | TABLE | IS | NULL |
| 82 | test | users | RECORD | S,GAP | 4 |
| 82 | test | users | RECORD | X,GAP | 4 |
+-----------+---------------+-------------+-----------+-----------+-----------+
insert into users values(3, 'u3', 12, NOW(), null, null); -- 阻塞,间隙锁锁不让插入
3.2.3 情形二
使用普通索引上等值查询时对匹配项加行锁,匹配项之前的间隙加间隙锁,匹配项之后的间隙加间隙锁。
-- 对users的name字段加普通索引
create index idx_users_age on users(age);
select * from users;
+-----+------+------+---------------------+-------------+---------+
| uid | name | age | create_time | update_time | new_col |
+-----+------+------+---------------------+-------------+---------+
| 1 | u1 | 1 | 2025-03-02 02:40:59 | NULL | NULL |
| 3 | u3 | 3 | 2025-03-02 02:40:59 | NULL | NULL |
| 7 | u7 | 7 | 2025-03-02 02:40:59 | NULL | NULL |
| 9 | u9 | 9 | 2025-03-02 02:40:59 | NULL | NULL |
| 13 | u13 | 13 | 2025-03-02 02:40:59 | NULL | NULL |
+-----+------+------+---------------------+-------------+---------+
start transaction;
select * from users where age = 3 lock in share mode;
select THREAD_ID, OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA
from performance_schema.data_locks;
+-----------+---------------+-------------+---------------+-----------+---------------+-----------+
| THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_DATA |
+-----------+---------------+-------------+---------------+-----------+---------------+-----------+
| 82 | test | users | NULL | TABLE | IS | NULL |
| 82 | test | users | idx_users_age | RECORD | S | 3, 3 | -- 临键锁
| 82 | test | users | PRIMARY | RECORD | S,REC_NOT_GAP | 3 |
| 82 | test | users | idx_users_age | RECORD | S,GAP | 7, 7 |
+-----------+---------------+-------------+---------------+-----------+---------------+-----------+
3.2.4 情形三
使用唯一索引进行范围查询时,会加行锁和临键锁。
select * from users;
/*
+-----+------+------+---------------------+-------------+---------+
| uid | name | age | create_time | update_time | new_col |
+-----+------+------+---------------------+-------------+---------+
| 1 | u1 | 1 | 2025-03-02 02:40:59 | NULL | NULL |
| 3 | u3 | 3 | 2025-03-02 02:40:59 | NULL | NULL |
| 7 | u7 | 7 | 2025-03-02 02:40:59 | NULL | NULL |
| 9 | u9 | 9 | 2025-03-02 02:40:59 | NULL | NULL |
| 13 | u13 | 13 | 2025-03-02 02:40:59 | NULL | NULL |
+-----+------+------+---------------------+-------------+---------+
*/
start transaction;
select * from users where uid >= 9 lock in share mode;
/*
+-----+------+------+---------------------+-------------+---------+
| uid | name | age | create_time | update_time | new_col |
+-----+------+------+---------------------+-------------+---------+
| 9 | u9 | 9 | 2025-03-02 02:40:59 | NULL | NULL |
| 13 | u13 | 13 | 2025-03-02 02:40:59 | NULL | NULL |
+-----+------+------+---------------------+-------------+---------+
*/
select THREAD_ID, OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA
from performance_schema.data_locks;
/* 对于 "="项加行锁,对于 ">" 项加临键锁,uid=13为最后一项,还需对(13,+∞)加临键锁
+-----------+---------------+-------------+------------+-----------+---------------+------------------------+
| THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_DATA |
+-----------+---------------+-------------+------------+-----------+---------------+------------------------+
| 82 | test | users | NULL | TABLE | IS | NULL |
| 82 | test | users | PRIMARY | RECORD | S,REC_NOT_GAP | 9 |
| 82 | test | users | PRIMARY | RECORD | S | supremum pseudo-record |
| 82 | test | users | PRIMARY | RECORD | S | 13 |
+-----------+---------------+-------------+------------+-----------+---------------+------------------------+
*/
4. 补充
4.1 常用术语
表锁:共享锁(S,读锁),排他锁/独占锁(X,写锁)
意向锁:意向共享锁(IS),意向排他/独占锁(IX)
元数据锁:共享锁(SHARED_READ:读锁,SHARED_WRITE:写锁),排他/独占锁(EXCLUSIVE)
锁主要有两种类型:共享锁和排他锁(独占锁);这两种锁在不同层面出现:表锁,意向锁,元数据锁,行锁,间隙锁,临键锁。导致不同的特点。最好是具体讨论,而且需要注意有些用在处理并发事务层面的,有些是应用在会话层面的。
4.2 参考
https://www.bilibili.com/video/BV1Kr4y1i7ru
936

被折叠的 条评论
为什么被折叠?



