事務指的是訪問並可能更新資料庫中的各種資料項的乙個程式執行單元,在mysql中,通常被認為是必須原子執行的幾個sql語句,all or none,要不都成功,要不都失敗。作為併發控制的基本單位,事務是在併發中乙個繞不過的話題,mysql為實現一致性和併發的平衡,做了大量的工作。
原子性(atomicity):乙個事務是乙個不可分割的工作單位,事務中包括的操作要麼都做,要麼都不做。
一致性(consistency):事務必須是使資料庫從乙個一致性狀態變到另乙個一致性狀態。一致性與原子性是密切相關的。
隔離性(isolation):乙個事務的執行不能被其他事務干擾。即乙個事務內部的操作及使用的資料對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
永續性(durability):永續性也稱永久性(permanence),指乙個事務一旦提交,它對資料庫中資料的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
讀未提交(read-uncommitted):在乙個事務中,可以讀取到其他事務未提交的資料變化,這種讀取其他會話還沒提交的事務,叫做髒讀現象,在生產環境中切勿使用。
讀已提交(read-committed):在乙個事務中,可以讀取到其他事務已經提交的資料變化,這種讀取也就叫做不可重複讀,因為兩次同樣的查詢可能會得到不一樣的結果。
可重複讀(repetable-read):mysql預設隔離級別,在乙個事務中,直到事務結束前,都可以反覆讀取到事務剛開始時看到的資料,並一直不會發生變化,避免了髒讀、不可重複讀現象,但是它還是無法解決幻讀問題。
序列化(serializable):這是最高的隔離級別,它強制事務序列執行,避免了前面說的幻讀現象,簡單來說,它會在讀取的每一行資料上都加鎖,所以可能會導致大量的超時和鎖爭用問題。
隔離級別
資料一致性
髒讀不可重複讀
幻讀讀未提交
最低級別,只保證不讀取物理上損壞的資料有有
有讀已提交
語句級無有有
可重複讀
事務級無無有
序列化最高端別,事務級無無
無值得注意的一點是,由於並不是所有mysql的儲存引擎都支援事務,本文講的內容都是基於innodb儲存引擎,感興趣的話可以翻閱mysql關於innodb的官方文件,mvcc的全稱是multi-version concurrency control,即多版本併發控制,是一種樂觀鎖的實現方式。我們知道,在讀已提交隔離級別,乙個事務可能會讀到其他事務提交的資料,造成讀到的資料不一致。在記錄中增加了以下幾個字段:
db_trx_id:事務id,表示最後插入或者更新的事務,刪除也被視為更新,將行中的特殊位標誌位刪除。
db_roll_ptr:滾動指標,指向undo log 在回滾段中的位置,一旦行被更新了,undo log可以儲存更新前的資訊。
db_row_id:包含乙個行id,該id會隨著記錄的插入而單調增加,如果沒有主鍵,innodb會自動生成聚簇索引,索引包含該id,顯示指定了主鍵後,這個id不會出現在任何索引中。
mvcc對於crud的操作如下所示:
insert:新插入的資料,事務id為該事務的id。
update:修改事務id為該事務,標記為刪除,新插入一條記錄,並將事務id設定為該事務id。
delete:將事務id標記為該事務,標記為刪除,innodb只會在丟棄相應的undo log時才會物理刪除資料和索引資料,這個移除操作在innodb中叫著purge,這個動作非常迅速,通常跟sql的執行順序一致。
query:1.查詢未刪除,事務id小於該事務的記錄;
2.已刪除,事務id大於該事務的記錄。
通過這種機制,mysql就可以讀到被其他事務更新前的資料,就可以保證讀的一致性了。
我們在看這些內容的時候,有乙個重要的概念需要解釋一下,那就是undo log,mysql通過undo log和以上三個字段實現了mvcc,undo log 是乙個鍊錶,每次插入或修改都會講新紀錄插入undo log的頭部,而上面的db_roll_ptr就是指向了undo log在回滾段中的位置。undo log 有兩個重要的作用。
1.資料回滾,通過undo log記錄的歷史版本回滾資料;
2.可以保證事務讀到被修改之前的歷史資料,保證讀的一致性。
對於insert的新資料,在事務提交或丟棄後,該記錄的undo log就可以刪除了,但是對於update log,只要有其他事務仍然讀取該記錄,就不能被丟棄,因此如果不及時提交事務,哪怕是一致性讀,也會造成undo log的堆積。
以上介紹的情況都是針對聚簇索引的,對於二級索引,如果二級索引被刪除或更新,二級索引會產生兩個記錄,不能就地修改,不會修改undo log,而聚簇索引會就地修改,並且修改undo log,具體的細節可以自行了解,這裡不贅述。
關於幻讀是乙個經常被提起,也爭議頗多的問題,網上對幻讀有很多定義,在這裡,我們採用官網的定義:
所謂的幻讀就是在乙個事物裡,兩次執行相同的查詢語句,得到不同的查詢結果。例如,在第二次查詢的時候,會查到第一次查不到的行,這個行被稱為幻影行。
select * from child where id > 100 for update;
對於這個sql,假設child表中只有id為90和102的記錄,假設有另乙個事務插入了id為101的資料,因為沒有鎖定90到102的範圍,因此第二次查詢資料的時候就會查到id為101的資料,為什麼會這樣呢?
mysql的讀分為兩種:
1. consistent nonlocking reads:使用的方式為快照讀
2.locking reads:使用的方式為當前讀
對於 select ... for update,執行的是locking reads,會讀取當前最新的資料,mvcc中的快照資料無法起到作用,為了解決幻讀問題,mysql引入了next-key locking,為了了解next-key locking,我們要先了解mysql的鎖機制。
按照鎖的策略,可以分為悲觀鎖和樂觀鎖,mysql通過實現mvcc機制實現了樂觀鎖,按照鎖的粒度,mysql實現了表鎖和行級鎖,其中,表鎖是由mysql的server部分實現的,和儲存引擎無關,myisam也可以使用表鎖,但是表鎖的效能比較低,innodb實現了行級鎖。我們下邊要說的,就是innodb的鎖實現,可以通過show engine innodb status
檢視鎖。
1.共享鎖和排它鎖
共享鎖(shared lock/s):又稱讀鎖,可以允許事務讀一行記錄
排它鎖(exclusive lock/x):又稱寫鎖,允許事務對記錄進行修改或刪除
2.意向鎖
意向鎖是innodb支援的一種表級鎖,使得資料庫可以支援多種不同的鎖粒度,意向鎖有以下兩種:
意向共享鎖(intention shared lock/is):表示事務打算對各個行設定共享鎖。
意向排它鎖(intention exclusive lock/ix):表示事務打算對各個行設定排它鎖。
根據協議,事務要獲取某行的共享鎖之前,必須首先獲取該表中的is鎖或者更強的鎖(ix)。事務要獲取某行的排它鎖之前,必須先獲取該表中的ix鎖。相容矩陣如下,行和列分表代表不同事務,當另乙個事務持有不同鎖時,該事務是否可以獲得所需鎖。xix
sisx❌
❌❌❌ix
❌❌❌✅
s❌❌✅
✅is❌✅
✅✅3.記錄鎖
記錄鎖定始終鎖定索引記錄,即使沒有定義索引的表也是如此。 對於這種情況,innodb建立乙個隱藏的聚集索引並將該索引用於記錄鎖定。
4.間隙鎖
間隙鎖會對索引之間的記錄加鎖,此時,任何對間隙的修改都是被禁止的,間隙鎖是併發性和效能權衡的結果。。發生間隙鎖的情況如下(指的都是locking reads):
1.範圍查詢,如between,> , < 等,會對範圍內的間隙都加鎖;
2.where id = 1,如果id不是唯一索引或沒有索引都會加間隙鎖;
3.where id = 1 and age = 12,即使id和age都是唯一索引也會加間隙鎖。
對於這種等於的情況,例如,child表的id分別為 1, 3,12,20,如過查詢id = 6,會對(3,12)這個範圍加鎖。
5.next-key locks
next-key locks是記錄鎖和間隙鎖的結合,
(負無窮大,為10]
(10,11]
(11、13]
(13,20]
(20,正無窮大)
講完鎖的概念,我們回來再看這條語句:
select * from child where id > 100 for update;
在第乙個事務執行該語句的時候,會鎖定[90, 102]這個間隙,其他事物無法插入資料到這個範圍,這樣就保證了讀取的一致性,解決了幻讀問題。
參考文件:
Mysql事務與鎖深入剖析
1.事務知識準備 1.檢視資料庫版本 select version 2.檢視資料庫引擎 select variables like engine 3.檢視資料事務隔離級別 select global variables like tx isolation 2.如何開啟事務及事務四大特性 1.手動開啟...
深入學習mysql 一
相信很多人都說過一句玩笑話,刪庫跑路哈哈哈 說到刪庫跑路,那麼你是否了解過,如何給mysql設定許可權來防止這樣的事情發生呢 如果你使用的是root許可權的賬號,你需要給不同的賬號來分配許可權時,可以使用如下的sql語句來建立對應的賬號和許可權 grant select on practice.to...
Mysql查詢 深入學習
1 開篇 在學習的過程中我們一起進步,成長。有什麼寫的不對的還望可以指出。2 查詢 1 多表之間的查詢 笛卡爾積 100 100 10000 查詢的資料量可能非常大 給表名取別名 2 關聯查詢 隱示內連線查詢。sql92 內連線查詢。sql99 left join on 如果我寫 left join...