写点什么

MySQL InnoDB 存储引擎 - 锁

用户头像
Arthur
关注
发布于: 2020 年 06 月 26 日

1、数据库中锁的作用

数据库系统使用锁是为了支持对【共享资源】进行【并发访问】,提供【数据】的【完整性】和【一致性】;


2、Lock 与 Latch


3、InnoDB 存储引擎中的锁

3-1、锁的类型

InnoDB 存储引擎实现了两种标准的行级锁:

  • 共享锁(S Lock),允许事务读一行数据;

  • 排他锁(X Lock),允许事务删除或更新一行数据;


不同的锁之间兼容性不相同:

锁兼容(Lock Compatible):一个事务获得一行数据【共享锁】,另一个事务也可以获得同一行的【共享锁】;

锁不兼容:一个事务获得行的排他锁,必须等待其他事务释放共享锁或排他锁;

S 和 X 锁都是行锁,兼容是指对【同一记录】(row)锁的兼容性情况;


3-2、一致性【非锁定读】

一致性非锁定读(consistent nonlocking read) 是指 InnoDB 存储引擎通过行多版本控制(multi versioning) 的方式来读取当前执行时间数据库中行的数据。

如果读取的行正在执行 DELETE 或 UPDATE 操作,这时读取操作不会因此等待行上锁的释放,而是去读取行的一个【快照数据】。

之所以称其为【非锁定读】,因为不需要等待访问行上 X 锁的释放。

优点:

  • 【快照数据】是指该行之前版本的数据,该实现通过 undo 段来完成,而 undo 用来在事务中回滚数据,因此【快照数据本身没有额外开销】;

  • 读取【快照数据】不需要上锁,因为没有事务需要对历史数据进行修改操作;

  • 【非锁定读】提高了数据库的并发性

  • 这是 InnoDB 引擎默认读取方式,但不是每个事务隔离级别都采用非锁定的一致性读;在事务隔离级别 READ COMMITTED 和 REPEATABLE READ(InnoDB 存储引擎的默认事务隔离级别),InnoDB 存储引擎使用非锁定的一致性读;对于快照数据,READ COMMITTED 下读取最新一份快照数据,REPEATABLE READ 读取【事务开始】时的行数据版本;



3-2-1、多版本并发控制(Multi Version Concurrency Control, MVCC)

快照数据其实就是当前行数据之前的历史版本,每行记录可能有多个版本;一个行记录可能不止一个快照数据,一般称这种技术为行多版本技术;

关于多版本并发控制,

多版本控制: 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了 InnoDB 的并发度。在内部实现中,与 Postgres 在数据行上实现多版本不同,InnoDB 是在 undolog 中实现的,通过 undolog 可以找回数据的历史版本。找回的数据历史版本可以提供给用户读(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也可以在回滚的时候覆盖数据页上的数据。在 InnoDB 内部中,会记录一个全局的活跃读写事务数组,其主要用来判断事务的可见性。


3-3、一致性【锁定读】

对数据库读取操作进行加锁以保证数据逻辑的一致性;

InnoDB 存储引擎对于 SELECT 语句支持两种一致性的锁定读(locking read):

  • SELECT ... FOR UPDATE:读取行记录加一个 X 锁;其他事务不能对已锁定的行加任何锁;

  • SELECT ... LOCK IN SHARE MODE:对读取的行记录加一个 S 锁,其他事务可以对行加 S 锁,但如果加 X 锁,则会被阻塞;

对于一致性【非锁定读】,即使读取的行已被执行 SELECT ... FOR UPDATE,也可以进行读取;


4、锁的算法

4-1、行锁的 3 种算法

InnoDB 存储引擎有 3 种行锁算法,分别是:

  • Record Lock:单个行记录上的锁;

  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身;

  • Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身;

如果索引有 10,11,13,20 这四个值,在 Next-Key Locking 下索引的区间为:

(-∞,10],即 -∞ < Lock <= 10,因为要锁定记录本身;

(10, 11],

(11, 13],

(13, 20],

(20, +∞)


关于 Next-Key Lock:

  • Next-Key Lock 设计目的是为了解决 幻读(Phantom Problem);

  • Next-Key Lock 如果降级为 Record Lock 仅在查询的列是【唯一索引】的情况下;对于【唯一键值】的锁定,Next-Key Lock 降级为【Record Lock】仅存在于查询【所有的】唯一索引列;查询其中一个还是用 Next-Key Lock;


4-2、解决 Phantom Problem

幻读(Phantom Problem)是指在【同一事务下】,连续执行两次【同样的 SQL 语句】可能导致不同的结果,第二次的 SQL 语句可能会返回之前不存在的行;

当表中存在 1,2,5 数据,

  1. 事务 T1 第一次 执行 select * from table where id >= 1,返回 1,2,5 三条数据,

  2. 此时事务 T2 执行 insert into table (id) value (1) 或 INSERT INTO table SELECT 4 后,

  3. 事务 T1 再次执行 select * from table where id >= 1,返回 1,2,4,5;


4-2-1、采用 Next-Key Locking 算法避免 幻读(Phantom Problem)

InnoDB 引擎采用 Next-Key Locking 算法避免幻读,当执行 SELECT * FROM table WHERE id >= 1 FOR UPDATE 时,是对 [1, +∞) 这个范围加了 X 锁,因此对这个范围的插入都是不被允许的,从而避免幻读;

事务不同隔离级别,采取的加锁算法不同:

  • 可重复读(REPEATABLE READ),采用 Next-Key Locking 方式加锁;

  • 读已提交(READ COMMITTED),采用 Record Lock;


5、锁问题

5-1、脏读

脏读:事务对缓冲池中行记录的修改,并且还没有被提交;也就是,在不同事务下,当前事务可以读到另外事务未提交的数据;

脏读是指【未提交的】数据,如果读到脏数据,即一个事务可以读到【另一个事务】【未提交的】数据,违反了数据库的隔离性

脏读出现在【事务隔离级别】设置为【读未提交(READ UNCOMMITTED)】;

5-2、不可重复读

不可重复读,是指在【一个事务内】【多次读取同一数据集合】,事务未结束时,另一事务访问该数据集合并做了 DML 操作并提交,在第一个事务中两次读数据之间,由于第二个事务修改,第一个事务中每次读到的数据可能不同;

不可重复读,违反了数据库【事务一致性】的要求;

不可重复读和脏读的区别:脏读是读到【未提交】的数据,不可重复读读到【已提交】的数据;

不可重复读 出现在【事务隔离级别】设置为【读已提交(READ COMMITTED)】;

InnoDB 存储引擎中,通过使用 Next-Key Lock 算法 避免 【不可重复读】,MySQL 官方将【不可重复读】定义为 Phantom Problem,即幻像问题;

在【 Next-Key Lock】算法下,对于索引扫描,不仅锁住扫描的索引,还锁住索引覆盖的范围(GAP),因此在这个范围内的插入都是不允许的,从而避免不可重复读问题;


5-3、丢失更新

丢失更新,一个事务的更新操作会被另一个事务的更新操作覆盖,从而导致数据不一致

例如:

1)事务 T1 将 行记录 R 更新为 V1,但事务 T1 并未提交;

2) 同时,事务 T2 将 行记录 R 更新为 V2,事务 T2 未提交;

3) 事务 T1 提交;

4) 事务 T2 提交;

还有一种情况:

1)事务 T1 查询一行数据,并返回界面给 User1;

2) 事务 T2 查询同一行数据,并返回用户界面给 User2 展示;

3) User1 修改这行记录,更新数据库并提交;

4) User2 修改这行记录,更新数据库并提交;

User1 修改的数据【丢失】了;

例如银行转账,账户 A 里有 1 万元,此时 User1 向另一个账户 B 转账 9000,由于网络问题,需要等待;而 User2 向账户 C 转账 1000,结果可能导致 账户余额是 9000,User1 转账更新账户 A 的结果被 User2 的转账操作覆盖了;


要避免这种情况,最好将操作变成【串行化】;


6、阻塞


7、死锁

7-1、死锁的概念

死锁是指两个或两个以上的事务在执行过程中,因【争夺锁资源】而造成的一种【相互等待】的现象


7-1-1、解决死锁问题的方法

1、不要有等待

将任何等待都回滚,并且事务重新开始;

优点:简单

缺点:

  • 导致并发能力下降,甚至任何事务不能进行;

  • 导致资源浪费;


2、超时

当两个事务互相等待时,当一个等待时间超过设置的阈值时,其中一个事务进行回滚,另一个事务就能继续执行;

在 InnoDB 引擎中,参数【innodb_lock_wait_timeout】用来设置超时时间;

缺点:

超时的事务权重比较大,事务更新行数多,占用较多的 undo log,回滚该事务所占用时间可能会很多;


3、等待图(wait-for graph)

【等待图】比起【超时】的解决方案,是一种更为【主动的】死锁检测方式。

等待图要求数据库保存两个信息:

  • 锁的信息链表

  • 事务等待链表

通过上述链表可以构造出一张图,图中若存在【回路】,就代表存在死锁,因此资源间相互发生等待;

等待图中【边】的定义:

  • 一个事务 T1 等待另一个事务 T2 所占用的资源;

  • 一个事务 T1【最终等待】另一个事务 T2 所占用的资源,也就是事务之间在等待【相同的资源】,而事务 T1 发生在事务 T2 后面;

总结:

  • 【等待图】的【节点】由事务组成;

  • 【等待图】的【边】:由事务等待被另一事务占用的相同资源,事务到另一事务存在一个边;


举例:

由上图看出,

  • 当前存在 4 个事务,因此在【等待图】中有 4 个节点;

  • 事务 T2 对 Row1 占 X 锁,事务 T1 对 Row1 占 S 锁,事务 T1 在 T2 之后,且需要等待 T2 释放资源,因此存在事务 T1 指向事务 T2 的边;

  • 事务 T2 等待事务 T1、T4 占用的 Row2,因此存在 T2 到 T1、T4 的边;

  • 同样,T3 也有到 T1、T2、T4 的边;

因此当前事务【等待图】如下:



事务请求锁等待都会判断是否存在【回路】,若存在则有死锁;存储引擎选择回滚 undo 量最小的事务;

死锁检测采用【深度优先算法】实现;


7-1-2、死锁发生的概率

死锁发生的因素有:

  • 系统中事务的数量,数量越多发生死锁的概率越大;

  • 每个事务操作的数量,每个事务操作的数量越多,发生死锁的概率越大;

  • 操作数据的集合,越小则发生死锁的概率越大;


发布于: 2020 年 06 月 26 日阅读数: 87
用户头像

Arthur

关注

还未添加个人签名 2018.08.31 加入

还未添加个人简介

评论

发布
暂无评论
MySQL InnoDB 存储引擎 - 锁