精华内容
下载资源
问答
  • 经常提到数据库的事,事务隔离还有隔离级别,那什么是事务隔离隔离级别又是什么呢?本文就帮大家梳理一下。 MySQL 事务 本文所说的 MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事务的。 数据库...

    经常提到数据库的事,事务隔离还有隔离级别,那什么是事务隔离,隔离级别又是什么呢?本文就帮大家梳理一下。

    MySQL 事务

          本文所说的 MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事务的。

    数据库事务指的是一组数据操作,事务内的操作要么就是全部成功,要么就是全部失败,什么都不做,其实不是没做,是可能做了一部分但是只要有一步失败,

    就要回滚所有操作。

    假设一个网购付款的操作,用户付款后要涉及到订单状态更新、扣库存以及其他一系列动作,这就是一个事务,如果一切正常那就相安无事,一旦中间有某个环节异常,

    那整个事务就要回滚,总不能更新了订单状态但是不扣库存吧,这问题就大了

    事务具有te原子性(Atomicity)、一致性(Consisncy)、隔离性(Isolation)、持久性(Durability)四个特性,简称 ACID,缺一不可。今天要说的就是隔离性

    概念说明

    以下几个概念是事务隔离级别要实际解决的问题,所以需要搞清楚都是什么意思。

    脏读

    脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。

    读到了并不一定最终存在的数据,这就是脏读。

    可重复读

    可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。

    不可重复读

    对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。

    通常针对数据更新(UPDATE)操作

    幻读

    幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,

    并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。

    事务隔离级别

    SQL 标准定义了四种隔离级别,MySQL 全都支持。这四种隔离级别分别是:

    1. 读未提交(READ UNCOMMITTED)commit
    2. 读提交 (READ COMMITTED)
    3. 可重复读 (REPEATABLE READ)repeatable
    4. 串行化 (SERIALIZABLE)serializable

    从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。

    事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,下面展示了 4 种隔离级别对这三个问题的解决程度。

    只有串行化的隔离级别解决了全部这 3 个问题,其他的 3 个隔离级别都有缺陷。

    一探究竟

    下面,我们来一一分析这 4 种隔离级别到底是怎么个意思。

    如何设置隔离级别

    我们可以通过以下语句查看当前数据库的隔离级别,通过下面语句可以看出我使用的 MySQL 的隔离级别是 REPEATABLE-READ,也就是可重复读,这也是 MySQL 的默认级别。

     查看事务隔离级别 5.7.20 之后
    show variables like 'transaction_isolation';
    SELECT @@transaction_isolation
    
    +---------------+-----------------+
    | Variable_name | Value           |
    +---------------+-----------------+
    | tx_isolation  | REPEATABLE-READ |
    +---------------+-----------------+

    修改隔离级别的语句是:set [作用域] transaction isolation level [事务隔离级别],
    SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}。

    其中作用于可以是 SESSION 或者 GLOBAL,GLOBAL 是全局的,而 SESSION 只针对当前回话窗口。隔离级别是 {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} 这四种,不区分大小写。

    比如下面这个语句的意思是设置全局隔离级别为读提交级别。

    mysql> set global transaction isolation level read committed;

    事务的执行过程如下,以 begin 或者 start transaction 开始,然后执行一系列操作,最后要执行 commit 操作,事务才算结束。当然,如果进行回滚操作(rollback),事务也会结束。

    preview

    需要注意的是,begin 命令并不代表事务的开始,事务开始于 begin 命令之后的第一条语句执行的时候。例如下面示例中,select * from xxx 才是事务的开始,

    begin;
    select * from xxx; 
    commit; -- 或者 rollback;

    另外,通过以下语句可以查询当前有多少事务正在运行。

    select * from information_schema.innodb_trx;

    好了,重点来了,开始分析这几个隔离级别了。

    接下来我会用一张表来做一下验证,表结构简单如下:

    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(30) DEFAULT NULL,
      `age` tinyint(4) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

    初始只有一条记录:

    mysql> SELECT * FROM user;
    +----+-----------------+------+
    | id | name            | age  |
    +----+-----------------+------+
    |  1 | 古时的风筝        |    1 |
    +----+-----------------+------+

    读未提交

    MySQL 事务隔离其实是依靠锁来实现的,加锁自然会带来性能的损失。而读未提交隔离级别是不加锁的,所以它的性能是最好的,没有加锁、解锁带来的性能开销。但有利就有弊,

    这基本上就相当于裸奔啊,所以它连脏读的问题都没办法解决。

    任何事务对数据的修改都会第一时间暴露给其他事务,即使事务还没有提交。

    下面来做个简单实验验证一下,首先设置全局隔离级别为读未提交。

    preview

    set global transaction isolation level read uncommitted;

    设置完成后,只对之后新起的 session 才起作用,对已经启动 session 无效。如果用 shell 客户端那就要重新连接 MySQL,如果用 Navicat 那就要创建新的查询窗口。

    启动两个事务,分别为事务A和事务B,在事务A中使用 update 语句,修改 age 的值为10,初始是1 ,在执行完 update 语句之后,在事务B中查询 user 表,会看到 age 的值已经是 10 了,

    这时候事务A还没有提交,而此时事务B有可能拿着已经修改过的 age=10 去进行其他操作了。在事务B进行操作的过程中,很有可能事务A由于某些原因,进行了事务回滚操作,

    那其实事务B得到的就是脏数据了,拿着脏数据去进行其他的计算,那结果肯定也是有问题的。

    读未提交,其实就是可以读到其他事务未提交的数据,但没有办法保证你读到的数据最终一定是提交后的数据,如果中间发生回滚,那就会出现脏数据问题,

    读未提交没办法解决脏数据问题。更别提可重复读和幻读了,想都不要想。

    读提交

    既然读未提交没办法解决脏数据问题,那么就有了读提交。读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据。那脏数据问题迎刃而解了。

    读提交事务隔离级别是大多数流行数据库的默认事务隔离界别,比如 Oracle,但是不是 MySQL 的默认隔离界别。

    我们继续来做一下验证,首先把事务隔离级别改为读提交级别。

    set global transaction isolation level read committed;

    之后需要重新打开新的 session 窗口,也就是新的 shell 窗口才可以。

    同样开启事务A和事务B两个事务,在事务A中使用 update 语句将 id=1 的记录行 age 字段改为 10。此时,在事务B中使用 select 语句进行查询,我们发现在事务A提交之前,

    事务B中查询到的记录 age 一直是1,直到事务A提交,此时在事务B中 select 查询,发现 age 的值已经是 10 了。

    这就出现了一个问题,在同一事务中(本例中的事务B),事务的不同时刻同样的查询条件,查询出来的记录内容是不一样的,事务A的提交影响了事务B的查询结果,

    这就是不可重复读,也就是读提交隔离级别。

    可重复读做到了,这只是针对已有行的更改操作有效,但是对于新插入的行记录,就没这么幸运了,幻读就这么产生了。我们看一下这个过程:

    事务A开始后,执行 update 操作,将 age = 1 的记录的 name 改为“风筝2号”;

    事务B开始后,在事务执行完 update 后,执行 insert 操作,插入记录 age =1,name = 古时的风筝,这和事务A修改的那条记录值相同,然后提交。

    事务B提交后,事务A中执行 select,查询 age=1 的数据,这时,会发现多了一行,并且发现还有一条 name = 古时的风筝,age = 1 的记录,这其实就是事务B刚刚插入的,这就是幻读

    preview

    要说明的是,当你在 MySQL 中测试幻读的时候,并不会出现上图的结果,幻读并没有发生,MySQL 的可重复读隔离级别其实解决了幻读问题,这会在后面的内容说明

    串行化

    串行化是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,

    它就相当于单线程,后一个事务的执行必须等待前一个事务结束。

    MySQL 中是如何实现事务隔离的

    首先说读未提交,它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。

    再来说串行化。读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。

    最后说读提交和可重复读。这两种隔离级别是比较复杂的,既要允许一定的并发,又想要兼顾的解决问题。

    实现可重复读

    为了解决不可重复读,或者为了实现可重复读,MySQL 采用了 MVVC (多版本并发控制) 的方式。

    我们在数据库表中看到的一行记录可能实际上有多个版本,每个版本的记录除了有数据本身外,还要有一个表示版本的字段,记为 row trx_id,而这个字段就是使其产生的事务的 id,

    事务 ID 记为 transaction id,它在事务开始的时候向事务系统申请,按时间先后顺序递增。

    按照上面这张图理解,一行记录现在有 3 个版本,每一个版本都记录这使其产生的事务 ID,比如事务A的transaction id 是100,那么版本1的row trx_id 就是 100,同理版本2和版本3。

    在上面介绍读提交和可重复读的时候都提到了一个词,叫做快照,学名叫做一致性视图,这也是可重复读和不可重复读的关键,可重复读是在事务开始的时候生成一个当前事务全局性的快照,

    而读提交则是每次执行语句的时候都重新生成一次快照。

    对于一个快照来说,它能够读到那些版本数据,要遵循以下规则:

    1. 当前事务内的更新,可以读到;
    2. 版本未提交,不能读到;
    3. 版本已提交,但是却在快照创建后提交的,不能读到;
    4. 版本已提交,且是在快照创建前提交的,可以读到;

    利用上面的规则,再返回去套用到读提交和可重复读的那两张图上就很清晰了。还是要强调,两者主要的区别就是在快照的创建上,可重复读仅在事务开始是创建一次,

    而读提交每次执行语句的时候都要重新创建一次。

    并发写问题

    存在这的情况,两个事务,对同一条数据做修改。最后结果应该是哪个事务的结果呢,肯定要是时间靠后的那个对不对。并且更新之前要先读数据,

    这里所说的读和上面说到的读不一样,更新之前的读叫做“当前读”,总是当前版本的数据,也就是多版本中最新一次提交的那版。

    假设事务A执行 update 操作, update 的时候要对所修改的行加行锁,这个行锁会在提交之后才释放。而在事务A提交之前,事务B也想 update 这行数据,

    于是申请行锁,但是由于已经被事务A占有,事务B是申请不到的,此时,事务B就会一直处于等待状态,直到事务A提交,事务B才能继续执行,

    如果事务A的时间太长,那么事务B很有可能出现超时异常。如下图所示。

     preview

    加锁的过程要分有索引和无索引两种情况,比如下面这条语句

    update user set age=11 where id = 1

    id 是这张表的主键,是有索引的情况,那么 MySQL 直接就在索引数中找到了这行数据,然后干净利落的加上行锁就可以了。

    而下面这条语句

    update user set age=11 where age=10

    表中并没有为 age 字段设置索引,所以, MySQL 无法直接定位到这行数据。那怎么办呢,当然也不是加表锁了。MySQL 会为这张表中所有行加行锁,没错,是所有行。

    但是呢,在加上行锁后,MySQL 会进行一遍过滤,发现不满足的行就释放锁,最终只留下符合条件的行。虽然最终只为符合条件的行加了锁,但是这一锁一释放的过程对性能也是影响极大的。

    所以,如果是大表的话,建议合理设计索引,如果真的出现这种情况,那很难保证并发度。

    解决幻读

    上面介绍可重复读的时候,那张图里标示着出现幻读的地方实际上在 MySQL 中并不会出现,MySQL 已经在可重复读隔离级别下解决了幻读的问题。

    前面刚说了并发写问题的解决方式就是行锁,而解决幻读用的也是锁,叫做间隙锁,MySQL 把行锁和间隙锁合并在一起,解决了并发写和幻读的问题,这个锁叫做 Next-Key锁。

    假设现在表中有两条记录,并且 age 字段已经添加了索引,两条记录 age 的值分别为 10 和 30。

    此时,在数据库中会为索引维护一套B+树,用来快速定位行记录。B+索引树是有序的,所以会把这张表的索引分割成几个区间。

    如图所示,分成了3 个区间,(负无穷,10]、(10,30]、(30,正无穷],在这3个区间是可以加间隙锁的。

    之后,我用下面的两个事务演示一下加锁过程。

    在事务A提交之前,事务B的插入操作只能等待,这就是间隙锁起得作用。当事务A执行update user set name='风筝2号’ where age = 10; 的时候,

    由于条件 where age = 10 ,数据库不仅在 age =10 的行上添加了行锁,而且在这条记录的两边,也就是(负无穷,10]、(10,30]这两个区间加了间隙锁,

    从而导致事务B插入操作无法完成,只能等待事务A提交。不仅插入 age = 10 的记录需要等待事务A提交,age<10、10<age<30 的记录页无法完成,

    而大于等于30的记录则不受影响,这足以解决幻读问题了。

    这是有索引的情况,如果 age 不是索引列,那么数据库会为整个表加上间隙锁。所以,如果是没有索引的话,不管 age 是否大于等于30,

    都要等待事务A提交才可以成功插入。

    总结

    MySQL 的 InnoDB 引擎才支持事务,其中可重复读是默认的隔离级别。

    读未提交和串行化基本上是不需要考虑的隔离级别,前者不加锁限制,后者相当于单线程执行,效率太差。

    读提交解决了脏读问题,行锁解决了并发更新的问题。并且 MySQL 在可重复读级别解决了幻读问题,是通过行锁和间隙锁的组合 Next-Key 锁实现的。

     

     

     

     

     

    展开全文
  • 一、MySQL 事务   本文所说的 MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事务的。   数据库事务指的是一组数据操作,事务内的操作要么全部成功,要么全部失败。什么都不做,不一定是真的什么都没...
    一、MySQL 事务

      本文所说的 MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事务的。

      数据库事务指的是一组数据操作,事务内的操作要么全部成功,要么全部失败。什么都不做,不一定是真的什么都没做,有可能做了一部分但是只要有一步失败,就要回滚所有操作,有点一不做二不休的意思,效果就是什么都没做。假设一个网购付款的操作,用户付款后要涉及订单状态更新、扣库存以及其他一系列动作,这就是一个事务。如果一切正常那就相安无事,一旦中间有某个环节异常,那整个事务就要回滚,总不能更新了订单状态但是不扣库存。

      事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)四个特性,简称 ACID,缺一不可,详见 《MySQL 事务的特性》。本文总结下事务的隔离性。

    二、概念说明

      以下几个概念是事务隔离级别要实际解决的问题,所以需要搞清楚都是什么意思。

    • 脏读(Dirty Read)
      脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。
    • 可重复读(Repeatable Read)
      可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。
    • 不可重复读(Non-Repeatable Read)
      对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。
    • 幻读(Phantom Read)
      幻读多是针对数据插入(INSERT)操作来说的。事务 A 按照一定条件进行数据读取, 期间事务 B 插入了相同搜索条件的新数据,事务 A 再次按照原先条件进行读取时,发现了事务 B 新插入的数据,称为幻读。详见官方文档:Phantom Rows
    三、事务隔离级别

      SQL 标准定义了四种隔离级别,MySQL 全部支持。这四种隔离级别分别是:

    • 读未提交(READ UNCOMMITTED)
    • 读提交 (READ COMMITTED)
    • 可重复读 (REPEATABLE READ)
    • 串行化 (SERIALIZABLE)

      从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。

      事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,下面展示了 4 种隔离级别对这三个问题的解决程度。

    隔离级别脏读不可重复读幻读
    读未提交可能可能可能
    读提交不可能可能可能
    可重复读不可能不可能可能
    串行化不可能不可能不可能

      只有串行化的隔离级别全部解决了这 3 个问题,其他的 3 个隔离级别都有缺陷。

    四、如何设置隔离级别

      我们可以通过以下语句查看当前数据库的隔离级别,通过下面语句可以看出我使用的 MySQL 的隔离级别是 REPEATABLE-READ,也就是可重复读,这也是 MySQL 的默认级别。

    mysql> select version();
    +-----------+
    | version() |
    +-----------+
    | 5.6.17    |
    +-----------+
    1 row in set (0.04 sec)
    # 5.7.20 之前
    mysql> SELECT @@tx_isolation;
    +-----------------+
    | @@tx_isolation  |
    +-----------------+
    | REPEATABLE-READ |
    +-----------------+
    1 row in set (0.02 sec)
    
    mysql> show variables like 'tx_isolation';
    +---------------+-----------------+
    | Variable_name | Value           |
    +---------------+-----------------+
    | tx_isolation  | REPEATABLE-READ |
    +---------------+-----------------+
    1 row in set (0.00 sec)
    
    mysql>
    # 查看事务隔离级别 5.7.20 之后
    mysql> show variables like 'transaction_isolation';
    
    mysql> SELECT @@transaction_isolation;
    

      修改隔离级别的语句是:set [作用域] transaction isolation level [事务隔离级别]。示例:SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}。其中,作用域可以是 SESSION 或者 GLOBAL,GLOBAL 是全局的,而 SESSION 只针对当前回话窗口;事务隔离级别可以是 {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} 这四种,不区分大小写。比如下面这个语句的意思是设置全局隔离级别为读提交级别。

    mysql> set global transaction isolation level read committed;
    

      设置完成后,只对之后新开启的 session 才起作用,对已经启动的 session 无效。如果用 shell 客户端那就要重新连接 MySQL,如果用 Navicat 那就要创建新的查询窗口。

    五、MySQL 中执行事务

      事务的执行过程如下,以 begin 或者 start transaction 开始,然后执行一系列操作,最后要执行 commit 操作,事务才算结束。当然,如果进行回滚操作(rollback),事务也会结束。
    在这里插入图片描述
      需要注意的是,begin 命令并不代表事务的开始,事务开始于 begin 命令之后的第一条语句执行的时候。例如下面示例中,select * from xxx 才是事务的开始。

    mysql> begin;
    mysql> select * from xxx; 
    mysql> commit; -- 或者 rollback;
    

      另外,通过以下语句可以查询当前有多少事务正在运行。

    mysql> select * from information_schema.innodb_trx;
    

      创建如下一张简单的表来辅助分析几种隔离级别,表结构如下。

    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(30) DEFAULT NULL,
      `age` tinyint(4) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
    

      初始只有一条记录:

    mysql> SELECT * FROM user;
    +----+-----------------+------+
    | id | name            | age  |
    +----+-----------------+------+
    |  1 | 古时的风筝        |    1 |
    +----+-----------------+------+
    1 row in set (0.00 sec)
    
    5.1 读未提交

      MySQL 事务隔离其实是依靠锁来实现的,加锁自然会带来性能的损失。而读未提交隔离级别是不加锁的,所以它的性能是最好的,没有加锁、解锁带来的性能开销。但有利就有弊,任何事务对数据的修改都会第一时间暴露给其他事务,即使事务还没有提交,所以它连脏读的问题都没办法解决。

      下面来做个简单实验验证一下,首先设置全局隔离级别为读未提交:set global transaction isolation level read uncommitted;设置完成后,只对之后新起的 session 才起作用,对已经启动的 session 无效,所以需要重新开启 2 个会话连接来模拟两个事务。

      启动两个事务,分别为事务 A 和事务 B,在事务 A 中使用 update 语句,修改 age 的值为 10,初始是 1,在执行完 update 语句之后,在事务 B 中查询 user 表,会看到 age 的值已经是 10 了,这时候事务 A 还没有提交,而此时事务 B 有可能拿着已经修改过的 age =10 去进行其他操作了。在事务 B 进行操作的过程中,很有可能事务 A 由于某些原因,进行了事务回滚操作,那其实事务 B 得到的就是脏数据了,拿着脏数据去进行其他的计算,那结果肯定是有问题的。

      顺着时间轴往下,表示两事务操作的执行顺序,重点看图中 age 字段的值。
    在这里插入图片描述
      读未提交,其实就是可以读到其他事务未提交的数据,但没有办法保证你读到的数据最终一定是提交后的数据,如果中间发生回滚,那就会出现脏数据问题,读未提交没办法解决脏数据问题,更别提可重复读和幻读了。

    5.2 读提交

      既然读未提交没办法解决脏数据问题,那么就有了读提交。读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据。那脏数据问题就迎刃而解了。

      读提交事务隔离级别是大多数流行数据库的默认事务隔离级别,比如 Oracle,但不是 MySQL 的默认隔离级别。

      我们来继续做一下验证,首先把事务隔离级别改为读提交级别:set global transaction isolation level read committed;之后需要重新打开新的 session 窗口。

      同样开启事务 A 和事务 B 两个事务,在事务 A 中使用 update 语句将 id = 1 的记录的 age 字段改为 10。此时,在事务 B 中使用 select 语句进行查询,我们发现在事务 A 提交之前,事务 B 中查询到的记录 age 一直是 1,直到事务 A 提交,此时在事务 B 中 select 查询,发现 age 的值已经是 10 了。

      这就出现了一个问题,在同一事务中(本例中的事务 B ),事务的不同时刻同样的查询条件,查询出来的记录内容是不一样的,事务 A 的提交影响了事务 B 的查询结果,这就是不可重复读,也就是读提交隔离级别。
    在这里插入图片描述
      每个 select 语句都有自己的一份快照,而不是一个事务一份,所以在不同的时刻,查询出来的数据可能是不一致的。

      读提交解决了脏读的问题,但是无法做到可重复读,也没办法解决幻读。

    5.3 可重复读

      可重复是对比不可重复而言的,上面说不可重复读是指同一事务不同时刻读到的数据值可能不一致,而可重复读是指,事务不会读到其他事务对已有数据的修改,即使其他事务已提交,也就是说,事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。

      同样的,需改全局隔离级别为可重复读级别:set global transaction isolation level repeatable read;在这个隔离级别下,启动两个事务,两个事务同时开启。

      首先看一下可重复读的效果,事务 A 启动后修改了数据,并且在事务 B 之前提交,事务 B 在事务开始和事务 A 提交之后两个时间节点所读取的数据相同,已经可以看出可重复读的效果。
    在这里插入图片描述
      做到了可重复读,这只是针对已有行的更改操作有效,但是对于新插入的记录,就没这么幸运了,幻读就这么产生了。我们看一下这个过程:
      事务 A 开始后,执行 update 操作,将 age = 1 的记录的 name 改为“风筝2号”;
      事务 B 开始后,在事务 A 执行完 update 后,执行 insert 操作,插入记录 age = 1,name = 古时的风筝,这和事务 A 修改的那条记录值相同,然后提交。
      事务 B 提交后,事务 A 中执行 select,查询 age = 1 的数据,这时会发现多了一行,并且发现还有一条 name = 古时的风筝,age = 1 的记录,这其实就是事务 B 刚刚插入的,这就是幻读。
    在这里插入图片描述
      要说明的是,当你在 MySQL 中测试幻读的时候,并不会出现上图的结果,幻读并没有发生,MySQL 的可重复读隔离级别部分解决了幻读问题,这会在后面的内容说明。

    5.4 串行化

      串行化是 4 种事务隔离级别中隔离效果最好的,解决了脏读、不可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。

    六、MySQL 如何实现事务隔离

      首先说读未提交,它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。

      再来说串行化,读的时候加共享锁,也就是其他事务可以并发读,但是不能写;写的时候加排它锁,其他事务不能并发写也不能并发读。

      最后说读提交和可重复读。这两种隔离级别是比较复杂的,既要允许一定的并发,又要兼顾的解决问题。

    6.1 实现可重复读

      为了解决不可重复读,或者为了实现可重复读,MySQL 采用了 MVCC (多版本并发控制) 的方式。

      我们在数据库表中看到的一行记录可能实际上有多个版本,每个版本的记录除了有数据本身外,还要有一个表示版本的字段,记为 row trx_id,而这个字段就是使其产生的事务的 id,事务 ID 记为 transaction id,它在事务开始的时候向事务系统申请,按时间先后顺序递增。
    在这里插入图片描述
      按照上面这张图理解,一行记录现在有 3 个版本,每一个版本都记录着使其产生的事务 ID,比如事务 A 的 transaction id 是 100,那么版本 1 的 row trx_id 就是 100,同理版本 2 和版本 3 的 row trx_id 分别为 200 和 300。

      在上面介绍读提交和可重复读的时候都提到了一个词,叫做快照,学名叫做一致性视图,这也是可重复读和不可重复读的关键,可重复读是在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照。

      对于一个快照来说,它能够读到哪些版本数据,要遵循以下规则:

    1. 当前事务内的更新,可以读到;
    2. 版本未提交,不能读到;
    3. 版本已提交,但是却在快照创建后提交的,不能读到;
    4. 版本已提交,且是在快照创建前提交的,可以读到。

      利用上面的规则,再返回去套用到读提交和可重复读的那两张图上就很清晰了。还是要强调,两者主要的区别就是在快照的创建上,可重复读仅在事务开始时创建一次,而读提交每次执行语句的时候都要重新创建一次

    6.2 并发写问题

      存在这样的情况,两个事务,对同一条数据做修改,最后结果应该是哪个事务的结果呢?肯定得是时间靠后的那个对不对。并且更新之前要先读数据,这里所说的读和上面说到的读不一样,更新之前的读叫做“当前读”,总是当前版本的数据,也就是多版本中最新一次提交的那版。

      快照读就是读取数据的时候会根据一定规则读取事务可见版本的数据。 而当前读就是读取最新版本的数据。
      什么情况下使用的是快照读(快照读,一般不会加锁)?一般的 select * from .... where ... 语句都是快照读。
      什么情况下使用的是当前读(当前读,会在搜索的时候加锁)?一般的 select * from .... where ... for update; select * from .... where ... lock in share mode; update .... set .. where ... ; delete from. . where .. 语句都是当前读。

      假设事务 A 执行 update 操作, update 的时候要对所修改的行加行锁,这个行锁会在提交之后才释放。而在事务 A 提交之前,事务 B 也想 update 这行数据,于是申请行锁,但是由于已经被事务 A 占有,事务 B 是申请不到的,此时,事务 B 就会一直处于等待状态,直到事务 A 提交,事务 B 才能继续执行,如果事务 A 的时间太长,那么事务 B 很有可能出现超时异常。如下图所示。
    在这里插入图片描述
      加锁的过程要分有索引和无索引两种情况,比如下面这条语句 update user set age = 11 where id = 1,id 是这张表的主键,是有索引的情况,那么 MySQL 直接就在索引数中找到了这行数据,然后干净利落地加上行锁就可以了。

      而下面这条语句 update user set age = 11 where age = 10,表中并没有为 age 字段设置索引,所以, MySQL 无法直接定位到这行数据,那怎么办呢?当然也不是加表锁了,MySQL 会为这张表中所有行加行锁,没错,是所有行。但是呢,在加上行锁后,MySQL 会进行一遍过滤,发现不满足的行就释放锁,最终只留下符合条件的行。虽然最终只为符合条件的行加了锁,但是这一锁一释放的过程对性能也是影响极大的。所以,如果是大表的话,建议合理设计索引,如果真的出现这种情况,那很难保证并发度。

    6.3 部分解决幻读
    6.3.1 无幻读示例

      上面介绍可重复读的时候,那张图里标示着出现幻读的地方实际上在 MySQL 中并不会出现,MySQL 已经在可重复读隔离级别下部分解决了幻读的问题。

      前面刚说了并发写问题的解决方式就是行锁,而解决幻读用的也是锁,叫做间隙锁,MySQL 把行锁和间隙锁合并在一起,部分解决了并发写和幻读的问题,这个锁叫做 Next-Key 锁。

      假设现在表中有两条记录,并且 age 字段已经添加了索引,两条记录 age 的值分别为 10 和 30。

    mysql> select * from user;
    +----+-----------------+------+
    | id | name            | age  |
    +----+-----------------+------+
    |  1 | 古时的风筝        |   10 |
    |  2 | 风筝2|   30 |
    +----+-----------------+------+
    2 rows in set (0.00 sec)
    

      此时,在数据库中会为索引维护一套 B+ 树,用来快速定位记录。B+ 索引树是有序的,所以会把这张表的索引分割成几个区间。
    在这里插入图片描述
      如图所示,分成了3 个区间,(负无穷,10]、(10,30]、(30,正无穷],在这3个区间是可以加间隙锁的。

      之后,我用下面的两个事务演示一下加锁过程。
    在这里插入图片描述
      在事务 A 提交之前,事务 B 的插入操作只能等待,这就是间隙锁起的作用。当事务 A 执行 update user set name = '风筝2号’ where age = 10; 的时候,由于条件 where age = 10,age 字段是索引列,数据库不仅在 age = 10 的行上添加了行锁,而且在这条记录的两边,也就是 (负无穷,10]、(10,30] 这两个区间加了间隙锁,从而导致事务 B 插入操作无法完成,只能等待事务 A 提交。不仅插入 age = 10 的记录需要等待事务 A 提交,age < 10、10 < age < 30 的记录也无法完成,而大于等于30的记录则不受影响,这种效果看上去解决了幻读问题。

      这是有索引的情况,如果 age 不是索引列,那么数据库会为整个表加上间隙锁。所以,如果是没有索引的话,不管 age 是否大于等于30,都要等待事务A提交才可以成功插入。

    6.3.2 有幻读示例

      假设上表中初始数据与 6.3.1 小节的数据相同,看如下示例。

    时间Session1Session2
    BEGIN;BEGIN;
    T1SELECT * FROM USER WHERE age = 10;
    一条结果(1, 古时的风筝,10)
    T2INSERT INTO USER VALUES (NULL, “古时的风筝”, 10);
    COMMIT;
    T3SELECT * FROM USER WHERE age = 10;
    一条结果(1, 古时的风筝,10)
    T4UPDATE USER SET NAME = “风筝2号” WHERE age = 10;
    影响的数据:2行
    T5SELECT * FROM USER WHERE age = 10;
    两条结果:(1, 风筝2号,10)、(14, 风筝2号,10)
    COMMIT;

      来分析下情形:

    • T1时刻:读取年龄为 10 的数据, Session1 拿到了 1 条记录。
    • T2时刻:另一个进程 Session2 插入了一条新的记录,年龄也为 10,并提交了事务。
    • T3时刻:Session1 再次读取年龄为 10 的数据,发现还是 1 条数据,貌似 Session2 新插入的数据并未影响到 Session1 的事务读取。

      对于 T1 — T3 时刻的情形,从结果来看,在可重复读隔离级别下似乎解决了幻读的问题。

    • T4时刻:Session1 修改年龄为 10 的数据,发现影响行数为 2 条。 为什么 T3 时候只能查到 1 条数据,但现在修改数据时却修改了 2 条呢?
    • T5时刻:Session1 再次读取年龄为 10 的数据,发现结果变成了 2 条,我们知道新增的第二条记录就是 Session2 在 T2 时刻新增的那条数据,并在 T4 时刻被修改 name 字段。

      T4,T5 的结果来看,Session1 读到了 Session2 新插入的数据,产生了幻读现象。

      如果事务中都使用快照读,那么就不会产生幻读现象,但是快照读和当前读混用就会产生幻读。当我们在事务中每次读取都使用当前读,也就是人工把 InnoDb 变成了串行化,一定程度上降低了并发性,但是也同样避免了幻读的情况。可重复读隔离级别下,一个事务中只使用当前读,或者只使用快照读都能避免幻读。

    七、总结

      MySQL 的 InnoDB 引擎才支持事务,其中可重复读是默认的隔离级别。

      读未提交和串行化基本上是不需要考虑的隔离级别,前者不加锁限制,后者相当于单线程执行,效率太差。

      读提交解决了脏读问题,行锁解决了并发更新的问题。并且 MySQL 在可重复读级别部分解决了幻读问题,是通过行锁和间隙锁的组合 Next-Key 锁实现的。

    文章参考:
    展开全文
  • 开发中经常提到数据库的事务,那你知道数据库还有事务隔离的说法吗,事务隔离还有隔离级别,那什么是事务隔离隔离级别又是什么呢?MySQL 事务本文所说的 MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持...

    开发中经常提到数据库的事务,那你知道数据库还有事务隔离的说法吗,

    事务隔离还有隔离级别,那什么是事务隔离,隔离级别又是什么呢?

    MySQL 事务

    本文所说的 MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事务的。

    数据库事务指的是一组数据操作(例如新增、修改、删除、添加索引、修改索引等操作),事务内的操作要么全部成功,要么全部失败。全部失败,什么都不做,其实不是没做,是可能做了一部分但是只要有一步失败,就要回滚所有操作,有点一不做二不休的意思。

    示例:一个网购付款的操作,用户付款后要涉及到收款、订单状态更新、扣库存、退款等一系列动作,这就是一个事务,如果一切正常那就相安无事,一旦中间有某个环节异常,那整个事务就要回滚,总不能收款了,更新了订单状态,但不扣库存吧;退款了,也要更新订单状态,恢复库存吧,这问题就大了。

    事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)四个特性,简称 ACID,缺一不可。今天要说的就是隔离性。

    1)原子性(Atomicity)

    事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

    2)一致性(Consistency)

    事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。

    3)隔离性(Isolation)

    一个事务的执行不能其它事务干扰,即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

    4)持续性(Durability)

    也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的,接下来的其它操作或故障不应该对其执行结果有任何影响。

    事务隔离的概念说明

    以下几个概念是事务隔离级别要实际解决的问题,所以需要搞清楚都是什么意思。

    1、脏读(UPDATE)

    脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了不一定最终存在的数据,这就是脏读。

    2、不可重复读(UPDATE)

    对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。

    3、可重复读(UPDATE)

    可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。

    4、幻读(INSERT)

    幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改(UPDATE),但是还未提交,此时事务B插入(INSERT)了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。

    MySQL 事务隔离级别

    SQL 标准定义了四种隔离级别,MySQL 全都支持。这四种隔离级别分别是:

    1)读未提交(READ UNCOMMITTED)   脏数据

    2)读提交 (READ COMMITTED)        不可重复读

    3)可重复读 (REPEATABLE READ)    幻读

    4)串行化 (SERIALIZABLE)

    从上往下,隔离强度逐渐增强,性能逐渐变差,事务都有两面性。

    采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读(RR)是 MySQL 的默认级别。

    事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,

    下面展示了 4 种隔离级别对这三个问题的解决程度。

    隔离级别

    脏读

    不可重复读

    幻读

    读未提交(脏数据)

    可能

    可能

    可能

    读提交   (不可重复读)

    不可能

    可能

    可能

    可重复读(幻读)

    不可能

    不可能

    可能

    串行化

    不可能

    不可能

    不可能

    只有串行化的隔离级别解决了全部这 3 个问题,其他的 3 个隔离级别都有缺陷。

    MySQL 四种隔离级别

    SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。

    低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

    1)Read Uncommitted(读取未提交内容)

    读未提交的隔离级别,所有事务都可以看到其他未提交事务的执行结果。

    本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。

    读取未提交的数据,也被称之为脏读(Dirty Read)

    2)Read Committed(读取提交内容)

    这是大多数数据库系统(如 DB2、Oracle)的默认隔离级别(但不是MySQL默认的,MySQL默认的是可重复度级别)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),即同一事务的其他实例在该实例处理期间可能会有新的commit,所以同一select可能返回不同结果。

    3)Repeatable Read(可重读)

    这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,MultiVersion Concurrency Control)机制解决了该问题。

    4)Serializable(可串行化)

    这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。

    简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

    事务隔离级别

    读未提交 (READ UNCOMMITED)

    ->读已提交 (READ COMMITTED)

    ->可重复读 (REPEATABLE READ)

    ->序列化 (SERIALIZABLE)

    隔离级别依次增强,但是导致的问题是并发能力的减弱。

    隔离级别

    脏读

    不可重复读

    幻读

    概念

    READ UNCOMMITED

    Y

    Y

    Y

    事务能够看到其他事务没有提交的修改,当另一个事务又回滚了修改后的情况,又被称为脏读dirty read

    READ COMMITTED

    X

    Y

    Y

    事务能够看到其他事务提交后的修改,这时会出现一个事务内两次读取数据可能因为其他事务提交的修改导致不一致的情况,称为不可重复读

    REPEATABLE READ

    X

    X

    Y

    事务在两次读取时读取到的数据的状态是一致的

    SERIALIZABLE

    X

    X

    X

    可重复读中可能出现第二次读读到第一次没有读到的数据,也就是被其他事务插入的数据,这种情况称为幻读phantom read, 该级别中不能出现幻读

    大多数数据库系统的默认隔离级别都是READ COMMITTED(但MySQL不是),MySQL InnoDB存储引擎默认隔离级别REPEATABLE READ,通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

    隔离级别的原理

    下面,我们来分析这 4 种隔离级别到底是怎么个意思。

    我们可以通过以下语句查看当前数据库的隔离级别,通过下面语句可以看出使用的 MySQL 的隔离级别是 REPEATABLE-READ,也就是可重复读,这也是 MySQL 的默认级别。

    show variables like 'tx_isolation';

    # 查看事务隔离级别 5.7.20 之后

    show variables like 'transaction_isolation';

    SELECT @@transaction_isolation

    # 5.7.20 之后

    SELECT @@tx_isolation

    show variables like 'tx_isolation'

    +---------------+-----------------+

    | Variable_name | Value |

    +---------------+-----------------+

    | tx_isolation | REPEATABLE-READ |

    +---------------+-----------------+

    稍后,我们要修改数据库的隔离级别,所以先了解一下具体的修改方式。

    修改隔离级别的语句是:set [作用域] transaction isolation level [事务隔离级别]

    SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED| READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

    其中,作用于可以是 SESSION 或者 GLOBAL,GLOBAL 是全局的,而 SESSION 只针对当前回话窗口。

    隔离级别是 {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} 这四种,MySQL不区分大小写。

    例如,下面这个语句的意思是设置全局隔离级别为读提交级别。

    mysql> set global transaction isolation level read committed;

    MySQL 中执行事务

    事务的执行过程如下,以 begin 或者 start transaction 开始,然后执行一系列操作,最后要执行 commit 操作,事务才算结束。当然,如果进行回滚操作(rollback),事务也会结束。

    643956b7ac6fa051e4b0113b961745c6.png

    需要注意的是,begin 命令并不代表事务的开始,事务开始于 begin 命令之后的第一条语句执行的时候。

    例如,下面示例中,select * from xxx 才是事务的开始,

    begin;

    select * from xxx;

    commit; -- 或者 rollback;

    另外,通过以下语句可以查询当前有多少事务正在运行。

    select * from information_schema.innodb_trx;

    > select * from information_schema.innodb_trx;

    +-----------------+-----------+---------------------+-----------------------+------------------+------------+---------------------+-----------+---------------------+-------------------+-------------------+------------------+-----------------------+-----------------+-------------------+-------------------------+---------------------+-------------------+------------------------+----------------------------+------------------+----------------------------+

    | trx_id | trx_state | trx_started | trx_requested_lock_id | trx_wait_started | trx_weight | trx_mysql_thread_id | trx_query | trx_operation_state | trx_tables_in_use | trx_tables_locked | trx_lock_structs | trx_lock_memory_bytes | trx_rows_locked | trx_rows_modified | trx_concurrency_tickets | trx_isolation_level | trx_unique_checks | trx_foreign_key_checks | trx_last_foreign_key_error | trx_is_read_only | trx_autocommit_non_locking |

    +-----------------+-----------+---------------------+-----------------------+------------------+------------+---------------------+-----------+---------------------+-------------------+-------------------+------------------+-----------------------+-----------------+-------------------+-------------------------+---------------------+-------------------+------------------------+----------------------------+------------------+----------------------------+

    | 421972982987472 | RUNNING | 2021-01-18 15:52:23 | NULL | NULL | 0 | 8629489 | NULL | NULL | 0 | 0 | 0 | 1136 | 0 | 0 | 0 | REPEATABLE READ | 1 | 1 | NULL | 0 | 0 |

    | 421972982991688 | RUNNING | 2021-01-18 15:52:12 | NULL | NULL | 0 | 8629464 | NULL | NULL | 0 | 0 | 0 | 1136 | 0 | 0 | 0 | REPEATABLE READ | 1 | 1 | NULL | 0 | 0 |

    | 421972982983256 | RUNNING | 2021-01-18 15:51:58 | NULL | NULL | 0 | 8629405 | NULL | NULL | 0 | 0 | 0 | 1136 | 0 | 0 | 0 | REPEATABLE READ | 1 | 1 | NULL | 0 | 0 |

    | 421972982941096 | RUNNING | 2021-01-18 15:51:07 | NULL | NULL | 0 | 8629295 | NULL | NULL | 0 | 0 | 0 | 1136 | 0 | 0 | 0 | REPEATABLE READ | 1 | 1 | NULL | 0 | 0 |

    | 319255680 | RUNNING | 2021-01-18 15:50:56 | NULL | NULL | 2 | 8629246 | NULL | NULL | 0 | 1 | 2 | 1136 | 1 | 0 | 0 | REPEATABLE READ | 1 | 1 | NULL | 0 | 0 |

    | 421972982995904 | RUNNING | 2021-01-18 15:48:43 | NULL | NULL | 0 | 8628861 | NULL | NULL | 0 | 0 | 0 | 1136 | 0 | 0 | 0 | REPEATABLE READ | 1 | 1 | NULL | 0 | 0 |

    | 319254768 | RUNNING | 2021-01-18 15:46:32 | NULL | NULL | 2 | 8628542 | NULL | NULL | 0 | 1 | 2 | 1136 | 1 | 0 | 0 | REPEATABLE READ | 1 | 1 | NULL | 0 | 0 |

    +-----------------+-----------+---------------------+-----------------------+------------------+------------+---------------------+-----------+---------------------+-------------------+-------------------+------------------+-----------------------+-----------------+-------------------+-------------------------+---------------------+-------------------+------------------------+----------------------------+------------------+----------------------------+

    7 rows in set (0.002 sec)

    好了,重点来了,开始分析这几个隔离级别了。

    接下来我会用一张表来做一下验证,表结构简单如下:

    CREATE TABLE `user` (

    `id` int(11) NOT NULL AUTO_INCREMENT,

    `name` varchar(30) DEFAULT NULL,

    `age` tinyint(4) DEFAULT NULL,

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

    初始只有一条记录:

    mysql> SELECT * FROM user;

    +----+-----------------+------+

    | id | name | age |

    +----+-----------------+------+

    | 1 | 古时的风筝 | 1 |

    +----+-----------------+------+

    1、读未提交  (脏数据)

    MySQL 事务隔离其实是依靠锁来实现的,加锁自然会带来性能的损失。

    而读未提交隔离级别是不加锁的,所以它的性能是最好的,没有加锁、解锁带来的性能开销。

    但有利就有弊,这基本上就相当于裸奔啊,所以它连脏读的问题都没办法解决。

    任何事务对数据的修改都会第一时间暴露给其他事务,即使事务还没有提交。

    下面来做个简单实验验证一下,首先设置全局隔离级别为读未提交。

    set global transaction isolation level read uncommitted;

    设置完成后,只对之后新起的 session 才起作用,对已经启动 session 无效。

    如果用 shell 客户端那就要重新连接 MySQL;

    如果用 Navicat ,MySQL Workbench、SecureCRT等,连接MySQL,那就要创建新的查询窗口。

    测试详细步骤如下:

    1)启动两个事务,分别为事务A和事务B,

    2)在事务A中使用 update 语句,修改 age 的值为10,初始是1 ,在执行完 update 语句之后,在事务B中查询 user 表,会看到 age 的值已经是 10 了,这时候事务A还没有提交,而此时事务B有可能拿着已经修改过的 age=10 去进行其他操作了。

    3)在事务B进行操作的过程中,很有可能事务A由于某些原因,进行了事务回滚操作,重新把 age 回滚到了初始值1,那其实事务B得到的数值10 就是脏数据了,拿着脏数据去进行其他的计算,那结果肯定也是有问题的。

    如下图,顺着中间粉色的时间轴,往表示两事务中操作的执行顺序,重点看图中 age 字段的值。

    21eb0b47298a72e55f4eaa352744b9f4.png

    读未提交,其实就是可以读到其他事务未提交的数据,但没有办法保证你读到的数据最终一定是提交后的数据,如果中间发生回滚,那就会出现脏数据问题,读未提交没办法解决脏数据问题。更别提可重复读和幻读了,想都不要想。

    2、读已提交  (不可重复度)

    既然读未提交没办法解决脏数据问题,那么就有了读提交。

    读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据,这样脏数据问题迎刃而解了。

    读已提交事务隔离级别是大多数流行数据库的默认事务隔离界别,比如 Oracle,但是不是 MySQL 的默认隔离界别。

    我们继续来做一下验证,首先把事务隔离级别改为读提交级别。

    set global transaction isolation level read committed;

    之后需要重新打开新的 session 窗口,也就是新的 shell 窗口才可以。

    1)同样开启事务A和事务B两个事务,在事务A中使用 update 语句将 id=1 的记录行 age 字段改为 10。

    2)此时,在事务B中使用 select 语句进行查询,我们发现在事务A提交之前,事务B中查询到的记录 age 一直是1,直到事务A提交,此时在事务B中 select 查询,发现 age 的值已经是 10 了。

    这就出现了一个问题,在同一事务中(本例中的事务B),事务的不同时刻同样的查询条件,查询出来的记录内容是不一样的,事务A的提交影响了事务B的查询结果,这就是不可重复读,也就是读提交隔离级别。

    7289840b9a06f9afa12f87bfadecb1ee.png

    每个 select 语句都有自己的一份快照,而不是一个事务一份,所以在不同的时刻,查询出来的数据可能是不一致的。

    读提交解决了脏读的问题,但是无法做到可重复读,也没办法解决幻读。

    3、可重复读 (幻读)

    可重复是对比不可重复而言的,上面说的不可重复读是指同一事务不同时刻读到的数据值可能不一致。

    而可重复读是指,事务不会读到其他事务对已有数据的修改,及时其他事务已提交,也就是说,事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。

    同样的,需改全局隔离级别为可重复读级别。

    set global transaction isolation level repeatable read;

    在这个隔离级别下,启动两个事务,两个事务同时开启。

    首先看一下可重复读的效果,事务A启动后修改了数据,并且在事务B之前提交,事务B在事务开始和事务A提交之后两个时间节点都读取的数据相同,已经可以看出可重复读的效果。

    bcea747c72d54868562754c01aadceae.png

    可重复读做到了,这只是针对已有行的更改(Update)操作有效,但是对于新插入(Insert)的行记录,就没这么幸运了,幻读就这么产生了。幻读是因为新插入数据引发的问题。

    我们看一下这个过程:

    1)事务A开始后,执行 update 操作,将 age = 1 的记录的 name 改为"风筝2号";

    2)事务B开始后,在事务A执行完 update 后,事务B执行 insert 操作,插入记录 age =1,name = "古时的风筝",这时事务B和事务A修改的那条记录值相同,然后提交。

    3)事务B提交后,事务A中执行 select,查询 age=1 的数据,这时,会发现多了一行,并且发现还有一条 name = "古时的风筝",age = 1 的记录,这其实就是事务B刚刚插入的,这就是幻读。

    dad213ab512ffa405b70d5c0aa094505.png

    需重点说明的是,当你在 MySQL 中测试幻读的时候,并不会出现上图的结果,幻读并没有发生,

    MySQL 的可重复读隔离级别,其实解决了幻读问题,这会在后面的内容说明

    4、串行化

    串行化是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束,非常不利于数据库的高并发操作。

    MySQL 中是如何实现事务隔离的

    首先说读未提交,它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。

    再来说串行化:

    1)读的时候加共享锁,也就是其他事务可以并发读,但是不能写。

    2)写的时候加排它锁,其他事务不能并发写也不能并发读,因为写操作还没有完成,读操作可能会出现幻读的错误。

    最后说读已提交和可重复读。这两种隔离级别是比较复杂的,既要允许一定的并发,又想要兼顾的解决问题。

    1、实现可重复读

    为了解决不可重复读,或者为了实现可重复读,MySQL 采用了 MVCC (多版本并发控制) 的方式。

    我们在数据库表中看到的一行记录可能实际上有多个版本,每个版本的记录除了有数据本身外,还要有一个表示版本的字段,记为 row trx_id,而这个字段就是使其产生的事务的 id,事务 ID 记为 transaction id,它在事务开始的时候向事务系统申请,按时间先后顺序递增。

    1cf31aa31ba7bb633f26743583c2c6bf.png

    按照上面这张图理解,一行记录现在有 3 个版本,每一个版本都记录这使其产生的事务 ID,比如事务A的transaction id 是100,那么版本1的row trx_id 就是 100,同理版本2和版本3。

    在上面介绍读提交和可重复读的时候都提到了一个词,叫做快照,学名叫做一致性视图,这也是可重复读和不可重复读的关键,可重复读是在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照。

    对于一个快照来说,它能够读到那些版本数据,要遵循以下规则:

    1)当前事务内的更新,可以读到;

    2)版本未提交,不能读到;

    3)版本已提交,但是却在快照创建后提交的,不能读到;

    4)版本已提交,且是在快照创建前提交的,可以读到;

    利用上面的规则,再返回去套用到读提交和可重复读的那两张图上就很清晰了。还是要强调,两者主要的区别就是在快照的创建上,可重复读仅在事务开始是创建一次,而读提交每次执行语句的时候都要重新创建一次

    MySQL MVCC 实现原理

    MVCC(Multi Version Concurrency Control,多版本并发控制)

    与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)

    MVCC最大的优势:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能

    MySQL的逻辑架构大概分为四层:

    1)顶层是接入层,不同语言的客户端通过mysql的协议与mysql服务器进行连接通信,接入层进行权限验证、连接池管理、线程管理等。

    2)下面是mysql服务层,包括sql解析器、sql优化器、数据缓冲、缓存等。

    3)再下面是mysql中的存储引擎层,mysql中存储引擎是基于表的。

    4)最后是系统文件层,保存数据、索引、日志等。

    2e9140c3dd05f85bf1d6aea0e365c829.png

    第一层: 服务层(为客户端服务)

    为请求做连接处理,授权认证,安全等。

    第二层:核心服务

    比如查询解析,优化,缓存,内置函数;存储过程,触发器,视图等。

    对于第二层来说,所有跨存储引擎的功能都在这一层实现。

    第三层:存储引擎

    负责MySQL中数据的存储和提取。服务器通过api与存储引擎通信,这些接口屏蔽了存储引擎之间的差异。也就是说,接口的存在,导致不同存储引擎的差异不会影响到上层查询过程。

    架构分层之后,可以更好的理解一些问题,比如我们很关心的并发问题。

    MySQL层面的并发控制,实际上是分为两种:

    1)服务层的并发

    2)存储引擎层的并发的

    第四层:系统文件层,保存数据、索引、日志等。

    61c274c077cad92c5934f011ce4e0bc8.png

    MYSQL 事务日志

    事务日志可以帮助提高事务的效率。使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回到磁盘。目前大多数存储引擎都是这样实现的,我们通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。

    如果数据的修改已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。

    MySQL Innodb中跟数据持久性、一致性有关的日志,有以下几种:

    1)Bin Log

    是MySQL服务层产生的日志,常用来进行数据恢复、数据库复制,常见的 MySQL 主从架构,就是采用 Slave同步Master的binlog实现的

    2)Redo Log

    记录了数据操作在物理层面的修改,MySQL中使用了大量缓存,修改操作时会直接修改内存,而不是立刻修改磁盘,事务进行中时会不断的产生redo log,在事务提交时进行一次flush操作,保存到磁盘中。当数据库或主机失效重启时,会根据redo log进行数据的恢复,如果redo log中有事务提交,则进行事务提交修改数据。

    3)Undo Log

    除了记录redo log外,当进行数据修改时还会记录undo log,undo log用于数据的撤回操作,它记录了修改的反向操作,比如,插入对应删除,修改对应修改为原来的数据,通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC

    2、并发写问题

    存在这的情况,两个事务,对同一条数据做修改,最后结果应该是哪个事务的结果呢,肯定要是时间靠后的那个对不对。并且更新之前要先读数据,这里所说的读和上面说到的读不一样,更新之前的读叫做“当前读”,总是当前版本的数据,也就是多版本中最新一次提交的那版。

    假设事务A执行 update 操作, update 的时候要对所修改的行加行锁,这个行锁会在提交之后才释放。而在事务A提交之前,事务B也想 update 这行数据,于是申请行锁,但是由于已经被事务A占有,事务B是申请不到的,此时,事务B就会一直处于等待状态,直到事务A提交,事务B才能继续执行,如果事务A的时间太长,那么事务B很有可能出现超时异常。如下图所示。

    c17a7bd124920f00ac4fafbcae016cd2.png

    加锁的过程要分有索引和无索引两种情况,比如下面这条语句

    update user set age=11 where id = 1;

    id 是这张表的主键,是有索引的情况,那么 MySQL 直接就在索引数中找到了这行数据,然后干净利落的加上行锁就可以了。

    而下面这条语句

    update user set age=11 where age=10;

    表中并没有为 age 字段设置索引,所以, MySQL 无法直接定位到这行数据。那怎么办呢,当然也不是加表锁了。MySQL 会为这张表中所有行加行锁,没错,是所有行。但是呢,在加上行锁后,MySQL 会进行一遍过滤,发现不满足的行就释放锁,最终只留下符合条件的行。虽然最终只为符合条件的行加了锁,但是这一锁一释放的过程对性能也是影响极大的。所以,如果是大表的话,建议合理设计索引,如果真的出现这种情况,那很难保证并发度。

    3、解决幻读

    上面介绍可重复读的时候,那张图里标示着出现幻读的地方实际上在 MySQL 中并不会出现,MySQL 已经在可重复读隔离级别下解决了幻读的问题。

    前面刚说了并发写问题的解决方式就是行锁,而解决幻读用的也是锁,叫做间隙锁,MySQL 把行锁和间隙锁合并在一起,解决了并发写和幻读的问题,这个锁叫做 Next-Key锁。

    假设现在表中有两条记录,并且 age 字段已经添加了索引,两条记录 age 的值分别为 10 和 30

    11050d622405b5eebc304c26bce0c8e2.png

    此时,在数据库中会为索引维护一套B+树,用来快速定位行记录。

    B+索引树是有序的,所以会把这张表的索引分割成几个区间。

    7e9dd021bdf698caa41d006c1bb6cd20.png

    如图所示,分成了3 个区间,(负无穷,10]、(10,30]、(30,正无穷],在这3个区间是可以加间隙锁的。

    之后,我用下面的两个事务演示一下加锁过程。

    0c9a79fe39cfb198c5eaa1dc4a26a71d.png

    在事务A提交之前,事务B的插入操作只能等待,这就是间隙锁起得作用。

    当事务A执行update user set name='风筝2号’ where age = 10; 的时候,由于条件 where age = 10 ,数据库不仅在 age =10 的行上添加了行锁,而且在这条记录的两边,也就是(负无穷,10]、(10,30]这两个区间加了间隙锁,从而导致事务B插入操作无法完成,只能等待事务A提交。不仅插入 age = 10 的记录需要等待事务A提交,age<10、10

    这是有索引的情况,如果 age 不是索引列,那么数据库会为整个表加上间隙锁。

    所以,如果是没有索引的话,不管 age 是否大于等于30,都要等待事务A提交才可以成功插入。

    总结

    MySQL 的 InnoDB 引擎才支持事务,其中可重复读是默认的隔离级别。

    读未提交和串行化基本上是不需要考虑的隔离级别,前者不加锁限制,后者相当于单线程执行,效率太差。

    读提交解决了脏读问题,行锁解决了并发更新的问题。

    并且 MySQL 在可重复读级别解决了幻读问题,是通过行锁和间隙锁的组合 Next-Key 锁实现的。

    本文转自:

    参考推荐:

    展开全文
  • 为什么需要事务隔离?当多个事务在并发运行的时候,可能出现以下问题:脏读:事务1 读取到了 事务2 还未提交的数据,如果此时 事务2 进行了回滚,那么 事务1 读取到的数据将会是脏数据;不可重复读:事务1 在过程中...

    该文章基于 8.0.22 版本 mysql innodb 进行分析;理论上对于较新版本 mysql 都可适用。

    为什么需要事务隔离?

    当多个事务在并发运行的时候,可能出现以下问题:脏读:事务1 读取到了 事务2 还未提交的数据,如果此时 事务2 进行了回滚,那么 事务1 读取到的数据将会是脏数据;

    不可重复读:事务1 在过程中多次读取数据,同时 事务2 对数据进行了修改(包括新增,修改,删除)并进行了提交,那么 事务1 可能多次读取到的数据不一致;

    幻影行(Phantom Rows):事务1 在过程中更新了id 在 [1,5] 区间的数据 a 字段为 "trx1",假设此时表中没有id = 4 这个条数据,此时 事务2 插入了 id = 4 这条数据且 a 字段为 "trx2"并进行了提交,当执行完 事务1 的一瞬间,在 1-5 区间内居然还存在 a 字段不等于 "trx1" 的数据;(注:很多博客会把 幻影行 叫做幻读,但我认为对于 innodb 那不是一种准确的叫法,因为在该问题中已经不再关注事务中的读,而是关注事务中进行的对数据的改变操作)

    对于以上的问题,mysql 实现了 SQL 1992 标准中的四个隔离级别:事务隔离级别脏读不可重复读幻影行

    读未提交(read uncommitted)允许出现允许出现允许出现

    读已提交(read committed)不允许出现允许出现允许出现

    可重复读(repeatable read)不允许出现不允许出现标准:允许出现

    innodb:不会出现

    串行化(serializable)不允许出现不允许出现不允许出现

    mysql 怎么实现的事务隔离级别?

    读未提交(read uncommitted)

    因为可能导致的问题在该隔离级别中都被允许出现,所以无需做特别的处理;

    串行化(serializable)

    在该事务级别中,innodb 会为所有的读写操作加上对于的共享锁或者独占锁以 next-key 锁;保证读写操作串行化运行,从而达到该级别的要求;

    读已提交(read committed)AKA RC

    innodb 使用 MVCC 来解决脏读的问题;

    在 innodb 内部,会为每一行添加多余三个字段;6-byte DB_TRX_ID :标识改行是由哪个事务进行修改的

    7-byte DB_ROLL_PTR :回滚指针,用于记录 undo log

    6-byte DB_ROW_ID :行 id,如果 innoDB 表没有指定聚簇索引,将会自动生成该数据并用于二级索引;如果指定,将不会;

    可以大致参考下图帮助理解:图片来源(侵删):https://juejin.im/post/6844903808376504327

    tips:删除在内部被视为更新,会设置行中的一个特殊位代表该行已删除。

    二级索引与主索引处理上有差别,主索引接地更新 undoLog;而二级索引将会标记新增索引并标记删除久的索引,对于标记删除或者是二级索引被更新时,将会从主索引查询数据;

    ReadView

    有了以上的储存结构,我们还需要在查询的时候融入逻辑才能进行正确的数据读取,所以就有了 ReadView 的实现;

    ReadView 主要包含以下字段:

    /** The read should not see any transaction with trx id >= thisvalue. In other words, this is the "high water mark". */

    trx_id_t m_low_limit_id;

    /** The read should see all trx ids which are strictlysmaller (

    trx_id_t m_up_limit_id;

    /** trx id of creating transaction, set to TRX_ID_MAX for freeviews. */

    trx_id_t m_creator_trx_id;

    /** Set of RW transactions that was active when this snapshotwas taken */

    ids_t m_ids;

    在进行读取行数据的时候会使用 ReadView 对行数据 DB_TRX_ID 进行判断,如果符合要求将会返回,不符合要求将会在 undoLog 中进行查找;

    /** Check whether the changes by id are visible.@param[in]idtransaction id to check against the view@param[in]n ametable name@return whether the view sees the modifications of id. */

    bool changes_visible(trx_id_t id, const table_name_t &name) const

    MY_ATTRIBUTE((warn_unused_result)) {

    ut_ad(id > 0);

    // 小于最小trx_id或者等于当前id将会可见 if (id < m_up_limit_id || id == m_creator_trx_id) {

    return (true);

    }

    check_trx_id_sanity(id, name);

    // 大于生成 readView 时的最大系统id将会不可见 if (id >= m_low_limit_id) {

    return (false);

    } else if (m_ids.empty()) {

    return (true);

    }

    const ids_t::value_type *p = m_ids.data();

    // 在生成 readView 时正在执行的事务 id 中将会不可见 return (!std::binary_search(p, p + m_ids.size(), id));

    }

    void ReadView::prepare(trx_id_t id) {

    ut_ad(mutex_own(&trx_sys->mutex));

    // 设置生成 readView 的 id m_creator_trx_id = id;

    // 将对最大 id 与最小id 设置为当前系统事务最大id m_low_limit_no = m_low_limit_id = m_up_limit_id = trx_sys->max_trx_id;

    if (!trx_sys->rw_trx_ids.empty()) {

    // 复制当前事务id到 m_ids 数组 同时会重新设置 m_up_limit_id copy_trx_ids(trx_sys->rw_trx_ids);

    } else {

    m_ids.clear();

    }

    ut_ad(m_up_limit_id <= m_low_limit_id);

    if (UT_LIST_GET_LEN(trx_sys->serialisation_list) > 0) {

    const trx_t *trx;

    trx = UT_LIST_GET_FIRST(trx_sys->serialisation_list);

    if (trx->no < m_low_limit_no) {

    m_low_limit_no = trx->no;

    }

    }

    ut_d(m_view_low_limit_no = m_low_limit_no);

    m_closed = false;

    }

    通过上面的逻辑我们可以知道,我们可以通过 ReadView 实现对于没有提交的数据不进行读取,即解决了脏读的问题;

    同时对于该隔离级别,ReadView 会在每次执行 select 的时候重新生成一次 ReadView;

    可重复读(repeatable read)AKA RR

    对于该隔离级别,复用 read committed 相同的方式来解决脏读,同时该级别下 ReadView 的生成时机发生了变化,该级别下 ReadView 会在事务中第一进行 select 时生成 ReadView,之后的查询都会复用第一次生成的 ReadView 从而实现可重复读;从实现方式来看,RC 每次生成 ReadView 的代价比 RR 的代价还高,且每次生成还会导致不可重复读的问题,为什么 RC 还要这样子设计呢?我认为可能是能在事务进行时获取到别的事务的提交数据在某些业务场景下是需要的,所以这样子设计,你觉得呢?欢迎讨论!

    innodb 怎么解决幻影行问题?

    我们在数据库开启两个事务并执行如下操作测试:

    创建表并插入数据

    CREATE TABLE IF NOT EXISTS `test`(

    `id` INT UNSIGNED AUTO_INCREMENT,

    `name` VARCHAR(100) NOT NULL,

    `balance` INT NOT NULL,

    PRIMARY KEY ( `id` )

    )ENGINE=InnoDB DEFAULT CHARSET=utf8;

    insert into test values(1, "test1", 1);

    insert into test values(2, "test2", 1);

    insert into test values(3, "test3", 1);

    insert into test values(4, "test4", 1);

    事务1

    // step 1;

    begin;

    // step 2;

    update test set balance = 1000 where id > 2;

    事务2

    // step 3

    begin;

    // step 4

    // 这一步将会被堵塞,知道锁超时或者 事务1 commit;

    insert into test values(5, "test5", 1);

    由上可知,innodb 使用了某种锁机制阻止了 事务2 中对于在 事务1 更新范围内但是不在数据库中的数据的写操作;这就是 Next-Key Lock;

    Next-Key Lock 由 Record Lock(行级锁) 与 Gap Lock(间歇锁)组成:Record Lock:锁住相应的操作的行数据;

    Gap Lock:锁住某两条数据间的间隙,间隙锁的锁定范围由已存在数据库中的数据决定,不完全等同于执行操作的范围条件;例如数据库中存在 [1,5,6,10]数据,我们在事务1使用条件 id > 2 & id < 8 的条件更新时,将会被锁定的间歇是 [(1,5),(6,10)],即如果此时在事务中写 id = 9 的操作也将会被堵塞;

    参考资料

    展开全文
  • 大家脑子里一定很容易蹦出一堆事务的相关知识,如事务的ACID特性,隔离级别,解决的问题(脏读,不可重复读,幻读)等等,但是可能很少有人真正的清楚事务的这些特性又是怎么实现的,为什么要有四个隔离级别。...
  • 回顾在MySQL的众多存储引擎中,只有InnoDB支持事务,所有这里说的事务隔离级别指的是InnoDB下的事务隔离级别。读未提交:一个事务可以读取到另一个事务未提交的修改。这会带来脏读、幻读、不可重复读问题。(基本没用...
  • 不同事务隔离级别解决了什么问题? spring中的事务与mysql中的事务有什么区别? 开启事务的方式有哪几种? 莫慌本文一一来探究~~~~~来自大三狗的自述 什么叫做事务?事务是什么? 事务: java硬编码角度:...
  • 一致性(consistency):数据库总是从一致性状态到另一个一致性状态,它只包含成功事务提交的结果隔离型(isolation):事务所做的修改在最终提交一起,对其他事务是不可见的持久性(durability):一旦事务提交,则其所做...
  • 大家脑子里一定很容易蹦出一堆事务的相关知识,如事务的ACID特性,隔离级别,解决的问题(脏读,不可重复读,幻读)等等,但是可能很少有人真正的清楚事务的这些特性又是怎么实现的,为什么要有四个隔离级别。...
  • 经常提到数据库的事务,那你知道数据库还有事务隔离的说法吗,事务隔离还有隔离级别,那什么是事务隔离隔离级别又是什么呢?本文就帮大家梳理一下。 MySQL 事务 本文所说的 MySQL 事务都是指在 InnoDB 引擎下,...
  • 今天介绍下,在MySQL的InnoDB存储引擎中,事务隔离是如何实现的。InnoDB里面每个事务有一个唯一的事务ID,叫作transaction id。它是在事务开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。对于数据库...
  • SQL 标准的事务隔离级别包括: 读未提交(read uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。避免发生脏写 读提交(read committed):一个事务提交之后,它做的变更才会被其他事务看到。避免...
  • 目录事务(Transaction)什么是事务事务原理事务的特性事务的隔离性(隔离级别)第一级别:读未提交(read uncommitted)第二级别:读已提交 (read committed)第三级别:可重复读(repeatable read)第四级别:序列化读 / ...
  • 原理隐藏的三个字段InnoDB的MVCC,是通过在每行纪录后面保存三个隐藏的列来实现的。这三个列,一个保存了行的创建时间,一个保存了行的过期时间(或删除时间),当然存储的并不是...在可重复读隔离级别下,MVCC具体...
  • MySQL 事务隔离级别实现原理回顾在 MySQL 的众多存储引擎中, 只有 InnoDB 支持事务, 所有这里说的事务隔离级别指的是 InnoDB 下的事务隔离级别读未提交: 一个事务可以读取到另一个事务未提交的修改这会带来脏读幻...
  • # MySQL的事务隔离级别实现原理# 1.MySQL事务并发时可能出现的情况mysql事务并发的时候回出现三种情况 脏读(Dirty Read)、不可重复读(Non-Repeatable Read)、幻读(Phantom Read)# 1.1脏读一个事务读到了另外一个...
  • 所谓的数据库事务操作其实就是一组原子性的操作,要么全部操作成功,要么全部操作失败并行事务的四大问题:1.更新丢失:和别的事务读到相同的东西,各自写,自己的写被...对应隔离级别1.READ UNCOMMITTED:读未提交...
  • 在使用Spring框架时,可以有2种使用事务的方式,一种是编程式,一种是声明式,@Transactional注解就是声明式。 首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让...
  • 在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的,@Transactional注解就是申明式的。首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能...
  • oracle事务隔离级别

    2021-05-01 07:27:30
    事务隔离级别:可能允许或者不允许的三种现象隔离级别是根据三个现象来定义的:Dirtyread:脏读,也就是说,可以读取未提交的数据。我不知道其他的数据库是否支持,但是我知道oracle肯定是不支持的Oracle的事务有...
  • 大家脑子里肯定很容易蹦出一堆事务的相干常识,如事务的ACID个性,隔离级别,解决的问题(脏读,不可反复读,幻读)等等,然而可能很少有人真正的分明事务的这些个性又是怎么实现的,为什么要有四个隔离级别。...
  • 在使用Spring框架时,可以有两种使用事务的方式,一种时编程式的,@Transaction注解就是申明式的。 首先,事务这个概念是数据库层面的,Spring只是基于数据库中的s
  • mysql隔离级别实现原理探究关于这个话题,在网上看到了多种说法,总是撸不通思路,于是决定自己探究,先把结论贴出来未提交读写时加排他锁,写完释放;(读时不加锁;)提交读写时加排他锁,事务结束后释放读时通过...
  • 大家脑子里一定很容易蹦出一堆事务的相关知识,如事务的ACID特性,隔离级别,解决的问题(脏读,不可重复读,幻读)等等,但是可能很少有人真正的清楚事务的这些特性又是怎么实现的,为什么要有四个隔离级别。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 52,347
精华内容 20,938
关键字:

事务隔离级别实现原理