文章目录

  • 一、事务概念及ACID特性
  • 二、隔离级别介绍
  • 三、事务具体实现
    • 3.1 undo log与redo log
    • 3.2 MVCC
    • 3.3 锁
      • 3.3.1 粒度锁类型
      • 3.2 锁算法介绍
  • 四、并发读异常分析与解决
    • 4.1 脏读
    • 4.2 不可重复读
    • 4.3 幻读
    • 4.4 丢失更新
    • 4.5 对比
  • 五、总结

一、事务概念及ACID特性

何谓事务,我脑海里首先想起的是算法的概念,算法其实就是为了解决某个问题而需要的一系列步骤的集合,那么事务其实就是为了达成某个目的而需要的一组SQL的集合。既然是要达成目的,那么结果无非就两种,成功or失败,即该事务内的语句要么全部执行成功,要么全部执行失败,这其实也是事务的标准特征之一。

为了更好的讲述事务的ACID特性,我们以经典的RMB数据为例。假设小明想从支付宝转100元到银行卡,那么需要完成以下几个步骤:

  1. 检查支付宝中余额是否大于100元;
  2. 从支付宝余额中减去100元;
  3. 银行卡余额增加100元

显然,以上三个步骤用于实现支付宝转账的这个事务,只要有一个步骤出问题,则转账不成功。

下面给出ACID特征的具体概念和实现方法:
1. 原子性
一个事务必须视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,不可能只执行其中的一部分操作,即事务的原子性。

这里的回滚一般是利用undolog(记录事务中的具体操作)来实现。用上面小明转账的例子来说,不可能只执行第三步就能让银行卡凭空增加100元的,虽然小明很想让他的银行卡多出100哈。

2. 一致性
数据库总是从一个一致性的状态转换到另一个一致性的状态。同样的,小明转账时,其状态要么是支付宝少100,银行卡多一百,要么是支付宝和银行卡余额都不变,只要转账的这个事务没有成功提交,那么余额也是不会变动的。

3. 隔离性
一个事务所作的修改在最终提交之前,对于其他事务来说是不可见的。即小明还未完成支付宝转账的事务提交之前,其他事务如查看银行卡余额时是看不到转账事务的中间状态的。

当然这里涉及到隔离级别的区别,也就是设置不同级别能影响其他事务的查询结果,一般通过MVCC(多版本并发控制)和锁来实现,MVCC主要解决一致性非锁定读,通过记录和获取行版本,而不是使用锁去限制读操作,从而实现高效并发读性能;锁则用来处理并发的增删改操作(DML操作),数据库中提供不同粒度的锁,针对表(聚集索引B+树)、页(聚集索引B+树叶子节点)、行(叶子节点当中某一段记录行)三种粒度加锁。

换句话说,小明是有可能看到支付宝余额减少,而银行卡余额也没增加的诡异画面的哈!

4. 持久性
事务一旦被提交,则所做的修改会被永久保存到磁盘中,即使系统奔溃宕机也不会丢失数据。

持久特性中的写磁盘主要是借助redolog来实现,我们平时在修改数据时并不是直接写磁盘,这样造成的磁盘随机IO效率很低下,而只是将修改行为追加记录到磁盘中的redolog,写日志的过程就是磁盘顺序IO,效率要高得多,具体写磁盘则是后台慢慢读日志写磁盘。总之,修改数据时其实写了两次磁盘,只要数据修改已经被redolog记录,就算没有及时写入磁盘,而系统发生奔溃,重启时依旧能自动修复数据。

持久性其实还是一个相对的概念,没有真正意义上的百分之百持久性的策略,也就是说小明辛辛苦苦存的钱也有可能消失哦。

二、隔离级别介绍

合适的隔离级别能在很大程度上提升数据库的并发性能,不同隔离级别对于数据库事务执行的并发程度不一,系统的开销也不一样,下面简单介绍四种隔离级别:

1. read uncommitted(未提交读)
最低的隔离级别,事务修改即使没提交,对其他事务而言也都是可见的,即出现脏读问题,该级别下读不加锁,写加排他锁,锁在事务提交或回滚后释放。

2. read committed(提交读)
大多数数据库默认的隔离级别(MySQL不是),该级别下,一个事务从开始直到提交时,只能看到已经提交的事务所做的修改,但是会出现不可重复读的问题,即一个事务中多次执行同样的查询,可能得到不一样的结果。

从该级别后支持 MVCC (多版本并发控制),也就是提供一致性非锁定读,此时读操作将会读取历史的快照数据;提交读的隔离级别下读取的是最新版本的行数据

3. repeatable read(可重复读)
MySQL默认的隔离级别,保证了在同一个事务中多次读取同样记录的结果是一致的。但是该级别会出现幻读的问题,即两次读取某个范围内数据结果不一致。

该级别下也支持 MVCC,此时读取作读取的是事务开始时的版本数据

4. serializable(可串行化)
最高隔离级别,强制事务串行执行,在读取的每一行数据上都加锁,会导致大量超时和锁竞争的问题,一般在非常需要确保数据一致性且没有并发的情况下,才考虑该级别。

小结

三、事务具体实现

事务的概念以及相关特性前面已经理解的差不多了,而每个事务的稳定有序执行更离不开以下几个方法的配合:事务日志、mvcc以及锁。

3.1 undo log与redo log

这里推荐一篇文章,写的很不错:【详细分析MySQL事务日志】https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html
redo log即重做日志,提供前滚操作;undo log即回滚日志,提供回滚操作。
undo log不是redo log的逆向过程,但它们都是用来恢复数据的日志:

  1. redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
  2. undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录,可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。

值得注意的是,这里说的undo log和redo log一般包括两部分,一是内存中的日志缓冲(log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(log file),该部分日志是持久的。innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file和undo log file中进行持久化。

为了确保每次日志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件的过程中都会调用一次操作系统的fsync操作(即fsync()系统调用)。因为MySQL是工作在用户空间的,MySQL的log buffer处于用户空间的内存中。要写入到磁盘上的log file,中间还要经过操作系统内核空间的os buffer,调用fsync()的作用就是将OS buffer中的日志刷到磁盘上的log file中。

从redo log buffer写日志到磁盘的redo log file中,过程如下:

这里之所以要经过一层os buffer,是因为open日志文件的时候,open没有使用O_DIRECT标志位,该标志位意味着绕过操作系统层的os buffer,IO直写到底层存储设备。不使用该标志位意味着将日志进行缓冲,缓冲到了一定容量,或者显式fsync()才会将缓冲中的刷到存储设备。使用该标志位意味着每次都要发起系统调用。比如写abcde,不使用O_DIRECT将只发起一次系统调用,使用O_DIRECT将发起5次系统调用。

3.2 MVCC

MVCC,Multi-Version Concurrency Control,即多版本并发控制,MVCC在MySQL的InnoDB中的实现主要是为了提高数据库并发性能,用来实现一致性的非锁定读,非锁定读是指不需要等待访问的行上X锁的释放,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

说起MVCC,首先要了解两个概念:

当前读
比如:“select * from table where … lock in share mode(共享锁)”、“ select * from table where … for update”、“update”、“insert”、“delete”(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

快照读
比如:“select * from table where … ”,这种不加锁的select操作就是快照读,即不加锁的非阻塞读。快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。

对于innodb而言,在read committed 和 repeatable read两种隔离级别下使用MVCC,此时MVCC对于快照数据的定义不同:

在read committed 隔离级别下,对于快照数据总是读取被锁定行的最新一份快照数据;
在repeatable read 隔离级别下,对于快照数据总是读取事务开始时的行数据版本。

这里有一点要注意,之所以使用快照读能实现MVCC的非锁定读,即不需要加锁,是因为没有事务需要对历史的数据进行修改操作。

那么在不同的RC和RR两种隔离级别中使用MVCC到底解决了什么问题呢?本文的第四节将详细介绍。

3.3 锁

3.3.1 粒度锁类型

锁是实现系统并发控制的常用方法之一,MySQL当中事务采用的是粒度锁,针对表(B+树)、页(B+树叶子节点)、行(B+树叶子节点当中某一段记录行)三种粒度加锁,当然不同的存储引擎支持的锁粒度和锁策略不一致,有兴趣的同学可以继续深入了解哈,本节主要介绍两种最重要的锁策略:表锁和行锁。

常用的锁类型有,共享锁(S锁)排他锁(X锁)意向共享锁(IS锁) 以及 意向排他锁(IX锁),其中,意向共享锁和意向排他锁都是表级别的锁,共享锁和排他锁都是行级锁,innodb支持行锁,myisam不支持。

共享锁
事务读操作加的锁,对某一行加锁。

在 SERIALIZABLE 隔离级别下,默认帮读操作加共享锁;
在 REPEATABLE READ 隔离级别下,需手动加共享锁,可解决幻读问题;
在 READ COMMITTED 隔离级别下,没必要加共享锁,采用的是 MVCC;
在 READ UNCOMMITTED 隔离级别下,既没有加锁也没有使用 MVCC。

排他锁
事务删除或更新加的锁,对某一行加锁,在4种隔离级别下,都添加了排他锁,事务提交或事务回滚后释放锁。

意向共享锁
对一张表中某几行加的共享锁。

意向排他锁
对一张表中某几行加的排他锁,目的是为了告诉其他事务,此时这条表被一个事务在访问;作用是可以排除表级别读写锁 (全面扫描加锁)。

小结

如上表,AI(自增锁)是一种表锁,由于innodb支持的是行级别的锁,意向锁并不会阻塞除了全表扫描以外的任何请求;

  1. 意向锁之间是互相兼容的;
  2. IS锁只对排他锁不兼容;
  3. 当想为某一行添加 S 锁,先自动为所在的页和表添加意向锁 IS,再为该行添加 S 锁;
  4. 当想为某一行添加 X 锁,先自动为所在的页和表添加意向锁 IX,再为该行添加 X 锁;
  5. 当事务试图读或写某一条记录时,会先在表上加上意向锁,然后才在要操作的记录上加上读锁或写锁。这样判断表中是否有记录加锁就很简单了,只要看下表上是否有意向锁就行了;
  6. 意向锁之间是不会产生冲突的,也不和 AUTO_INC表锁冲突,它只会阻塞表级读锁或表级写锁;
  7. 意向锁也不会和行锁冲突,行锁只会和行锁冲突。

3.2 锁算法介绍

MySQL常用的锁算法有以下几种:

记录锁 Record Lock

  • 单个行记录上的锁,锁住的是索引记录,而不是真正的数据记录;
  • 如果锁的是非主键索引,会在自己的索引上面加锁之后然后再去主键上面加锁锁住;
  • 如果表上没有索引(包括没有主键),则会使用隐藏的主键索引进行加锁;
  • 如果要锁的没有索引,则会进行全表记录加锁。

间隙锁 Gap Lock

  • 锁定一个范围,但不包含记录本身;
  • 全开区间;
  • REPEATABLE READ级别及以上支持间隙锁。

间隙锁+记录锁 Next-Key Lock

  • 锁定一个范围,并且包含记录本身;
  • 左开右闭区间;
  • 可以解决幻读问题。

插入意向锁 Insert Intention Lock

  • insert操作的时候产生;在多事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。
  • 假设有一个记录索引包含键值4和7,两个不同的事务分别插入5和6,每个事务都会产生一个加在4-7之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。
  • 如果有间隙锁了,插入意向锁会被阻塞。

自增锁 AUTO-INC Lock

  • 一种特殊的表级锁,发生在 AUTO_INCREMENT 约束下的插入操作;
  • 采用的一种特殊的表锁机制(较低概率造成B+树分裂);
  • 完成对自增长值插入的SQL语句后立即释放;
  • 在大数据量的插入会影响插入性能,因为另一个事务中的插入会被阻塞。

锁兼容

四、并发读异常分析与解决

由于MySQL不同隔离级别之间的天然差异(请回顾第二节),同时兼顾系统性能,经常在读取数据时会出现一些异常情况,下面将详细分析常见的几种异常并给出解决方案。

为了更好的分析问题,我们事先准备一个“account_t”表进行测试:

DROP TABLE IF EXISTS `account_t`;
CREATE TABLE `account_t` (`id` INT(11) NOT NULL,`name` VARCHAR(255) DEFAULT NULL,`money` INT(11) DEFAULT 0,PRIMARY KEY (`id`),KEY `idx_name` (`name`)
)ENGINE = INNODB AUTO_INCREMENT=0 DEFAULT CHARSET = utf8;INSERT INTO `account_t` VALUES (1, 'C', 1000),(2, 'B', 1000),(3, 'A', 1000);

4.1 脏读

脏读即一个事务可以读到另外一个事务中未提交的数据,脏读一般发生在READ UNCOMMITTED级别下。

根据上表SQL的执行顺序,事务B能查询到事务A中name为A减少了100元,而name为B却并没有减少,可以提高隔离级别来解决脏读问题。

4.2 不可重复读

一个事务可以读到另外一个事务中提交的数据,通常发生在一个事务中两次读到的数据是不一样的情况,不可重复读在隔离级别 READ COMMITTED 存在。

不可重复读在隔离级别 READ COMMITTED 存在。一般而言,不可重复读的问题是可以接受的,因为读到已经提交的数据,一般不会带来很大的问题,所以很多厂商(如Oracle、SQL Server)默认隔离级别就是READ COMMITTED。当然,可以通过提高隔离级别来解决该问题。

4.3 幻读

两次读取同一个范围内的记录得到的结果集不一样。
例如:以 name 为唯一键的表,一个事务中查询 “select * from t where name = ‘mark’; 不存在,接下来 insert into t(name) values (‘mark’);” 出现错误,此时另外一个事务也执行了 insert 操作;幻读在隔离级别REPEATABLE READ 及以下存在。


可以在 REPEATABLE READ 级别下通过读加锁(使用next-key lock)解决,如下图:

4.4 丢失更新

脏读、不可重复读、幻读都是一个事务写,一个事务读,由于一个事务的写导致另一个事务读到了不该读的数据;而丢失更新则是两个事务都写
丢失更新分为提交覆盖和回滚覆盖,其中回滚覆盖数据库拒绝,不可能产生,重点关注提交覆盖。

4.5 对比

  • 脏读和不可重复读的区别在于,脏读是读取了另一个事务未提交的数据;而不可重复读是读取了另一个事务提交之后的修改,本质上都是其他事务的修改影响了本事务的读取。
  • 不可重复读和幻读比较类似,不可重复读是两次读取同一条记录,得到不一样的结果;而幻读是两次读取同一个范围内的记录得到的结果集不一样(可能不同个数,也可能相同个数内容不一样,比如删除一行后又添加新行);不可重复读是因为其他事务进行了 update 操作,幻读是因为其他事务进行了 insert 或者 delete 操作。

具体区别如下图:

五、总结

文章写到这里就暂时告一段落啦,希望能帮助同学们梳理下MySQL事务相关的概念,对隔离级别、MVCC、读异常等能有个比较清楚的认识。

参考文献:
《高性能MYSQL-第三版》
https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html#auto_id_10
https://www.cnblogs.com/xuwc/p/13873611.html
https://blog.csdn.net/qq_40378034/article/details/90904573
https://www.cnblogs.com/wade-luffy/p/9689975.html#_label1_0
https://blog.csdn.net/weigeshikebi/article/details/81368591

MySQL事务与锁详解,并发读异常与隔离策略相关推荐

  1. mysql如何实现读提交锁_MySQL学习笔记(二)—MySQL事务及锁详解

    一.事务 数组库的一组操作,要么全部成功,要么全部失败 举例:银行转账 A账户向B账户转100 A账户余额扣去100 B账户余额增加100 上述两个操作要么全部成功,要么全部失败,部分成功或失败,数据 ...

  2. MySQL系列---事务与锁详解

    table of contents 1. 背景 2. 事务隔离级别 2.1. 事务及其ACID属性 2.2. 并发事务带来的问题 2.3. 数据库事务隔离级别 3. 锁机制 3.1. 定义 3.2. ...

  3. mysql乐观锁与事务_[数据库事务与锁]详解七: 深入理解乐观锁与悲观锁

    注明: 本文转载自http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库 ...

  4. MySQL事务实现原理详解

    1.事务概述 事务的详细概述 什么是事务? 事务时是访问和更新数据的程序执行单元,事务中可能含有一个或多个SQL语句,这些语句要么全部执行,要么都不执行 回顾MySQL的逻辑架构与存储引擎 如上图所示 ...

  5. MySQL事务以及MVCC详解

    文章目录 什么是事务 事务的特性(ACID) ACID之间的关系: Innodb的隔离性有哪些 每个隔离性会造成什么问题 事务怎么保证ACID 事务怎么保证一致性 事务怎么保证原子性 事务怎么保证持久 ...

  6. MySQL(六)InnoDB锁详解

    目录 InnoDB 锁的基本类型 锁的基本模式 共享锁(Shared Locks ) 排它锁(Exclusive Locks) 意向锁 锁的原理 锁的算法 记录锁 间隙锁 临键锁 总结​ 事务隔离级别 ...

  7. [数据库事务与锁]详解一: 彻底理解数据库事务

    注明: 本文转载自http://www.hollischuang.com/archives/898 事务 事务(Transaction),一般是指要做的或所做的事情.在计算机术语中是指访问并可能更新数 ...

  8. mysql 事务排他锁_[数据库事务与锁]详解六: MySQL中的共享锁与排他锁

    注明: 本文转载自http://www.hollischuang.com/archives/923 在MySQL中的行级锁,表级锁,页级锁中介绍过,行级锁是Mysql中锁定粒度最细的一种锁,行级锁能大 ...

  9. MySQL数据库的锁详解

    目录 悲观锁 悲观锁按使用性质分类 共享锁(读锁.S锁) 互斥锁(排它锁.独占锁.写锁.X锁) 更新锁(U锁) 自旋锁 悲观锁按作用范围分类(按锁的粒度分类) 行锁 表锁 乐观锁 乐观锁的实现 版本号 ...

最新文章

  1. 网易云音乐消息队列改造之路
  2. 初始化栈、入栈、出栈、栈空、数制转换函数和主函数,实现1348转换成8进制的功能。
  3. C4C销售订单行项目价格维护方法
  4. iOS: bundle name, bundle display name, bundle identifier...
  5. git 拉取代码失败
  6. PAT (Basic Level) Practice1012 数字分类
  7. 乐高机器人编程和编程的区别
  8. 一种基于地理信息的服务方式
  9. 软件项目管理:使用PERT评价不确定性的方法
  10. Sklearn常用数据预处理方法介绍
  11. 【CF37E】 Trial for Chief
  12. 2345浏览器兼容性设置在哪里
  13. Incomplete chess boards 有趣.
  14. 推荐一些能提升工作幸福度的小工具
  15. MySQL的下载、安装和配置
  16. 光模块自动测试系统软件,一种用于测试光模块的多通道自动测试方法及系统
  17. 【答学员问】完全零基础培训IT,学习能跟的上吗?
  18. M1安装homebrew以及错误解决办法
  19. mysql 连接闪断自动重连的方法
  20. manjora上好玩的游戏_manjaro安装教程

热门文章

  1. bootstrap 精美_基于Bootstrap 4和Vuejs构建的精美资源
  2. 性能优化——图片压缩、加载和格式选择
  3. HDU2549:壮志难酬
  4. c语言imagesize怎么用里面的参数如何填写,【学习笔记】【C语言】sizeof
  5. ncr管理系统_NCR餐饮系统操作指南
  6. 渠道、裂变、留存,App获客增长转化方案
  7. 地球人口承载力估计(c++基础)
  8. 建议收藏 | H.265编码原理入门
  9. 富士通打印机调整位置_打印机页首空调整和左边距调整
  10. 打破金属打印性能世界纪录,这家中国公司开发纳米改性超级金属-1