计算机系统应用教程网站

网站首页 > 技术文章 正文

干货总结:彻底搞懂MySQL数据库锁机制(上篇)

btikc 2024-10-08 01:18:01 技术文章 11 ℃ 0 评论

本文是对数据库锁机制的总结。对于数据库锁的了解,是区分一个程序员,尤其是Java程序员中高级的重要标志。在日常开发中,数据库锁也是我们容易踩坑的地方。如果你的系统没有被高并发大访问量流量洗礼,可能你压根没有遇到过锁问题。可一旦有朝一日你有幸遇到了大流量,恰你不幸没经受住这波幸福的考验,那损失不是一般的小,说不定领导会找你喝茶。好了,说了这些,为了让你明白锁机制的重要性,接下来我们一步步体验MySQL的锁机制之旅。

一、什么是锁

二、锁的分类

三、事务隔离级别

四、MVCC

五、一致性非锁定读与一致性锁定读

六、 InnoDB锁的类型

七、 InnoDB行锁几种算法

八、测试case

九、 特殊case

十、锁的兼容性

十一、死锁

1、什么是死锁

2、不同行互相操作一个例子

3、锁兼容性的一个死锁例子


鉴于篇幅,本文仅涉及提纲一至五部分,其余留给后续系列文章介绍。

一、什么是锁

锁机制是用于管理对共享资源的并发访问。

在数据库中,lock与latch都可以被称为锁。latch 一般称为闩锁(轻量级的锁) 因为其要求锁定的时间非常短, lock的对象是事务,用来锁定的是数据库中的对象,如表、页、行。

latch一般用于锁住事务号,内存等一些资源。而lock一般用于锁住记录,latch基本用户感知不到,lock可能是我们需要经常感知到的,下面介绍的锁为lock锁。

二、 锁的分类

MySQL存在多种不同的引擎,各种不同引擎类型面对的使用场景不一样,不同引擎之间的锁粒度可能有差别。例如InnoDB能支持行级锁,MyISAM支持表级锁,BDB支持页级锁,他们的特定如下:

1: 行级锁具有较高的并发性,比较适合并发度较高的场景,同时也更耗费资源。 但是如果锁住大量的记录,会比页级锁或者表级锁更慢,需要获取大量锁资源。

2:表级锁消耗资源更少,但是并发读较低比较适合需要经常锁住大量数据的场景

3:页级锁界于行锁以及表级锁之间,但是在无法解决热点数据的并发问题

接下来分别创建两张不同引擎的表,分别演示分别在myisam以及InnoDB大量数据插入的情况下,另一个会话更新一条数据的block状态。以及锁类型,模拟过程为:

(1):创建一张myisam(myisam_lock_test)或者是InnoDB引擎的表(innodb_lock_test)。

(2):开启一个会话sessionA,插入一条数据。

(3):使用load指令进行大量数据插入。

(4):开启另一个会话更新步骤二刚插入的数据。

表结构均为:

myisam测试

sessionA:

 load data infile '/Users/it2eye/mysql_lock/create/data.txt' into table myisam_lock_test(name);            

sessionB:

update myisam_lock_test set name='123' where id=1;

分别执行上述两条语句后,update一直阻塞住,并且等待表锁

InnoDB测试

sessionA:


load data infile '/Users/it2eye/mysql_lock/create/data.txt' into table innodb_lock_test(name);           

sessionB:

update innodb_lock_test set name='123' where id=1;

执行语句后,update并不会阻塞并且很快执行完毕

三、事务隔离级别

数据库事务的隔离级别有4种,由低到高分别为:

1、Read uncommitted (读未提交)

读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。事务中的修改,即使没有提交,对其它事务也是可见的。这种现象也被称为脏读。

事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。

分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。

那怎么解决脏读呢?Read committed!读提交,能解决脏读问题

2、Read committed(读已提交)

读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。一个事务只能看到已提交的事务修改,换句话说,一个事务从开始到提交之前,所做的任何修改对其它事务都是可见的。

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…

分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内执行两次同样的查询却返回了不同结果,这个级别也叫不可重复读。

那怎么解决可能的不可重复读问题?下面第3种Repeatable read !

3、Repeatable read(可重复读)

重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。该级别保证了在一个事务中多次读取同样的记录的结果是一致的。但还是没解决幻读(Phantom Read)的问题(下面有举例介绍)。该级别是MySQL的默认事务隔离级别

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。说明可重复读可以解决不可重复读问题。

什么时候会出现幻读?

事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。

现在你明白了,所谓幻读:指的是当某个事务在读取某个范围的记录时,另一个事务又在该范围内插入了新的记录,当之前事务再次读取该范围的记录时,会产生幻行(Phantom Row)。不过MySQL的InnoDB存储引擎可以通过gap锁解决幻读的问题

分析:可重复读可以解决不可重复读问题。到这里,你应该豁然开朗了,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

那怎么解决幻读问题?Serializable!

4、Serializable (可串行化)

这是最高的隔离级别。在该级别下,强制事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。Serializable 会在读取的每一行数据上都加锁,可能导致大量的超时和锁争用的问题。

值得一提的是:大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。Mysql的默认隔离级别是Repeatable read。

提交读的幻读问题

会话A

会话B插入数据

会话A出现幻读问题(将满足index_key为1的记录的no_index_key更新为2,但是执行查询发现仍然有一条数据未进行修改,就像发生幻觉一样)

可重复读gap锁解决幻读问题

会话A

会话b执行插入操作被block住后重启事务

执行select * from information_schema.innodb_locks;

可以看到锁类型为x锁,以及gap锁,后面系列文章将详细介绍下InnoDB行锁算法。

四、 MVCC

大部分的MySQL的存储 引擎,比如InnoDB并不是简简单单的使用行锁机制。它们都使用了行锁结合一种提高并发的技术,被称为MVCC(多版本并发控制)。MVCC并不单单应用在MySQL中,其他的数据库如Oracle,PostgreSQL也使用这个技术。

  MVCC避免了许多需要加锁的情形以及降低消耗。这取决于它实现的方式,它允许非阻塞读取,在写的操作的时候阻塞必要的记录。

  MVCC保存了某一时刻数据的一个快照。意思就是无论事物运行了多久,它们都能看到一致的数据。也就是说在相同的时间下,不同的事物看相同表的数据是不同的。

  每个存储引擎实现MVCC的方式不同。有许多种包含了乐观(optimistic)和悲观(pessimistic)的并发控制。下面用InnoDB的行为来举例说明MVCC工作方式。

InnoDB实现MVCC的方法是,它存储了每一行的两个(1)额外的隐藏字段,这两个隐藏字段分别记录了行的创建的时间和删除的时间。在每个事件发生的时候,每行存储版本号,而不是存储事件实际发生的时间。每次事物的开始这个版本号都会增加。自记录时间开始,每个事物都会保存记录的系统版本号。依照事物的 版本来检查每行的版本号。

SELECT

InnoDB检查每行数据,确保他们符合两个标准

   1、InnoDB只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行

   2、行的删除操作的版本一定是未定义的或者大于当前事务的版本号。确定了当前事务开始之前,行没有被删除。

  符合了以上两点则返回查询结果。

INSERT

   InnoDB为每个新增行记录当前系统版本号作为创建ID。

DELETE

   InnoDB为每个删除行的记录当前系统版本号作为行的删除ID。

UPDATE

  InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号作为了删除行的版本。

对于InnoDB可重复读来说

开启一个事务后,第一次进行查询操作。会创建一个当前快照读的一个数据结构,readView,主要包含三方面的内容,当前活动事务的最小事务号up_limit_id,当前活动事务的最大事务号low_limit_id,以及非自身的其它活动事务id组成的数组,trx_ids[],

注意的是,上述的当前事务版本号是当前活动事务号的版本。

五、 一致性非锁定读与一致性锁定读


一致性非锁定读

指InnoDB存储引擎通过行多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据,快照读,读一般不加行级lock锁

一致性锁定读

需要加锁显式的保证数据的一致性。读需要加lock锁

select **** for update

select **** lock in share mode

insert….where

update *** where…..


我是一名简单可靠、纯洁可爱的分享者,若您喜欢我的文章,欢迎转发,也欢迎关注我。

我会经常分享让你喜欢的互联网技术知识。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表