7 锁
7.1 概述
- 介绍
- 锁是计算机协调多个进程或者线程并发访问某一资源的机制。
- 数据也是一种供许多用户共享的资源。如何保持数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
- 分类
- 全局锁:锁定数据库中所有表
- 表级锁:每次操作锁住整张表
- 行级锁:每次操作锁住对应的行数据
7.2 全局锁
- 介绍
- 全局锁是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML和DDL语句,已经更新操作的事务提交语句将会被阻塞。
- 其典型使用场景是做全局的逻辑备份,对所有的表进行锁定,从而获得一致性视图,保证数据的完整性。
- 一致性数据备份
- 进行全局锁
mysql> flush tables with read lock;
- 可以查,但是不可以改了
mysql> update weather_service set summary_ts = '2021-12-25 10:23:50' where id = 1;
- 备份下
[root@hadoop ~]# mysqldump -uroot -pxxxxxxxx weather_service > /home/fenfen/MySQL/weather_service.sql
mysqldump: [Warning] Using a password on the command line interface can be insecure.
- 释放锁
mysql> unlock tables;
- 在InnoDB引擎中,我们可以在备份时加上参数–single-transaction参数来完成不加锁的一致性数据备份
mysqldump --single-transaction -uroot -pxxxxxxxx itcast > itcast.sql
- 特点
- 如果在主库上备份,那么在备份期间都不能执行更新,业务基本就得停止。
- 在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟 – 这个估计后面主从复制还会讲
7.3 表级锁
- 介绍
- 每次操作锁住整张表。锁表粒度大,发生锁冲突的概率最高,并发度最低。
- 表级锁分类
- 表锁
- 表共享读锁
- 表独占写锁
- 元数据锁
- 意向锁
- 表锁
- 语法
lock tables 表名... read/write
unlock tables/直接关闭客户端
- 读锁案例
-- 客户端1
-- 设置读锁
mysql> lock tables course read;
Query OK, 0 rows affected (0.00 sec)
-- 读
mysql> select * from course;
+----+-------------+
| id | name |
+----+-------------+
| 6 | ES |
| 1 | javaEE |
| 3 | MySQL |
| 5 | Oracle |
| 2 | SpringBoot |
| 4 | SpringCloud |
+----+-------------+
-- 写
mysql> update course set name = 'GO' where id = 6;
ERROR 1099 (HY000): Table 'course' was locked with a READ lock and can't be updated
-- 释放表锁
unlock tables;
-- 客户端2
-- 执行读语句可以,但是执行写的语句,发现阻塞了,除非界面1释放表锁
mysql> update course set name = 'GO' where id = 6;
- 写锁案例
-- 客户端1
-- 设置写锁
mysql> lock tables course write;
Query OK, 0 rows affected (0.00 sec)
-- 读写都可以
mysql> select * from course;
+----+-------------+
| id | name |
+----+-------------+
| 6 | GO |
| 1 | javaEE |
| 3 | MySQL |
| 5 | Oracle |
| 2 | SpringBoot |
| 4 | SpringCloud |
+----+-------------+
6 rows in set (0.00 sec)
mysql> update course set name = 'ES' where id = 6;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- 客户端2
-- 读写都不行,处于阻塞状态
mysql> select * from course;
mysql> update course set name = 'GO' where id = 6;
- 元数据锁
-
介绍
-
MDL加锁过程是系统自己控制的,访问一张表的时候就自动加上了
-
元数据理解为表结构,MDL锁主要的作用就是维护表元数据的数据一致性,在表上有活动事务的时候未提交,不可以对元数据进行写入操作。
-
目的是:为了避免DML和DDL冲突,保证读写的正确性。即当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)
-
怎么理解呢?这么理解:就是你改表结构的时候,是不能有别的未提交事务存在的,因为MDL写锁和其他共享锁都是互斥的,提交了就不阻塞了。
-
- 案例
- 查看元数据锁
select object_type,object_schema,object_name,lock_type,lock_duration from
performance_schema.metadata_locks ;
-- 这个时候就有锁,有了对两个事务增删改查的后的锁
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 | itcast | course | SHARED_READ | TRANSACTION |
| TABLE | itcast | course | SHARED_READ | TRANSACTION |
| TABLE | performance_schema | metadata_locks | SHARED_READ | TRANSACTION
+-------------------+--------------------+----------------+---------------------+---------------+
- 意向锁
- 场景
- 为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
- 作用
- 直接去判断意向锁和表锁兼不兼容,表锁不用一行一行看有没有行锁了,而意向锁是自动添加的
-
分类
- 意向共享锁IS:与表锁共享锁(read)兼容,和表锁排他锁(write)互斥。
- 意向锁排他锁IX:与表锁共享锁(read)和表锁排他锁(write)都互斥
-
查看意向锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
-
增加意向锁
- 意向共享锁IS,有语句select …lock in share mode添加
- 意向排他锁IX:由insert、update、delete、select … for update添加
-
意向共享锁案例
-- 客户端1
-- 查询,加上一个行锁共享锁
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from course where id = 6 lock in share 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 |
+---------------+-------------+------------+-----------+---------------+-----------+
| itcast | course | NULL | TABLE | IS | NULL |
| itcast | course | PRIMARY | RECORD | S,REC_NOT_GAP | 6 |
+---------------+-------------+------------+-----------+---------------+-----------+
-- 客户端2
-- 加表读锁成功,是因为意向共享锁和表锁共享锁(read)是兼容的
mysql> lock tables course read;
Query OK, 0 rows affected (0.00 sec)
-- 加表写锁失败,原因是意向共享锁和表锁排他锁(write)不兼容,即互斥
mysql> lock tables course write;
-- 此时事务客户端1事务提交就锁释放了
mysql> lock tables course write;
Query OK, 0 rows affected (9.06 sec)
- 意向排他锁案例
-- 客户端1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- 更新
mysql> update course set name = 'PHP' where id = 6;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- 此时查看下意向锁情况,第一行显示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 |
+---------------+-------------+------------+-----------+---------------+-----------+
| itcast | course | NULL | TABLE | IX | NULL |
| itcast | course | PRIMARY | RECORD | X,REC_NOT_GAP | 6 |
+---------------+-------------+------------+-----------+---------------+-----------+
-- 客户端2
-- -- 加表读锁失败,原因是意向排他锁和表锁共享锁(read)不兼容,即互斥
mysql> lock tables course read;
-- 加表写锁失败,原因是意向共享锁和表锁排他锁(write)不兼容,即互斥
mysql> lock tables course write;
-- 此时事务客户端1事务提交就锁释放了
mysql> lock tables course write;
Query OK, 0 rows affected (3.96 sec)
7.4 行级锁
- 介绍
- 行级锁,每次操作锁住的是对应的行数据
- 锁定粒度最小,发生锁冲突的概率最低,并发率最高。
- 应用在InnoDB存储引擎中,MyISAM不用
- 分类
- 行锁:锁住单个记录的锁,防止事务对其进行update和delete操作。在RC读已提交,RR可重复读隔离级别下都支持
- 间隙锁:锁定索引记录间隙(但不包含该记录),确保索引的间隙不变,防止其他事务在这个间隙中insert,出现幻读。在RR可重复读隔离级别下都支持
- 临键锁:行锁和间隙锁组合,同时锁住数据,并锁住数据的前面的间隙GAP。在RR可重复读的隔离级别下支持。
- 行锁
- 分类
- 共享锁S:允许事务读取一行,但是阻止其他事务获得相同数据集的排他锁。共享锁和共享锁是兼容的,共享锁和排他锁是互斥
- 排他锁X:允许获取排他锁的事务更新数据,防止其他事务获得相同数据集的共享锁和排他锁。
- 怎么记:就是共享和共享可以,其他都不行!和上面表级锁一个逻辑!有点难!
- InnoDB的行锁是针对与索引加的锁,不通过索引条件检索数据,那么InnoDB将会对表中的所有记录加锁,就会升级成表锁
- 查看意向锁及行锁的加锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
-
select案例
- select压根就没有加任何的行锁,因此客户端2也开启事务的话,可是能查的!
- select压根就没有加任何的行锁,因此客户端2也开启事务的话,可是能查的!
-
select …lock in share mode案例
-- 客户端1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from course where id = 6 lock in share mode;
+----+------+
| id | name |
+----+------+
| 6 | PHP |
+----+------+
1 row in set (0.00 sec)
-- 第二行中的S代表的就是共享锁
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 |
+---------------+-------------+------------+-----------+---------------+-----------+
| itcast | course | NULL | TABLE | IS | NULL |
| itcast | course | PRIMARY | RECORD | S,REC_NOT_GAP | 6 |
+---------------+-------------+------------+-----------+---------------+-----------+
2 rows in set (0.00 sec)
-- 客户端2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- 同样用客户端1的查询share mode查询也是可以的,因为共享和共享式兼容的!
mysql> select * from course where id = 6 lock in share mode;
+----+------+
| id | name |
+----+------+
| 6 | PHP |
+----+------+
1 row in set (0.00 sec
-- 再查下行锁情况,发现id为6的加了两个共享锁!
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 |
+---------------+-------------+------------+-----------+---------------+-----------+
| itcast | course | NULL | TABLE | IS | NULL |
| itcast | course | PRIMARY | RECORD | S,REC_NOT_GAP | 6 |
| itcast | course | NULL | TABLE | IS | NULL |
| itcast | course | PRIMARY | RECORD | S,REC_NOT_GAP | 6 |
+---------------+-------------+------------+-----------+---------------+-----------+
4 rows in set (0.00 sec)
-- 后面记得提交事务,提交后发现s共享锁没了
- update行锁排他锁案例
-- 客户端1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- 修改
mysql> update course set name = 'Go' where id = 6;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings:
-- 客户端2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- 读可以,发现没变,客户端1 中更改的没变,也验证了事务的RR隔离性,出现了幻读。
mysql> select * from course;
+----+-------------+
| id | name |
+----+-------------+
| 1 | javaEE |
| 3 | MySQL |
| 5 | Oracle |
| 6 | PHP |
| 2 | SpringBoot |
| 4 | SpringCloud |
+----+-------------+
6 rows in set (0.00 sec)
-- 写就不可以,因为排他锁和排他锁互斥
mysql> update course set name = 'Go' where id = 6;
-- 此时客户端1的事务提交后就解除阻塞了
mysql> update course set name = 'Go' where id = 6;
Query OK, 0 rows affected (8.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
- 行锁升级成表锁的案例
-- 客户端1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update course set name = 'Redis' where name = 'Go';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- 客户段2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- 即使是不同的数据行,客户段1中update的name字段由于没有索引,将行锁升级成表锁,因为你更新id字段也是不行
mysql> update course set name = 'Hive' where id = 5;
-- 当客户端1中的事务提交,就接触阻塞了
mysql> update course set name = 'Hive' where id = 5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
- 间隙锁
-
介绍:InnoDB在RR事务隔离级别的时候,默认使用next-key锁进行搜索和索引扫描,以防止幻读
- 索引上的等值查询(唯一索引),给不存在的记录加锁,优化为间隙锁
- 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询条件时,next_key lock 退化为间隙锁
- 索引上的范围查询(唯一索引)-- 会访问到不满足条件的第一个值为止
-
查看行锁和意向锁的情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
- 查询不存在记录(唯一索引)案例
-- 客户端1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from course;
+----+-------------+
| id | name |
+----+-------------+
| 1 | javaEE |
| 3 | MySQL |
| 5 | Oracle |
| 6 | Redis |
| 9 | Spark |
| 2 | SpringBoot |
| 4 | SpringCloud |
+----+-------------+
7 rows in set (0.00 sec)
mysql> update course set name = 'JS' where id = 7;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
-- 第二行中lock_type时record时行锁的意思,lock_mode是X表示是排他锁,GAP指的是间隙锁,锁的是9之前的间隙,即6-9的间隙
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 |
+---------------+-------------+------------+-----------+---------------+-----------+
| itcast | course | NULL | TABLE | IX | NULL |
| itcast | course | PRIMARY | RECORD | X,GAP | 9 |
+---------------+-------------+------------+-----------+---------------+-----------+
-- 客户段2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- 此时插入6-9的中插入值,就会阻塞
mysql> insert into course values (8,'RabbitMQ');
-- 此时释放掉客户端1的事务就会接触阻塞
mysql> insert into course values (8,'RabbitMQ');
Query OK, 1 row affected (4.65 sec)
- 等值查询(普通索引)案例
- 第一行S,是临键锁,锁住了1-3的间隙和数据
- 第二行S,REC_NOT_GAP,是行锁,锁住了第三行的数据
- 第三行S,GAP,是间隙锁,锁住了3-7的间隙,锁上的原因是age是非唯一索引,为了防止其他事务往这边加入相同的数据,出现幻读现象
- 范围查询(唯一索引)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from course where id>= 6 lock in share mode;
+----+----------+
| id | name |
+----+----------+
| 6 | Hive |
| 8 | RabbitMQ |
| 9 | Spark |
+----+----------+
3 rows in set (0.00 sec)
-- 第二行S,REC_NOT_GAP行锁锁住id为6的行,第五行S是临键锁表示锁住6-8的数据和间隙,第四行临键锁表示锁住8-9行的数据和间隙,第三行是正无穷,表示锁住9后面的数据和间隙
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 |
+---------------+-------------+------------+-----------+---------------+------------------------+
| itcast | course | NULL | TABLE | IS | NULL |
| itcast | course | PRIMARY | RECORD | S,REC_NOT_GAP | 6 |
| itcast | course | PRIMARY | RECORD | S | supremum pseudo-record |
| itcast | course | PRIMARY | RECORD | S | 9 |
| itcast | course | PRIMARY | RECORD | S | 8 |
+---------------+-------------+------------+-----------+---------------+------------------------+
5 rows in set (0.00 sec)