目录
1、MySQL InnoDB事务隔离级别脏读、可重复读、幻读
2、隔离级别实验
2.2、实验二:测试READ-COMMITTED与REPEATABLE-READ
2.3、实验三:测试SERIALIZABLE事务对其他的影响
2.4、实验四:幻读
首页 数据库 mysql教程 MySQL InnoDB四个事务级别与脏读、不重复读、幻读是什么

MySQL InnoDB四个事务级别与脏读、不重复读、幻读是什么

Sep 19, 2017 am 09:59 AM
innodb mysql 级别

1、MySQL InnoDB事务隔离级别脏读、可重复读、幻读

MySQL InnoDB事务的隔离级别有四级,默认是“可重复读”(REPEATABLE READ)。

·        1).未提交读(READUNCOMMITTED)。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据(脏读)( 隔离级别最低,并发性能高 )

·        2).提交读(READCOMMITTED)。本事务读取到的是最新的数据(其他事务提交后的)。问题是,在同一个事务里,前后两次相同的SELECT会读到不同的结果(不重复读)。会出现不可重复读、幻读问题(锁定正在读取的行)

·        3).可重复读(REPEATABLEREAD)。在同一个事务里,SELECT的结果是事务开始时时间点的状态,因此,同样的SELECT操作读到的结果会是一致的。但是,会有幻读现象(稍后解释)。会出幻读(锁定所读取的所有行)。

·        4).串行化(SERIALIZABLE)。读操作会隐式获取共享锁,可以保证不同事务间的互斥(锁表)。


四个级别逐渐增强,每个级别解决一个问题。

·        1).脏读。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据。

·        2).不重复读。解决了脏读后,会遇到,同一个事务执行过程中,另外一个事务提交了新数据,因此本事务先后两次读到的数据结果会不一致。

·        3).幻读。解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。但是,如果另一个事务同时提交了新数据,本事务再更新时,就会“惊奇的”发现了这些新数据,貌似之前读到的数据是“鬼影”一样的幻觉。

    具体地:

    1). 脏读 

      首先区分脏页和脏数据

      脏页是内存的缓冲池中已经修改的page,未及时flush到硬盘,但已经写到redo log中。读取和修改缓冲池的page很正常,可以提高效率,flush即可同步。脏数据是指事务对缓冲池中的行记录record进行了修改,但是还没提交!!!,如果这时读取缓冲池中未提交的行数据就叫脏读,违反了事务的隔离性。脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

    2). 不可重复读 

     是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,第二个事务已经提交。那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题

    3). 幻读 :

     是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。

2、隔离级别实验

       以下实验基于博主MySQL Server 5.6

       首先创建一个表,如下:

USE test;  
CREATE TABLE `t` (  
  
  `a` int(11) NOT NULL PRIMARY KEY  
  
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
登录后复制


2.1、实验一:解释脏读、可重复读问题  


 

事务A READ-UNCOMMITTED

事务B READ-COMMITTED,

事务C-1 REPEATABLE-READ

事务C-2 REPEATABLE-READ

事务D SERIALIZABLE

set autocommit =0;

         

start transaction ;

     

start transaction;

 

insert into t(a)values(4);

         
 

select * from t;

1,2,3,4(脏读:读取到了未提交的事务中的数据)

select * from t;

1,2,3(解决脏读)

select * from t;

1,2,3

select * from t;

1,2,3

select * from t;

1,2,3

           
           

commit;

         
 

select * from t:

1,2,3,4

select * from t:

1,2,3,4

select * from t:

1,2,3,4 (与上面的不在一个事务中,所以读到为事务提交后最新的,所以可读到4)

select * from t:

1,2,3(重复读:由于与上面的在一个事务中,所以只读到事务开始事务的数据,也就是重复读)

select * from t:

1,2,3,4

       

commit(提交事务,下面的就是一个新的事务,所以可以读到事务提交以后的最新数据)

 
       

select * from t:

1,2,3,4

 

READ-UNCOMMITTED 会产生脏读,基本很少适用于实际场景,所以基本不使用。


2.2、实验二:测试READ-COMMITTED与REPEATABLE-READ

事务A

事务B READ-COMMITTED

事务C REPEATABLE-READ

set autocommit =0;

   

start transaction ;

start transaction;

start transaction;

insert into t(a)values(4);

   
 

select * from t;

1,2,3

select * from t;

1,2,3

     
     

commit;

   
 

select * from t:

1,2,3,4

select * from t:

1,2,3(重复读:由于与上面的在一个事务中,所以只读到事务开始事务的数据,也就是重复读)

   

commit(提交事务,下面的就是一个新的事务,所以可以读到事务提交以后的最新数据)

   

select * from t:

1,2,3,4

REPEATABLE-READ可以确保一个事务中读取的数据是可重复的,也就是相同的读取(第一次读取以后,即使其他事务已经提交新的数据,同一个事务中再次select也并不会被读取)。

READ-COMMITTED只是确保读取最新事务已经提交的数据。

当然数据的可见性都是对不同事务来说的,同一个事务,都是可以读到此事务中最新数据的。如下,

  1. start transaction;  
    insert into t(a)values(4);  
    select *from t;    
    1,2,3,4;  
    insert into t(a)values(5);  
    select *from t;  
    1,2,3,4,5;
    登录后复制


2.3、实验三:测试SERIALIZABLE事务对其他的影响


事务A SERIALIZABLE

事务B READ-UNCOMMITTED

事务C READ-COMMITTED,

事务D REPEATABLE-READ

事务E SERIALIZABLE

set autocommit =0;

       

start transaction ;

   

start transaction;

 

select a from t union all select sleep(1000) from dual;

       
 

insert into t(a)values(5);

insert into t(a)values(5);

insert into t(a)values(5);

insert into t(a)values(5);

 

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 

SERIALIZABLE 串行化执行,导致所有其他事务不得不等待事务A结束才行可以执行,这里特意使用了sleep函数,直接导致事务B,C,D,E等待事务A持有释放的锁。由于我sleep了1000秒,而innodb_lock_wait_timeout为120s。所以120s到了就报错HY000错误。

SERIALIZABLE是相当严格的串行化执行模式,不管是读还是写,都会影响其他读取相同的表的事务。是严格的表级读写排他锁。也就失去了innodb引擎的优点。实际应用很少。


2.4、实验四:幻读

一些文章写到InnoDB的可重复读避免了“幻读”(phantom read),这个说法并不准确。做个实验:(以下所有试验要注意存储引擎和隔离级别)

  1. CREATE TABLE `t_bitfly` (  

  2. `id` bigint(20) NOT NULL default '0',  

  3. `value` varchar(32) default NULL,  

  4. PRIMARY KEY (`id`)  

  5. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  

  6.   

  7. select @@global.tx_isolation, @@tx_isolation;  

  8. +-----------------------+-----------------+  

  9. | @@global.tx_isolation | @@tx_isolation  |  

  10. +-----------------------+-----------------+  

  11. REPEATABLE-READ       | REPEATABLE-READ |  

  12. +-----------------------+-----------------+  

实验4-1:


Session A

Session B

start transaction ; start transaction ;

SELECT * FROM t_bitfly;
empty set

 


INSERT INTO t_bitfly VALUES (1, 'a');COMMIT;
SELECT * FROM t_bitfly;
| empty set


INSERT INTO t_bitfly VALUES (1, 'a');
|ERROR 1062 (23000):
|Duplicate entry '1' for key 1
(刚刚明明告诉我没有这条记录的)
I

如此就出现了幻读,以为表里没有数据,其实数据已经存在了,提交后,才发现数据冲突了。

实验4-2:


Session A

Session B

start transaction ;

start transaction ;

SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |a     |
| +------+-------+

 
 

INSERT INTO t_bitfly VALUES (2, 'b');

SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |a     |
| +------+-------+

 

 

COMMIT;

SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |a     |
| +------+-------+

 

UPDATE t_bitfly SET value='z';
| Rows matched: 2  Changed:2  Warnings: 0

(怎么多出来一行)

 

SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |z     |
| |    2 |z     |
| +------+-------+

 

本事务中第一次读取出一行,做了一次更新后,另一个事务里提交的数据就出现了。也可以看做是一种幻读。

附说明


那么,InnoDB指出的可以避免幻读是怎么回事呢?

http://dev.mysql.com/doc/refman/5.0/en/innodb-record-level-locks.html

By default, InnoDB operatesin REPEATABLE READ transaction isolation level and with the innodb_locks_unsafe_for_binlog system variable disabled. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 13.6.8.5, “Avoidingthe Phantom Problem Using Next-Key Locking”).

准备的理解是,当隔离级别是可重复读,且禁用innodb_locks_unsafe_for_binlog的情况下,在搜索和扫描index的时候使用的next-keylocks可以避免幻读。

关键点在于,是InnoDB默认对一个普通的查询也会加next-key locks,还是说需要应用自己来加锁呢?如果单看这一句,可能会以为InnoDB对普通的查询也加了锁,如果是,那和序列化(SERIALIZABLE)的区别又在哪里呢?

MySQL manual里还有一段:

13.2.8.5. Avoiding the PhantomProblem Using Next-Key Locking (http://dev.mysql.com/doc/refman/5.0/en/innodb-next-key-locking.html)

Toprevent phantoms, InnoDB usesan algorithm called next-key locking that combinesindex-row locking with gap locking.

Youcan use next-key locking to implement a uniqueness check in your application:If you read your data in share mode and do not see a duplicate for a row you are going to insert, then you can safely insert your row and know that the next-key lock set on the success or of your row during the read prevents anyone mean while inserting a duplicate for your row. Thus, the next-key locking enables you to “lock” the nonexistence of something in your table.

我的理解是说,InnoDB提供了next-key locks,但需要应用程序自己去加锁。manual里提供一个例子:

SELECT * FROM child WHERE id> 100 FOR UPDATE;

这样,InnoDB会给id大于100的行(假如child表里有一行id为102),以及100-102,102+的gap都加上锁。

可以使用show engine innodb status来查看是否给表加上了锁。


再看一个实验,要注意,表t_bitfly里的id为主键字段。

实验4-3:


Session A

Session B

start transaction ;

start transaction ;

SELECT * FROM t_bitfly
 WHERE id<=1
 FOR UPDATE;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |a     |
| +------+-------+

 
 

 INSERT INTO t_bitfly   VALUES (2, 'b');
| Query OK, 1 row affected

SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |a     |
| +------+-------+

 

 

INSERT INTO t_bitfly VALUES (0, '0');
|  (waiting for lock ...
|   then timeout) ERROR 1205 (HY000):Lock wait timeout exceeded;
|try restarting transaction

SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |a     |
| +------+-------+

 

 

COMMIT;

SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |a     |
| +------+-------+

 

可以看到,用id<=1加的锁,只锁住了id<=1的范围,可以成功添加id为2的记录,添加id为0的记录时就会等待锁的释放。

附说明:

MySQL manual里对可重复读里的锁的详细解释:

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read

For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE),UPDATE, and DELETE statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition. For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it. For other search conditions, InnoDB locks the index range scanned, using gap locks or next-key (gap plus index-record)locks to block insertions by other sessions into the gaps covered by the range.

一致性读和提交读,先看实验,

实验4-4:


Session A

Session B

start transaction ;

start transaction ;

SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |a     |
| +------+-------+

 
 

 INSERT INTO t_bitfly   VALUES (2, 'b');

 

COMMIT;

SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 |a     |
| +------+-------+

 

SELECT * FROM t_bitfly LOCK IN SHARE MODE;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 |a     |
| |  2 |b     |
| +----+-------+

 

SELECT * FROM t_bitfly FOR UPDATE;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 |a     |
| |  2 |b     |
| +----+-------+

 

SELECT * FROM t_bitfly;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 |a     |
| +----+-------+

 

附说明:如果使用普通的读,会得到一致性的结果,如果使用了加锁的读,就会读到“最新的”“提交”读的结果。

本身,可重复读和提交读是矛盾的。在同一个事务里,如果保证了可重复读,就会看不到其他事务的提交,违背了提交读;如果保证了提交读,就会导致前后两次读到的结果不一致,违背了可重复读。

可以这么讲,InnoDB提供了这样的机制,在默认的可重复读的隔离级别里,可以使用加锁读去查询最新的数据。

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html

If you want to see the “freshest” state of the database, you should use either theREAD COMMITTED isolation level or a locking read:
SELECT * FROM t_bitfly LOCK IN SHARE MODE;

------

3、总结

结论:MySQL InnoDB事务默认隔离级别是可重复读并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是next-key locks。

以上是MySQL InnoDB四个事务级别与脏读、不重复读、幻读是什么的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

mysql:简单的概念,用于轻松学习 mysql:简单的概念,用于轻松学习 Apr 10, 2025 am 09:29 AM

MySQL是一个开源的关系型数据库管理系统。1)创建数据库和表:使用CREATEDATABASE和CREATETABLE命令。2)基本操作:INSERT、UPDATE、DELETE和SELECT。3)高级操作:JOIN、子查询和事务处理。4)调试技巧:检查语法、数据类型和权限。5)优化建议:使用索引、避免SELECT*和使用事务。

phpmyadmin怎么打开 phpmyadmin怎么打开 Apr 10, 2025 pm 10:51 PM

可以通过以下步骤打开 phpMyAdmin:1. 登录网站控制面板;2. 找到并点击 phpMyAdmin 图标;3. 输入 MySQL 凭据;4. 点击 "登录"。

navicat premium怎么创建 navicat premium怎么创建 Apr 09, 2025 am 07:09 AM

使用 Navicat Premium 创建数据库:连接到数据库服务器并输入连接参数。右键单击服务器并选择“创建数据库”。输入新数据库的名称和指定字符集和排序规则。连接到新数据库并在“对象浏览器”中创建表。右键单击表并选择“插入数据”来插入数据。

MySQL和SQL:开发人员的基本技能 MySQL和SQL:开发人员的基本技能 Apr 10, 2025 am 09:30 AM

MySQL和SQL是开发者必备技能。1.MySQL是开源的关系型数据库管理系统,SQL是用于管理和操作数据库的标准语言。2.MySQL通过高效的数据存储和检索功能支持多种存储引擎,SQL通过简单语句完成复杂数据操作。3.使用示例包括基本查询和高级查询,如按条件过滤和排序。4.常见错误包括语法错误和性能问题,可通过检查SQL语句和使用EXPLAIN命令优化。5.性能优化技巧包括使用索引、避免全表扫描、优化JOIN操作和提升代码可读性。

navicat怎么新建连接mysql navicat怎么新建连接mysql Apr 09, 2025 am 07:21 AM

可在 Navicat 中通过以下步骤新建 MySQL 连接:打开应用程序并选择“新建连接”(Ctrl N)。选择“MySQL”作为连接类型。输入主机名/IP 地址、端口、用户名和密码。(可选)配置高级选项。保存连接并输入连接名称。

SQL删除行后如何恢复数据 SQL删除行后如何恢复数据 Apr 09, 2025 pm 12:21 PM

直接从数据库中恢复被删除的行通常是不可能的,除非有备份或事务回滚机制。关键点:事务回滚:在事务未提交前执行ROLLBACK可恢复数据。备份:定期备份数据库可用于快速恢复数据。数据库快照:可创建数据库只读副本,在数据误删后恢复数据。慎用DELETE语句:仔细检查条件,避免误删数据。使用WHERE子句:明确指定要删除的数据。使用测试环境:在执行DELETE操作前进行测试。

redis怎么使用单线程 redis怎么使用单线程 Apr 10, 2025 pm 07:12 PM

Redis 使用单线程架构,以提供高性能、简单性和一致性。它利用 I/O 多路复用、事件循环、非阻塞 I/O 和共享内存来提高并发性,但同时存在并发性受限、单点故障和不适合写密集型工作负载的局限性。

phpmyadmin连接mysql phpmyadmin连接mysql Apr 10, 2025 pm 10:57 PM

如何使用 phpMyAdmin 连接到 MySQL?访问 phpMyAdmin 的 URL,通常为 http://localhost/phpmyadmin 或 http://[您的服务器 IP 地址]/phpmyadmin。输入您的 MySQL 用户名和密码。选择您要连接的数据库。点击 "连接" 按钮以建立连接。

See all articles