关于InnoDB事务的一个“诡异”现象

简介:

  在隔离机制中,InnoDB默认采用的Repeatable Read 和MVCC机制保证在事务内部尽量保证逻辑一致性。但如下的现象依然让人觉得不太合理。

 

1、复现

a)      表结构

CREATE TABLE `t` (
  `a` int(11) NOT NULL DEFAULT ‘0′,

  `b` int(11) DEFAULT NULL,

  PRIMARY KEY (`a`)

) ENGINE=InnoDB DEFAULT CHARSET=gbk

表中2条记录

| 1 |  100 |

| 4 |  400 |

+—+——+

 

b)      操作过程:开两个session,操作序列如下

Session 1 Session 2
1)Begin  
2)Select * from t;

 

| 1 |  100 |

| 4 |  400 |

2 rows in set (0.01 sec)

 
  3)Insert into t vlaues(2, 200);
4)Select * from t;

 

| 1 |  100 |

| 4 |  400 |

2 rows in set (0.01 sec)

 
5)Update t set b = 200 where a = 2;

 

Query OK, 0 rows affected (0.01 sec)

Rows matched: 1  Changed: 0  Warnings: 0

 
6)Select * from t;

 

| 1 |  100 |

| 2 |  200 |

| 4 |  400 |

3 rows in set (0.01 sec)

 

 

从session 1整个过程看来,它试图更新一个不存在的记录(a=2),结果更新成功,并且之后这个记录可以访问

 

2、分析

       从其他正常的表象看来,在事务内,只要不涉及更新,事务外的任何更新都是不可见的。上面试验中session 1内update之前执行的select *得到的结果仍是2条记录。

       虽然更新冲突时的策略见仁见智,但例子中的这个现象应该提供一种可以选择的方式(至少应该允许配置)。

       接下来的篇幅主要分析出现这种现象的原因,以及通过简单修改实现如下的方式:对于查询不可见的记录,update操作不应该成功。

       由于更新冲突策略的复杂性,本文不解决更多的问题,简单比如:insert操作由于主键冲突的原因,插入依旧不允许。

 

3、源码相关

       先来说明一下为什么步骤4)中的查询结果仍为2条记录。

       Innodb内部每个事务开始时,都会有一个事务id, 同时事务对象中还有一个read_view变量,用于控制该事务可见的记录范围(MVCC)。对于每个访问到的记录行,会根据read_view的trx_id(事务id)与行记录的trx_id比较,判断记录是否逻辑上可见。

       Session 2中插入的记录不可见,原因即为session 1先于session 2,因此新插入的数据经过判断,不在可见范围内。对应的源码在row/row0sel.c [4040-4055].

       {说明: 源码版本5.1.45, 下同}

       发生的逻辑为

If(!lock_clust_rec_cons_read_sees(..)){

 

    //检查该记录是否本事务可见 

   row_sel_build_prev_vers_for_mysql(….); //不可见则找上一个版本      

   if (old_vers == NULL) {goto next_rec;} //上一个版本没有这个记录,放弃

}

 

       注意到表格中出现的Rows matched: 1。 这里是例子出现诡异的开始,也是根源。我们知道innoDB内部更新数据实际上是“先查后改”,跟这个Rows matched: 1结合起来,不难联想到,在执行update操作是,在“查”的阶段,事务能够访问到新插入的行。

猜测:问题出在,执行更新的时候,是否没有判断事务可见范围?

       事实上确实如此,源代码上翻几行可以看到,在行数[3897-4017-4071]这个if-else逻辑。

if (prebuilt->select_lock_type != LOCK_NONE) { 

 

            //该操作需要加锁

  }

else{

       //{CODES A}

}

执行查询语句走的是else的逻辑,而控制版本可见范围的代码就在{CODES A}的位置中。

       而当我们在session 1中执行update操作时,走的是if()的逻辑,这里,没有判断版本可见范围。

 

      4、简单修改 

既然是因为update的“查”过程没有检查版本可见范围造成,我们试着加上。

在row/row0sel.c[3907]行插入如下:

if(trx->read_view){

 

    if (UNIV_LIKELY(srv_force_recovery < 5) 

                && !lock_clust_rec_cons_read_sees(rec, clust_index, offsets, trx->read_view)) { 

        rec_t*  old_vers;

        err = row_sel_build_prev_vers_for_mysql(

                                        trx->read_view, clust_index,

                                        prebuilt, rec, &offsets, &heap,

                                        &old_vers, &mtr);

       if (err != DB_SUCCESS) {

           goto lock_wait_or_error;

       }      

      if (old_vers == NULL) {

           goto next_rec;

      }      

   }      

}      

 

新的执行结果为

Session 1 Session 2
1)Begin  
2)Select * from t;

 

| 1 |  100 |

| 4 |  400 |

2 rows in set (0.01 sec)

 
  3)Insert into t vlaues(2, 200);
4)Select * from t;

 

| 1 |  100 |

| 4 |  400 |

2 rows in set (0.01 sec)

 
5)Update t set b = 200 where a = 2;

 

Query OK, 0 rows affected (0.01 sec)

Rows matched: 0  Changed: 0  Warnings: 0

 
6)Select * from t;

 

| 1 |  100 |

| 4 |  400 |

2 rows in set (0.01 sec)

 

 

重申:这个修改仅仅从本文的例子出发,达到“事务内查询无法访问的记录,不能更新”这个目的, 其他更新冲突策略不在此范围内。 仅作交流使用 -_-

目录
相关文章
|
7月前
|
存储 缓存 关系型数据库
⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制
⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制
224 0
|
关系型数据库 MySQL 数据库
InnoDB事务和锁定信息:如何识别和解决阻塞查询问题
InnoDB事务和锁定信息:如何识别和解决阻塞查询问题
|
SQL 关系型数据库 MySQL
【Mysql-InnoDB 系列】事务模型
提到事务,大家都有基本的了解,例如mysql的事务隔离级别包括:读未提交、读已提交、可重复读、串行化;InnoDB默认是RR(可重复读);基本的MVCC等等。但大部分人对深入一些的原理就知之甚少了。本文整理事务模型的相关内容,仅供参考。
128 0
|
6月前
|
存储 关系型数据库 MySQL
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
|
7月前
|
SQL 安全 关系型数据库
详解InnoDB(1)——事务
详解InnoDB(1)——事务
87 1
|
7月前
|
存储 关系型数据库 MySQL
InnoDB 引擎底层事务的原理
InnoDB 引擎底层事务的原理
|
存储 SQL 关系型数据库
【Mysql-InnoDB 系列】事务提交过程
MySQL InnoDB的事务模型、锁机制等设计,都是InnoDB架构设计的一部分。要全面了解它的设计实践,就必须从头看起。只有这样,才能够弄清楚它的设计思想,理解其实现上的精妙之处。
585 1
|
算法 关系型数据库 MySQL
【Mysql-InnoDB 系列】幻读、死锁与事务调度
本篇继续分析Mysql InnoDB引擎中的幻读、死锁和事务调度的相关问题
99 0
|
存储 SQL 缓存
MySQL InnoDB如何保证事务特性
MySQL InnoDB如何保证事务特性
143 0
|
SQL 存储 算法
InnoDB事务隔离实现原理
### 前言 大部分服务端系统都是数据密集型应用,主要的功能是基于数据库对各种业务数据进行增删查改。在互联网这种高并发场景,如何确保数据的准确性以及保证系统的吞吐量,事务的隔离性有很大一部分功劳,本文主要探究MySQL数据库InnoD存储引擎的事务隔离级别及其背后实现原理,并且会回答以下问题: 1. 不同事务隔离级别解决的问题,如何解决的? 2. MVCC和数据库锁之间的相同和不同之处是什么?
188 0