MySQL锁机制&事务

深入解析MySQL事务机制和锁机制 MySQL的事务机制和锁机制是确保数据一致性和完整性的基础。通过深入理解并合理应用这些机制,可以显著提高数据库的并发处理能力和稳定性。希望本文能够帮助读者更好地掌握MySQL的事务机制和锁机制,为实际开发和运维提供有价值的参考。如果你对MySQL的事务和锁机制有更多的问题或经验分享,欢迎在评论区讨论! 阅读详情

引言

锁锁锁,到哪到离不开这桩琐事,并发琐事,redis琐事,如今是MySQL琐事,这其中琐事,还跟MySQL另一个重要的东西--事务息息相关。


这篇将从以下几点,带你解开这把爱情的苦锁:

🎏本篇速览脑图

🎯常规表锁&行锁

这一部分较为常规,若有前置知识,可以直接跳到下边的【表级锁扩展】部分开始阅读 建议借助侧边栏,有emoji表情的属于重点

锁概述

锁是计算机协调多个进程或线程并发访问某一资源的机制(避免争抢)。

在数据库中,除传统的计算资源(如 CPU、RAM、I/O 等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

锁分类

从对数据操作的粒度分 :

1) 表锁:操作时,会锁定整个表。

2) 行锁:操作时,会锁定当前操作行。

从对数据操作的类型分:

1) 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。

2) 写锁(排它锁):当前操作没有完成之前,它会阻断其他写锁和读锁。

Mysql 锁

相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。下表中罗列出了各存储引擎对锁的支持情况:

存储引擎表级锁行级锁页面锁(了解)
MyISAM支持不支持不支持
InnoDB支持支持(默认)不支持
MEMORY支持不支持不支持
BDB支持不支持支持

MySQL这3种锁的特性可大致归纳如下 :

锁类型特点
表级锁偏向MyISAM 存储引擎,开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁偏向InnoDB 存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,**发生锁冲突的概率最低,**并发度也最高。
页面锁开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

粒度小,自然发生锁冲突的概率就低

从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!

仅从锁的角度来说:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web 应用;

而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并查询的应用,如一些在线事务处理(OLTP)系统。

MyISAM 表锁

MyISAM 存储引擎只支持表锁,这也是MySQL开始几个版本中唯一支持的锁类型。

如何加表锁

MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。

显示加表锁语法:

加读锁 : lock table table_name read;

加写锁 : lock table table_name write;
复制代码

读锁案例

准备环境

create database demo_03 default charset=utf8mb4;

use demo_03;

CREATE TABLE `tb_book` (
  `id` INT(11) auto_increment,
  `name` VARCHAR(50) DEFAULT NULL,
  `publish_time` DATE DEFAULT NULL,
  `status` CHAR(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;

INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'java编程思想','2088-08-01','1');
INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'solr编程思想','2088-08-08','0');



CREATE TABLE `tb_user` (
  `id` INT(11) auto_increment,
  `name` VARCHAR(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;

INSERT INTO tb_user (id, name) VALUES(NULL,'令狐冲');
INSERT INTO tb_user (id, name) VALUES(NULL,'田伯光');
复制代码

读操作

客户端一对book表加了锁,并拿到了book表的锁,在该锁未释放之前,不能去查别的表;
而客户端二能查到book和其他表,是因为读锁是共享锁,他并没有真正拿到这把锁,自然可以肆意妄为,不受未释放锁的束缚;

写操作

  • 客户端①直接报错,因为读锁会排斥写操作
  • 客户端②陷入了阻塞状态,得等待客户端①释放锁

  • unlock后,客户端②的写操作就能正常执行了。

总结

  • 读锁对于加锁的客户端:会限制对其他表的查询以及对任何表的写操作
  • 读锁对于其他客户端:不会限制任何查询,但会阻塞对该表的写操作

🎈助记

自己拿到了读锁,那自己当然不能再去读其他表,而又因为读锁不会影响到其他客户端读的结果,那其他客户端自然可以任意读。

而对于写操作:自己还在读,就别想着去做写操作了!而对于其他客户端,如果对该表写操作。肯定会影响到当前客户端的读取结果,所以其他客户端不能对该表进行写操作

  • 简而言之:自己不能三心二意【操作其他表】,而对他人则考虑自己所做的操作会不会导致两个客户端拿到不一致的数据,会的话就是不允许的。

写锁案例

客户端 一 :

1)获得tb_book 表的写锁

lock table tb_book write ;
复制代码

2)执行查询操作

select * from tb_book ;
复制代码

3)执行更新操作

update tb_book set name = 'java编程思想(第二版)' where id = 1;
复制代码

更新操作执行成功 ;

客户端二 :

4)执行查询操作

select * from tb_book ;
复制代码
  • 陷入阻塞状态,因为写锁是排他锁,排斥其他客户端的写和读操作。

当在客户端一中释放锁指令 unlock tables 后 , 客户端二中的 select 语句 就会立即执行

🎈总结

  • 写的优先级很高,对于锁定的表可写可读,但同样不能三心二意!!!而其他客户端对于锁定的表啥也干不了

结论

锁模式的相互兼容性如表中所示:

由上表可见:

1) 对MyISAM 表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞其他用户对同一表的写请求;

2) 对MyISAM 表的写操作,则会阻塞其他用户对同一表的读和写操作;

此外,MyISAM 的读写锁调度是写优先,这也是MyISAM不适合做写为主的表的存储引擎的原因。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。

查看锁的争用情况

show open tables;
复制代码

In_user : 表当前被查询使用的次数。如果该数为零,则表是打开的,但是当前没有被使用。

Name_locked:表名称是否被锁定。名称锁定用于取消表或对表进行重命名等操作。

show status like 'Table_locks%';
复制代码

Table_locks_immediate : 指的是能够立即获得表级锁的次数,每立即获取锁,值加1。

Table_locks_waited : 指的是不能立即获取表级锁而需要等待的次数,每等待一次,该值加1,此值高说明存在着较为严重的表级锁争用情况。

InnoDB 行锁

行锁介绍

行锁特点 :偏向InnoDB 存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

InnoDB 与 MyISAM 的最大不同有两点:一是支持事务;二是采用了行级锁。(两者是息息相关的)

🎈🎋事务

事务及其ACID属性

事务是由一组SQL语句组成的逻辑处理单元。

事务具有以下4个特性,简称为事务ACID属性。

ACID属性含义
原子性(Atomicity)事务是一个原子操作单元,其对数据的修改,要么全部成功,要么全部失败。
一致性(Consistent)在事务开始和完成时,数据都必须保持一致状态。
隔离性(Isolation)数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的 “独立” 环境下运行。
持久性(Durable)事务完成之后,对于数据的修改是永久的。

并发事务处理带来的问题

问题含义
丢失更新(Lost Update)当两个或多个事务选择同一行,最初的事务修改的值,会被后提交的事务修改的值覆盖。
脏读(Dirty Reads)读到了另一个事务还未提交的数据
不可重复读(Non-Repeatable Reads)一个事务执行同样的两次select语句,前后查询出来的结果不一致
幻读(Phantom Reads)一个事务按照相同的查询条件重新读取以前查询过的数据,却发现其他事务插入了满足其查询条件的新数据。

幻读

幻读:就像出现了“幻影”一般,原本查不到这个人,然后要插入的时候,突然又说这个人存在

  • 场景:注册问题吧,查询某个主键id是否存在,第一次查询不存在,即将插入新数据时【刚好另一个人插入了该主键id】,导致这边注册失败

  1. 幻读在“当前读”下才会出现。
  2. 幻读仅专指“新插入的行”【update的不算】

🎈事务隔离级别

为了解决上述提到的事务并发问题,数据库提供一定的事务隔离机制来解决这个问题。数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使用事务在一定程度上“串行化” 进行,这显然与“并发” 是矛盾的。

数据库的隔离级别有4个,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏写、脏读、不可重复读、幻读这几类问题。

隔离级别丢失更新脏读不可重复读幻读
Read uncommitted×√√√
Read committed××√√
Repeatable read(默认)×××√
Serializable(串行化)××××

备注 : √ 代表可能出现 , × 代表不会出现 。

  1. 读未提交:别人修改了某行数据,还未提交我们就能看到。
  2. 读已提交:别人修改了某行数据,得等到提交后我们才能看到。 -- 解决脏读
  3. 可重复读:别人修改了某行数据,我们也不去读那一行数据,还是读我们当前事务最初的那个未被修改的值。 -- 解决不可重复读
  4. 串行化:对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

例子

  1. 读未提交:v1=v2=v3=2;B还未提交,A就可以看到了。
  2. 读已提交:v1=1,v2=v3=2;等到B提交后,A才能看到。
  3. 可重复读:v1=v2=1,v3=2;也就是说,所谓的可重复读,是说在当前事务提交之前,只会读取当前事务最初的值,而不去读取其他的事务;
  4. 串行化:v1=1,v2=1,v3=2;事务A中查询得到值1的时候,就会加了“读锁”,会阻塞其他事务对该行的写操作(上文我们已经有提及到相关的读锁和写锁,忘记了的小伙伴可以翻阅上文看看)所以在事务B执行“将1改成2”的时候,会被锁住。直到事务A提交后,事务B才可以继续执行。

Mysql 的数据库的默认隔离级别为 Repeatable read, 查看方式:

show variables like 'tx_isolation';
复制代码

InnoDB 的行锁模式

InnoDB 实现了以下两种类型的行锁。

  • 共享锁(S):又称为读锁,简称S锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
  • 排他锁(X):又称为写锁,简称X锁,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加**排他锁**(X);

对于普通SELECT语句,InnoDB不会加任何锁;

可以通过以下语句显示给记录集加共享锁或排他锁 。

共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

排他锁(X) :SELECT * FROM table_name WHERE ... FOR UPDATE    (悲观锁)
						即手动锁定一行
复制代码

悲观锁和乐观锁


悲观锁:事务必须排队执行。数据锁住了,不允许并发。(行级锁:select后面添加for update)

乐观锁:支持并发,事务也不需要排队,只不过需要一个版本号。
 

案例准备工作

create table test_innodb_lock(
	id int(11),
	name varchar(16),
	sex varchar(1)
)engine = innodb default charset=utf8;

insert into test_innodb_lock values(1,'100','1');
insert into test_innodb_lock values(3,'3','1');
insert into test_innodb_lock values(4,'400','0');
insert into test_innodb_lock values(5,'500','1');
insert into test_innodb_lock values(6,'600','0');
insert into test_innodb_lock values(7,'700','0');
insert into test_innodb_lock values(8,'800','1');
insert into test_innodb_lock values(9,'900','1');
insert into test_innodb_lock values(1,'200','0');

create index idx_test_innodb_lock_id on test_innodb_lock(id);
create index idx_test_innodb_lock_name on test_innodb_lock(name);
复制代码

行锁基本演示

  • 我们采用两个客户端,首先要关闭掉自动提交功能:set autocommit = 0;
    • 普通的select不加锁,没有什么影响
    • 而insert和update就不一样了,会加排它锁,其他客户端陷入阻塞状态,不能对该行(注意得两个客户端操作的是同一行,才会阻塞,因为是行锁)进行修改,直到加锁的客户端提交完事务(相当于释放锁)

无索引行锁升级为表锁

如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。


由于 执行更新时 , name字段本来为varchar类型, 我们是作为数组类型使用,存在类型转换,索引失效,最终行锁变为表锁 ;(字符串类型,在SQL语句使用的时候没有加单引号,导致索引失效,查询没有走索引,进行全表扫描,索引失效,行锁就升级为表锁)

InnoDB 行锁争用情况

show  status like 'innodb_row_lock%';
复制代码

  • Innodb_row_lock_current_waits: 当前正在等待锁定的数量

  • Innodb_row_lock_time: 从系统启动到现在锁定总时间长度

  • Innodb_row_lock_time_avg:每次等待所花平均时长

  • Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间

  • Innodb_row_lock_waits: 系统启动后到现在总共等待的次数

当等待的次数很高,而且每次等待的时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手制定优化计划。

总结

InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面带来了性能损耗可能比表锁会更高一些,但是在整体并发处理能力方面要远远高于MyISAM的表锁的。当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势。

但是,InnoDB的行级锁同样也有其脆弱的一面,当我们使用不当的时候,可能会让InnoDB的整体性能表现不仅不能比MyISAM高,甚至可能会更差。

优化建议

  • 尽可能让所有数据检索都能通过索引来完成,避免无索引行锁升级为表锁。
  • 合理设计索引,尽量缩小锁的范围。
  • 尽可能减少索引条件,及索引范围,避免间隙锁。
  • 尽量控制事务大小,减少锁定资源量和时间长度。
  • 尽可使用低级别事务隔离(但是需要业务层面满足需求)

🎯表级锁扩展

全局锁

特点

备份的一致性问题

来看下边这个场景,比如我们创建的购买操作,涉及到了用户余额表+订单表,流程顺序如下:

  1. 当前正在备份用户余额表,备份了小明同学的余额是100
  2. 此时小明刚好下了订单,理应扣减50元
    1. 但由于用户余额表已经备份完毕,余额表不会受到影响
  3. 小明下好单了,如今来备份订单表了,能够备份到小明刚下的单

到这里是否发现问题了,就是备份后的结果是:小明的余额没扣钱,但却有相关的订单数据,出现了数据不一致的情况

  • 那我们该如何规避这种现象呢?

1. 加全局锁

通俗易懂,就是锁住整个表,此时所有对数据的增删改操作都会被阻塞

2. 不加锁的一致性数据备份

上边提到,备份时加上参数 --single-transaction就能实现此效果,具体是怎么做到的呢?

如果数据库的引擎支持的事务支持可重复读的隔离级别,那么在备份数据库之前先开启事务,会先创建 Read View,然后整个事务执行期间都在用这个 Read View,而且由于 MVCC 的支持,备份期间业务依然可以对数据进行更新操作。

即使其他事务更新了表的数据,也不会影响备份数据库时的 Read View,这就是事务四大特性中的隔离性,这样备份期间备份的数据一直是在开启事务时的数据。

上文也提到了可重复读,顾名思义就是,开启事务后,无论其他事务是否更新了A数据,我们查到的依旧是开始事务时的原始A数据,而不会是更改后的,因此能保证在备份期间,即使有别的事务来更新,我们也不会备份到【进而就规避了数据不一致的情况】

元数据锁

当存在事务,在对表的增删查改语句时,其他事务若要改变表结构,会被阻塞。。


当有线程在执行 select 语句( 加 MDL 读锁)的期间,如果有其他线程要更改该表的结构( 申请 MDL 写锁),那么将会被阻塞,直到执行完 select 语句( 释放 MDL 读锁)。

反之,当有线程对表结构进行变更( 加 MDL 写锁)的期间,如果有其他线程执行了 CRUD 操作( 申请 MDL 读锁),那么就会被阻塞,直到表结构变更完成( 释放 MDL 写锁)。

两者是互斥的,谁先来谁办事,直到一方前者处理完毕

  • MDL 不需要显示调用,那它是在什么时候释放的?

MDL 是在事务提交后才会释放,这意味着事务执行期间,MDL 是一直持有的。

隐含问题

那如果数据库有一个长事务(所谓的长事务,就是开启了事务,但是一直还没提交),那在对表结构做变更操作的时候,可能会发生意想不到的事情,比如下面这个顺序的场景:

  1. 首先,线程 A 先启用了事务(但是一直不提交),然后执行一条 select 语句,此时就先对该表加上 MDL 读锁;
  2. 然后,线程 B 也执行了同样的 select 语句,此时并不会阻塞,因为「读读」并不冲突;
  3. 接着,线程 C 修改了表字段,此时由于线程 A 的事务并没有提交,也就是 MDL 读锁还在占用着,这时线程 C 就无法申请到 MDL 写锁,就会被阻塞

那么在线程 C 阻塞后,后续所有对该表的 select 语句,就都会被阻塞,如果此时有大量该表的 select 语句的请求到来,就会有大量的线程被阻塞住,这时数据库的线程很快就会爆满了。

  • 为什么线程 C 因为申请不到 MDL 写锁,而导致后续的申请读锁的查询操作也会被阻塞?

这是因为申请 MDL 锁的操作会形成一个队列,队列中写锁获取优先级高于读锁,一旦出现 MDL 写锁等待,会阻塞后续该表的所有 CRUD 操作。

如何解决

  1. 解决长事务。

为了能安全的对表结构进行变更,在对表结构变更前,先要看看数据库中的长事务,是否有事务已经对表加上了 MDL 读锁,如果可以考虑 kill 掉这个长事务,然后再做表结构的变更。

  1. 对于热点数据的表【kill掉后立马又有长事务】

此时单单kill是没用了,我们只能给这个alter语句设置等待时间,若超时未拿到MDL写锁,就放弃,不阻塞后续的select语句

🎈意向锁

为什么要引入意向锁

比如有两个事务A跟B,和一个表G

A对G中的某一行加了行锁,之后B要对G加表锁的时候,行锁跟表锁就会产生冲突

  • 为了解决冲突,B就需要遍历全表,判断是否有行锁,这样效率太低了,因此引入了意向锁

如何解决

当A对G中的某一行加了行锁后,会顺便给表G加上意向锁

  • B要对G加表锁的时候,只需要判断表G的意向锁,跟自己要加的表锁是否兼容即可,无需再遍历全表

意向锁类型

意向锁跟表锁的兼容性

共享锁的话,跟表锁共享锁兼容,但跟表锁排它锁是互斥的
排它锁,自然都互斥

注意,意向锁之间是兼容的,并且意向锁不会与行级的共享锁和排它锁互斥

🎈AUTO-INC 锁

数据库的数据自增机制,就是基于这个锁机制实现的,使得我们可以在insert的时候,不用指明数据的值。

AUTO-INC 锁是特殊的表锁机制,锁不是在一个事务提交后才释放,而是在执行完插入语句后就会立即释放。【因此不遵循两阶段锁协议(下文会提及到该协议)】

在插入数据时,会加一个表级别的 AUTO-INC 锁,然后为被 AUTO_INCREMENT 修饰的字段赋值递增的值,等插入语句执行完成后,才会把 AUTO-INC 锁释放掉。
那么,一个事务在持有 AUTO-INC 锁的过程中,其他事务的如果要向该表插入语句都会被阻塞,从而保证插入数据时,被 AUTO_INCREMENT 修饰的字段的值是连续递增的。

  • 当然,这样也有弊端

在对大量数据进行插入的时候,会影响插入性能,因为另一个事务中的插入会被阻塞。

因此, 在 MySQL 5.1.22 版本开始,InnoDB 存储引擎提供了一种轻量级的锁来实现自增。

一样也是在插入数据的时候,会为被 AUTO_INCREMENT 修饰的字段加上轻量级锁,然后给该字段赋值一个自增的值,就把这个轻量级锁释放了,而不需要等待整个插入语句执行完后才释放锁。

InnoDB 存储引擎提供了个 innodb_autoinc_lock_mode 的系统变量,是用来控制选择用 AUTO-INC 锁,还是轻量级的锁。

  • 当 innodb_autoinc_lock_mode = 0,就采用 AUTO-INC 锁;

  • 当 innodb_autoinc_lock_mode = 2,就采用轻量级锁;

  • 当 innodb_autoinc_lock_mode = 1,这个是默认值,两种锁混着用,如果能够确定插入记录的数量就采用轻量级锁,不确定时就采用 AUTO-INC 锁。

  • 自增值一旦分配了就会加一,即使回滚了,自增值也不会减一,而是继续使用下一个值,所以自增值有可能不是连续的。

总结

  1. 常规的锁住整个表,直到插入语句执行完毕后才释放
  2. 为被 AUTO_INCREMENT修饰的字段加上的轻量级锁无需等到插入语句执行完毕后才释放

🎯行级锁扩展

🎈两阶段锁协议

  • 一个事务中,可能有多条语句,每条语句可能会加上锁,那么这些锁是什么时候才会释放呢?

答案是:需要在事务commit之后才释放,所以说,如果我们的事务中需要锁多个行,要把尽可能粒度大的操作放到后边!

行级锁分类

  • 行锁(Record Lock) :单个行记录上的锁。
  • 间隙锁(Gap Lock) :锁定一个范围,不包括记录本身。【解决幻读现象】
  • 临键锁(Next-key Lock) :Record Lock+Gap Lock【行锁+间隙锁】,锁定一个范围,包含记录本身。行锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。

🎈间隙锁&&临键锁

定义

间隙锁:锁定一个范围,但不包含数据本身
临键锁:锁定一个范围,并且包含数据本身

对记录加锁时,加锁的基本单位是 next-key lock,它是由记录锁和间隙锁组合而成的,next-key lock 是左开右闭区间,而间隙锁是左开右开区间。

假设一个索引包含值10、11、13和20。此索引可能的next-key锁包括以下区间:

(-∞, 10]

(10, 11]

(11, 13]

(13, 20]

(20, ∞ ]
复制代码

对于最后一个间隙,∞不是一个真正的索引记录,因此,实际上,这个next-key锁只锁定最大索引值之后的间隙。

加锁原则

两个“原则”、两个“优化”和一个“bug”。

  1. 原则1:加锁的基本单位是next-key lock。
  2. 原则2:查找过程中访问到的对象才会加锁。
  3. 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁。
  4. 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。
  5. 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

🎈退化问题

但是,next-key lock 在一些场景下会退化成记录锁或间隙锁。

案例准备

以下例子均在 MySQL 8.0.23版本下测试

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
复制代码
id【主键】c【非唯一索引】d
000
555
101010
151515
202020
252525

唯一索引等值查询

  1. 若查询的记录【7】存在,则退化为记录锁,锁的只是id为7这一个索引
  2. 若查询的记录不存在:
    1. 原则1:先统一加上next-key lock,(5,10];
    2. 再根据优化2,这是一个等值查询(id=7),遍历到最后发现id=10不满足查询条件,next-key lock退化成间隙锁,因此最终加锁的范围是**(5,10)**。

🎈🎈理解

不要忘了我们引入间隙锁的初衷,是为了解决幻读现象,那这里我们是唯一索引:

  1. 如果查询出来的id=7已经存在了,则不可能还会有其他事务能够插入id为7的幻影进来,因为是唯一索引嘛,因此自然不需要再锁间隙了,只需要锁这一行就够了,退化为行锁

  2. 如果查询出来的id=7不存在,相当于索引树里边还没有7这个节点,我们要锁住他,就只能通过他的相邻节点5跟10,把这段区间锁住

    1. 同时5跟10用不用锁呢?我们这里是唯一索引,而且是7,不等于5也不等于10,所以5跟10不会影响到我们的7,不需要锁,故只是锁(5,10)

非唯一索引等值查询

这里session A要给索引c上c=5的这一行加上读锁。

  1. 原则1,先加next-key lock,左开右闭,(0,5]
  2. 这里c是普通索引,不是唯一索引,所以不能确保只有当前c=5这一条记录,还需要锁住后边的【因为后边可能还会插入c=5】,因此还需要向后遍历,直到c=10这条记录,访问到的都要加锁【原则2】,(5,10]
  3. 优化2:等值判断,向右遍历,最后一个值不满足c=5这个等值条件,因此退化成间隙锁(5,10)。

因此sessionC的操作会被阻塞,这是可以理解的。那sessionB呢?为什么不会被阻塞呢?

  1. 根据原则2 ,只有访问到的对象才会加锁,这个查询使用覆盖索引,并不需要访问主键索引,所以主键索引上没有加任何锁,因此sessionC不会被阻塞。

锁的是索引

在这个例子中,lock in share mode只锁覆盖索引,但是如果是for update就不一样了。 执行 for update时,系统会认为你接下来要更新数据,因此会顺便给主键索引上满足条件的行加上行锁。

同时,如果你要用lock in share mode来给行加读锁避免数据被更新的话,就必须得绕过覆盖索引的优化,在查询字段中加入索引中不存在的字段。
比如,将session A的查询语句改成 select d from t where c=5 lock in share mode。

这样就不得不回表,就会涉及到主键索引了【其实就是让覆盖索引失效】

唯一索引范围锁

  1. 等值查询,先给10加上间隙锁,(5,10]
  2. 优化1:退化成行锁,只锁10这一行
  3. 由于是范围查询,继续往后遍历,直到15这一行停下来,访问到的都要加next-key lock,(10,15]
  4. 由于15不满足查询条件,故会退化为间隙锁,(10,15)

因此最后的范围是[10,15),sessionB的第二条insert会被阻塞,其他都不会

非唯一索引范围查询

跟唯一索引范围锁的区别在于,普通索引中的next-key lock不会退化为间隙锁和记录锁

  1. next-key lock,(5,10],由于c不是唯一索引,所以不会退化为行锁
  2. 继续往后遍历,直到15,next-key lock,(10,15]

因此最后的范围是:(5,15],两条语句都会被阻塞

🍔非索引查询

如果使用的是没有索引的字段,比如update user set age=7 where name=‘xxx(即使没有匹配到任何数据)’,那么会给全表加入gap锁。同时,它不能像上文中行锁一样经过MySQL Server过滤自动解除不满足条件的锁,因为没有索引,则这些字段也就没有排序,也就没有区间。除非该事务提交,否则其它事务无法插入任何数据。

🎈死锁

间隙锁死锁

间隙锁潜在问题

注意,间隙锁与间隙锁之间是不会冲突的

  1. session A 执行 select … for update 语句,由于 id=9 这一行并不存在,因此会加上间隙锁 (5,10);
  2. session B 执行 select … for update 语句,同样会加上间隙锁 (5,10),间隙锁之间不会冲突,因此这个语句可以执行成功;
  3. session B 试图插入一行 (9,9,9),被 session A 的间隙锁挡住了,只好进入等待;
  4. session A 试图插入一行 (9,9,9),被 session B 的间隙锁挡住了。
  • 两个 session 进入互相等待状态,形成死锁。此时我们来看如何应对死锁...

解决死锁方案

  1. 直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。
  2. 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。

如果选用第一种策略,其实不好估量,我们不确定这个超时时间要设置为多少合适,因此一般使用第二种策略。

但是它也是有额外负担的。

每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待【死锁产生的条件之一】,也就是死锁。

每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。

在操作系统里边,应对死锁的最好方法是:预防死锁的产生hhh,这个预防,可能很难跟我们开发工程师牵扯上,更多涉及到DBA那边了。

常见的解决死锁的方法

1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
如果业务处理不好可以用分布式事务锁或者使用乐观锁

总结

还是那张脑图,再看一遍,尝试复述出来,就过关啦

硅纪元应用测评 | AI搜索引擎大比拼:360AI搜索、秘塔搜索、GenSpark AI和天工AI对比评测 搜索引擎输出的每个段落都有对应的出处,如果有错误,也是信息源的问题,从普通用户的角度来看,这与AI搜索的产品关系不大,能让用户对AI搜索的服务和得到的信息有一个隔离。同时,提供更多的定制选项,允许用户根据自己的偏好调整搜索结果的权重和排序,将使得搜索体验更加个性化和满足用户的具体需求。此外,大部分搜索工具还提供了一个无广告的搜索环境(360AI搜索部分内容会有广告),极大地提升了用户的搜索体验。AI搜索的本质依然是搜索,但它通过AI的逻辑分析和语言重新组织,使用户能够快速得到想要的信息。 阅读详情

相关推荐

别再傻傻分不清PBR和NPR了!从《原神》到《双城之战》,聊聊游戏里那些让你眼前一亮的渲染风格

本文深入解析了PBR(基于物理的渲染)与NPR(非真实感渲染)在游戏视觉风格中的应用差异,通过《原神》和《双城之战》的案例,展示了两种渲染技术如何创造出截然不同的艺术效果。从技术原理到实战案例,帮助读者理解如何根据项目需求选择合适的渲染风格。

weixin_30425949的博客 147

MySQL事务和锁

逻辑上的一组操作,要么都执行,要么都不执行。原子性、一致性、隔离性、持久性。

weixin_37672801的博客 2742

华为OD机试_2025 B卷_模拟消息队列(Python,100分)(附详细解题思路)

时间排序:确保事件处理顺序正确消费者管理:集合操作高效维护状态优先级实现:利用消费者ID自然排序边界处理:正确处理空消费者情况。

测试工程师的成长之路 1119

详解MySQL事务与锁

事务和锁在 MySQL 中是非常重要的一部分,锁和事务对 MySQL 的数据访问并发有着举足轻重的影响,涉及到的知识篇幅很多,需要静下心好好反反复复几遍地细细品味。本文是对MySQL的事务和锁的整理,一些相关深入的细节,还是需要找到相关书籍来继续夯实

FlyingFish868的博客 1077

从FER2013到AffectNet:表情识别数据集十年演进史与你的项目选型攻略

本文回顾了从FER2013到AffectNet等主流人脸表情识别数据集的十年演进历程,详细分析了各数据集的特点、适用场景及技术突破。针对不同项目需求,提供了从移动端应用到高精度研究的选型策略,特别强调AffectNet在现代FER项目中的核心价值,并给出混合数据集构建的实战方案。

weixin_26808439的博客 522

如何用Python+Librosa实现轴承异响检测?5步搞定工业设备故障预警

本文详细介绍了如何利用Python和Librosa库,通过5个步骤构建工业轴承异响检测与故障预警系统。从音频采集、预处理、频谱特征提取,到构建机器学习模型并进行实时监测,提供了完整的代码级解决方案。该方法能有效识别轴承早期故障特征,实现低成本、高效的设备预测性维护。

rnn9storyteller的博客 632

mysql开启事务自动加共享锁_事务和锁机制是什么关系? 开启事务就自动加锁了吗?...

数据库锁因为数据库要解决并发控制问题。在同一时刻,可能会有多个客户端对同一张表进行操作,比如有的在读取该行数据,其他的尝试去删除它。为了保证数据的一致性,数据库就要对这种并发操作进行控制,因此就有了锁的概念。锁的分类从对数据库操作的类型分读锁(共享锁):针对同一块数据,多个读操作可以同时进行而不会互相影响。由读表操作加上的锁,加锁后其他用户只能获取该表或行的共享锁,不能获取排它锁,也就是说只能读不...

weixin_31842821的博客 681

STM32 ST-LINK Utility 无法烧录程序的解决方法

当你打开STM32 ST-LINK Utility,硬件连接好st-link V2,打开了要烧写的文件,点那个Program verify烧录时, 如果出错了: Programming error @ 0x08000000! 我的方法是: 点你想要烧录那个HEX文件的小格,复制 然后去到你前面的文件的格子里,点粘贴: 成功! ...

wocute的博客 9521

高性能MySQL实战第02讲:深入理解事务与锁机制(上)

事务及其特性你好,欢迎来到第 2 课时,本课时讲解的主要内容是:“MySQL 事务与锁机制”。事务及其特性首先看看什么是事务?事务具有哪些特性?关于事务,上大学的时候,你应该有接触过相关的课程。简单来说,事务是指作为单个逻辑工作单元执行的一系列操作,这些操作要么全做,要么全不做,是一个不可分割的工作单元。一个逻辑工作单元要成为事务,在关系型数据库管理系统中,必须满足 4 个特性,即所谓的 ACID:原子性、一致性、隔离性和持久性。

fegus的博客 366

【智能流体力学】使用ANSYS Fluent + SpaceClaim + SCDM制作流体力学前处理的几何模型

生成来源.scdoc文件来自前处理工具生成的模型数据,或在 FLUENT 中创建和保存的模型。生成方式: 通过前处理工具导出网格数据,或在 FLUENT 中设置并保存模型。作用: 用于储存模型的结构信息和设置,确保在 FLUENT 中的正确导入和使用。.x_t文件是 Parasolid 文件格式的扩展名,用于存储三维模型数据。在 ANSYS Fluent 中,这种格式通常用于在前处理工具之间交换几何数据。要创建.x_t。

源代码杀手的博客 3265

【MATLAB代码】基于水声通信(ACOMMS)下的到达时间差(TDOA)定位的AUV自主导航例程,附完整代码

水下AUV导航系统仿真研究 本研究基于到达时间差(TDOA)技术实现了水下自主航行器(AUV)的三维导航仿真系统。系统采用四个声学信标作为定位基准,通过TDOA测量数据迭代求解AUV位置。仿真结果表明: 导航性能:系统能够有效跟踪AUV从起点(30,30,-20)到目标点(70,70,-20)的航行轨迹,定位误差保持在合理范围内。 误差分析:定位误差变化曲线显示系统具有稳定的定位精度,平均误差为0.5米左右。 技术特点:采用两步最小二乘迭代算法求解非线性TDOA方程,提高了定位精度和计算效率。 该仿真系统为

985工科博士毕业,专攻定位、导航和滤波等算法研究。从业10年,主要使用MATLAB 320

MySql详解(三)--锁和事务篇

Mysql-详解,看这篇就够了

qq_35105015的博客 1315

基于YOLOv5/v8/v10的垃圾分类识别系统:设计实现与性能优化(附代码+数据集)

做完这个毕设,你已经掌握了从数据准备、模型训练到界面开发的全流程。把模型部署到树莓派上,做个真正的“智能垃圾桶”硬件装置;加入语音提示功能,检测到垃圾后自动语音播报类别;试试融合多个YOLO模型的优势,让分类精度再上一个台阶。按照这个教程一步步做,你的毕设不仅能顺利完成,还能做出很多亮点。要是过程中遇到啥问题,别慌,慢慢琢磨或者咱们一起聊聊,肯定能把这个毕设做成让你满意的作品。

know_F0727的博客 463

MySQL事务&锁机制

一、事务(transaction) 1、概念 事物用于保护多个SQL语句的执行,只要有一个失败则全部失败,反之都成功。 2、事务语法(SQL语句) 开启事务:start transaction 或 begin 提交事务:commit 回滚事务:rollback 注意: -> 存储引擎必须是innodb -> 默认是开启事务的 -> 后期通过PHP判断是否都执行成功commit,失...

杨妞儿的博客 249

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

「MySQL高级篇」MySQL锁机制&事务

锁锁锁,到哪到离不开这桩琐事,并发琐事,redis琐事,如今是MySQL琐事,这其中琐事,还跟MySQL另一个重要的东西--事务息息相关。这篇将从以下几点,带你解开这把爱情的苦锁:锁是计算机协调多个进程或线程并发访问某一资源的机制(避免争抢)。在数据库中,除传统的计算资源(如 CPU、RAM、I/O 等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,

jcc4261的博客 382

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

MySql 事务&锁机制

事务一. MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务.1.在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。 2.事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。 3

qq_37487520的博客 444

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

MySQL事务隔离机制&锁机制&MVCC详解

MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行update、insert、delete操作会自动给涉及的表加写锁。InnoDB在执行查询语句SELECT时(非串行隔离级别),不会加锁。但是update、insert、delete操作会加行锁。另外,读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞。Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一下,但是在整体并发处理能力方面要远远优于MYISAM的表级锁定的。

Carino_U的博客 439

MySQL事务隔离级别&锁机制分析论证

基本概念 1、事务隔离级别说明 ANSI/ISO SQL标准定义了4中事务隔离级别:未提交读(read uncommitted),提交读(read committed),重复读(repeatable read),串行读(serializable)。 对于不同的事务,采用不同的隔离级别分别有不同的结果。不同的隔离级别有不同的现象。主要有下面3种现在: a、脏读(dirty read):一

海锋博客 1431

Mysql 事务隔离级别&MVCC机制&锁机制(Innodb)

MVCC(Multi-Version Concurrency Control),即多版本并发控制。是一种用于解决数据库读写冲突的技术。它通过创建数据的多个版本来实现并发控制,使得在一个事务正在修改数据的同时,其他事务仍然可以读取到数据的旧版本,从而避免了传统数据库锁定机制中可能导致的资源浪费和效率降低的问题。MVCC的核心理念:undolog版本链、read-view视图、事务时间戳概念介绍:每个事务在启动时,系统会为其分配一个唯一的事务ID。

StrongerB的博客 993

Mysql架构&事务原理与锁机制&MVCC日志(undo log redolog binlog)两阶段提交&组提交

想要进一线互联网大厂公式,MYSQL是绕不开的坎,对于MYSQL的事务隔离机制以及日志相关知识网上众说纷纭,你抄我我抄你,今天我要详细总结一下相关知识,形成完整体系,补充一句,下面所讲全是干货,废话就不多说了,直接讲解知识,欢迎留言评论,不足纠正! 下文主要针对MYSQL的innodb存储引擎。 mysql的架构 首先,我们从架构开始说起。mysql分为server层与存储引擎层,server层包含连接器、分析器、优化器、执行器。 接下来以一条sql查询语句执行过程介绍各个部分功能。 客户端执行一条sql:

小龙coding的博客鸭~ 742

MySQL 事务与锁机制详解

MySQL 的事务与锁机制共同构成了数据库并发控制的核心,通过遵循 ACID 原则和合理设置事务隔离级别,可以有效地保障数据的一致性和完整性。同时,了解和运用行级锁、表级锁以及意向锁等机制,对于开发者在高并发场景下优化性能至关重要。本文将从事务的基本概念、ACID 特性、事务隔离级别以及 MySQL 中的锁机制进行详细介绍,帮助开发者在实际应用中更好地设计和优化数据库操作。对关键业务逻辑进行压力测试,确保在高并发环境下事务和锁机制能正常工作,避免出现性能瓶颈。

男Ren、麦根的博客 2561

从 MySQL 的事务 到 锁机制 再到 MVCC

转眼又一年~~2023马上就要到尾声了,在最后的几天中,我想给大家分享一下 MySQL 的一些小知识。在MySQL InnoDB引擎层面,又有新的解决方案 (解决加锁后读写性能问题),叫做MVCC(Multi-Version Concurrency Control)多版本并发控制。在MVCC下,就可以做到读写不阻塞且避免了类似脏读这样的问题。那MVCC是怎么做的呢?MVCC通过生成数据快照 (Snapshot)并用这个快照来提供一定级别 (语句级或事务级)的一致性读取。

船到桥头自然直 5300

MySQL事务与锁机制详细讲解

在并发操作中,多个事务可能对同一资源进行读写操作,为了防止数据不一致或损坏,MySQL 采用锁机制来控制并发访问。:银行转账,A账户转100元给B账户,要么两步都完成(A扣款,B加款),要么都不完成(回滚)。事务是一组操作的集合,这些操作要么全部执行成功,要么全部失败回滚,保证数据库的。:当两个事务同时更新同一条数据时,事务 A 的操作不会影响事务 B,反之亦然。事务中的所有操作是一个不可分割的整体,要么全部执行,要么全部不执行。并发执行的事务互不影响,每个事务的操作在完成前对其他事务是不可见的。

error_log7的博客 1316

MySQL 锁机制

本文从加锁粒度、模式、方式三方面系统分类,重点剖析 InnoDB 存储引擎下的行级锁(记录锁、间隙锁、临键锁)及其在 REPEATABLE READ 隔离级别中防止幻读的实现原理。此外,详解了意向锁、死锁产生与检测机制、锁兼容矩阵,并对 MyISAM 与 InnoDB 的锁策略进行对比分析。文章末附实用监控命令与开发优化建议,并配备常考锁机制练习题及答案,帮助读者理论结合实战,全面掌握锁的使用场景与风险控制策略。

专注于 Agent 工程、AI 产品、Harness Engineering、LLM 算法等方向,OpenMathModel、Nivik 等开源项目作者 3万+

MySQL的事务和锁机制的详细介绍

本文系统性地介绍了MySQL事务与锁机制的核心内容。首先阐述了事务的ACID特性(原子性、一致性、隔离性、持久性)及四种隔离级别(读未提交、读提交、可重复读、串行化),重点分析了MVCC多版本并发控制如何解决脏读、不可重复读问题。其次详细解析了MySQL的锁机制,包括全局锁、表级锁(共享读锁、独占写锁、元数据锁)和行级锁(独占锁、共享锁),并探讨了不同隔离级别下的锁行为差异。接着深入剖析了InnoDB的锁实现细节,包括记录锁、间隙锁、临键锁和意向锁的运作原理。最后分析了MVCC与临键锁的协同工作机制,并提供

weixin_50055999的博客 1793
上一篇: Spring底层核心原理解析
下一篇: 解放双手 根据代码自动生成时序图,这款IDEA插件真香
程序员职业指南
博客等级 码龄4年 2136粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值