14.5.2.3 一致性非阻塞讀

原文:https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html

14.5.2.3 Consistent Nonlocking Reads

一致性讀意味着InnoDB引擎使用多版本控制(multi-versioning)提供查詢在某一時間點快照的能力。這個查詢能夠看到在這個時間點之前提交的數據,但是不能看到其它事務之後的更改和尚未提交的更改。例外情況:查詢語句能夠看到同一個事務在查詢語句執行前操作的數據。此特例導致下面一個異常情況:當你更新了表中的某些行,select語句就能夠看到被更新數據的最新版本。如果其它session同時更新了同一個表,這時你可能看到表處於數據庫未定義的狀態。

如果事務的隔離級別是REPEATABLE READ (默認級別),同一個事務的所有的一致性讀都是從第一次read時建立的快照版本中讀取的。commit當前事務然後在查詢就會得到更新一些的快照。

在READ COMMITTED隔離級別下,每一個一致性讀都會刷新快照並從快照中讀取數據。

一致性讀是InnoDB在READ COMMITTED and REPEATABLE READ下默認的查詢模式,一致性讀不會對訪問的表加任何鎖,因此其它事務的修改表操作可以和查詢動作並行執行。

假設你是在REPEATABLE READ下運行,當發生一致性讀操作時(也就是說,執行了一條普通的查詢語句),InnoDB引擎會給的事務一個”時間點”,該時間點決定了事務看到的數據範圍。如果在該時間點之後另外一個事務刪除了一行數據並提交了事務,你不會看到這行數據已經被刪除。insert和update也是類似的處理方式。

注意
數據庫對SELECT提供了快照查詢功能,但是快照對DML語句並不一定有效。如果你在一個事務中insert或修改某些數據然後commit,另外一個事務的即使在REPEATABLE READ級別下執行update或delete語句也可能對剛纔提交的數據產生影響,即使該事務在快照中查找不到這些數據。
一旦一個事務update 或 delete 了其它事務提交的數據,那麼當前會看到這些數據,並且是最新版本。你可能遇到過下面的情況:

  ``` sql
      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語句或重新開啓一個事務執行查詢,以便更新“時間點”。

這就是多版本併發控制(multi-versioned concurrency control)。

下面的例子中,session A只有在session B提交了insert數據並且A也commit後,才能看到B插入的數據。這樣session A的時間點才能在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 * FROM t LOCK IN SHARE MODE;

在READ COMMITTED隔離級別下,每個一致性讀事務都會刷新快照然後在從快照中讀取。在LOCK IN SHARE下,一個讀鎖替代快照:一個查詢語句將會被阻塞直到它獲取最新的數據。

一致性讀在一些DDL語句中不起效:

  • 一致性讀不會在DROP TABLE語句下工作,因爲MySQL無法使用已經drop的表。

  • 一致性讀不會在ALTER TABLE語句下工作,因爲這個語句會生成原始表的臨時備份,一旦備份構建成功原始表將會被刪除。如果此時用一致性讀,會查不到數據。因爲表中並不存在該事務時間點之前的數據。在這種情況下,會產生一個錯誤:ER_TABLE_DEF_CHANGED(table 的定義已經變更,請重試該事務)。

    譯者注:在MySQL5.7 RR下實驗表明:只要開啓了事務並在表中執行了語句(select,update or delete),只要沒有commit,drop table,alter table就會被阻塞。

一些select語句,像INSERT INTO … SELECT, UPDATE … (SELECT), and CREATE TABLE … SELECT ,這些沒有聲明FOR UPDATE or LOCK IN SHARE MODE,使用一致性讀的情況如下:

  • 默認情況下,InnoDB會使用較粗粒度的鎖,select部分與在READ COMMITTED模式下執行相同:即使是同一個事務,也會刷新快照。

  • 如果打開了innodb_locks_unsafe_for_binlog選項在READ UNCOMMITTED,READ COMMITTED, or REPEATABLE READ (除了SERIALIZABLE以外的級別),在這種情況下,對查詢的表不會加鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章