1. 程式人生 > >mysql Innodb引擎

mysql Innodb引擎

斜體字表示個人觀點和註釋
翻譯自 MYSQL 5.7英文文件
事務隔離級別 中的 READ COMMITTED 節中有提到 RR 和 RC 隔離級別下,innodb 如何執行 update 操作。

使用Innodb引擎的優點

1.如果您的伺服器因硬體或軟體問題而崩潰,無論當時資料庫中發生了什麼情況,重新啟動資料庫後無需執行任何特殊操作。 InnoDB 崩潰恢復會自動完成崩潰前提交的所有更改,並撤消正在處理但未提交的任何更改。只需重新啟動並從你離開的地方繼續。

2.Innodb 引擎維護有自己的快取池(buffer pool),用來在主存中快取表和索引資料。經常使用的資料會直接在記憶體中處理。此快取適用於許多型別的資訊並加速處理。在專用的資料庫伺服器中,會賦予快取池多達80%的實體記憶體。

3.外來鍵

4.如果資料在磁碟或記憶體中損壞,則校驗和機制在使用前會提醒。

5.引用 WHERE 子句,ORDER BY 子句,GROUP BY 子句和連線操作中的主鍵列是非常快的。

6.插入、刪除和修改會被 change buffering機制優化。Innodb 會快取改變的資料來精簡I/O

7.當從表中反覆訪問相同的行時,稱為自適應雜湊索引的功能會接手以使這些查詢更快,就像它們從雜湊表中出來一樣。

8.可以壓縮表和關聯的索引

9.建立和刪除索引對效能和可用性影響更小

10.截斷每個表的檔案表空間非常快,並且可以釋放磁碟空間供作業系統重用,而不是釋放只有InnoDB才能重用的系統表空間內的空間。

11.使用 DYNAMIC 行格式,表格資料的儲存佈局對於 BLOB 和長文字欄位更高效。

12.可以通過查詢 INFORMATION_SCHEMA 表來監控儲存引擎的內部工作

13.可以通過查詢 Performance Schema 表來監控儲存引擎的效能細節

14.可以將Innodb引擎的表和別的引擎的表混合使用,即使是同一個statement中

15.InnoDB在處理大量資料時專為提高CPU效率和最大效能而設計。

16.即使在檔案大小限制為2GB的作業系統上,InnoDB表也可以處理大量資料。

Innodb引擎的最佳實踐方法

1.指定查詢中最常使用的一列或幾列作為主鍵,沒有的話就使用一個自增值做主鍵。

2.當從多表中基於相同ID取資料時,使用joins連線多表。為了提升join效能,在join列上定義外來鍵,且join列有相同的資料型別。增加外來鍵來確保引用的列是被索引的。外來鍵會導致多表的級聯刪除或更新。

3.關閉 autocommit.。每秒提交數百次會限制性能。

4.通過使用START TRANSACTION和COMMIT語句將相關的DML操作集合分組到事物中,避擴音交過於頻繁,也要避免執行了幾個小時還沒有提交的 Insert 和 update 語句。

5.不要使用 LOCK TABLE 語句。Innodb 支援多個會話對錶的同時讀寫,而不犧牲可靠性和效能。使用 SELECT … FOR UPDATE 語法獲取一些行的獨佔的寫許可權,這隻鎖住你計劃要 UPDATE 的行。

6.開啟 innodb_file_per_table 選項(預設開啟)或使用通用表空間,來把不同表的資料和索引放到不同的檔案中,不要使用系統表空間(system space)。

7.評估資料和連線模式是否能夠從 Innodb 表或頁的壓縮特性中獲益。可以在不犧牲讀寫效能的情況下壓縮 Innodb 表。

8.執行伺服器時帶上--sql_mode=NO_ENGINE_SUBSTITUTION 選項來防止帶有 ENGINE= 的建表語句建立不同引擎的表。

Innodb 引擎與ACID

1.Atomicity:
- 自動提交設定
- COMMIT 語句
- ROLLBACK 語句
- INFORMATION_SCHEMA 中的操作資料

2.Consistency:
- doublewrite buffer(見下文)
- 容災恢復

3.Isolation:
- 自動提交設定
- SET ISOLATION LEVEL 語句
- Innodb 鎖的底層細節。在效能調優期間,您可以通過 INFORMATION_SCHEMA 表格檢視這些詳細資訊。

4.Durability
- doublewrite buffer
- innodb_flush_log_at_trx_commit 配置項
- sync_binlog 選項
- innodb_file_per_table 選項
- 儲存裝置中的寫快取
- 儲存裝置中的電池備份快取
- 執行mysql的作業系統,尤其是是否支援fsync()系統呼叫
- 不間斷電源(UPS)保護執行MySQL伺服器和儲存MySQL資料的所有計算機伺服器和儲存裝置
- 您的備份策略,例如備份頻率和型別以及備份保留期
- 對於分散式或託管資料應用程式,MySQL伺服器硬體所在資料中心的特定特徵以及資料中心之間的網路連線。
-

doublewrite buffer

轉自http://blog.csdn.net/linuxheik/article/details/62444753

當 mysql 在寫一個 innodb page 到磁碟上時,如果在寫這個 page 的過程中發生了意外的事件,比如斷電,mysql 崩潰等
就會使這個 page 資料出現不一樣的情形.從而形成一個”斷裂”的 page .使資料產生混亂.這個時候 innodb 對這種塊錯誤錯誤是無能為力的.
通過引入 doublewrite buffer 的方案,每次 innodb 在準備寫出一個 page 時,先把 page 寫到 doublewrite buffer 中.如果在寫 doublewrite buffer 時,發生了意外,但是資料檔案中的原來的 page 不受影響,這樣在下次啟動時,可以通過 innodb 的 redolog 進行恢復.如果在寫 doublewrite buffer 成功後,mysql 會把 doublewrite buffer 的內容寫到資料檔案中,如果在這個過程又出現了意外,沒有關係,重啟後 mysql 可以通過從 doublewrite buffer 找到好的 page ,再用該好的 page 去覆蓋磁碟上壞的page即可.所以在正常的情況下,mysql 寫資料 page 時,會寫兩遍到磁碟上,第一遍是寫到 doublewrite buffer,第二遍是從 doublewrite buffer 寫到真正的資料檔案中。
在某些情況下可以關閉 doublewrite buffer ,從而提高效能,比如比較穩定的系統中,有比較好的主從備份等。

InnoDB Multi-Versioning

一致讀 consistent read
插入撤銷日誌 insert undo log
更新撤銷日誌 update undo log

Innodb 是一個多版本儲存引擎:它保留有關舊版本更改行的資訊,以支援事務性功能,如併發和回滾。該資訊儲存在表空間中的稱為回滾段的資料結構中。InnoDB使用回滾段中的資訊執行事務回滾所需的撤消操作。它還使用這些資訊構建一個行的早期版本以進行一致的讀取。
Innodb 內部會在資料庫的每行資料裡增加三個區域。
一個6位元組的 DB_TRX_ID 欄位,來表示最近一次 UPDATE 或 INSERT 該行的事務的識別碼。另外,刪除在內部被視為更新,其中行中的特殊位被用來將其標記為已刪除。
一個7位元組的DB_ROLL_PTR 欄位,叫做回滾指標(roll pointer),指向寫入回滾段中的撤銷日誌記錄。如果該行被 UPDATE 過,撤銷日誌記錄包含重建該行被 UPDATE 前的內容的必要資訊。
一個6位元組的 DB_ROW_ID,包含一個隨新行插入而單調遞增的行ID。如果InnoDB自動生成聚集索引,則索引包含行ID值。否則, DB_ROW_ID 不出現在任何索引中。

回滾段中的撤銷日誌分為插入和更新撤消日誌。插入撤消日誌只在事務回滾中需要,並且只要事務提交就可以丟棄。更新撤消日誌也用於一致性讀取,但只有當沒有事務存在時才會丟棄它們,InnoDB 會為其(指的是事務)分配一個快照,在一致讀中可能需要更新撤消日誌中的資訊來構建早期版本的資料庫行(事務都完成或者提交了,更新撤銷日誌也就沒用了吧)。

定期提交事務,包括那些只有一致讀的事務。否則,InnoDB不能從更新撤消日誌中丟棄資料,並且回滾段可能變得太大,填滿你的表空間。

回滾段中撤銷日誌記錄的物理大小通常小於相應的插入或更新行。您可以使用此資訊來計算回滾段所需的空間。

在 InnoDB 多版本方案中,當您使用SQL語句刪除行時,行不會立即從資料庫中物理刪除。當它丟棄為刪除而寫入的更新撤銷日誌時,Innodb 才會物理刪除相應行和索引記錄。這個刪除操作被叫做 purge,它相當快,通常與實現刪除的sql語句的時間順序一致。

如果在表中以大致相同的速率插入和刪除小批量行,則清除執行緒(purge thread)可能會滯後,並且由於所有“死”行,表可能變得越來越大,從而使所有東西都變成磁碟繫結的(記憶體不足需要記錄到磁碟上?),而且非常慢。在這種情況下,通過調整 innodb_max_purge_lag 系統變數來限制新行操作,併為清除執行緒分配更多資源。

Multi-Versioning 和 Secondary Indexes

InnoDB 多版本併發控制(MVCC)將二級索引視為與聚簇索引不同。聚簇索引中的記錄在原地更新,其隱藏的系統列指向撤銷日誌記錄,從中可以重建早期版本的記錄(資料本身就是按照聚簇索引順序存放的,二級索引只有主鍵值,需要根據主鍵值再到聚簇索引中查詢資料)。與聚集索引記錄不同,二級索引記錄不包含隱藏的系統列,也不就地更新。

當更新二級索引列時,舊的二級索引記錄被刪除標記,插入新記錄後才會清除帶有刪除標記的記錄。當輔助索引記錄被刪除標記或輔助索引頁面被更新的事務更新時,InnoDB 將在聚簇索引中查詢資料庫記錄。在聚簇索引中,檢查記錄的DB_TRX_ID,如果記錄在讀取事務啟動後被修改,則從撤消日誌中檢索正確版本的記錄。

如果輔助索引記錄被標記為刪除或者輔助索引頁面被更新的事務更新,則不使用覆蓋索引技術。 InnoDB 不會從索引結構返回值,而需要在聚簇索引中查詢資料記錄。(如果能夠使用覆蓋索引的話就可以不需要再到聚簇索引中查詢資料了,二級索引已經包含了全部所需的資料)
但是,如果啟用了索引條件下推(ICP)優化,並且可以僅使用索引中的欄位來評估 WHERE 條件的某些部分,那麼MySQL伺服器仍然會將這部分 WHERE 條件向下推送到儲存引擎,儲存引擎則會使用索引評估。如果沒有找到符合條件的記錄,就不再需要到聚簇索引中查詢了。如果找到匹配記錄,即使帶有刪除標記,InnoDB 也會在聚簇索引中查詢記錄。

Innodb 鎖和事務模型

對於實現大型,繁忙或高度可靠的資料庫應用程式,移植來自不同資料庫系統的實際程式碼或調整MySQL效能,瞭解 InnoDB 鎖和 InnoDB 事務模型非常重要。

Innodb 鎖

主要描述 Innodb 使用的各型別的鎖
  • 共享和獨佔鎖
    Innodb 用共享鎖(shared locks, s)和排他鎖(exclusive locks, x)實現標準的行級鎖。
    共享鎖允許持有該鎖的事務READ行。
    排他鎖允許持有該鎖的事務UPDATE或DELETE行。

如果事務 T1 在 r 行上持有共享鎖,事務 T2 在 r 行上對鎖的請求將被如下處理:
- 如果 T2 請求的是共享鎖,將被立即授予。最終 T1 和 T2 都持有 r 行的共享鎖。
- 如果 T2 請求的是排他鎖,不會被立即授予。(需要等 T1 釋放鎖)

如果 T1 在 r 行上持有排他鎖,T2 對鎖的請求都需要等待 T1 鎖的釋放。

  • 意向鎖(Intention Locks)
    Innodb 支援允許行鎖和表鎖共存的多粒度鎖(multiple granularity locking)。舉例,例如LOCK TABLES ... WRITE語句會在表上持有排他鎖。Innodb 使用意向鎖來實現多個粒度級別的鎖定。意向鎖是表級鎖,表明一個事務將要在表中的某行請求共享鎖還是排他鎖。有兩種型別的意向鎖:
    • 意向共享鎖(IS)表示事務意圖在表中的單個行上設定共享鎖。
    • 意向排他鎖(IX)表明事務意圖在表中的單個行上設定獨佔鎖。

舉例,SELECT ... LOCK IN SHARE MODE會設定一個意向共享鎖,SELECT ... FOR UPDATE會設定一個意向排他鎖。
意向鎖協議如下:
- 在事務能夠獲取在表上某行的共享鎖之前,它必須先在表上獲取意向共享鎖或更強的鎖(獲取意向共享鎖或意向排他鎖)。
- 在事務能夠獲取在表上某行的排他鎖之前,它必須先在表上獲取意向排他鎖

下表是表級鎖的相容性:

` X IX S IS
X 衝突 衝突 衝突 衝突
IX 衝突 相容 衝突 相容
S 衝突 衝突 相容 相容
IS 衝突 相容 相容 相容

如果事務請求的鎖能與已存在的鎖相容,則事務將被授予該鎖;如果衝突,就需要等待已存在的鎖釋放。如果鎖定請求與現有的鎖衝突並且因為會導致死鎖而無法被授予,則會發生錯誤。

意向鎖只會阻塞全表請求(例如LOCK TABLES ... WRITE)。意向鎖的主要目的是顯示正在鎖定一行,或者將要鎖定表中的一行。

TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
  • 記錄鎖(Record Locks)
    記錄鎖是加在一條索引記錄上的鎖。例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;組織任何在滿足c1 = 10條件上的行的INSERT,UPDATE,DELETE.

記錄鎖總是鎖住索引記錄,即使表沒有被定義索引。對於這種情況,Innodb 會建立一個隱藏的聚簇索引,記錄鎖會加到這個隱藏索引上。

  • 間隙鎖(Gap Locks)
    間隙鎖是索引記錄之間的間隙的鎖,以及第一條記錄之前和最後一條索引記錄之後的間隙的鎖。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;會阻止其他事物插入c1 = 15的記錄,無論是否已經存在c1 = 15的記錄,因為所有滿足 where 條件的記錄的間隙都會加上間隙鎖。

間隙鎖是效能和併發性之間折中的一部分,只在某些事務隔離級別中使用。

對於使用唯一索引鎖定行以搜尋唯一行的語句,不需要使用間隙鎖定。 (這不包括搜尋條件僅包含多列唯一索引中的某些列的情況;在這種情況下,會發生間隙鎖定)。例如,如果 id 列具有唯一索引,則以下語句僅會對於 ID 為100的行的加上索引記錄鎖,無論其他會話是否在上述間隔中插入行:

SELECT * FROM child WHERE id = 100;

如果id未被索引或者具有非唯一索引,則該語句會產生間隙鎖。

間隙可以持有衝突鎖。例如,事務A可以在間隙上儲存共享間隙鎖(間隙S鎖),而事務B在同一間隙上保留獨佔間隙鎖(間隙X鎖)。允許衝突間隙鎖定的原因是,如果從索引中清除記錄,則必須合併由不同事務記錄儲存的間隙鎖定。

間隙鎖只會阻止其他事務插入到間隙中,不同的事務可以在同一個間隙上持有間隙鎖。因此,間隙X鎖具有與間隙S鎖相同的效果。
(看樣子間隙鎖主要是用來解決幻讀問題的)

可以顯式禁用間隙鎖。如果將事務隔離級別更改為 READ COMMITTED 或啟用 innodb_locks_unsafe_for_binlog 系統變數(現在已棄用),則會發生這種情況。在這些情況下,對搜尋和索引掃描禁用間隙鎖,間隙鎖僅用於外來鍵約束檢查和重複鍵檢查。

  • Next-Key Locks
    Next-Key Lock 是索引記錄上的記錄鎖和索引記錄前的間隙鎖的組合。
    當 Innodb 搜尋或掃描一個表的索引時,它會通過在遇到的索引記錄上增加共享鎖或排他鎖來實現行級鎖定。因此行級鎖實際上是索引記錄鎖(index-record locks)。在索引記錄上的 Next-Key Lock 也會影響在該索引記錄前的間隙:除了給索引記錄加鎖,也會給索引記錄前的間隙加鎖。如果一個會話在索引記錄 R 上有一個共享鎖或排他鎖,另一個會話不能在記錄 R 前插入一個新的索引記錄。
    假設一個索引包含值10,11,13和20,那麼 Next-Key Lock 可能鎖住的間隔如下:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

預設情況下,InnoDB在REPEATABLE READ事務隔離級別下執行。在這種情況下,InnoDB使用next-key鎖進行搜尋和索引掃描,這可以防止幻讀。

  • 插入意向鎖(Insert Intention Locks)
    插入意向鎖是插入行之前由INSERT操作設定的一種間隙鎖。該鎖以這種方式表示插入的意圖:當多個事務向同一個索引間隙中插入時,只要不在間隙裡的同一個位置上插入,插入時就不需要相互等待。假設有4和7兩個索引記錄。當事務A、B分別嘗試插入索引值為5和6的兩條記錄時,都會在獲取被插入行的排他鎖前,用插入意向鎖鎖住4和7之間的間隙。但並不會互相阻塞,因為這兩行沒有衝突。(插入行的索引值不同就不會相互等待或阻塞)

以下例子演示一個事務在獲取用於插入的排他鎖前,如何獲取一個插入意向鎖。這個例子涉及兩個客戶端, A 和 B 。
客戶端 A 建立了一個包含90和102兩條索引記錄的表,然後啟動一個事務,該事務會在索引值大於100的記錄上加上排他鎖。

mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);

mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id  |
+-----+
| 102 |
+-----+

客戶端 B 啟動了一個事務來向間隙中插入一條記錄。當該事物等待獲取一個排他鎖時,會加上一個插入意向鎖。

mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
  • (自增鎖)AUTO-INC Locks
    自增鎖是事務插入帶有自增列的記錄時加的特殊表級鎖。在最簡單的情況下,當一個事務插入時,則任何其他事務都必須等待該表執行完插入操作,以便第一個事務插入的行接收連續的主鍵值。
    innodb_autoinc_lock_mode 配置選項控制用於自增鎖的演算法。它允許您選擇如何在可預測的自增值序列和插入操作的最大併發之間進行權衡。

  • 用於空間索引的謂詞鎖(Predicate Locks for Spatial Indexes)
    從沒使用過,難以理解,就不翻譯了

Innodb 事務模型

InnoDB 事務模型的目標是將多版本資料庫的最佳屬性與傳統的兩階段鎖定相結合。Innodb 使用行級鎖,預設使用不加鎖(nonlocking)一致讀的方式執行查詢。 InnoDB 中的鎖資訊以節省空間的方式儲存,因此不需要鎖升級。(不懂。。)。通常,允許多個使用者鎖定 InnoDB 表中的每一行或任何行的隨機子集,而不會導致 InnoDB 記憶體耗盡。

事務隔離級別

事務隔離是資料庫處理的基礎之一。隔離是ACID中的I;隔離級別是在多個事務進行更改並同時執行查詢時,對效能與可靠性,一致性和結果重複性之間的平衡進行微調的設定。
InnoDB 提供了 SQL:1992 標準描述的所有四種事務隔離級別:READ UNCOMMITTED,READ COMMITTED,REPEATABLE READ 和 SERIALIZABLE。 InnoDB 的預設隔離級別是 REPEATABLE READ。
使用者可以使用 SET TRANSACTION 語句更改單個會話或所有後續連線的隔離級別。要為所有連線設定伺服器的預設隔離級別,請在命令列或選項檔案中使用--transaction-isolation選項。有關隔離級別和級別設定語法的詳細資訊,請參見第13.3.6節“ SET TRANSACTION 語法”。
InnoDB 通過不同的鎖定策略來支援各個事務隔離級別。或者,當結果的精確性、一致性和可重複性不如減少鎖定開銷重要時,可以放寬一致性規則,即使用 READ COMMITTED 或 READ UNCOMMITTED。 SERIALIZABLE 執行比REPEATABLE READ 更嚴格的規則,主要用於特殊情況下,如 XA 事務和解決併發和死鎖問題。

以下描述了MySQL如何支援不同的事務級別。該列表從最常用的級別到最少使用的級別。
- 可重複讀(REPEATABLE READ)
Innodb 的預設隔離級別。在同一事務中的讀,都會讀取第一次讀取時建立的快照。
對於鎖定讀取(SELECT FOR WITH UPDATE或LOCK IN SHARE MODE),UPDATE 和 DELETE 語句,鎖定取決於語句使用具有唯一搜索條件的唯一索引還是範圍型別搜尋條件。對於具有唯一搜索條件的唯一索引,InnoDB 只給記錄加鎖,不給間隙加鎖。 對於其他搜尋條件,InnoDB 鎖定掃描的索引範圍,使用間隙鎖或 Next-Key Lock來阻止其他會話插入到範圍所覆蓋的間隙中。

  • Read Committed
    每個一致讀,即使是在同一個事務中,都會設定並讀取自己的新快照。

    對於加鎖讀(SELECT ... LOCK IN SHARE MODESELECT ... FOR UPDATE),UPDATE語句和DELETE語句,innodb 只鎖索引記錄,不對間隙加鎖,因此允許在加鎖記錄前後自由插入。間隙鎖只用來外來鍵約束檢查和重複鍵(duplicate-key)檢查,因此會出現幻行。

    如果使用 READ COMMITTED,則必須使用基於行的二進位制日誌記錄。

    使用 READ COMMITTED 還有額外的影響:

    • 對於 UPDATE 或 DELETE 語句,innodb 只會在要 update 或 delete 的行上加鎖。不匹配行的記錄鎖在 mysql 評估完 WHERE 語句後會被釋放。這將大大減少死鎖發生的機率。
    • 對於 UPDATE 語句,如果一行已經被加鎖了,innodb 執行一個半一致化讀(semi-consistent read),返回最近提交版本給 mysql ,來方便 mysql 判斷行是否匹配 WHERE 語句。如果行匹配(要被 UPDATE),mysql 再次讀取行,innodb 會在行上加鎖或等待加鎖。

    考慮下面的例子。表語句如下:

CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
COMMIT;

表沒有索引,因此搜尋和索引掃描使用隱藏的聚簇索引來加記錄鎖。

假設會話 A 執行如下語句:

# Session A
START TRANSACTION;
UPDATE t SET b = 5 WHERE b = 3;

然後會話 B 執行如下語句:

# Session B
UPDATE t SET b = 4 WHERE b = 2;

當 innodb 執行 UPDATE 語句時,先在讀取的每一行上獲取一個排它鎖,然後決定是否進行修改。如果 innodb 不修改該行,將會釋放該鎖;否則,innodb 將會持有該鎖直到事務結束。這會影響事務處理,如下所示。

當使用預設 REPEATABLE READ 隔離級別時,會話 A 的 UPDATE 語句在讀取的每一行上獲取 x-lock,不釋放:

x-lock(1,2); retain x-lock
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); retain x-lock
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); retain x-lock

會話 B 的 UPDATE 語句將會卡主,直到事務 A 提交或回滾:

x-lock(1,2); block and wait for first UPDATE to commit or roll back

如果使用的是 READ COMMITTED ,會話 B 會在讀取的每一行上加 x-lock 鎖,然後釋放不需要修改的行:

x-lock(1,2); unlock(1,2)
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); unlock(3,2)
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); unlock(5,2)

對於會話 B 的 UPDATE 語句,innodb 執行一個半一致性讀,返回給 mysql 每行的最近提交版本,mysql 判斷哪些行符合 WHERE 條件:

x-lock(1,2); update(1,2) to (1,4); retain x-lock
x-lock(2,3); unlock(2,3)
x-lock(3,2); update(3,2) to (3,4); retain x-lock
x-lock(4,3); unlock(4,3)
x-lock(5,2); update(5,2) to (5,4); retain x-lock

然而,如果 WHERE 包含一個索引列且 innodb 使用該索引,當獲取並保持記錄鎖時,只考慮索引列。在下面的例子中,第一個 UPDATE 語句在滿足 where 條件的行上獲取並保持 x-lock。第二個 UPDATE 語句會在獲取同樣記錄的 x-lock 上卡主,因為也使用了列 b 上的索引。

CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2,3),(2,2,4);
COMMIT;

# Session A
START TRANSACTION;
UPDATE t SET b = 3 WHERE b = 2 AND c = 3;

# Session B
UPDATE t SET b = 4 WHERE b = 2 AND c = 4;
  • Read Uncommitted
  • Serializable
    (未使用過的隔離級別暫不翻譯了)

自動提交,提交和回滾

在InnoDB中,所有的使用者活動都發生在一個事務中。如果啟用自動提交模式,則每個SQL語句將自行形成一個事務。MySQL 開啟每個新連線的會話時都會預設啟用自動提交。因此如果該語句沒有返回錯誤,則MySQL會在每個SQL語句後執行提交。如果語句返回錯誤,則提交或回滾行為取決於錯誤。
啟用自動提交的會話可以以下方式來執行一個多語句事務:通過以明確的 START TRANSACTION 或 BEGIN 語句啟動,並使用 COMMIT 或 ROLLBACK 語句結束。
如果在SET autocommit = 0的會話中禁用自動提交模式,則會話始終在一個事務中。 COMMIT 或 ROLLBACK 語句結束當前事務並開始一個新事務。 如果具有禁用自動提交功能的會話在未顯式提交最終事務的情況下結束,則MySQL會回滾該事務。 一些語句隱式地結束一個事務,就好像你在執行語句之前做了一個COMMIT一樣。有關詳細資訊,請參見第13.3.3節“導致隱式提交的語句”。 COMMIT 意味著在當前交易中所做的更改將永久化並對其他會話可見。另一方面,ROLLBACK語句會取消當前事務所做的所有修改。 COMMIT 和 ROLLBACK 都釋放當前事務期間設定的所有 InnoDB 鎖。

用事務對DML操作進行分組
預設情況下,與MySQL伺服器的連線從啟用自動提交模式開始,當您執行它時會自動提交每條SQL語句。如果您對其他資料庫系統有經驗,那麼這種操作模式可能並不熟悉,在這種模式下,釋出一系列 DML 語句並提交它們或將它們一起回滾是標準做法。
要使用多語句事務,請使用 SQL 語句SET autocommit = 0關閉自動提交,並根據需要使用 COMMIT 或 ROLLBACK 結束每個事務。要開啟自動提交,請使用START TRANSACTION開始每個事務並使用 COMMIT 或 ROLLBACK 結束它。以下示例顯示了兩個事務。第一個提交;第二個回滾。

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中,您可以像使用其他SQL語句(如SELECT或INSERT)一樣將事務控制語句(如COMMIT)作為字串傳送到MySQL伺服器。一些API還提供單獨的特殊事務提交和回滾函式或方法。

一致不加鎖讀

一致讀取意味著 InnoDB 使用多版本化在某個時間點向查詢呈現資料庫的快照。該查詢可以檢視在該時間點之前提交的事務所做的更改,無法看到之後或未提交的事務所做的更改,但是可以看到同一事務中之前的語句所做的更改。

如果事務隔離級別為 REPEATABLE READ(預設級別),則同一事務中的所有一致讀取將讀取該事務中第一次讀取所建立的快照。您可以通過提交當前事務並在發出新查詢之後為您的查詢獲得更新鮮的快照。
通過 READ COMMITTED 隔離級別,事務中的每個一致讀取都會設定並讀取其自己的新快照。

一致讀是 InnoDB 在 READ COMMITTED 和 REPEATABLE READ 隔離級別中處理 SELECT 語句的預設模式。 一致讀不會在它訪問的表上設定任何鎖,因此其他會話可以自由修改這些表,同時在表上執行一致讀。 假設您正在執行預設的 REPEATABLE READ 隔離級別。當您發出一致讀(即普通的 SELECT 語句)時,InnoDB 會為您的事務提供一個根據您的查詢檢視資料庫的時間點。如果另一個事務刪除了一行並在分配了時間點後提交,則不會看到該行已被刪除。插入和更新的處理方式相似。

注意:
資料庫狀態的快照適用於事務中的SELECT語句,而不一定適用於DML語句。如果插入或修改某些行然後提交該事務,則從另一個併發REPEATABLE READ事務發出的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.

您可以通過提交事務來推進您的時間點,然後再進行另一個 SELECTSTART TRANSACTION WITH CONSISTENT SNAPSHOT

這稱為多版本併發控制。

在下面的例子中,只有當 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 * FROM t FOR SHARE;

在 READ COMMIT 隔離級別下,每個一致讀都會設定並讀取自己的快照。在LOCK IN SHARE MODE下就會使用鎖定讀:SELECT 會阻塞,直到包含最新行的事務結束。

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

  • 一致性讀取不能在DROP TABLE上工作,因為MySQL不能使用已經被刪除的表。
  • 一致讀不能在ALTER TABLE上工作。因為該語句建立了原表的臨時副本,並在副本建立完後刪除原表。在事務中再次使用一致讀時,新表中的行不可見,因為這些行不存在事務快照中。在這種情況下,事務返回錯誤:ER_TABLE_DEF_CHANGED,“表定義已更改,請重試事務”。

如果語句中不包含FOR UPDATELOCK IN SHARE MODE,在INSERT INTO ... SELECTUPDATE ... (SELECT)CREATE TABLE ... SELECT語句中的讀型別會預設採用更強的鎖(就像READ COMMITTED):每個一致讀都會使用自己新的快照,即使在一個事務中。如果想要在這種情況下使用一致讀,開啟 innodb_locks_unsafe_for_binlog 選項並將隔離級別設定為 READ UNCOMMITTED,READ COMMITTED 或 REPEATABLE READ 。在這種情況下,從被選擇表中讀取的行將不會上鎖。

加鎖讀

如果你查詢資料然後在同一事務中插入或更新相關資料,則常規的 select 語句不能提供足夠的保護。另一些事務能夠更新或刪除查詢的行。(假設事務A中查詢+更新,事務B可以在事務A還沒有執行更新語句時,刪除A查詢的行。在一般情況下好像這也不會有什麼問題?)。Innodb 支援兩種型別的加鎖讀,能夠提供額外的安全性:

  • SELECT ... LOCK IN SHARE MODE
    在所有讀取的行上設定一個共享鎖。其他會話能夠讀取行,但是不能修改直到你的事務提交。但如果這些行中的任意行被別的事務修改了還未提交,你的查詢就必須等該事務結束。
  • SELECT ... FOR UPDATE
    這和你執行 UPDATE 語句一樣,對於搜尋遇到的索引記錄,鎖定行和任何關聯的索引條目。其他想要更新、執行SELECT ... LOCK IN SHARE MODE或者從特定隔離級別下讀取這些行的事務都會被阻塞。一致讀忽略讀取檢視中存在的記錄上設定的任何鎖。(一條記錄的舊版本不會被鎖;它們由撤銷日誌在記錄的記憶體副本中重建)

這些子句在處理樹形結構或圖形化結構時非常有用,無論在一個表或分成多個表(沒遇到過。。)。您可以將邊或樹的分支從一處移到另一處,同時保留返回並更改這些“指標”值的權利。

注意
SELECT FOR UPDATE對行的加鎖只在自動提交關閉時有效(設定 autocommit 為0,或者用START TRANSACTION開啟事務)。如果開啟了自動提交,匹配行將不會被加鎖。

鎖定讀示例
假設你想要在表child中插入一條新行,同時確保該行在表parent中有一條父行。你的應用程式碼使用這一系列的操作來確保參照完整性(referential integrity)。
首先,在表parent中執行一致讀來確保父行的存在。然後你就可以安全的在表child中插入行嗎?不,其他會話能夠在你的 SELECT 操作和 INSERT 操作之間刪除父行。
為了防止這個潛在的問題,需要在 SELECT 語句中帶上LOCK IN SHARE MODE

SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;

LOCK IN SHARE MODE的查詢返回父行時,你就可以安全的在表child中插入記錄並提交。任意事務想要獲取排他鎖都必須等你完成,這就是說,直到表中的所有資料處於一致狀態。

另一個示例,考慮在表child_codes中的整數計數字段,用於為插入表中的每行記錄分配唯一識別符號。不要使用一致讀或共享模式讀來獲取該欄位的當前值,因為兩個使用者可能會看到計數器的同一個值,然後會出現一個鍵值重複的錯誤如果兩個事物嘗試在表中插入相同識別符號的行。

在這裡,LOCK IN SHARE MODE不是一個好的解決方案,因為如果兩個使用者同時讀取計數器,至少有一個使用者在嘗試更新計數器時會死鎖。

為了實現讀取並增加計數器,先用FOR UPDATE執行一個鎖定讀,然後在增加計數器。舉例:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

一個SELECT ... FOR UPDATE讀取最新可用的資料(會使用最新的快照而不是事物最開始設定的快照),在讀取的每一行上設定一個排他鎖。因此,它在行上設定的鎖和 UPDATE 語句相同。

在 MYSQL 中,產生一個唯一的識別符號實際上可以通過對錶的單一連線完成:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();

SELECT語句僅檢索識別符號資訊(特定於當前連線)。它不訪問任何表。(別的連線中插入的行,並不會改變該連線 LAST_INSERT_ID() 的值)

Innodb 中不同sql語句設定的鎖

加鎖讀、UPDATE 或 DELETE 通常會在 sql 語句處理過程中給每條掃描到的索引記錄加記錄鎖,無論 where 條件是否會排除該行。Innodb 不會記住詳細的 where 條件,但是知道掃描的索引範圍。這些鎖通常是 Next-Key Lock,也會阻止在間隙中插入記錄。然而, 間隙鎖能被顯示禁用,導致 Next-Key Lock 無法使用。事務隔離級別也會影響設定的鎖。

如果查詢中使用了一個第二索引,而且索引記錄鎖是排他性的,innodb 也會獲取相應的聚簇索引的記錄並加鎖。

如果你在語句中沒有使用合適的索引而且 MYSQL 必須掃描整個表來處理語句,表的每行都會加鎖,其他使用者的插入都會被阻塞。一個好的索引很重要,能夠避免你的查詢掃描很多不必要的行。

對於SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE,掃描時會加鎖,結果集中不滿足條件的行的鎖應被釋放(舉例,當不滿足 where 子句給出的條件時)。然而,在一些案例中,行可能不會被馬上解鎖,由於查詢期間結果行和原始源之間的關係丟失了。舉例,在一個 UNION 中,掃描和被加鎖的行在被評估是否滿足結果集條件前會被插入臨時表中。在這種情況下,臨時表和原表間行的關係丟失了,後來的行將不會被解鎖直到查詢執行結束。

Innodb 會根據查詢型別設定特定型別的鎖:

  • SELECT ... FROM 是一個一致讀,讀取資料庫快照,不會加鎖除非事務隔離級別是 SERIALIZABLE。在 SERIALIZABLE 下,查詢會在遇到的記錄上設定共享的 Next-Key Lock ,然而對於使用唯一索引查詢唯一行的語句,只需要一個索引記錄鎖。
  • SELECT ... FROM ... LOCK IN SHARE MODE會在遇到的所有索引記錄上設定共享的 Next-Key Lock。然而對於使用唯一索引查詢唯一行的語句,只需要一個索引記錄鎖。
  • SELECT ... FROM ... FOR UPDATE會在遇到的所有索引記錄上設定排他的 Next-Key Lock。然而對於使用唯一索引查詢唯一行的語句,只需要一個索引記錄鎖。
    對於遇到的索引記錄,SELECT ... FROM ... FOR UPDATE會阻塞其他執行SELECT ... FROM ... IN SHARE MODE的會話,也會在特定事務隔離級別下阻塞讀取。一致性讀忽略在讀檢視中加在記錄上的任意鎖。
  • UPDATE ... WHERE ...會在遇到的所有索引記錄上設定排他的 Next-Key Lock。然而對於使用唯一索引查詢唯一行的語句,只需要一個索引記錄鎖。
  • 當 UPDATE 修改一條聚簇索引記錄時,會在第二索引上增加隱式鎖。當插入新的第二索引記錄時,或在插入新的第二索引記錄前執行重複性檢查掃描時,UPDATE 操作會在受影響的第二索引記錄上加共享鎖。
  • INSERT 會在插入行上加排他鎖。這個鎖是索引記錄鎖,不是一個 Next-Key Lock(即,沒有間隙鎖),不會阻塞其他想要在行邊間隙插入行的會話。
    在插入前,會在間隙上加插入意向鎖。該鎖的意思是:當多個事務同時在某個間隙中插入時,只要不在間隙的同一個位置中插入行(唯一索引不同),就不需要互相等待。假設有4和7兩個索引記錄。當事務A、B分別嘗試插入索引值為5和6的兩條記錄時,都會在獲取被插入行的排他鎖前,用插入意向鎖鎖住4和7之間的間隙。但並不會互相阻塞,因為這兩行沒有衝突。
    當出現重複鍵值錯誤時,會在重複的索引記錄上加共享鎖。如果另一個會話已具有排他鎖,則如果有多個會話嘗試插入同一行,則此共享鎖的使用可能導致死鎖。如果另一個會話刪除該行,則會發生這種情況。(不理解的話看下面的例子)。假設一個 InnoDB 表t1具有以下結構:
CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;

現在假設三個會話按順序執行下列操作:
會話1:

START TRANSACTION;
INSERT INTO t1 VALUES(1);

會話2:

START TRANSACTION;
INSERT INTO t1 VALUES(1);

會話3:

START TRANSACTION;
INSERT INTO t1 VALUES(1);

會話1:

ROLLBACK;

會話1的第一個操作獲取了行的排他鎖。會話2和3的操作都會導致重複鍵錯誤,並且它們都會為該行請求共享鎖。當會話1回滾時,它釋放了行上的排他鎖,然後會授予會話2和3共享鎖。此時,會話2和3死鎖了:都不能獲取行的排他鎖,因為對方有共享鎖。

相似的情況,當表已經有了鍵值為1的行,三個會話按順序執行下列操作:
會話1:

START TRANSACTION;
DELETE FROM t1 WHERE i = 1;

會話2:

START TRANSACTION;
INSERT INTO t1 VALUES(1);

會話3:

START TRANSACTION;
INSERT INTO t1 VALUES(1);

會話1:

commit;
  • INSERT ... ON DUPLICATE KEY UPDATE與簡單的 INSERT 操作不同,當出現重複鍵錯誤時,會在行上加一個排他鎖而不是共享鎖。對於重複的主鍵,會加一個排他的索引記錄鎖。對於唯一索引,會加一個排他的 Next-Key Lock。
  • REPLACE 和 INSERT 操作類似,如果沒有在唯一索引上衝突的話。否則會在行上加一個排他的 Next-Key Lock。
  • INSERT INTO T SELECT ... FORM ... S WHERE ...會在被插入表T的每行上加排他的索引記錄鎖(沒有間隙鎖)。如果事務隔離級別是 READ COMMITTED,或者 innodb_locks_unsafe_for_binlog 被啟用,並且事務隔離級別不是 SERIALIZABLE,InnoDB 將把在S上執行的搜尋作為一致讀(無鎖)。否則會在S上設定共享的 Next-Key Lock。Innodb 在下面的情況下必須加鎖:在從備份中前滾恢復時,必須按照原來的方式執行每個SQL語句。
    CREATE TABLE ... SELECT ...使用共享的 Next-Key Lock 或作為一致讀取執行SELECT,如同INSERT ... SELECT
    當在REPLACE INTO t SELECT ... FROM s WHERE ...或在UPDATE t ... WHERE col IN (SELECT ... FROM s ...)使用 SELECT 時,Innodb 會在表s上設定共享的 Next-Key Lock。

  • 在初始化一個表的先前指定的 AUTO_INCREMENT 列時,InnoDB 在與 AUTO_INCREMENT 列關聯的索引的末尾設定排它鎖。在訪問自增計數器時,InnoDB 使用特定的 AUTO-INC 表鎖模式,其中鎖只持續到當前SQL語句的末尾,而不是整個事務的末尾。當有 AUTO-INC 鎖時,其他會話不能在表中插入。
    InnoDB獲取先前初始化的 AUTO_INCREMENT 列的值時不設定任何鎖。

  • 如果在表上定義了 FOREIGN KEY 約束,則需要檢查約束條件的任何插入,更新或刪除都會在它檢查約束的記錄上設定共享記錄鎖。在約束失敗的情況下,InnoDB 也會設定這些鎖。
  • LOCK TABLES設定表鎖,但它是 InnoDB 層之上的 MYSQL 層設定的。如果innodb_table_locks = 1(預設值)和autocommit = 0,InnoDB知道表鎖,InnoDB上面的MySQL層知道行級鎖。
    否則,InnoDB 的自動死鎖檢測無法檢測涉及這種表鎖的死鎖。另外,因為在這種情況下,較高的MySQL層不知道行級別鎖定,所以可以在當另一個會話具有行鎖的表上獲得表鎖。但是,這不會危及事務的完整性。

幻行

事務中的幻行問題:當相同的查詢在不同時間產生不同的行時。例如,如果SELECT執行了兩次,但是第二次返回的是第一次未返回的行,則該行是”幻行”。
假設在子表的 id 列上有一個索引,並且您希望讀取並鎖定識別符號值大於100的表中的所有行,以便稍後更新所選行中的某一列:

SELECT * FROM child WHERE id > 100 FOR UPDATE;

查詢從 ID 大於100的第一條記錄開始掃描索引。讓該表包含 id 值為 90 和 102 的行。如果在掃描範圍內的索引記錄上設定的鎖不鎖定間隙中的插入(在這種情況下,間隙在90和102之間),則另一個會話可以在表中插入一個新行,其ID為101。如果要在同一個事務中執行相同的 SELECT,則會看到一個新行–查詢返回的結果集中的ID為 101的行 (“幻行”​​)。如果我們將一組行視為一個數據項,那麼新的幻行將違反事務的隔離原則:事務處理期間它所讀取的資料不會改變。
為了防止幻行,InnoDB 使用一種名為 next-key locking 的演算法,將索引行鎖與間隙鎖相結合。 InnoDB 以這樣的方式執行行級鎖定,即當它搜尋或掃描表索引時,它會在遇到的索引記錄上設定共享鎖或排它鎖。因此,行級鎖實際上是索引記錄鎖。另外,索引記錄上的 next-key lock 也會影響該索引記錄之前的“間隙”。也就是說,next-key lock 是索引記錄鎖加上索引記錄之前的間隙上的間隙鎖。如果一個會話對索引中的記錄 R 具有共享或獨佔鎖定,則另一個會話不能在索引順序中的 R 之前的間隙中插入新的索引記錄。
當InnoDB掃描索引時,它也可以鎖定索引中最後一條記錄後的間隔。在前面的例子中就是這樣:為了防止任何插入到ID大於100的表中,InnoDB 會在id值102之後的間隙中加上鎖。
您可以使用 next-key lock 在應用程式中實現唯一性檢查:如果您以共享模式讀取資料,並且沒有看到要插入的行的重複項,那麼您可以安全地插入行並知道在讀取過程中設定在行的後繼上的 next-key lock 可以防止任何人同時插入相同的行。因此,next-key lock 使您能夠“鎖定”表中不存在的東西。 可以禁用間隙鎖定,如第14.5.1節“InnoDB 鎖定”中所述。這可能會導致幻行,因為當禁用間隙鎖