赞
踩
锁!这个词汇在编程中出现的次数尤为频繁,几乎主流的编程语言都会具备完善的锁机制,在数据库中也并不例外,为什么呢?这里牵扯到一个关键词:高并发,由于现在的计算机领域几乎都是多核机器,因此再编写单线程的应用自然无法将机器性能发挥到最大,想要让程序的并发性越高,多线程技术自然就呼之欲出,多线程技术一方面能充分压榨CPU资源,另一方面也能提升程序的并发支持性。
多线程技术虽然能带来一系列的优势,但也因此引发了一个致命问题:线程安全问题,为了解决多线程并发执行造成的这个问题,从而又引出了锁机制,通过加锁执行的方式解决这类问题。
事务是基于数据库连接的,而每个数据库连接在MySQL
中,又会用一条工作线程来维护,也意味着一个事务的执行,本质上就是一条工作线程在执行,当出现多个事务同时执行时,这种情况则被称之为并发事务,所谓的并发事务也就是指多条线程并发执行。
多线程并发执行自然就会出问题,而对于这些问题又可以通过调整事务的隔离级别来避免,那为什么调整事务的隔离级别后能避免这些问题产生呢?这是因为不同的隔离级别中,工作线程执行
SQL
语句时,用的锁粒度、类型不同。
也就是说,数据库的锁机制本身是为了解决并发事务带来的问题而诞生的,主要是确保数据库中,多条工作线程并行执行时的数据安全性。
但要先弄明白一点,所谓的并发事务肯定是基于同一个数据而言的,例如事务
A
目前在操作X
表,事务B
在操作Y
表,这是一个并发事务吗?答案显然并不是,因为两者操作的都不是同一个数据,没有共享资源自然也不会造成并发问题。多个事务共同操作一张表、多个事务一起操作同一行数据等这类情景,这才是所谓的并发事务。
MySQL
的锁机制与索引机制类似,都是由存储引擎负责实现的,这也就意味着不同的存储引擎,支持的锁也并不同,这里是指不同的引擎实现的锁粒度不同。但除开从锁粒度来划分锁之外,其实锁也可以从其他的维度来划分,因此也会造出很多关于锁的名词,下面先简单梳理一下MySQL
的锁体系:
MDL
锁:基于表的元数据加锁,加锁后整张表不允许其他事务操作。InnoDB
中为了支持多粒度的锁,为了兼容行锁、表锁而设计的。AUTO-INC
锁:这个是为了提升自增ID的并发插入性能而设计的。Record
锁:也就是行锁,一条记录和一行数据是同一个意思。Gap
锁:InnoDB
中解决幻读问题的一种锁机制。Next-Key
锁:间隙锁的升级版,同时具备记录锁+间隙锁的功能。S
锁:不同事务之间不会相互排斥、可以同时获取的锁。X
锁:不同事务之间会相互排斥、同时只能允许一个事务获取的锁。DDL
语句时使用的锁。SQL
语句时,手动指定加锁的粒度。SQL
语句时,根据隔离级别自动为SQL
操作加锁。放眼望下来,是不是看着还蛮多的,但总归说来说去其实就共享锁、排他锁两种,只是加的方式不同,加的地方不同,因此就演化出了这么多锁的称呼。
共享锁又被称之为S
锁,它是Shared Lock
的简称,这点很容易理解,而排他锁又被称之为X
锁,对于这点我则不太理解,因为排他锁的英文是Exclusive Lock
,竟然不叫E
锁,反而叫X
锁,到底是红杏出了墙还是…,打住,回归话题本身来聊一聊这两种锁。
其实有些地方也将共享锁称之为读锁,排他锁称之为写锁,这乍一听并没啥问题,毕竟对同一数据做读操作是可以共享的,写则是不允许。但这个说法并不完全正确,因为读操作也可以是排他锁,即读操作发生时也不允许其他线程操作,而
MySQL
中也的的确确有这类场景,比如:
一条线程在读数据时加了一把锁(读锁),此时当另外一条线程来尝试对相同数据做写操作时,这条线程会陷入阻塞,因为MySQL
中一条线程在读时不允许其他线程改。
在上述这个案例中,读锁明显也会存在排斥写操作,因此前面说法并不正确,共享锁就是共享锁,排他锁就是排他锁,不能与读锁、写锁混为一谈。
共享锁的意思很简单,也就是不同事务之间不会排斥,可以同时获取锁并执行,但这里所谓的不会排斥,仅仅只是指不会排斥其他事务来读数据,但其他事务尝试写数据时,就会出现排斥性,举个例子理解:
事务
T1
对ID=88
的数据加了一个共享锁,此时事务T2、T3
也来读取ID=88
的这条数据,这时T2、T3
是可以获取共享锁执行的,但此刻又来了一个事务T4
,它则是想对ID=88
的这条数据执行修改操作,此时共享锁会出现排斥行为,不允许T4
获取锁执行。
在MySQL
中,我们可以在SQL
语句后加上相关的关键字来使用共享锁,语法如下:
SELECT ... LOCK IN SHARE MODE;
-- MySQL8.0之后也优化了写法,如下:
SELECT ... FOR SHARE;
这种通过在SQL
后添加关键字的加锁形式,被称为显式锁,而实际上为数据库设置了不同的事务隔离级别后,MySQL
也会对SQL
自动加锁,这种形式则被称之为隐式锁。
此时来做个关于共享锁的小实验,先打开两个cmd
窗口并与MySQL
建立连接:
-- 窗口1:
-- 开启一个事务
begin;
-- 获取共享锁并查询 ID=1 的数据
select * from `zz_users` where user_id = 1 lock in share mode;
-- 窗口2:
-- 开启一个事务
begin;
-- 获取共享锁并查询 ID=1 的数据
select * from `zz_users` where user_id = 1 lock in share mode;
此时两个事务都是执行查询的操作,因此可以正常执行,如下:
紧接着再在窗口2
中,尝试修改ID=1
的数据:
-- 修改 ID=1 的姓名为 猫熊
update `zz_users` set `user_name` = "猫熊" where `user_id` = 1;
复制代码
此时执行后会发现,窗口2
没了反应,这条写SQL
显然并未执行成功,如下:
显然当另一个事务尝试对具备共享锁的数据进行写操作时,会被共享锁排斥,因此从这个实验中可以得知:共享锁也具备排他性,会排斥其他尝试写的线程,当有线程尝试修改同一数据时会陷入阻塞,直至持有共享锁的事务结束才能继续执行,如下:
当第一个持有共享锁的事务提交后,此时第二个事务的写操作才能继续往下执行,从上述截图中可明显得知:第二个事务/线程被阻塞24.74s
后才执行成功,这是由于第一个事务迟迟未结束导致的。
上面简单的了解了共享锁之后,紧着来看看排他锁,排他锁也被称之为独占锁,当一个线程获取到独占锁后,会排斥其他线程,如若其他线程也想对共享资源/同一数据进行操作,必须等到当前线程释放锁并竞争到锁资源才行。
值得注意的一点是:排
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。