春华秋实之MySQL进阶-05 锁

image.png

7 锁

7.1 概述

  1. 介绍
  • 锁是计算机协调多个进程或者线程并发访问某一资源的机制。
  • 数据也是一种供许多用户共享的资源。如何保持数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
  1. 分类
  • 全局锁:锁定数据库中所有表
  • 表级锁:每次操作锁住整张表
  • 行级锁:每次操作锁住对应的行数据

7.2 全局锁

  1. 介绍
  • 全局锁是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML和DDL语句,已经更新操作的事务提交语句将会被阻塞。
  • 其典型使用场景是做全局的逻辑备份,对所有的表进行锁定,从而获得一致性视图,保证数据的完整性。
  1. 一致性数据备份
  • 进行全局锁
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
  1. 特点
  • 如果在主库上备份,那么在备份期间都不能执行更新,业务基本就得停止。
  • 在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟 – 这个估计后面主从复制还会讲

7.3 表级锁

  1. 介绍
  • 每次操作锁住整张表。锁表粒度大,发生锁冲突的概率最高,并发度最低。
  1. 表级锁分类
  • 表锁
    • 表共享读锁
    • 表独占写锁
  • 元数据锁
  • 意向锁
  1. 表锁
  • 语法
lock tables 表名... read/write
unlock tables/直接关闭客户端
  • 读锁案例

image.png

-- 客户端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;

image.png

  • 写锁案例

image.png

-- 客户端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;

image.png

  1. 元数据锁
  • 介绍

    • MDL加锁过程是系统自己控制的,访问一张表的时候就自动加上了

    • 元数据理解为表结构,MDL锁主要的作用就是维护表元数据的数据一致性,在表上有活动事务的时候未提交,不可以对元数据进行写入操作。

    • 目的是:为了避免DML和DDL冲突,保证读写的正确性。即当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)

    • 怎么理解呢?这么理解:就是你改表结构的时候,是不能有别的未提交事务存在的,因为MDL写锁和其他共享锁都是互斥的,提交了就不阻塞了。

image.png

  • 案例

image.png

image.png

  • 查看元数据锁
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   
+-------------------+--------------------+----------------+---------------------+---------------+
  1. 意向锁
  • 场景
    • 为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。

image.png

  • 作用
    • 直接去判断意向锁和表锁兼不兼容,表锁不用一行一行看有没有行锁了,而意向锁是自动添加的

image.png

  • 分类

    • 意向共享锁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 行级锁

  1. 介绍
  • 行级锁,每次操作锁住的是对应的行数据
  • 锁定粒度最小,发生锁冲突的概率最低,并发率最高。
  • 应用在InnoDB存储引擎中,MyISAM不用
  1. 分类
  • 行锁:锁住单个记录的锁,防止事务对其进行update和delete操作。在RC读已提交,RR可重复读隔离级别下都支持
  • 间隙锁:锁定索引记录间隙(但不包含该记录),确保索引的间隙不变,防止其他事务在这个间隙中insert,出现幻读。在RR可重复读隔离级别下都支持

image.png

  • 临键锁:行锁和间隙锁组合,同时锁住数据,并锁住数据的前面的间隙GAP。在RR可重复读的隔离级别下支持。
  1. 行锁
  • 分类
    • 共享锁S:允许事务读取一行,但是阻止其他事务获得相同数据集的排他锁。共享锁和共享锁是兼容的,共享锁和排他锁是互斥
    • 排他锁X:允许获取排他锁的事务更新数据,防止其他事务获得相同数据集的共享锁和排他锁。
    • 怎么记:就是共享和共享可以,其他都不行!和上面表级锁一个逻辑!有点难!
    • InnoDB的行锁是针对与索引加的锁,不通过索引条件检索数据,那么InnoDB将会对表中的所有记录加锁,就会升级成表锁
      image.png

image.png

  • 查看意向锁及行锁的加锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
  • select案例

    • select压根就没有加任何的行锁,因此客户端2也开启事务的话,可是能查的!
      image.png
  • 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
  1. 间隙锁
  • 介绍: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是非唯一索引,为了防止其他事务往这边加入相同的数据,出现幻读现象

image.png

  • 范围查询(唯一索引)
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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值