前序:
锁可以看作是一把现实生活中的锁,它的作用就是锁住东西确保安全。如果大家有其他语言开发经验,其实就很容易理解锁原理。例如 Java 语言中,Synchronized 与 Lock锁,就是用来保证并发安全性的。
1、Mysql锁的概念
Mysql锁是管理并发控制与数据一致性的机制。Mysql提供了很多锁类型,以确保多用户情况下的数据完整性与一致性。
Mysql锁根据类型可以分为三种。
1:全局锁:锁住整个数据库实例节点,相当于只有一个线程可以操作,生产环境基本上不会使用,只有在备份数据库时才会使用全局锁(目前备份表也不一定会使用全局锁,有其他的方式来操作)。
2、表级锁:锁住整个数据表,锁粒度较大,并发能力低,相当于只有一个线程可以访问,其他线程必须等获取锁线程释放后才运行访问。
3:行级锁:锁住一行数据,粒度最小,并发能力高。相当于锁住表中某行,除了锁住行之外,其他线程可以访问其他行数据,行锁使用过程中应该避免出现升级表锁情况。
2、Mysql全局锁
2.1、概念
全局锁:锁住整个数据库实例,所有数据库与表都只允许读,即所有线程只允许读取数据,其他执行DML,DDL语句以及事务提交会阻塞。生产环境业务系统基本上不会使用,只有在备份数据库时才会使用全局锁(目前备份表也不一定会使用全局锁,有其他的方式来操作)。
2.2、优缺点
优点:安全性高,保证数据一致性。
缺点:性能低,不适合业务场景。
2.3、场景
- 数据备份:生产环境每天都会备份上一天的数据。
- Mysql服务升级:Mysql版本升级
- 数据迁移
2.4、详解
2.4.1、命令脚本
## 打开全局表,只允许数据读取(select),阻塞DML与DDL类型语句执行
flush tables with read lock;
## 释放锁
unlock tables;
2.4.2、认证脚本
窗口一:开启全局锁:
窗口二:查询数据:允许查询。
窗口二:更新数据:阻塞,不允许查询
窗口一:关闭全局锁:关闭锁后,窗口二阻塞状态消息,直接运行成功了。
2.4.3、案例(备份数据库)
分析:为什么备份数据库需要开启全局锁?
我们先考虑不开启全局锁情况会出现什么问题。在生产环境中,用户无时无刻都在访问系统,数据库中也会存储用户操作的数据,如果在我们备份时,用户又在操作数据,那会不会对我们备份完的数据造成影响呢。其实会的,这也是为什么会公司凌晨备份数据库的原因。那为了保证备份数据的一致性与完整性,在实际操作中就需要开启全局锁,以保证数据一致性。
2.4.3.1、全局锁方式
脚本:
## -u 登录名
## -p 登录密码,注意不要在命令中书写,在执行时会提示书写密码
## databaseName 数据库名
## fileName 存放文件名
## path 存放路径
## > 将输出重定向到文件
mysqldump -u 用户名 -p databaseName > path/fileName.sql
开启全局锁:
开启备份:可以看到执行完命令会生成一个SQL文件,里面就是整个数据库结构与数据。
2.4.3.2、非全局锁方式
本质上还是使用mysqlDump命令,只不过在后面加了一个--single-transaction 关键字。这里简单说明一哈--single-transaction 关键字的本质:内部采用RR事务隔离级别级别,也就是说之执行命令时会开启事务,采用RR隔离级别,同时使用快照读来确保数据一致性,但是这只适用具有事务的存储引擎(MyIsam存储引擎就会有问题)。
脚本:
## -u 登录名
## -p 登录密码,注意不要在命令中书写,在执行时会提示书写密码
## databaseName 数据库名
## fileName 存放文件名
## path 存放路径
## > 将输出重定向到文件
mysqldump --single-transaction -u 用户名 -p databaseName > path/fileName.sql
执行命令:
3、Mysql表级锁
3.1、概念
表级锁:锁住整张表(每次操作数据表都会锁住),避免其他线程对表数据产生影响。本质上还是确保数据一致性与完整性。但是锁粒度比较大,锁冲突高,并发能力相对较弱。
3.2、分类
主要可以分为三类:表锁,元数据锁,意向锁三类。
3.2.1、表锁
表锁可以分为 共享表读锁,独占表写锁两类。
注意:使用表锁必须先进入数据库(use 数据库名称)。
3.2.1.1、语法
## 加表读锁命令
lock tables 表名 read
## 加表写锁命令
lock tables 表名 write;
## 解锁
unlock tables;
## 查看锁
select OBJECT_TYPE , OBJECT_SCHEMA , OBJECT_NAME , LOCK_TYPE from performance_schema.metadata_locks;
3.2.1.2、共享读锁(读锁)
共享读锁:通过名称就可以看出所有线程是可以读取数据的,但是写数据是不允许的。那下面我们就来验证这个结论是否成立。
a)开启两个事务
b)事务一开启共享表读锁
可以看到锁类型(lock_type)值 shared_read_only(只读)。表示已经加锁成功了,且是只读。
c)事务一读取数据,事务二读取数据。
可以看出开启表读锁后,事务一二都可以读取数据。
c)事务一写数据、更新数据、移除数据、更改表结构
错误信息:Table 'd_user' was locked with a READ lock and can't be updated
可以看出就算本身获取到共享表读锁,其DML,DDL语句也不允许执行,也验证了SHARED_ONLY_READ是对的。
d)事务二写数据、更新数据、更改表结构
可以看出,窗口二不能操作表中的数据与表结构,都会进行阻塞,也只能读。
e)释放共享读锁(只读)
图中可以看到,d_user表目前已经释放锁了。
总结:共享表读锁 是只读,不允许本身即其他事务进行非读操作(DML,DDL)。
3.2.1.3、独占写锁(写锁)
a)窗口一开启独占写锁
图中可知:Lock_type值:SHARED_NO_READ_WRITE(独占锁)。
含义:获取锁线程可以进行读写操作,其他线程阻塞不允许读写。
b)窗口一读取数据,更新数据
图中可知:获取写锁的线程可以进行DQL、DML、DDL相关语句的操作。
c)窗口二读取数据
图中可知:当没有获取独占写锁时,是无法进行数据访问的。
d)窗口二更新数据
图1可知:当没有获取独占写锁时,是无法进行更改的。
总结:独占表写锁时,只有获取到独占锁才允许操作增上改查以及更改表结构。未获取锁事务,必须排队等待获取锁,否则无法进行查询、更改数据、更改表结构。
3.2.1.4、总结
通过上诉 共享读锁、独占写锁 分析可知。共享读锁只多线程允许读,但都不允许更改数据与表结构。独占写锁只允许获取锁线程操作数据与表结构,其他都只能阻塞等待获取锁。
锁类型 | 描述 | 作用 |
共享表读锁 | 共享锁 | 只允许读,不允许写操作和更改表结构。 |
独占表写锁 | 排他锁 | 获取锁事务可以进行 增删改查以及更改表结构,否则必须等待锁。 |
3.2.2、元数据锁
元数据锁是系统自身进行判定的,用户无法进行更改与限制。这也是我们平常开发过程中,并不会主动加锁,但是系统还是会判断加锁阻塞问题。
它的核心作用就是避免DML与DDL冲突,保证数据正确写入。例如我们在新增数据时,其他事务有更改了表结构同时提交,那我们新增数据时就可能出现异常情况,毕竟新增字段的非必填项数据没有填充。(如果大家整篇文章有疑问,可以仔细思考这个核心作用,它的本质也是为了解决DML与DDL直接的冲突,其他的可以不做思考,因为InnoDB本质上默认使用行锁)
我们先得出结论,下面一步一步认证它:
结论:读取数据(DQL),系统会添加共享读锁。增删改时(DML),系统会加上共享写锁。更改表结构时(DDL),会加上排他锁。共享:大家都可以使用互不影响。排他:只允许一个使用。
3.2.2.1、读锁(SHARE READ)
a)查询数据
图中可以看出,在select语句中,系统会自动加上share_read锁(共享读锁),它与表锁的共享读锁(share_only_read 只读)不一样,不要混到一起了。
注意:1)查询时开启事务会加锁,不开启事务时不会进行加锁(走的行锁,innoDB默认使用行锁)2)锁类型在 performance_schema.metadata_locks 表中,与意向锁行锁存放( performance_schema.data_locks)表。3)大家可能会有疑问,为啥select会加锁,平常使用时不加锁,其他这个锁本质上可以看作是一个约束。
b)新增数据
图中执行顺序可以看到,加了共享读锁(share_read)但是还是可以写同时加共享写锁(share_write),这就是元数据锁的魅力所在,系统自动帮助我们去管控锁,读读不互斥,读写不互斥,写写互斥。
3.2.2.2、写锁(SHARE WRITE)
a)新增数据
图中可以看到先加共享写锁(share_write)后,其他事务也可以读取数据同时加共享读锁(share_read)。其他事务其实也可以进行新增,移除,更新等操作,但是不允许操作已经锁住的数据,这就是后面需要将的行级锁了。
3.2.2.3、排他锁(exclusive)
图1可知:在事务中更改数据,其他事务不允许更改表结构,直接阻塞,等待事务提交才会执行。
图2可知:在查询表结构(DESC table_name
或SHOW CREATE TABLE
)命令会获取SHARED_HIGH_PRIO类型锁,其他事务更改表结构会阻塞。
注意:先执行alter操作后,其他事务依然可以执行DML语句,原因:alter默认是非事务运行。
2.2.2.4、总结
通过上面三个类型锁的介绍,大家都有了一定的认识,元数据锁中的共享读/写锁,是真正的共享。排他锁是互斥的,这个主要是为了避免DQL语句执行过程中其他事务在执行DDL语句。
类型 | 锁 | 描述 |
select | share_read(共享读锁) | share_read与share_write兼容的,可以互相存在。 |
update, insert ,delete | share_write(共享写锁) | share_read与share_write兼容的,可以互相存在。但是share_write对共同操作的数据是互斥的。exclusive互斥。 |
alter | exclusive(排他锁) | 只有在先执行DML后,在执行DDL语句会户次 |
3.2.3、意向锁
意向锁:协调锁与锁之间的关系,确保锁的兼容性和避免死锁。例如:当事务一更新数据时,会锁定更改的行,事务二直接开始加表锁,在加表锁过程中,由于事务二不知道有没有行锁,所以需要整表判断是否有行锁(一行一行检测,直接检测有),这个过程是非常浪费性能的,入宫加上意向锁,在加表锁的时候直接判断是否有意向锁就可以直接可以不可以加表锁了,这样有助于提交性能。
意向锁分为:意向共享锁(IS),意向排他锁(IX)。
意向锁信息语句:
## 查看锁
select OBJECT_SCHEMA , OBJECT_NAME , LOCK_TYPE , LOCK_MODE, LOCK_STATUS from performance_schema.data_locks;
3.2.3.1、意向共享锁
既然是协调锁与锁之间的关系,那我们主要与表锁来进行讲解,看看有什么效果;
a)查询时共享锁
## 添加共享锁,也是当前读
select ... share lock in share mode;
b)事务二加表读锁
图中可知:意向共享锁 与 共享表读锁 兼容。
c)事务三加表写锁
图中可知:意向共享锁 与 独占表写锁 互斥。
3.2.3.2、意向排他锁
a)事务一更新数据,事务二加共享表读锁
图中可知:阻塞
b)事务一更新数据,事务二加独占表写锁
图中可知:阻塞
3.2.3.3、总结
意向共享锁(IS)中,与共享表读锁兼容与独占表写锁互斥。
意向排他锁(IX)中,与共享表读锁与独占表写锁互斥。
4、Mysql行级锁
4.1、概念
行级锁:锁住某一行数据,锁粒度低,并发能力强,适合高并发场景。本质上是保证数据一致性时,又提交数据并发能力。
4.2、分类
根据粒度可以分为三种类型。1、记录锁(Record Lock)。2、间隙锁(Gap Lock)。3、临键锁(Next-Key Lock)。
4.2.1、记录锁(Record Lock)
记录锁锁定的是单个/多个行记录。在可重复读(REPEATABLE READ)隔离级别下,记录锁有两种模式:共享锁(S锁)和排他锁(X锁)。共享锁允许其他事务读取该行,而排他锁则阻止其他事务读取或修改该行。
下面就来验证上面的结论。
4.2.1.1、共享锁(S)
a)事务一读取某一行数据并加上共享锁(lock in share mode)
图中可知:查询的1、2行数据已经加了共享锁(S),3行数据加了共享锁与间隙锁。
b)事务二读取数据并加共享锁
图中可知:可以加共享锁,所以 共享锁直接是兼容的。
c)事务二更新未加共享锁数据。
图中可知:未加共享锁的数据可以加排他锁。
d)事务二更新已加共享锁数据。
图中可知:阻塞
结论:共享锁与共享锁直接复用,与排他锁互斥。
4.2.1.2、排他锁(X)
a)事务一更新数据(会自动加排他锁)
b)事务二查询已加排他锁数据同时加共享锁
图中可知:阻塞
c)事务二查询未加排他锁数据同时加共享锁
图中可知:可以
总结:排他锁与共享锁互斥,排他锁与排他锁互斥。
注意:常规的 select 语句不会加锁,这也是为什么排他锁数据可以查询到的原因。
4.2.2、间隙锁(Gap Lock)
间隙锁:在锁定范围之间未存在的数据。即锁定的是索引记录之间的间隙,但不包括记录本身。这种锁的目的是在可重复读隔离级别下防止幻读现象。
下面我们来验证。
a)事务一查询id>10的角色信息
图中可知:锁住了13、17、21行的数据,中间的数据是否锁住,目前还不能给出结论。
b)事务二新增id=18的角色信息
图中可知:既然data_lock表中没有锁住,但是我们又无法新增,所以Mysql中会自动锁住数据与数据之间的间隙(主键id)。
结论:间隙锁在会锁住主键id之间没有填充的数据,但是不包括本身相当于 xxx<主键id<xxx,并不会包含边界数据。
4.2.3、临键锁(Next-Key Lock)
临键锁是记录锁和间隙锁的组合,它同时锁定一个记录以及该记录之前的间隙。这种锁也是在可重复读隔离级别下使用的,用于处理索引记录和索引间隙。
这是InnoDB存储引擎中RR级别下默认的。在以下情况会优化为间隙锁。
1、主键id等值查询不存在的记录加锁时,优化成间隙锁(Gap Lock),有值时优化为记录锁(Record Lock)。
2、普通索引查询等值查询时,继续向右遍历时且最后一个值不满足等值条件,当对索引进行等值查询,并且在向右遍历过程中遇到第一个不满足等值条件的值时,Next-Key Lock会退化为间隙锁。这种情况下,Next-Key Lock不再包含记录锁部分,只保留间隙锁部分。
以下情况不会优化为间隙锁:
1、唯一索引上的范围查询,访问到不满足条件的第一个值为止,这种情况下,Next-Key Lock不会退化为间隙锁,而是保持为临建锁(Next-Key Lock)。
验证:
结论一:根据主键id等值查询,有值则优化为记录锁(Record Lock),否则 优化为间隙锁(Gap Lock)
a)事务一读取id=1角色信息
图中可知:Lock_Mode的值为 S(共享锁),REC_NOT_GAP(记录锁),可以看出等值查询主键id有值情况,加了记录锁(Record Lock)与共享锁,而不是临建锁(Next-Key Lock)。
b)事务一读取id=7角色信息(不存在的角色)
图中可知:主键id等值查询无值时,采用了S(共享锁),GAP(间隙锁),Lock_data = 8 代表 锁住了 (id=7左边有值的id)<行<8,具体的可查看图2。
结论二: 普通索引查询等值查询时,继续向右遍历时且最后一个值不满足等值条件,临建锁优化为间隙锁。
目前角色表只存在主键索引。
a)单事务查询角色名为董事长角色。(无数据情况,行锁升级为表锁)
图中可知:在不是索引字段查询加锁,锁类型为优化为 间隙锁(GRANTED),同时查看 Lock_data数据,与左边的id一一对比,发现基本上都有,还有一个 supremum pseudo-record(正无穷),这就是表锁了,即 行锁升级表锁问题。总结:当对无索引字段进行查询加锁/更新数据时,不会加行锁,直接走表锁了。
b)单事务查询角色名为董事长A角色(有值,行锁升级为表锁)。
图中可知:有值无值,其实本质上还是a案例一样,行锁升级为表锁。
解决方案:如何处理这个问题呢,对经常查询字段需要加索引,避免升级表锁情况。
c)角色名 加普通索引,再次查询(有值情况)。
图中可知:注意查看 lock_data值,其中 《 '7', 3》加了一个记录锁和共享锁,
《 '3', 2》这一行加了共享锁和间隙锁(不包含这个数据),相当于将 role_desc 3 <= 范围 < 7 这中间加了间隙锁。
标签:事务,##,Lock,查询,Mysql,机制,数据,共享 From: https://blog.csdn.net/2401_85207246/article/details/144622473