行级锁
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:行锁(Record Lock)、间隙锁(Gap Lock)和临键锁(Next-Key Lock)
行锁(Record Lock)
介绍
行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在 RC、RR隔离级别下都支持。比如我们对id为34的一行数据进行加锁,那么其他事物就不可以对34进行update和delete。

共享锁(S)与排他锁(X)
InnoDB实现了以下两种类型的行锁:
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
- 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
两种行锁的兼容情况如下:

常见的SQL语句,在执行时,所加的行锁如下:
SQL | 行锁类型 | 说明 |
---|---|---|
INSERT … | 排它锁 | 自动加锁 |
UPDATE … | 排它锁 | 自动加锁 |
DELETE … | 排它锁 | 自动加锁 |
SELECT(正常) | 不加任何锁 | |
SELECT … LOCK IN SHARE MODE | 共享锁 | 需要手动在SELECT之后加LOCK IN SHARE MODE |
SELECT … FOR UPDATE | 排它锁 | 需要手动在SELECT之后加FOR UPDATE |
查看及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
演示
普通的select语句,执行时,不会加锁:
-- 窗口A
mysql> select * from stu where id=1;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | tom | 1 |
+----+------+-----+
1 row in set (0.00 sec)
-- 窗口B
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
Empty set (0.02 sec)
select...lock in share mode
,加共享锁,共享锁与共享锁之间兼容。
-- 窗口A
mysql> begin; # 发起事务
Query OK, 0 rows affected (0.00 sec)
mysql> select * from stu where id=1 lock in share mode; # 加共享锁
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | tom | 1 |
+----+------+-----+
1 row in set (0.00 sec)
-- ######################################################################
-- 窗口C :
-- lock_type=TABLE 表示 表级锁
-- lock_type=RECORD 表示 行级锁
-- lock_mode=S 表示 共享锁
-- lock_mode=REC_NOT_GAP 表示 没有间隙
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+---------------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+---------------+-----------+
| temp | stu | NULL | TABLE | IS | NULL |
| temp | stu | PRIMARY | RECORD | S,REC_NOT_GAP | 1 |
+---------------+-------------+------------+-----------+---------------+-----------+
2 rows in set (0.00 sec)
-- ######################################################################
-- 窗口B:
-- 对于不加锁的select和加共享锁的select都可以执行,操作id=1的数据
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from stu where id=1;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | tom | 1 |
+----+------+-----+
1 row in set (0.00 sec)
mysql> select * from stu where id=1 lock in share mode;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | tom | 1 |
+----+------+-----+
1 row in set (0.00 sec)
共享锁与排他锁之间互斥
-- 窗口1:对id=1的数据加共享锁
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from stu where id=1 lock in share mode;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | tom | 1 |
+----+------+-----+
1 row in set (0.00 sec)
###############################################################
-- 窗口2:修改id=1的数据(带着排它锁)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update stu set name = 'jerry' where id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
############################################################################
-- 要等到窗口1的数据commit之后,窗口2才能修改
mysql> commit; -- 窗口1 commit之后解锁
Query OK, 0 rows affected (0.00 sec)
-- 窗口2 的数据才能修改,否则超时
mysql> update stu set name = 'jerry' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
排它锁与排他锁之间互斥
-- 窗口1 修改id=1 的数据
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update stu set name = 'jerry2' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- 窗口3 查看锁的情况
-- lock_type=RECORD, lock_mode=X 表示使用了排它锁,锁主的数据 lock_data=1
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+---------------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+---------------+-----------+
| temp | stu | NULL | TABLE | IX | NULL |
| temp | stu | PRIMARY | RECORD | X,REC_NOT_GAP | 1 |
+---------------+-------------+------------+-----------+---------------+-----------+
-- 窗口2 修改id=1的数据
mysql> update stu set name = 'marry' where id=1;
# 光标一直闪烁,表示sql被阻塞
-- 窗口1 提交事务,释放锁
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
-- 窗口2 修改才能成功
mysql> update stu set name = 'marry' where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
无索引行锁 升级为表锁
-- 窗口1:对name=marry的数据进行修改,也就是id=1的数据
mysql> select * from stu;
+----+-------+-----+
| id | name | age |
+----+-------+-----+
| 1 | marry | 1 |
| 3 | cat | 3 |
| 8 | rose | 8 |
| 11 | jetty | 11 |
| 19 | lily | 19 |
| 25 | luci | 25 |
+----+-------+-----+
6 rows in set (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update stu set name='bob' where name='marry';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
###################################################################
-- 窗口2 :修改id=2的数据
mysql> update stu set name = 'xiexie' where id=2;
# 光标一直闪烁,表示sql被阻塞
#################################################################
-- 窗口3:查询数据库锁的情况———— 发现整张表都被锁了 lock_type=RECORD,lock_mode=X
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+-----------+------------------------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+-----------+------------------------+
| temp | stu | NULL | TABLE | IX | NULL |
| temp | stu | PRIMARY | RECORD | X | supremum pseudo-record |
| temp | stu | PRIMARY | RECORD | X | 3 |
| temp | stu | PRIMARY | RECORD | X | 8 |
| temp | stu | PRIMARY | RECORD | X | 11 |
| temp | stu | PRIMARY | RECORD | X | 19 |
| temp | stu | PRIMARY | RECORD | X | 25 |
| temp | stu | PRIMARY | RECORD | X | 1 |
+---------------+-------------+------------+-----------+-----------+------------------------+
8 rows in set (0.01 sec)
##################################################################################################
-- 窗口1 提交事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
###################################################################################################
-- 窗口2 : 修改成功
mysql> update stu set name = 'xiexie' where id=2;
Query OK, 1 row affected (9.17 sec)
Rows matched: 1 Changed: 1 Warnings: 0
##################################################################################################
-- 窗口3 查看锁的情况,在窗口1提交事务之后,都解锁了
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
Empty set (0.00 sec)
之所以这样是因为,InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时 就会升级为表锁。如果想优化,可以对上面的name字段添加索引
间隙锁(Gap Lock)和临键锁(Next-Key Lock)
介绍
间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。
所谓间隙就是两个数之间的范围,比如(18, 29) 又比如(29,34),不包含两边的值。这样就无法再把数据insert进入这个间隙了。

临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。
比如,给id=34的锁加临键锁,那么id=34的会加锁,它之后的间隙(如(29,34))都会加锁

默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜索和索引扫描,以防止幻读。
- 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。
- 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-keylock 退化为间隙锁。
- 索引上的范围查询(唯一索引)–会访问到不满足条件的第一个值为止。
演示
索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。
-- 窗口1 查询数据
mysql> select * from stu;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | xiexie | 1 |
| 3 | cat | 3 |
| 8 | rose | 8 |
| 11 | jetty | 11 |
| 19 | lily | 19 |
| 25 | luci | 25 |
+----+--------+-----+
6 rows in set (0.00 sec)
############################################################
-- 窗口1 开启事务,更新id=10的数据
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update stu set age =10 where id = 10;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
#############################################################################
-- 窗口3:查看数据库锁的情况
-- lock_mode=GAP 表示开启了间隙锁
-- lock_data = 11 表示11之后的间隙{(8,11)}的无法插入新的值
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+-----------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+-----------+-----------+
| temp | stu | NULL | TABLE | IX | NULL |
| temp | stu | PRIMARY | RECORD | X,GAP | 11 |
+---------------+-------------+------------+-----------+-----------+-----------+
2 rows in set (0.00 sec)
##########################################################################################
-- 窗口2 尝试插入间隙(8,11)失败
mysql> insert into stu value(9,'ming',11);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
-- 窗口2 但是插入间隙(3,8)成功
mysql> insert into stu value(5,'ming',11);
Query OK, 1 row affected (0.00 sec)
mysql> select * from stu;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | xiexie | 1 |
| 3 | cat | 3 |
| 5 | ming | 11 |
| 8 | rose | 8 |
| 11 | jetty | 11 |
| 19 | lily | 19 |
| 25 | luci | 25 |
+----+--------+-----+
7 rows in set (0.00 sec)
索引上的等值查询(非唯一普通索引),向右遍历(向数值越大的方向)时最后一个值不满足查询需求时,会对该值加临键锁,对被搜索的值也加临键锁
分析一下:
我们知道InnoDB的B+树索引,叶子节点是有序的双向链表。 假如,我们要根据这个二级索引查询值为18的数据,并加上共享锁,我们是只锁定18这一行就可以了吗? 并不是,因为是非唯一索引,这个结构中可能有多个18的存在,所以,在加锁时会继续往后找,找到一个不满足条件的值(当前案例中也就是29)。此时会对18加临键锁,并对29之前的间隙加锁。
- 此时会对18加临键锁:18这行数据会加锁,(16,18)的区间会有间隙锁
·
-- 窗口3 :查询stu表的信息
mysql> select * from stu order by age;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | xiexie | 1 |
| 3 | cat | 3 |
| 8 | rose | 8 |
| 5 | ming | 11 |
| 11 | jetty | 11 |
| 19 | lily | 19 |
| 25 | luci | 25 |
+----+--------+-----+
7 rows in set (0.00 sec)
-- 窗口3 给age创建索引
mysql> create index idx_stu_age on stu(age);
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
######################################################################
-- 窗口1:开启事务,用共享锁查询age=11的数据
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from stu where age=11 lock in share mode;
+----+-------+-----+
| id | name | age |
+----+-------+-----+
| 5 | ming | 11 |
| 11 | jetty | 11 |
+----+-------+-----+
2 rows in set (0.01 sec)
###############################################################################
-- 窗口3:查看锁的情况
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+-------------+-----------+---------------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+-------------+-----------+---------------+-----------+
| temp | stu | NULL | TABLE | IS | NULL |
| temp | stu | idx_stu_age | RECORD | S | 11, 5 |
| temp | stu | idx_stu_age | RECORD | S | 11, 11 |
| temp | stu | PRIMARY | RECORD | S,REC_NOT_GAP | 11 |
| temp | stu | PRIMARY | RECORD | S,REC_NOT_GAP | 5 |
| temp | stu | idx_stu_age | RECORD | S,GAP | 19, 19 |
+---------------+-------------+-------------+-----------+---------------+-----------+
6 rows in set (0.00 sec)
-- index_name=idx_stu_age的情况:变成了临建锁
-- -- | RECORD | S | 11, 11 | 用共享锁锁住了 age =11 id=11的数据
-- -- RECORD | S | 11, 5 |用共享锁锁住了 age =11 id=5的数据
-- -- | RECORD | S,GAP | 19, 19 | 表示锁住了age=19,id=19 之前的间隙(GAP 间隙)
#####################################################################################################
-- 窗口2
-- 向 age为(11,19)的区间插入一条数据:无法插入
mysql> insert into stu value(2,'hong',18);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
-- 向age为(8,11)的区间插入一条数据:无法插入
mysql> insert into stu value(30,'bai',9);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
-- 向age为(3,8)的区间插入一条数据:成功插入
mysql> insert into stu value(30,'bai',7);
Query OK, 1 row affected (0.00 sec)
-- 修改age=11的数据,无法修改
mysql> update stu set age = 12 where age=11;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
-- 修改id=11的数据,无法修改
mysql> update stu set age = 12 where id=11;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
-- 修改id=19的数据,无法修改
mysql> update stu set age = 12 where id=19;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
索引上的范围查询(唯一索引)会访问到不满足条件的第一个值为止。
-- 窗口1 查询id>=19的数据,并加共享锁
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from stu where id>=19 lock in share mode;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 19 | lily | 19 |
| 25 | luci | 25 |
| 30 | bai | 7 |
+----+------+-----+
3 rows in set (0.00 sec)
#######################################################################################################
-- 窗口3 查询锁的情况
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+---------------+------------------------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+---------------+------------------------+
| temp | stu | NULL | TABLE | IS | NULL |
| temp | stu | PRIMARY | RECORD | S,REC_NOT_GAP | 19 |
| temp | stu | PRIMARY | RECORD | S | supremum pseudo-record |
| temp | stu | PRIMARY | RECORD | S | 25 |
| temp | stu | PRIMARY | RECORD | S | 30 |
+---------------+-------------+------------+-----------+---------------+------------------------+
5 rows in set (0.00 sec)
查询的条件为id>=19,并添加共享锁。 此时我们可以根据数据库表中现有的数据,将数据分为三个部分:
[19],(19,25],(25,30],(30, supremum pseudo-record]–>(30,+∞]
所以数据库数据在加锁是,就是将19加了行锁,25的临键锁,30的临键锁和正无穷的键锁
-- 窗口2 测试各个区间的锁的情况
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into stu value(22,'hei',22);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> update stu set age = 12 where id=19;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into stu value(100,'hei',22);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> update stu set age = 12 where id=25;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into stu value(18,'hei',22);
Query OK, 1 row affected (0.00 sec)
表级锁
对于表级锁,主要分为以下三类:表锁、元数据锁(meta data lock,MDL)、意向锁
表锁
对于表锁,分为两类:表共享读锁(read lock)、表独占写锁(write lock)
语法:
- 加锁:
lock tables 表名... read/write
- 释放锁:
unlock tables
/ 客户端断开连接
表共享读锁(read lock)
读锁的特点:

左侧为客户端一,对指定表加了读锁,不会影响右侧客户端二的读,但是会阻塞右侧客户端的写。
-- 客户端一:加读锁
mysql> lock tables stu read;
Query OK, 0 rows affected (0.00 sec)
############################################################
-- 客户端二 : 可以读取数据
mysql> select * from stu;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | xiexie | 1 |
| 3 | cat | 3 |
| 5 | ming | 11 |
| 6 | hong | 1 |
| 7 | hong | 20 |
| 8 | rose | 8 |
| 11 | jetty | 11 |
| 18 | hei | 22 |
| 19 | lily | 19 |
| 25 | luci | 25 |
| 30 | bai | 7 |
+----+--------+-----+
11 rows in set (0.00 sec)
-- 客户端二:不可以新增修改和删除数据
mysql> delete from stu where id =25;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
表独占写锁(write lock)

左侧为客户端一,对指定表加了写锁,会阻塞右侧客户端的读和写。
-- 客户端一:加读锁
mysql> lock tables stu write;
Query OK, 0 rows affected (0.00 sec)
-- 客户端二:读操作和写操作都会阻塞
mysql> select * from stu;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> delete from stu where id =25;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
元数据锁
meta data lock , 元数据锁,简写MDL。
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。
这里的元数据,大家可以简单理解为就是一张表的表结构。 也就是说,某一张表涉及到未提交的事务时,是不能够修改这张表的表结构的。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL共享锁(读锁);当对表结构进行变更操作的时候,加MDL排它锁(写锁)。
- 共享锁(读锁):这里的读锁是相对表的结构来说的,增删改查相当于读取表结构
- 排它锁(写锁):这里的读锁是相对表的结构来说的,alter语句相当于修改表结构
查看元数据所的情况
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;
常见的SQL操作时,所添加的元数据锁:
对应SQL | 锁类型 | 说明 |
---|---|---|
lock tables xxx read / write | SHARED_READ_ONLY / SHARED_NO_READ_WRITE | |
select 、select … lock in share mode | SHARED_READ 读锁(共享) | 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥 |
insert 、update、delete、select … for update | SHARED_WRITE 写锁(排他) | 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥 |
alter table … | EXCLUSIVE | 与其他的MDL都互斥 |
当执行SELECT、INSERT、UPDATE、DELETE等语句时,添加的是元数据共享锁(SHARED_READ /SHARED_WRITE),之间是兼容的。
当执行SELECT语句时,添加的是元数据共享锁(SHARED_READ),会阻塞元数据排他锁(EXCLUSIVE),之间是互斥的。
-- 窗口1 开启使用,执行一条查询语句
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from stu;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | xiexie | 1 |
| 3 | cat | 3 |
| 5 | ming | 11 |
| 6 | hong | 1 |
| 7 | hong | 20 |
| 8 | rose | 8 |
| 11 | jetty | 11 |
| 18 | hei | 22 |
| 19 | lily | 19 |
| 25 | luci | 25 |
| 30 | bai | 7 |
+----+--------+-----+
11 rows in set (0.00 sec)
################################################################
-- 窗口3 查看元数据锁的情况,有一条 共享读锁 lock_type=SHARED_READ
mysql> select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;
+-------------+--------------------+----------------+-------------+---------------+
| object_type | object_schema | object_name | lock_type | lock_duration |
+-------------+--------------------+----------------+-------------+---------------+
| TABLE | temp | stu | SHARED_READ | TRANSACTION |
| TABLE | performance_schema | metadata_locks | SHARED_READ | TRANSACTION |
+-------------+--------------------+----------------+-------------+---------------+
###########################################################################
-- 窗口1 修改一条数据
mysql> update stu set age = 10 where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
###############################################################################
-- 窗口3 查看元数据锁的情况,有一条共享写锁
mysql> select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;
+-------------+--------------------+----------------+--------------+---------------+
| object_type | object_schema | object_name | lock_type | lock_duration |
+-------------+--------------------+----------------+--------------+---------------+
| TABLE | temp | stu | SHARED_READ | TRANSACTION |
| TABLE | temp | stu | SHARED_WRITE | TRANSACTION |
| TABLE | performance_schema | metadata_locks | SHARED_READ | TRANSACTION |
+-------------+--------------------+----------------+--------------+---------------+
3 rows in set (0.01 sec)
####################################################################################################
-- 窗口2 删除一条数据,删除成功,又新增一条共享写锁
mysql> delete from stu where id = 30;
Query OK, 1 row affected (0.00 sec)
-- 窗口 3
mysql> select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;
+-------------+--------------------+----------------+--------------+---------------+
| object_type | object_schema | object_name | lock_type | lock_duration |
+-------------+--------------------+----------------+--------------+---------------+
| TABLE | temp | stu | SHARED_READ | TRANSACTION |
| TABLE | temp | stu | SHARED_WRITE | TRANSACTION |
| TABLE | temp | stu | SHARED_WRITE | TRANSACTION |
| TABLE | performance_schema | metadata_locks | SHARED_READ | TRANSACTION |
+-------------+--------------------+----------------+--------------+---------------+
##############################################################################################
-- 窗口2 修改表结构,无法修改,会被阻塞
mysql> alter table stu add column gender int;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
意向锁
介绍
为什么需要意向锁
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
假如没有意向锁:
客户端一对表加了行锁后,客户端二如何给表加表锁呢,来通过示意图简单分析一下:
首先客户端一,开启一个事务,然后执行DML操作,在执行DML语句时,会对涉及到的行加行锁。

当客户端二,想对这张表加表锁时(lock tables xxx read/write),会检查当前表是否有对应的行锁,如果没有,则添加表锁,此时就会从第一行数据,检查到最后一行数据,效率较低。

有了意向锁之后 :
客户端一,在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁。

而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,而不用逐行判断行锁情况了。

分类
意向共享锁(IS):由语句select ... lock in share mode
添加 。与表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
意向排他锁(IX):由insert、update、delete、select...for update
添加 。与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。
一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。
查看意向锁及行锁的加锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
演示
意向共享锁与表的读锁是兼容的
-- 客户端一:添加共享锁
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from stu where id=1 lock in share mode;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | xiexie | 10 |
+----+--------+-----+
1 row in set (0.00 sec)
-- 这里 lock_mode=IS 就是意向共享锁
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+---------------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+---------------+-----------+
| temp | stu | NULL | TABLE | IS | NULL |
| temp | stu | PRIMARY | RECORD | S,REC_NOT_GAP | 1 |
+---------------+-------------+------------+-----------+---------------+-----------+
###############################################################################################
-- 客户端二 添加表所成功
mysql> lock tables stu read;
Query OK, 0 rows affected (0.00 sec)
###############################################################################################
-- 客户端二:添加读锁被阻塞
mysql> lock tables stu write;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
意向排他锁与表读锁、写锁都是互斥的
-- 客户端一:添加意向排它锁
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update stu set age = 14 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- lock_mode= IX 意向排他锁
mysql> select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
+---------------+-------------+------------+-----------+---------------+-----------+
| object_schema | object_name | index_name | lock_type | lock_mode | lock_data |
+---------------+-------------+------------+-----------+---------------+-----------+
| temp | stu | NULL | TABLE | IX | NULL |
| temp | stu | PRIMARY | RECORD | X,REC_NOT_GAP | 1 |
+---------------+-------------+------------+-----------+---------------+-----------+
2 rows in set (0.00 sec)
######################################################################################################
-- 客户端二:添加表读锁和表写锁都被阻塞
mysql> lock tables stu read;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> lock tables stu write;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
全局锁
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。
其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
全库逻辑备份加全局锁的原因
我们一起先来分析一下不加全局锁,可能存在的问题。
假设在数据库中存在这样三张表: tb_stock 库存表,tb_order 订单表,tb_orderlog 订单日志表。

在进行数据备份时,先备份了tb_stock库存表
然后接下来,在业务系统中,执行了下单操作,扣减库存,生成订单(更新
tb_stock
表,插入tb_order
表)。然后再执行备份
tb_order
表的逻辑。业务中执行插入订单日志操作
最后,又备份了
tb_orderlog
表此时备份出来的数据,是存在问题的。因为备份出来的数据,tb_stock表与tb_order表的数据不一致(有最新操作的订单信息,但是库存数没减)。
再来分析一下加了全局锁后的情况

对数据库进行进行逻辑备份之前,先对整个数据库加上全局锁,一旦加了全局锁之后,其他的DDL、DML全部都处于阻塞状态,但是可以执行DQL语句,也就是处于只读状态,而数据备份就是查询操作。那么数据在进行逻辑备份的过程中,数据库中的数据就是不会发生变化的,这样就保证了数据的一致性和完整性。
DDL(Data Definition Language)语句:
数据定义语言
,主要是进行定义/改变表的结构、数据类型、表之间的链接等操作。常用的语句关键字有 CREATE、DROP、ALTER 等。DML(Data Manipulation Language)语句:
数据操纵语言
,主要是对数据进行增加、删除、修改操作。常用的语句关键字有 INSERT、UPDATE、DELETE 等。DQL(Data Query Language)语句:
数据查询语言
,主要是对数据进行查询操作。常用关键字有 SELECT、FROM、WHERE 等。DCL(Data Control Language)语句:
数据控制语言
,主要是用来设置/更改数据库用户权限。常用关键字有 GRANT、REVOKE 等。一般人员很少用到DCL语句。
语法
# 加全局锁
flush tables with read lock ;
# 数据备份
# # 备份整个数据库
mysqldump -u username -p your_database > your_backup_file.sql
# # 备份数据库中的表
mysqldump -u username -p your_database table1 table2 > your_backup_file.sql
# 释放锁
unlock tables ;
数据备份演示
第一步:给数据库加全局锁
-- 客户端一:加全局锁
mysql> flush tables with read lock;
Query OK, 0 rows affected (0.07 sec)
-- 客户端二:可以查询
mysql> select * from stu;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 1 | xiexie | 14 |
| 3 | cat | 3 |
| 5 | ming | 11 |
| 6 | hong | 1 |
| 7 | hong | 20 |
| 8 | rose | 8 |
| 11 | jetty | 11 |
| 18 | hei | 22 |
| 19 | lily | 19 |
| 25 | luci | 25 |
+----+--------+-----+
10 rows in set (0.00 sec)
-- 客户端二:但是不能DDL或者DML
mysql> alter table stu add column gender int;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> delete from stu where id = 30;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
第二步:退出MySQL,在控制台进行备份
如果在MySQL命令行里面会报错误
mysql> mysqldump -uroot -p1234 temp stu > stu.sql;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'mysqldump -u root -p 1234 temp stu > stu.sql' at line 1
要在命令行里面运行
[root@centos01 ~]# mysqldump -uroot -p1234 temp stu > stu.sql;
mysqldump: [Warning] Using a password on the command line interface can be insecure.
[root@centos01 ~]# ll
总用量 32
-rw-r--r-- 1 root root 18623 11月 17 2021 03.jpg
-rw-------. 1 root root 1246 9月 15 2021 anaconda-ks.cfg
drwxr-xr-x 3 root root 18 11月 17 2021 docker-volumn
-rw-r--r-- 1 root root 45 11月 18 2021 index.html
-rw-r--r-- 1 root root 0 9月 25 16:24 mysqldump
-rw-r--r-- 1 root root 2073 9月 25 16:25 stu.sql
释放锁
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
全局锁特点
如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
在InnoDB引擎中,我们可以在备份时加上参数 –single-transaction 参数来完成不加锁的一致性数据备份。
mysqldump --single-transaction -uroot –p123456 itcast > itcast.sql
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com