资讯专栏INFORMATION COLUMN

mysql(InnoDB)事务隔离级别(READ UNCOMMITTED) 与 锁

dayday_up / 1682人阅读

摘要:所以可以得出隔离级别下读不会加任何锁。而写会加排他锁,并到事务结束之后释放。参考资料高性能官方文档慕课同学手记美团技术博客

前言

先针对自己以前错误的思维做个记录, 大家可以直接跳过

由于以前看到很多资料在谈到并发控制的时候, 都会提到用来控制并发, MySQL也不例外, 也有很多和锁相关的概念(留到后面会多带带整理一篇笔记出来), 所以一提到高并发产生的问题, 我会不自觉地提出一个疑问: 现在并发出问题了, 那怎么用锁的相关知识来解决?;

而且近期一段时间也一直在看很多有关MySQL锁相关的资料,书籍, 于是乎 死锁, 锁冲突, 行锁,表锁, 读锁, 写锁, 乐观锁, 悲观锁 ......等等 N多锁相关的名词(后面的笔记会把所有自己遇到的, 全部整理并进行分析), 大量的篇幅, 高深晦涩的描述, 直接导致我意识里认为嗯, 锁真tm高大上, 真tm高端, 肯定tm就是它了;

于是就进入了思想误区, 认为在解决脏读,不可重复读,幻读的资料中, 应该大篇幅的描述如何用锁相关的知识来解决这些问题, 然而略失落了, 资料倒是提了点儿锁的知识, 但更多的是用事务的哪个隔离级别来解决这些问题, 哪儿去了?

尤其是在分析脏读,不可重复读,幻读这几个问题的时候, 一上去就全乱了, 比如 脏读, 如果总是以MySQL锁的相关知识作为前提来分析, 就会陷入误区 "事务A读取数据的时候肯定会加S锁的, 事务B自然是无法对未完成的事务A中的数据进行修改的, 我Ca, 这种脏读的场景根本就不成立嘛!", 那为什么不提锁, 而是用隔离级别来解决。
......
......

晕了几天之后,终于稍微醒了点......

参考美团技术博客

显然, 事务隔离级别的核心就是锁, 各隔离级别使用了不同的加锁策略,在分析之前的几个高并发事务问题的时候, 隔离级别(锁)自然是不能作为前置知识点的, 而是最终问题的解决方案!

"READ UNCOMMITTED与锁"的困惑

(未提交读)

在READ UNCOMMITTED级别, 事务中的修改, 即使还没有提交, 对其他事务也都是可见的; 也就是说事务可以读取未提交的数据, 这也就造成了 脏读(Dirty Read) 的出现。

这个级别会导致很多问题, 而且从性能上来说, READ COMMITTED 并不会比其他的级别好太多, 却缺乏其他级别的很多好处, 在实际应用中一般很少使用。

虽然很少使用, 但还是有必要了解一下, 它这个隔离级别究竟是怎么隔离的, 竟然还能容许很多问题的存在? (老兄亏你还算个隔离级别, 怎么办事儿的...) 网上相关资料五花八门, 下面列几个出来(希望你看完不要激动):

美团技术博客:

segmentfault一篇文章

CSDN一篇文章

CSDN一篇文章

说实话, 资料查到这份儿上, 我已经快崩溃了, 就READ UNCOMMITTED这个隔离级别:

有说读写都不加锁的

有说"修改完数据立即加S锁的, 修改时撤掉S锁"

有说"写加S锁,事务结束释放"的

有说"写加X锁,事务结束释放"的

行啦, 不查了, 再查就崩溃了, 自己去测一下吧!!!

本次测试是使用MAMP PRO中mysql5.6版本

先准备一张测试表test_transaction:

DROP TABLE IF EXISTS `test_transaction`;
CREATE TABLE `test_transaction` (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT "主键",
  `user_name` char(20) NOT NULL COMMENT "姓名",
  `age` tinyint(3) NOT NULL COMMENT "年龄",
  `gender` tinyint(1) NOT NULL COMMENT "1:男, 2:女",
  `desctiption` text NOT NULL COMMENT "简介",
  PRIMARY KEY (`id`),
  KEY `name_age_gender_index` (`user_name`,`age`,`gender`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

INSERT INTO `test_transaction` VALUES (1, "金刚狼", 127, 1, "我有一双铁爪");
INSERT INTO `test_transaction` VALUES (2, "钢铁侠", 120, 1, "我有一身铁甲");
INSERT INTO `test_transaction` VALUES (3, "绿巨人", 0, 2, "我有一身肉");

如下:

mysql> select * from test_transaction;
+----+-----------+-----+--------+--------------------+
| id | user_name | age | gender | desctiption        |
+----+-----------+-----+--------+--------------------+
|  1 | 金刚狼 | 127 |      2 | 我有一双铁爪 |
|  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 |
|  3 | 绿巨人 |   0 |      2 | 我有一身肉    |
+----+-----------+-----+--------+--------------------+
3 rows in set (0.00 sec)

READ UNCOMMITTED与锁 测试 演该隔离级别脏读效果

先查看当前会话(当前客户端)事务的隔离级别: SELECT @@SESSION.tx_isolation;
可以看到: REPEATABLE READ 是InnoDB存储引擎的默认事务隔离级别

mysql> SELECT @@SESSION.tx_isolation;
+------------------------+
| @@SESSION.tx_isolation |
+------------------------+
| REPEATABLE-READ        |
+------------------------+
1 row in set (0.00 sec)
 
mysql> 

重新设置当前客户端事务隔离级别为read uncommitted: SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
注意, 此时只是当前会话端的隔离级别被改, 其余客户端连接自然还是默认的REPEATABLE READ隔离级别

接下来将客户端2的事务隔离级别也设置为read uncommitted;

客户端1开启事务,并执行一个查询"读取数据":

mysql> SELECT @@SESSION.tx_isolation;
+------------------------+
| @@SESSION.tx_isolation |
+------------------------+
| READ-UNCOMMITTED       |
+------------------------+
1 row in set (0.00 sec)
 
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from test_transaction where id=2;
+----+-----------+-----+--------+--------------------+
| id | user_name | age | gender | desctiption        |
+----+-----------+-----+--------+--------------------+
|  2 | 钢铁侠 | 120 |      1 | 我有一身铁甲 |
+----+-----------+-----+--------+--------------------+
1 row in set (0.00 sec)
 
mysql> 

注意, 客户端1此时的事务并未提交

客户端2开启事务, 并修改客户端1查询的数据

mysql> SELECT @@SESSION.tx_isolation;
+------------------------+
| @@SESSION.tx_isolation |
+------------------------+
| READ-UNCOMMITTED       |
+------------------------+
1 row in set (0.00 sec)
 
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> update test_transaction set user_name="钢铁侠-托尼" where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> 

此时发现, 客户端2可以对客户端1正在读取的记录进行修改, 而根据锁相关知识, 如果说客户端1在读取记录的时候加了S锁, 那么客户端2是不能加X锁对该记录进行更改的, 所以可以得出结论: 要么是客户端1读取记录的时候没有加S锁, 要么是客户端2更改记录的时候不加任何锁(这样即使客户端1加了S锁,对它这个不加锁的事务也无可奈何), 那么究竟是哪中情况导致的? 下面继续进行分析...

注意, 客户端2此时的事务也并未提交

切换到客户端1, 再次查询数据, 发现数据已经变成了"钢铁侠-托尼"; 然后客户端2 rollback 事务, 再到客户端1中查询,发现user_name又变成了"钢铁侠", 那之前独到"钢铁侠-托尼"就是脏数据了, 这就是一次 脏读

测试,分析该隔离级别如何加锁

重新构造测试条件

客户端1开启事务, 然后对数据做修改

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> update test_transaction set user_name="钢铁侠-rymuscle" where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> 

注意, 客户端1此时的事务并未提交

客户端2开启事务, 对相同的数据行做修改

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> update test_transaction set user_name="钢铁侠-rym" where id=2;
....阻塞等待了

最终会如下:

注意: 在上面的过程, 在客户端2阻塞阶段, 你可以通过一个新的客户端来分析, 客户端2在锁等待的情况下的 加锁情况事务状态:

查看表的加锁情况: select * from information_schema.INNODB_LOCKS;

事务状态 select * from information_schema.INNODB_TRX;

所以, READ UNCOMMITTED 隔离级别下, 写操作是会加锁的, 而且是X排他锁, 直到客户端1事务完成, 锁才释放, 客户端2才能进行写操作

接下来你肯定会纳闷 "既然该隔离级别下事务在修改数据的时候加的是x锁, 并且是事务完成后才释放, 那之前的测试客户端2在事务中修改完数据之后, 为什么事务还没完成, 也就是x锁还在, 结果客户端1却能读取到客户端2修改的数据"?这完全不符合排他锁的特性啊(要知道,排他锁会阻塞除当前事务之外的其他事务的读,写操作)

其实网上已经有人在sqlserver的官网上找到了相关资料:

ansactions running at the READ UNCOMMITTED level do not issue shared locks to prevent other transactions from modifying data read by the current transaction. 
READ UNCOMMITTED transactions are also not blocked by exclusive locks that would prevent the current transaction from reading rows that have been modified but not committed by other transactions. 
When this option is set, it is possible to read uncommitted modifications, which are called dirty reads. Values in the data can be changed and rows can appear or disappear in the data set before the end of the transaction. 
This option has the same effect as setting NOLOCK on all tables in all SELECT statements in a transaction. 
This is the least restrictive of the isolation levels.

翻译翻译, 在思考思考, 其实说的是

在 READ UNCOMMITTED 级别运行的事务不会发出共享锁来防止其他事务修改当前事务读取的数据, 既然不加共享锁了, 那么当前事务所读取的数据自然就可以被其他事务来修改。
而且当前事务要读取其他事务未提交的修改, 也不会被排他锁阻止, 因为排他锁会阻止其他事务再对其锁定的数据加读写锁, **但是可笑的是, 事务在该隔离级别下去读数据的话根本什么锁都不加, 这就让排他锁无法排它了, 因为它连锁都没有**。
这就导致了事务可以读取未提交的修改, 称为脏读。
         

所以可以得出: READ UNCOMMITTED隔离级别下, 读不会加任何锁。而写会加排他锁,并到事务结束之后释放。

参考资料:
-《高性能MySQL》

MySQL官方文档

慕课mark_rock同学手记

美团技术博客

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/47453.html

相关文章

  • mysql(InnoDB)事务隔离级别(READ COMMITTED) ,MVCC

    摘要:提交读了解了之前隔离级别是如何加锁的并且在文章中已经知道隔离级别可以解决脏读的问题那接下来对于隔离级别试想一下如果让你用锁来设计你会怎么做既然隔离级别可以解决脏读的问题也就是他可以让事务只能读其他事务已提交的的记录。 READ COMMITTED (提交读) 了解了之前 READ UNCOMMITTED 隔离级别是如何加锁的, 并且在文章中, 已经知道 READ COMMITTED...

    曹金海 评论0 收藏0
  • MySQL事务隔离级别

    摘要:隔离级别越低,事务请求的锁越少或持锁时间越短,因此大部分数据库的默认隔离级别为。但是有相关的分析也指出,隔离级别的性能开销几乎一样,因此用户无须通过调整隔离级别来提高性能。这也是隔离级别的一大特性。 如今的互联网,开发一个大型的多人APP,你一定离不开数据库。而如何保证所有人能够高并发的进行读写一直是一个高难度的架构问题,先刨去高并发,保证一致性读写这个问题最常用的手段是事务,而实现一...

    littleGrow 评论0 收藏0
  • 重新理解mysql事务隔离级别事务传播行为

    摘要:数据库多个事务之间操作可能出现的问题以及事务隔离级别是这篇文章介绍的重点。高级别的隔离可靠性较高,但系统开销较大。但该事务不要求与其他事务可串行化,可能会发生幻读。如果当前存在事务,则在嵌套事务内执行。 数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。ACID,是指在可靠数据库管理系统(DBMS)中,...

    chadLi 评论0 收藏0
  • 深入理解 MySQL ——事务并发控制

    摘要:锁共享锁与排他锁它们都是标准的行级锁。防止任何其他事务变动的行。间隙锁是性能和并发性之间权衡的一种折衷,用于某些特定的事务隔离级别,如级别级别,我司为了减少死锁,关闭了锁,使用级别。 本文首发于 vivo 互联网技术微信公众号 https://mp.weixin.qq.com/s/JF...作者:张硕 本文对 MySQL 数据库中有关锁、事务及并发控制的知识及其原理做了系统化的介绍和总...

    zhaot 评论0 收藏0
  • 深入理解 MySQL ——事务并发控制

    摘要:锁共享锁与排他锁它们都是标准的行级锁。防止任何其他事务变动的行。间隙锁是性能和并发性之间权衡的一种折衷,用于某些特定的事务隔离级别,如级别级别,我司为了减少死锁,关闭了锁,使用级别。 本文首发于 vivo 互联网技术微信公众号 https://mp.weixin.qq.com/s/JF...作者:张硕 本文对 MySQL 数据库中有关锁、事务及并发控制的知识及其原理做了系统化的介绍和总...

    Dr_Noooo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<