如今的互聯網,開發一個大型的多人APP,你一定離不開資料庫。而如何確保所有人能夠高並發的進行讀寫一直是一個高難度的架構問題,先刨去高並發,保證一致性讀寫這個問題最常用的手段是事務,而實現一個事務的關鍵點在於鎖機制。
今天我們就來介紹下InnoDB儲存引擎如何在高並發下實作鎖定機制來滿足一致性讀寫的原理與實作。
鎖定
資料庫的鎖定機制是區別於檔案系統的關鍵特性。用於管理對共享資源的並發存取。 InnoDB會在許多地方使用鎖定機制,例如操作緩衝池中的資料表、LRU頁列表、資料行,為了確保一致性和完整性,需要有鎖定的機制。
對於不同資料庫,鎖定機制的設計和實作完全不同:
● MyISAM引擎: 表鎖定設計,並發讀取沒有問題,並發寫效能差。
● Microsoft SQL Server: 支援樂觀並發和悲觀並發,樂觀並發下支援行級鎖,維持鎖的開銷大,在行鎖數量超過閾值後會升級為表鎖。
● InnoDB引擎: 支援行鎖,提供一致性的非鎖定讀取。行鎖沒有額外開銷,效能不會下降。
● Oracle:和InnoDB引擎非常類似。
兩類鎖定:lock和latch
#資料庫中lock和latch都可以稱為鎖,但有很大的差別。
latch一般稱為閂鎖,用於保證並發執行緒操作臨界資源的正確性,作用物件是記憶體資料結構,要求鎖定時間非常短,不會偵測死鎖。在InnoDB引擎中又分為mutex(互斥量)和rwlock(讀寫鎖定)。
lock是用來鎖定資料庫中的對象,如表、頁、行,作用對像是事務,在commit/rollback後釋放,會偵測死鎖。分為行鎖、表鎖、意向鎖。
我們下面的鎖指的都是lock類別鎖定。
四種鎖定類型
InnoDB支援四個鎖定:
● 共享鎖定(S Lock):允許事務讀一行資料
● 排他鎖(X Lock):允許交易刪除或更新一行資料
● 意向共享鎖(Intention S Lock):事務想要取得一張表中某幾行的共享鎖
● 意向排他鎖(Intention X Lock):事務想要獲得一張表中某幾行的排他鎖
當事務T1獲取了行r的共享鎖,由於讀取不會改變行數據,因此事務T2也可以直接獲得行r的共享鎖,此時稱為鎖相容(Lock Compatible)。
而當事務T3想要取得行r的排他鎖進行修改資料時,就需要等待T1/T2釋放行共享鎖定,此時稱為鎖不相容。
S鎖和X鎖都是行鎖,而IS鎖和IX鎖都為意向鎖,屬於表鎖。意向鎖的設計是為了在一個事務中揭示下一行將被要求的鎖類型,即在表鎖的更細粒度進行鎖定。由於InnoDB支援表鎖,因此意向鎖不會阻塞除全表掃描外的任何請求。
鎖定的相容性:
#IS | IX | S | X | |
IS | 相容 | #相容 | 相容 | 不相容 |
IX | 相容 | ##不相容 | 不相容 | |
#S | 相容 | |||
##不相容 | ##X | #不相容 | 不相容 |
不相容
存儲事務和鎖定資訊的三張表
show engine innodb status
指令在交易部分查看目前鎖定請求的資訊。三個表格的定義分別為:
| |
---|---|
INNODB_TRX | |
InnoDB儲存引擎內部唯一的交易ID | |
目前交易的狀態 | |
事務的開始時間 | |
等待交易的鎖定IDC,當狀態不為LOCK WAIT時為NULL | |
交易等待開始的時間 | |
交易的權重,反映一個交易修改和鎖定的行數。當需要回滾時,選擇該值最小的交易進行回滾 | |
MySQL的執行緒ID,show processlist顯示的結果 |
INNODB_LOCKS | |
---|---|
#lock_id | ##鎖定ID|
交易ID | |
鎖定的模式 | |
鎖定的類型,表格鎖定或行鎖定 | |
#要加鎖的表格 | |
鎖定的索引 | |
已鎖定物件的space id | |
交易鎖定頁的數量,表鎖定時為NULL | |
#交易鎖定行的數量,表鎖定時為NULL | |
交易鎖定記錄的主鍵值,表鎖定時為NULL |
INNODB_LOCK_WAITS
| |
---|---|
申請鎖定資源的交易ID | |
##申請的鎖的ID | |
阻塞的事務ID | |
#阻塞的鎖定的ID |
我們可以看到所有的事務,以及事務是否被阻塞,阻塞的鎖定ID是什麼。 之後,透過
INNODB_LOCKS
查看所有的鎖定資訊。 之後,透過
INNODB_LOCK_WAITS
可以查看到鎖的等待資訊以及阻塞關係。 透過這三種表格能夠較為清晰的查看事務和鎖的情況,也可以聯合查詢,在下面的一些場景下我們會來展示這三個表的內容。
隔離等級首先我們來說下資料庫的四個交易隔離等級:
● READ UNCOMMITTED(0 ): 瀏覽存取級別,存在髒讀、不可重複讀取、幻讀
● READ COMMITTED(1): 遊標穩定級別,存在不可重複度、幻讀
# ● REPEATABLE READ( 2): 存在幻讀
● SERIALIZABLE(3): 隔離級別,保證事務安全,但完全串行,性能低
這四種事務隔離級別是指定的SQL標準, InnoDB預設的隔離等級是REAPEATABLE READ,但當與其他資料庫不同的時,它同時使用了Next-Key-Lock鎖定的演算法,能夠避免幻讀的產生,因此能夠完全滿足事務的隔離性要求,即達到SERIALIZABLE隔離等級。
隔離等級越低,交易要求的鎖定越少或持鎖時間越短,因此大部分資料庫的預設隔離等級為READ COMMITED。但有相關的分析也指出,隔離等級的效能開銷幾乎一樣,因此使用者無須透過調整隔離等級來提高效能。
查看和修改事務隔離級別的命令:
mysql> select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | REPEATABLE-READ | +------------------------+ 1 row in set (0.00 sec) mysql> set session transaction isolation level SERIALIZABLE; Query OK, 0 rows affected (0.00 sec)
範例中修改了本次會話的事務隔離級別,如果需要修改全域參數,可以替換session為global。如果想要永久修改,需要修改設定檔:
[mysqld] transaction-isolation = READ-COMMITED
在SERIALIZABLE的交易隔離級別,InnoDB會對每個SELECT語句後自動加上LOCK IN SHARE MODE,來對讀取操作加上一個共享鎖,因此不再支援一致性的非鎖定讀取。
由於InnoDB在REPEATABLE READ隔離等級就可以達到SERIALIZABLE,因此一般不用使用最高隔離等級。
一致性非鎖定讀和多版本並發控制#一致性非鎖定讀(consistent nonlocking read)是指InnoDB通過行多版本控制(Multi Version Concurrency Control, MVCC)的方法來讀取目前執行時間資料庫中行的資料。
也就是如果讀取的行正在執行變更操作,這時讀取不會等待行鎖的釋放,而是會讀取行的一個快照資料。快照是指該行的一個歷史數據,透過undo操作來完成。這種方式極大地提高了資料庫的並發性,這也是InnoDB的預設值。
快照是當前行的一個歷史版本,但可能存在多個版本,行數據存在多個快照數據,這種技術成為行多版本技術,由此帶來的並發控制,稱為多版本並發控制(MVCC)。 InnoDB在READ COMMITED 和REPEATABLE READ隔離等級時,會使用非鎖定的一致性讀取,但是在這兩種隔離等級使用的快找資料定義卻不同:
● READ COMMITED: 總是讀取最新一份快照
● REPEATABLE READ: 總是讀取事務開始時的行資料版本
我們執行一個範例:
|
||
---|---|---|
會話A | 會話B | |
BEGIN | ||
select * from z where a = 3; | ||
#BEGIN |
||
update z set b=2 where a=3; |
||
select * from z where a = 3 ; | ||
COMMIT; |
||
##COMMIT; | ||
select * from z where a = 3; |
在这个例子中我们可以清晰的看到0、1、2三种隔离级别的区别:
#在事务开始前我们可以分别调整为0、1、2三种隔离级别,来查看不同的输出 mysql> set session transaction isolation level READ UNCOMMITTED; Query OK, 0 rows affected (0.00 sec) mysql> select @@tx_isolation; +------------------+ | @@tx_isolation | +------------------+ | READ-UNCOMMITTED | +------------------+ 1 row in set (0.00 sec) # A会话:T1事务 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from z where a = 3; +---+------+ | a | b | +---+------+ | 3 | 1 | +---+------+ 1 row in set (0.00 sec) # B会话:T2事务 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> update z set b=2 where a=3; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 # A会话:T1事务,如果此时隔离级别是READ-UNCOMMITTED,因为此刻事务2可能会回滚,所以出现了脏读 mysql> select * from z where a=3; +---+------+ | a | b | +---+------+ | 3 | 2 | +---+------+ 1 row in set (0.00 sec) # A会话:T1事务,如果此时隔离级别是大于READ-UNCOMMITTED的更高级别 mysql> select * from z where a=3; +---+------+ | a | b | +---+------+ | 3 | 1 | +---+------+ 1 row in set (0.00 sec) # B会话:T2事务 mysql> commit; Query OK, 0 rows affected (0.00 sec) # A会话:T1事务,如果此时隔离级别是READ-COMMITTED,因为数据和事务开始时读取的出现了不一致,因此称为不可重复读,能够读到其他事务的结果,违反了事务的隔离性 mysql> select * from z where a=3; +---+------+ | a | b | +---+------+ | 3 | 2 | +---+------+ 1 row in set (0.00 sec) # A会话:T1事务,如果此时隔离级别是大于READ-COMMITTED的更高级别 mysql> select * from z where a=3; +---+------+ | a | b | +---+------+ | 3 | 1 | +---+------+ 1 row in set (0.00 sec) # A会话:T1事务 mysql> commit; Query OK, 0 rows affected (0.00 sec)
一致性锁定读和SERIALIZABLE隔离
在默认的REPEATABLE READ隔离级别时,InnoDB使用的是一致性非锁定读。但有时我们也需要显示的指定使用一致性锁定读来保证读取操作时对数据进行加锁达到一致性。这要求数据库支持锁定读加锁语句:
● select ... for update: 读取时对行记录加X锁
● select ... lock in share mode:读取时对行记录加一个S锁
这两种锁必须在一个事务中,当事务提交后锁也就释放了,因此务必加上BEGIN, START TRANSACTION或者SET AUTOCOMMIT=0。
我们在前面隔离级别时也说过SERIALIZABLE隔离级别会对读操作自动加上LOCK IN SHARE MODE指令来加上一个共享锁,因此不再支持一致性的非锁定读。这也是隔离级别3的一大特性。
总结
由于锁的概念非常重要,这里先讲了锁的概念、锁的类型、锁的信息查看、事务的隔离级别和区别,后面我们会继续说锁的算法、锁的三种问题和幻读、死锁和锁升级。
推荐学习:MySQL教程
以上是MySQL之鎖和事務隔離等級(介紹)的詳細內容。更多資訊請關注PHP中文網其他相關文章!