`
丁林.tb
  • 浏览: 797632 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

关于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)

 

 

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

1
4
分享到:
评论
5 楼 247687009 2014-10-22  
为毛我是是被锁住了呢
4 楼 ccccccc2003 2013-12-30  
的确挺神奇的,刚查了一下,这个好像是Mysql的幻读问题。是Repeatable Read 中允许幻读的问题。
这里有篇文章 写的挺好的 http://blog.sina.com.cn/s/blog_499740cb0100ugs7.html
3 楼 丁林.tb 2010-11-07  
引用
明显是试图更新一个‘存在’的记录,不过因为更新值与原始值相同,所以没实际写入。如果你换个值就知道了


确实是更新了一个"存在"的记录
但实际上,这个记录在session1中,应该是“不存在”的
因为此时session1还没有commit, 这条记录应该是不可见的

所以才说“诡异”
2 楼 waiting 2010-11-07  
更正下mysql默认事务级别的确是Repeatable Read。
1 楼 waiting 2010-11-07  
引用
在隔离机制中,InnoDB默认采用的Repeatable Read

默认是读已提交吧。用
mysql> select @@tx_isolation;
命令查看。

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

引用
5)Update t set b = 200 where a = 2;
Query OK, 0 rows affected (0.01 sec)
Rows matched: 1  Changed: 0  Warnings: 0

明显是试图更新一个‘存在’的记录,不过因为更新值与原始值相同,所以没实际写入。如果你换个值就知道了
Update t set b = 233 where a = 2;

就这儿看当前事务应该是 read commited

相关推荐

    mysql_innoDB 事务与锁详解

    具体来说,一个事务内部的操作及使用的数据对其他事务是隔离的,以确保数据的一致性和准确性。不同的事务之间互相不会干扰,即使这些事务同时执行也是如此。 4. **持久性(Durability)**:一旦事务成功提交,它对...

    InnoDB事务、锁、多版本分析

    锁的等待和死锁检测是通过复杂的算法来实现的,当检测到死锁时,系统会自动选择一个事务进行回滚以打破死锁。此外,半一致读(semi-consistent read)允许在锁定记录前返回旧的数据版本,这对于优化读一致性很有帮助...

    Innodb存储引擎浅析—事务系统

    **案例一**:假设有一个事务需要执行一个UPDATE语句,修改`test`表中`id=47`的记录的`v`字段值为18(原值为8)。事务处理流程如下: 1. 开始事务(`BEGIN`); 2. 执行`UPDATE`语句; 3. 提交事务(`COMMIT`)。 ...

    InnoDB 事务/锁/多版本分析

    一个事务对象称为`trx_struct`,其中包含了事务的ID、事务状态、锁信息和相关的ReadView。 - **mini-transaction(微事务):** mini-transaction是InnoDB内部的一个抽象,用于确保page级别的操作具有原子性。对于...

    MySQL核心Innodb存储引擎浅析—事务系统

    - 可重复读(Repeatable Read):InnoDB默认隔离级别,确保同一个事务内的多次读取返回相同的结果,防止了脏读和不可重复读,但仍然可能出现幻读。 - 序列化读(Serializable):最高的隔离级别,通过加锁的方式...

    辛星笔记之InnoDB事务

    - **日志序列号(LSN)**: 每个事务都有一个唯一的LSN,用于追踪日志的顺序。当事务提交时,需要确保所有相关的redo日志都被写入磁盘,以保证事务的持久性。 - **日志缓冲区**: InnoDB使用日志缓冲区来暂存redo日志,...

    关于InnoDB的索引大小

    在MySQL数据库系统中,InnoDB存储引擎是默认的引擎,它提供了事务处理、行级锁定以及外键支持,使得InnoDB在许多业务场景下成为首选。本篇文章将深入探讨InnoDB存储引擎中的索引大小问题,包括其影响因素、限制以及...

    MySQL 核心引擎InnoDB-事务锁多版本分析

    InnoDB的事务提交分为XA事务和Group Commit两个流程,前者用于保证InnoDB的redo log与MySQL的binlog日志一致性,后者则是一个性能优化特性,允许多个事务可以合并提交,以减少I/O操作。 在实际工作中,资深数据库...

    MySQL锁和事务、InnoDB架构分析.zip

    MySQL锁和事务、InnoDB架构分析.zipMySQL锁和事务、InnoDB架构分析.zipMySQL锁和事务、InnoDB架构分析.zipMySQL锁和事务、InnoDB架构分析.zipMySQL锁和事务、InnoDB架构分析.zipMySQL锁和事务、InnoDB架构分析....

    innodb如何巧妙的实现事务隔离级别详解

    - **不可重复读**:同一个事务内,相同查询得到不同结果,因为其他事务修改了数据并提交。 - **幻读**:事务A在查询时没有某个记录,但在插入时发现已有相同记录,这可能是其他事务插入的。 2. **InnoDB实现的...

    关于使用InnoDB

    例如,创建一个名为`abc`的新表: ```sql CREATE TABLE abc (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100)); ``` 6. **查看表信息**:可以通过执行`SHOW CREATE TABLE abc;`命令来查看表`abc`的定义...

    InnoDB源码解析

    InnoDB是一个提供了ACID事务支持的存储引擎,它在MySQL中被广泛使用,特别是在事务性数据库系统中。InnoDB为表提供了行级锁定和外键约束的特性,并且通过MVCC(多版本并发控制)机制支持高并发读写操作。 2. InnoDB...

    MySQL_InnoDB之事务与锁详解.doc

    1. **读未提交(READ UNCOMMITTED)**:允许脏读,即一个事务可以读取另一个未提交事务的数据,可能导致不一致的结果。 2. **读已提交(READ COMMITTED)**:防止脏读,但可能出现不可重复读,即同一事务内多次读取相同...

    innodb锁

    InnoDB检测到死锁后,会回滚其中一个事务以解除死锁状态。 5. **锁定等待时间(Lock Wait Timeout)**:MySQL允许设置事务等待锁的最大时间,超过这个时间,事务会被自动回滚。 6. **死锁检测(Deadlock Detection...

    mysql支持事务配置,配置InnoDB引擎

    InnoDB是MySQL中默认的存储引擎之一,提供了ACID事务的支持,确保数据的一致性和可靠性。此外,InnoDB还支持行级锁定和外键约束,使其成为处理大量并发事务的理想选择。为了充分利用InnoDB的功能,需要对其进行适当...

    InnoDB 中文参考手册

    MySQL 的企业级解决方案,高实用性以及强健的数据完整性 MySQL 事务,行级锁定,热备份以及外键支持 - - 无需损失 MySQL 的高速性能 ...InnoDB 是 MySQL 上第一个提供外键约束(FOREIGN KEY constraints)的表引擎。

    MySQL技术内幕 InnoDB存储引擎.pdf

    最近在学习MySQL技术内幕 InnoDB存储引擎 第2版,整理了一些文档分享出来,同时也方便以后查看。若有不当之处,烦请批评指正。 1. MySQL体系结构和存储引擎 2. InnoDB存储引擎 2.1 InnoDB体系结构 2.2 ...

    py_innodb_page_info工具

    首先,`py_innodb_page_info`是一个Python库,它的主要功能是解析InnoDB的数据页和日志文件,帮助开发者获取有关MySQL数据库内部结构的详细信息。通过这个工具,我们可以查看InnoDB的页类型、页头信息、记录、B树...

    MySql Innodb 引擎特性详解

    - `innodb_flush_log_at_trx_commit`:控制日志刷新频率,2表示每秒刷新一次,这在大多数情况下是一个不错的折衷方案。 2. **优化主键设计**:选择尽可能小的主键类型,并确保索引尽可能小,以提高索引效率。 3...

Global site tag (gtag.js) - Google Analytics