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》

相關文章
相關標籤/搜索