Mysql InnoDB事務模型(官方文檔)

         Mysql InnoDB事務模型包括事務的隔離級別、autocommit, Commit, and Rollback、一致非鎖定讀取、鎖定讀取。事務的隔離級別能夠看下另外一篇juejin.im/post/5eb75a…,這篇會對其餘的三個官方文檔進行翻譯,寫的不對的地方歡迎幫忙指正。mysql

1、autocommit, Commit, and Rollback

         在InnoDB中,全部用戶活動都發生在事務中。若是啓用了autocommit模式,則每一個SQL語句都單獨造成一個事務。默認狀況下,MySQL在啓用autocommit的狀況下爲每一個新鏈接啓動會話,所以若是該語句沒有返回錯誤,MySQL會在每一個SQL語句以後執行commit。若是語句返回錯誤,則提交或回滾行爲取決於錯誤。"InnoDB錯誤處理"在下面進行介紹。sql

         啓用autocommit的會話能夠經過使用顯式START transaction或BEGIN語句啓動多語句事務,並使用COMMIT或ROLLBACK語句結束它來執行多語句事務。shell

         若是在設置爲autocommit=0的會話中禁用autocommit模式,則該會話始終有一個打開的事務。COMMIT或ROLLBACK語句結束當前事務並啓動新事務。數據庫

         若是禁用autocommit的會話在未顯式提交最終事務的狀況下結束,MySQL將回滾該事務。 api

         有些語句隱式地結束事務,就好像在執行語句以前執行了提交同樣。詳細信息在"致使隱式提交的語句",這個會有一篇進行分析。安全

          提交意味着當前事務中所作的更改將成爲永久性的,並對其餘會話可見。另外一方面,ROLLBACK語句取消當前事務所作的全部修改。COMMIT和ROLLBACK都釋放當前事務期間設置的全部InnoDB鎖。bash

          默認狀況下,與MySQL服務器的鏈接啓用autocommit模式開始,autocommit模式在您執行每個SQL語句時自動提交它。若是您有使用其餘數據庫系統的經驗,則可能不熟悉這種操做模式,在其餘數據庫系統中,標準作法是發出一系列DML語句並提交它們或將它們一塊兒回滾。服務器

          若要使用多個語句事務,請關閉autocommit,並將SQL語句設置爲autocommit=0,而後根據須要使用COMMIT或ROLLBACK結束每一個事務。要使autocommit保持打開狀態,請使用START transaction開始每一個事務,並使用COMMIT或ROLLBACK結束它。下面的示例顯示了兩個事務。第一個被提交;第二個被回滾。併發

shell> mysql test

mysql> CREATE TABLE customer (a INT, b CHAR (20), INDEX (a));
Query OK, 0 rows affected (0.00 sec)
mysql> -- Do a transaction with autocommit turned on.
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (10, 'Heikki');
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> -- Do another transaction with autocommit turned off.
mysql> SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO customer VALUES (15, 'John');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO customer VALUES (20, 'Paul');
Query OK, 1 row affected (0.00 sec)
mysql> DELETE FROM customer WHERE b = 'Heikki';
Query OK, 1 row affected (0.00 sec)
mysql> -- Now we undo those last 2 inserts and the delete.
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM customer;
+------+--------+
| a    | b      |
+------+--------+
|   10 | Heikki |
+------+--------+
1 row in set (0.00 sec)
mysql>
複製代碼

在PHP、Perl-DBI、JDBC、ODBC或MySQL的標準C調用接口等api中,能夠像SELECT或INSERT等其餘SQL語句同樣,將COMMIT等事務控制語句做爲字符串發送到MySQL服務器。一些api還提供單獨的特殊事務提交和回滾函數或方法。  
函數

2、一致非鎖定讀取

         一致讀取意味着InnoDB使用多版本控制在一個時間點查詢顯示數據庫的快照。查詢將看到在該時間點以前提交的事務所作的更改,而不會看到稍後或未提交的事務所作的更改。此規則的例外狀況是,查詢能夠看到同一事務中早期語句所作的更改。此異常致使如下異常:若是更新表中的某些行,SELECT將看到更新行的最新版本,但也可能看到任何行的較舊版本。若是其餘會話同時更新同一個表,則異常意味着您可能會看到該表處於數據庫中從未存在過的狀態。

          若是事務隔離級別是REPEATABLE READ(默認級別),則同一事務中的全部一致讀取都將讀取該事務中第一個讀取(快照讀)所創建的快照。經過提交當前事務並在該事務以後發出新查詢,能夠爲查詢獲取更新的快照。

          使用READ COMMITTED隔離級別,事務中的每一個一致讀取都會設置並讀取本身的新快照。

          一致讀取是InnoDB進程在READ COMMITTED和REPEATABLE READ隔離級別中SELECT語句的默認模式。一致讀取不會對其訪問的表設置任何鎖,所以其餘會話能夠在對錶執行一致讀取的同時自由修改這些表。  

           假設您正在默認的REPEATABLE READ隔離級別中運行。當發出一致讀取(即普通的SELECT語句)時,InnoDB會給事務一個時間點,根據這個時間點查詢能夠看到數據庫。若是另外一個事務在指定了您的時間點後刪除一行並提交,則您不會看到該行已被刪除。插入和更新的處理方式相似。

           數據庫狀態的快照應用於事務中的SELECT語句,而不必定應用於DML語句。若是插入或修改某些行,而後提交該事務,則從另外一個併發可重複讀取事務發出的DELETE或UPDATE語句可能會影響那些剛剛提交的行,即便會話沒法查詢它們。若是事務確實更新或刪除由其餘事務提交的行,則這些更改對當前事務可見。例如,您可能會遇到如下狀況:

SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz';
-- Returns 0: no rows match.
DELETE FROM t1 WHERE c1 = 'xyz';
-- Deletes several rows recently committed by other transaction.

SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc';
-- Returns 0: no rows match.
UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc';
-- Affects 10 rows: another txn just committed 10 rows with 'abc' values.
SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
-- Returns 10: this txn can now see the rows it just updated.複製代碼

您能夠經過提交事務,而後使用一致的快照執行另外一個SELECT或START事務來提早您的時間點。 這稱爲多版本併發控制。 在下面的示例中,會話A僅在B提交了插入操做而且A也提交了插入操做時纔看到B插入的行,所以時間點提早到B提交操做以後。

Session A              Session B

           SET autocommit=0;      SET autocommit=0;
time
|          SELECT * FROM t;
|          empty set
|                                 INSERT INTO t VALUES (1, 2);
|
v          SELECT * FROM t;
           empty set
                                  COMMIT;

           SELECT * FROM t;
           empty set

           COMMIT;

           SELECT * FROM t;
           ---------------------
           |    1    |    2    |
           ---------------------複製代碼

若是要查看數據庫的「最新」狀態,請使用READ COMMITTED(每次select都會新建新的快照視圖)隔離級別或鎖定讀取:  

SELECT * FROM t FOR SHARE;複製代碼

使用READ COMMITTED隔離級別,事務中的每一個一致讀取都會設置並讀取本身的新快照。對於FOR SHARE,將發生鎖定讀取:SELECT阻塞直到包含最新行的事務結束。

一致讀取不適用於某些DDL語句: 

    •  一致讀取不適用於DROP TABLE,由於MySQL不能使用已刪除的表,InnoDB會破壞該表。
    • 一致讀取不適用於ALTER TABLE,由於該語句生成原始表的臨時副本,並在生成臨時副本時刪除原始表。在事務中從新發出一致讀取時,新表中的行不可見,由於在獲取事務的快照時這些行不存在。在這種狀況下,事務返回一個錯誤:ER_TABLE_DEF_CHANGED,"表定義已更改,請重試事務"。  

對於像INSERT INTO ... SELECT, UPDATE ... (SELECT), 和 CREATE TABLE ... SELECT子句,讀取的類型有所不一樣,這些都沒有指定FOR UPDATE 或者 FOR SHARE:

    • 默認狀況下,InnoDB使用更強的鎖,SELECT部分的行爲相似於READ COMMITTED,在這裏,即便在同一個事務中,每一個一致的讀都設置並讀取本身的新快照。
    • 要在這種狀況下使用一致讀取,請將事務的隔離級別設置爲READ UNCOMMITTED, READ COMMITTED, 或 REPEATABLE READ(即,除SERIALIZABLE以外的任何內容)。在這種狀況下,不會對從選定表中讀取的行設置鎖。

3、鎖定讀取

        若是查詢數據,而後在同一事務中插入或更新相關數據,常規的SELECT語句不會提供足夠的保護。其餘事務能夠更新或刪除剛纔查詢的同一行。InnoDB支持兩種提供額外安全性的鎖定讀取:

    • SELECT ... FOR SHARE 
      • 對讀取的全部行設置共享模式鎖定。其餘會話能夠讀取行,可是在提交事務以前其餘會話沒法修改它們。若是這些行中的任何被另外一個還沒有提交的事務更改,則查詢將等待該事務結束,而後使用最新值。
        •  

          注意
          SELECT ... FOR SHARESELECT ... LOCK IN SHARE MODE的替代品,
          但LOCK IN SHARE MODE保持向後兼容,後續仍是版本仍是能夠用。可是FOR SHARE
          對於表名、NOWAITSKIP LOCKED選項的共享支持。
          用NOWAITSKIP LOCKED鎖定讀併發,下面會進行介紹
          複製代碼

    • SELECT ... FOR UPDATE
      • 對於搜索遇到的索引記錄,鎖定行和任何關聯的索引項(好比條件是使用普通索引,可是須要回表,主鍵索引也會加上鎖),就像對這些行發出UPDATE語句同樣。
      • 其餘事務沒法更新這些行,沒法執行SELECT ... FOR SHARE,或者從某些事務隔離級別讀取數據。一致讀取忽略在讀取視圖中存在的記錄上設置的任何鎖。(舊版本的記錄沒法鎖定;它們是經過對記錄的內存副本應用undo日誌來重建的)。 

這些子句主要用於處理樹結構或圖結構數據(在單個表中或在多個表中拆分)。您能夠從一個地方到另外一個地方遍歷邊或樹枝,同時保留返回並更改這些「指針」值的權利。

提交或回滾事務時,將釋放由FOR SHARE和FOR UPDATE查詢設置的全部鎖。

    • 注意
      只有當autocommit被禁用時(經過使用START transaction開始事務
      或經過將autocommit設置爲0),才能進行鎖定讀取。複製代碼

除非在子查詢中也指定了locking read子句,不然外部語句中的locking read子句不會鎖定嵌套子查詢中表的行。例如,下面的語句不鎖定表t2中的行。 

SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;複製代碼

要鎖定表t2中的行,請向子查詢添加一個locking read子句: 

SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;複製代碼

假設要將新行插入表child,並確保子行在表parent中有父行。應用程序代碼能夠確保整個操做序列的引用完整性。 首先,使用一致讀取查詢表parent並驗證父行是否存在。您能安全地將子行插入到表child嗎?不,由於在您的選擇和插入之間的某個其餘會話可能會刪除父行,而您不會意識到這一點。 要避免此潛在問題,請執行選擇用於共享:

SELECT * FROM parent WHERE NAME = 'Jones' FOR SHARE;複製代碼

在FOR SHARE查詢返回父「Jones」以後,能夠安全地將子記錄添加到child表並提交事務。任何試圖在父表中的適用行中獲取排他鎖的事務都將等待您完成,即直到全部表中的數據處於一致狀態。

例如,考慮表CHILD_CODES中的整數計數器字段,用於爲添加到表CHILD中的每一個子代碼分配惟一標識符。不要使用一致讀取或共享模式讀取來讀取計數器的當前值,由於數據庫的兩個用戶能夠看到計數器的相同值,若是兩個事務嘗試向表CHILD添加具備相同標識符的行,則會發生重複鍵錯誤。 在這裏,FOR SHARE不是一個好的解決方案,由於若是兩個用戶同時讀取計數器,那麼至少其中一個用戶在嘗試更新計數器時會死鎖。 要實現計數器的讀取和遞增,首先使用FOR UPDATE對計數器執行鎖定讀取,而後遞增計數器。例如:   

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;複製代碼

SELECT ... FOR UPDATE讀取最新的可用數據,在讀取的每一行上設置獨佔鎖。所以,它設置的鎖與搜索的SQL更新在行上設置的鎖相同。

前面的描述只是一個例子,說明如何SELECT ... FOR UPDATE工做,在MySQL中,生成惟一標識符的特定任務實際上能夠只使用對錶的一次訪問來完成:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();複製代碼

SELECT語句只檢索標識符信息(特定於當前鏈接)。它不訪問任何表。 

若是行被事務鎖定,SELECT ... FOR UPDATE 或者 SELECT ... FOR SHARE對於請求同一鎖定行的共享事務,必須阻塞等到事務釋放行鎖 。此行爲防止事務更新或刪除由其餘事務查詢更新的行。可是,若是但願查詢在請求的行被鎖定時當即返回,或者能夠從結果集中排除鎖定的行,則沒必要等待行鎖被釋放。

爲了不等待其餘事務釋放行鎖,NOWAIT和SKIP LOCKED選項能夠與SELECT ... FOR UPDATE 或 SELECT ... FOR SHARE鎖定讀語句。

NOWAIT

    • 使用NOWAIT的鎖讀永遠不會等待獲取行鎖。查詢當即執行,若是請求的行被鎖定,則會失敗並返回錯誤。

SKIP LOCKED

    •  使用SKIP LOCKED的鎖定讀取從不等待獲取行鎖定。查詢當即執行,從結果集中刪除鎖定的行。
    • 注意 跳過鎖定行的查詢返回不一致的數據視圖。所以,SKIP LOCKED不適用於通常事務性工做。可是,當多個會話訪問同一個類隊列表時,它能夠用來避免鎖爭用。

NOWAIT和SKIP LOCKED只適用於行級鎖。 使用NOWAIT或SKIP LOCKED的語句對於基於語句的複製是不安全的。 下面的示例演示NOWAIT和SKIP LOCKED。會話1啓動對單個記錄進行行鎖定的事務。會話2嘗試使用NOWAIT選項對同一記錄進行鎖定讀取。因爲請求的行被會話1鎖定,所以鎖定讀取當即返回並返回錯誤。在會話3中,帶SKIP LOCKED的locking read返回請求的行,會話1鎖定的行除外。

# Session 1:

mysql> CREATE TABLE t (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;

mysql> INSERT INTO t (i) VALUES(1),(2),(3);

mysql> START TRANSACTION;

mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE;
+---+
| i |
+---+
| 2 |
+---+

# Session 2:

mysql> START TRANSACTION;

mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE NOWAIT;
ERROR 3572 (HY000): Do not wait for lock.

# Session 3:

mysql> START TRANSACTION;

mysql> SELECT * FROM t FOR UPDATE SKIP LOCKED;
+---+
| i |
+---+
| 1 |
| 3 |
+---+複製代碼

4、InnoDB錯誤處理

       如下項目描述了InnoDB如何執行錯誤處理。InnoDB有時只回滾失敗的語句,有時回滾整個事務。

    • 若是表空間中的文件空間用完,就會出現MySQL Table is full錯誤,InnoDB會回滾SQL語句。 
    • 事務死鎖致使InnoDB回滾整個事務。發生這種狀況時重試整個事務。 
      • 鎖等待超時致使InnoDB只回滾等待鎖並遇到超時的單個語句。(要回滾整個事務,啓動服務器使用--innodb-rollback-on-timeout 選項。)若是使用當前行爲,請重試該語句;若是使用--innodb-rollback-on-timeout ,請重試整個事務。
      • 在繁忙的服務器上,死鎖和鎖等待超時都是正常的,應用程序必須意識到它們可能會發生,並經過重試來處理它們。您能夠經過在事務期間對數據的第一次更改和提交之間儘量少地執行操做來下降發生這種狀況的可能性,從而使鎖保持儘量短的時間和儘量少的行數。有時,在不一樣的事務之間分割工做多是實用和有用的。
      • 當事務回滾因爲死鎖或鎖等待超時而發生時,它將取消事務內語句的效果。可是,若是start transaction語句是start transaction或BEGIN語句,則rollback不會取消該語句。在COMMIT、ROLLBACK或致使隱式提交的某些SQL語句出現以前,其餘SQL語句將成爲事務的一部分。 
      • 若是未在語句中指定IGNORE選項,則重複鍵錯誤將回滾SQL語句。 
      • 行太長錯誤將回滾SQL語句。    

其餘錯誤主要由MySQL代碼層(InnoDB存儲引擎級別之上)檢測,並回滾相應的SQL語句。在單個SQL語句的回滾中不會釋放鎖。 

 在隱式回滾和顯式回滾SQL語句執行期間,SHOW PROCESSLIST在相關鏈接的State列中顯示回滾。

相關文章
相關標籤/搜索