03.07 MySQL InnoDB存儲引擎:一致性非鎖定讀


MySQL InnoDB存儲引擎:一致性非鎖定讀


一致性的非鎖定行讀(consistent nonlocking read)是指InnoDB存儲引擎通過行多版本控制(multi versioning)的方式來讀取當前執行時間數據庫中行的數據。如果讀取的行正在執行DELETE、UPDATE操作,這是讀取操作不會因此而會等待行上鎖的釋放,相反,InnoDB會去讀取行的一個快照數據。

下圖直觀展示了一致性的非鎖定行讀:


MySQL InnoDB存儲引擎:一致性非鎖定讀

之所以稱其為非鎖定讀,因為不需要等待訪問的行上X鎖的釋放。快照數據是指該行的之前版本的數據,該實現是通過undo段來完成。而undo段用來在此事務中回滾數據,因此快照數據本身是沒有額外的開銷。此外,讀取快照數據是不需要上鎖的,因為沒有事務需要對歷史的數據進行修改操作。

可以看到,非鎖定讀機制極大地提髙了數據庫的併發性。在InnoDB存儲引擎的默認設置下,這是默認的讀取方式,即讀取不會佔用和等待表上的鎖。但是在不同事務隔離級別下,讀取的方式不同,並不是在每個事務隔離級別下都是採用非鎖定的一致性讀。此外,即使都是使用非鎖定的一致性讀,但是對於快照數據的定義也各不相同。

通過圖6-4可以知道,快照數據其實就是當前行數據之前的歷史版本,每行記錄可能有多個版本。就圖6-4所顯示的,一個行記錄可能有不止一個快照數據,一般稱這種技術為行多版本技術。由此帶來的併發控制,稱之為多版本併發控制(MultiVersionConcurrencyControl MVCC)。

在事務隔離級別READ COMMITTED和REPEATABLE READ(InnoDB存儲引擎的默認事務隔離級別)下,InnoDB存儲引擎使用非鎖定的一致性讀。然而,對於快照數據的定義卻不相同。在READ COMMITTED事務隔離級別下,對於快照數據,非一致性讀總是讀取被鎖定行的最新一份快照數據。而在REPEATABLE READ事務隔離級別下,對於快照數據,非一致性讀總是讀取事務開始時的行數據版本(關鍵在於事務之間的隔離性)。來看下面的一個例子,首先在當前MySQL數據庫的連接會話A中執行如下SQL語句:

<code># Session A
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

Sql> SELECT * FROM parent WHERE id =1;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)/<code>

會話A中已通過顯式地執行命令BEGIN開啟了一個事務,並讀取了表parent中id為1的數據,但是事務並沒有結束。與此同時,用戶再開啟另一個會話B,這樣可以模擬併發的情況,然後對會話B做如下的操作:

<code>mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE parent SET id=3 WHERE id=l;
Query OK, 1 row affected (0.00 sec)

Rows matched: 1 Changed: 1 warnings: 0/<code>

在會話B中將事務表parent中id為1的記錄修改為id=3,但是事務同樣沒有提交,這樣id=1的行其實加了一個X鎖。這時如果在會話A中再次讀取id為1的記錄,根據InnoDB存儲引擎的特性,即在READ COMMITTED和REPEATETABLE READ的事務隔離級別下會使用非鎖定的一致性讀。回到之前的會話A,接著上次未提交的事務,執行SQL語句SELECT * FROM parent WHERE id=1的操作,這時不管使用READ COMMITTED還是REPEATABLE READ的事務隔離級別,顯示的數據應該都是:

<code>mysql> SELECT FROM parent WHERE id =l;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)/<code>

由於當前id=1的數據被修改了1次,因此只有一個行版本的記錄。接著,在會話B中提交上次的事務。

<code># Session B
mysql> commit
Query OK, 0 rows affected (0.01 sec)/<code>

在會話B提交事務後,這時在會話A中再運行SELECT * FROM parent WHERE id=1的SQL語句,在READ COMMITTED和REPEATABLE事務隔離級別下得到結果 就不一樣了。對於READ COMMITTED的事務隔離級別,它總是讀取行的最新版本,如果行被鎖定了,則讀取該行版本的最新一個快照(fresh snapshot)。在上述例子中,因為會話B已經提交了事務,所以READ COMMITTED事務隔離級別下會得到如下結果:

<code>mysql>SELECT @@tx_isolation\\G;
**************************** 1.row ****************************
@@tx_isolation: READ-COMMITTED
1 row in set (0.00 sec)

mysql> SELECT FROM parent WHERE id=1:
Empty set (0.00 sec)/<code>

而對於REPEATETABLE 的事務隔離級別,總是讀取事務開始時的行數據。因此對於REPEATETABLE READ事務隔離級別,其得到的結果如下:

<code>mysql> SELECT @@tx_isolation\\G;
**************************** 1.row ****************************
@@tx_isolation: REPEATABLE-READ
1 row in set (0.00 sec)

mysql> SELECT FROM parent WHERE id=1;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)/<code>

下面將從時間的角度展現上述演示的示例過程,如表6-8所示。需要特別注意的是,對於READ COMMITTED 的事務隔離級別而言,從數據庫理論的角度來看,其違反了事務ACID中的I的特性,即隔離性。


MySQL InnoDB存儲引擎:一致性非鎖定讀


MySQL InnoDB存儲引擎:一致性非鎖定讀


MySQL InnoDB存儲引擎:一致性非鎖定讀


分享到:


相關文章: