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

前言

先针对本身之前错误的思惟作个记录, 你们能够直接跳过html

  1. 因为之前看到不少资料在谈到并发控制的时候, 都会提到用来控制并发, MySQL也不例外, 也有不少和锁相关的概念(留到后面会单独整理一篇笔记出来), 因此一提到高并发产生的问题, 我会不自觉地提出一个疑问: 如今并发出问题了, 那怎么用锁的相关知识来解决?;
  2. 并且近期一段时间也一直在看不少有关MySQL锁相关的资料,书籍, 因而乎 死锁, 锁冲突, 行锁,表锁, 读锁, 写锁, 乐观锁, 悲观锁 ......等等 N多锁相关的名词(后面的笔记会把全部本身遇到的, 所有整理并进行分析), 大量的篇幅, 高深晦涩的描述, 直接致使我意识里认为嗯, 锁真tm高大上, 真tm高端, 确定tm就是它了;
  3. 因而就进入了思想误区, 认为在解决脏读,不可重复读,幻读的资料中, 应该大篇幅的描述如何用锁相关的知识来解决这些问题, 然而略失落了, 资料却是提了点儿锁的知识, 但更多的是用事务的哪一个隔离级别来解决这些问题, 哪儿去了?
  4. 尤为是在分析脏读,不可重复读,幻读这几个问题的时候, 一上去就全乱了, 好比 脏读, 若是老是以MySQL锁的相关知识做为前提来分析, 就会陷入误区 '事务A读取数据的时候确定会加S锁的, 事务B天然是没法对未完成的事务A中的数据进行修改的, 我Ca, 这种脏读的场景根本就不成立嘛!', 那为何不提锁, 而是用隔离级别来解决。
    ......
    ......
  5. 晕了几天以后,终于稍微醒了点......mysql

    参考美团技术博客
    clipboard.png
  6. 显然, 事务隔离级别的核心就是锁, 各隔离级别使用了不一样的加锁策略,在分析以前的几个高并发事务问题的时候, 隔离级别(锁)天然是不能做为前置知识点的, 而是最终问题的解决方案!

"READ UNCOMMITTED与锁"的困惑

(未提交读)sql

  1. 在READ UNCOMMITTED级别, 事务中的修改, 即便尚未提交, 对其余事务也都是可见的; 也就是说事务能够读取未提交的数据, 这也就形成了 脏读(Dirty Read) 的出现。
  2. 这个级别会致使不少问题, 并且从性能上来讲, READ COMMITTED 并不会比其余的级别好太多, 却缺少其余级别的不少好处, 在实际应用中通常不多使用。
  3. 虽然不多使用, 但仍是有必要了解一下, 它这个隔离级别到底是怎么隔离的, 居然还能允许不少问题的存在? (老兄亏你还算个隔离级别, 怎么办事儿的...) 网上相关资料五花八门, 下面列几个出来(但愿你看完不要激动):segmentfault

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

    • 有说读写都不加锁的
    • 有说'修改完数据当即加S锁的, 修改时撤掉S锁'
    • 有说'写加S锁,事务结束释放'的
    • 有说'写加X锁,事务结束释放'的
  5. 行啦, 不查了, 再查就崩溃了, 本身去测一下吧!!!
  • 本次测试是使用MAMP PRO中mysql5.6版本
  • 先准备一张测试表test_transaction:app

    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与锁 测试

演该隔离级别脏读效果

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

    mysql> SELECT @@SESSION.tx_isolation;
    +------------------------+
    | @@SESSION.tx_isolation |
    +------------------------+
    | REPEATABLE-READ        |
    +------------------------+
    1 row in set (0.00 sec)
     
    mysql>
  2. 从新设置当前客户端事务隔离级别为read uncommitted: SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    注意, 此时只是当前会话端的隔离级别被改, 其他客户端链接天然仍是默认的REPEATABLE READ隔离级别
    clipboard.png
  3. 接下来将客户端2的事务隔离级别也设置为read uncommitted;
    clipboard.png
  4. 客户端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此时的事务并未提交测试

  5. 客户端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此时的事务也并未提交
  6. 切换到客户端1, 再次查询数据, 发现数据已经变成了'钢铁侠-托尼'; 而后客户端2 rollback 事务, 再到客户端1中查询,发现user_name又变成了'钢铁侠', 那以前独到'钢铁侠-托尼'就是脏数据了, 这就是一次 脏读
    clipboard.png

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

  1. 从新构造测试条件
    clipboard.png
  2. 客户端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此时的事务并未提交

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

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

    最终会以下:
    clipboard.png

  4. 注意: 在上面的过程, 在客户端2阻塞阶段, 你能够经过一个新的客户端来分析, 客户端2在锁等待的状况下的 加锁状况事务状态:

    • 查看表的加锁状况: select * from information_schema.INNODB_LOCKS;
      clipboard.png
    • 事务状态 select * from information_schema.INNODB_TRX;
      clipboard.png
  5. 因此, READ UNCOMMITTED 隔离级别下, 写操做是会加锁的, 并且是X排他锁, 直到客户端1事务完成, 锁才释放, 客户端2才能进行写操做
  6. 接下来你确定会纳闷 "既然该隔离级别下事务在修改数据的时候加的是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》

相关文章
相关标签/搜索