当前位置:   article > 正文

MySQL锁机制:高并发场景下该如何保证数据读写的安全性?_sql本身并发安全吗

sql本身并发安全吗

锁!这个词汇在编程中出现的次数尤为频繁,几乎主流的编程语言都会具备完善的锁机制,在数据库中也并不例外,为什么呢?这里牵扯到一个关键词:高并发,由于现在的计算机领域几乎都是多核机器,因此再编写单线程的应用自然无法将机器性能发挥到最大,想要让程序的并发性越高,多线程技术自然就呼之欲出,多线程技术一方面能充分压榨CPU资源,另一方面也能提升程序的并发支持性。

多线程技术虽然能带来一系列的优势,但也因此引发了一个致命问题:线程安全问题,为了解决多线程并发执行造成的这个问题,从而又引出了锁机制,通过加锁执行的方式解决这类问题。

一、MySQL锁的由来与分类

事务是基于数据库连接的,而每个数据库连接在MySQL中,又会用一条工作线程来维护,也意味着一个事务的执行,本质上就是一条工作线程在执行,当出现多个事务同时执行时,这种情况则被称之为并发事务,所谓的并发事务也就是指多条线程并发执行。

多线程并发执行自然就会出问题,而对于这些问题又可以通过调整事务的隔离级别来避免,那为什么调整事务的隔离级别后能避免这些问题产生呢?这是因为不同的隔离级别中,工作线程执行SQL语句时,用的锁粒度、类型不同。

也就是说,数据库的锁机制本身是为了解决并发事务带来的问题而诞生的,主要是确保数据库中,多条工作线程并行执行时的数据安全性。

但要先弄明白一点,所谓的并发事务肯定是基于同一个数据而言的,例如事务A目前在操作X表,事务B在操作Y表,这是一个并发事务吗?答案显然并不是,因为两者操作的都不是同一个数据,没有共享资源自然也不会造成并发问题。多个事务共同操作一张表、多个事务一起操作同一行数据等这类情景,这才是所谓的并发事务。

1.1、MySQL锁机制的分类

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中一条线程在读时不允许其他线程改。
在上述这个案例中,读锁明显也会存在排斥写操作,因此前面说法并不正确,共享锁就是共享锁,排他锁就是排他锁,不能与读锁、写锁混为一谈

2.1、共享锁

共享锁的意思很简单,也就是不同事务之间不会排斥,可以同时获取锁并执行,但这里所谓的不会排斥,仅仅只是指不会排斥其他事务来读数据,但其他事务尝试写数据时,就会出现排斥性,举个例子理解:

事务T1ID=88的数据加了一个共享锁,此时事务T2、T3也来读取ID=88的这条数据,这时T2、T3是可以获取共享锁执行的,但此刻又来了一个事务T4,它则是想对ID=88的这条数据执行修改操作,此时共享锁会出现排斥行为,不允许T4获取锁执行。

MySQL中,我们可以在SQL语句后加上相关的关键字来使用共享锁,语法如下:

SELECT ... LOCK IN SHARE MODE;
-- MySQL8.0之后也优化了写法,如下:
SELECT ... FOR SHARE;
  • 1
  • 2
  • 3

这种通过在SQL后添加关键字的加锁形式,被称为显式锁,而实际上为数据库设置了不同的事务隔离级别后,MySQL也会对SQL自动加锁,这种形式则被称之为隐式锁。

此时来做个关于共享锁的小实验,先打开两个cmd窗口并与MySQL建立连接:

-- 窗口1:
-- 开启一个事务
begin;
-- 获取共享锁并查询 ID=1 的数据
select * from `zz_users` where user_id = 1 lock in share mode;
  • 1
  • 2
  • 3
  • 4
  • 5
-- 窗口2:
-- 开启一个事务
begin;
-- 获取共享锁并查询 ID=1 的数据
select * from `zz_users` where user_id = 1 lock in share mode;
  • 1
  • 2
  • 3
  • 4
  • 5

此时两个事务都是执行查询的操作,因此可以正常执行,如下:

紧接着再在窗口2中,尝试修改ID=1的数据:

-- 修改 ID=1 的姓名为 猫熊
update `zz_users` set `user_name` = "猫熊" where `user_id` = 1;
复制代码
  • 1
  • 2
  • 3

此时执行后会发现,窗口2没了反应,这条写SQL显然并未执行成功,如下:

显然当另一个事务尝试对具备共享锁的数据进行写操作时,会被共享锁排斥,因此从这个实验中可以得知:共享锁也具备排他性,会排斥其他尝试写的线程,当有线程尝试修改同一数据时会陷入阻塞,直至持有共享锁的事务结束才能继续执行,如下:

当第一个持有共享锁的事务提交后,此时第二个事务的写操作才能继续往下执行,从上述截图中可明显得知:第二个事务/线程被阻塞24.74s后才执行成功,这是由于第一个事务迟迟未结束导致的。

2.2、排他锁

上面简单的了解了共享锁之后,紧着来看看排他锁,排他锁也被称之为独占锁,当一个线程获取到独占锁后,会排斥其他线程,如若其他线程也想对共享资源/同一数据进行操作,必须等到当前线程释放锁并竞争到锁资源才行。

值得注意的一点是:排

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/404547
推荐阅读
相关标签
  

闽ICP备14008679号