MySQL學(xué)習(xí)之事務(wù)與并發(fā)控制
事務(wù)
概念
一個事務(wù)可以理解為一組操作,這一組操作要么全部執(zhí)行,要么全部不執(zhí)行。
特性
原子性
一個事務(wù)是一個獨(dú)立的原子單元,一個事務(wù)內(nèi)所有的操作,要么全部執(zhí)行,要么全部不執(zhí)行。關(guān)注的是一組操作的執(zhí)行結(jié)果(全部成功or全部失?。?。是通過undo log實(shí)現(xiàn)的。
一致性
看了網(wǎng)上很多博客對一致性的講解,總覺得沒有說到點(diǎn)子上,我就從我的個人角度來說說對一致性的理解:
一個事務(wù)使得數(shù)據(jù)庫從一個狀態(tài)A0,轉(zhuǎn)換到另一個狀態(tài)A1。相鄰的兩個狀態(tài)轉(zhuǎn)換之間,只能有一個事務(wù)起作用。如果當(dāng)前狀態(tài)A0轉(zhuǎn)換到下一個狀態(tài)A1,之間包括了2個事務(wù),則說明有一個事務(wù)的effect被覆蓋了。如事務(wù)并發(fā)問題中的丟失更新,典型的例子是轉(zhuǎn)賬問題,A向B轉(zhuǎn)賬,同時C也向B轉(zhuǎn)賬,最后會發(fā)現(xiàn)A或者C轉(zhuǎn)過去的錢不見了,這種例子隨便一搜就有,不贅述。用轉(zhuǎn)賬問題來解釋一致性,比較好理解,因?yàn)殄X的總數(shù)應(yīng)該是確定的,比如B本來有1000,A向B轉(zhuǎn)100,C向B轉(zhuǎn)200,那么B賬戶上最后應(yīng)該有1300,然而最終我們可能看到是1100或1200,因?yàn)橛幸粋€人的操作被覆蓋掉了。其實(shí)這也就是說,B賬戶的錢,從一個狀態(tài),到另一個狀態(tài),中間有2個事務(wù)起了effect,這樣是不對的。事務(wù)具有隔離性,數(shù)據(jù)庫的每一個狀態(tài),應(yīng)該都有且僅有一個與之對應(yīng)的事務(wù)在take effect。
隔離性
不同的事務(wù)之間,應(yīng)該是不能互相影響的。
4種隔離級別
- Read Uncommit
- Read Commit
- Repetable Read
- Serializable
RU相當(dāng)于沒有隔離,RC和RR是用MVCC實(shí)現(xiàn)(具體是通過undo log 和 read view),Serializable是用鎖實(shí)現(xiàn),事務(wù)串行執(zhí)行。
查看當(dāng)前的隔離級別
select @@tx_isolation; -- mysql 8的變量名變?yōu)槿缦碌男问? select @@transaction_isolation; -- 設(shè)置隔離級別 set transacation_isolation = '隔離級別名'
數(shù)據(jù)庫隔離級別是一種需求,不同的隔離級別有對應(yīng)的實(shí)現(xiàn)方式。
持久性
事務(wù)執(zhí)行成功后(提交后),對數(shù)據(jù)庫的更改是永久性的(即寫到磁盤)。是通過redo log + Force Log at Commit機(jī)制實(shí)現(xiàn)的
事務(wù)并發(fā)問題
丟失更新
第一類丟失更新
針對同一行數(shù)據(jù),事務(wù)A先開始,事務(wù)B后開始,事務(wù)B提交,隨后事務(wù)A回滾,則回滾會導(dǎo)致將事務(wù)B已提交的修改給覆蓋掉。這個問題在現(xiàn)在的數(shù)據(jù)庫軟件中已不會產(chǎn)生
第二類丟失更新
針對同一行數(shù)據(jù),事務(wù)A先開始,事務(wù)B后開始,事務(wù)A提交,隨后事務(wù)B提交,則事務(wù)B將事務(wù)A的修改給覆蓋掉了
臟讀
事務(wù)A讀到了事務(wù)B未提交的數(shù)據(jù),事務(wù)B后回滾,則事務(wù)A讀到的是臟數(shù)據(jù)
不可重復(fù)讀
事務(wù)A連續(xù)2次讀一行記錄,讀取到的是不一樣的。這是由于A連續(xù)2次讀的中間,事務(wù)B對這行記錄做了更新。
幻讀
表現(xiàn)為兩次讀取的數(shù)據(jù)數(shù)量不一致,發(fā)現(xiàn)變多了,或者變少了。
比如我讀取age > 10 的學(xué)生數(shù)據(jù),第一次讀發(fā)現(xiàn)有10個學(xué)生,第二次讀發(fā)現(xiàn)有20個學(xué)生,就好像出現(xiàn)了幻覺一樣,同樣的查詢條件,兩次讀取發(fā)現(xiàn)有學(xué)生增加或減少。
事務(wù)命令
MySQL命令行下默認(rèn)是autocommit的,即事務(wù)會自動提交。要顯示開啟一個事務(wù),需使用命令
BEGIN
或START TRANSACTION
BEGIN
或START TRANSACTION
開啟事務(wù)COMMIT
提交事務(wù)ROLLBACK
回滾事務(wù)
并發(fā)控制
兩種并發(fā)控制策略
MVCC
Multi-Version Concurrency Control
核心理念是快照,InnoDB主要通過undo log 和 read view來實(shí)現(xiàn)MVCC。
**讀不加鎖,讀寫不互斥。**讀會從多個版本的數(shù)據(jù)中挑選一個合適的版本返回。寫操作會產(chǎn)生一個新的版本。
每一行的記錄,會包含3個隱藏字段:row_id,tx_id,roll_ptr
其中tx_id表示最近操作該行記錄的事務(wù)id,roll_ptr則是回滾指針,指向一條undo log記錄,即指向該次改動之前的數(shù)據(jù)
undo log
insert undo log
由insert操作產(chǎn)生,可在事務(wù)提交后直接刪除。因?yàn)閕nsert操作只對當(dāng)前事務(wù)本身可見,其他事務(wù)不可見
update undo log
由update/delete產(chǎn)生。是對已有記錄的修改,為了提供MVCC機(jī)制,該undo log不能在事務(wù)提交后就刪除,而需要等待purge線程來進(jìn)行最后的刪除
使用update修改當(dāng)前行時,首先用X鎖鎖定,然后將該行當(dāng)前值復(fù)制到undo log,然后再執(zhí)行修改,最后填寫事務(wù)id,并使回滾指針指向undo log中修改前的行
read view
用于判斷數(shù)據(jù)可見性的一個數(shù)據(jù)結(jié)構(gòu),里面存儲了
- 當(dāng)前活躍事務(wù)的最小id:min_id
- 當(dāng)前活躍事務(wù)的最大id:max_id
- 當(dāng)前活躍事務(wù)id list:ids
若讀取到的某一行的某個版本tx_id < min_id,則說明此行的該版本在本次事務(wù)開啟之前就已經(jīng)提交,故這個數(shù)據(jù)對本次事務(wù)可見。
若讀取到的某一行的某個版本tx_id >= max_id,說明此行的該版本在本次事務(wù)開啟之后才開始進(jìn)行修改,故這個數(shù)據(jù)對本次事務(wù)不可見。
若讀取到的某一行的某個版本tx_id在min_id和max_id之間,則判斷此行的tx_id是否在ids內(nèi),若是,表明此行的事務(wù)還在活躍中,此行數(shù)據(jù)不可見,否則,說明此行的事務(wù)已經(jīng)提交,此行數(shù)據(jù)可見
簡單來說,若在某一時刻開啟了一個事務(wù)A,則會記錄下事務(wù)A開啟時,還活躍著的其他事務(wù)(記下這些活躍事務(wù)的id,保存為一個set,比如叫ids),這些事務(wù)按照開始的時間先后,會有從小到大的事務(wù)id(tx_id),tx_id小的事務(wù),說明是先開啟的,tx_id大的事務(wù),說明是后開啟的。若在事務(wù)A中,讀取到某一行數(shù)據(jù),這一行數(shù)據(jù)的tx_id小于ids中最小的id(min_id),說明這一行數(shù)據(jù)對應(yīng)的事務(wù),已提交過了(已不活躍了),這一行數(shù)據(jù)的修改已經(jīng)持久化,故該行數(shù)據(jù)對事務(wù)A來說是可見的。若這一行數(shù)據(jù)的tx_id大于或等于ids中的最大id,說明有一個事務(wù),在事務(wù)A開始之后,才開始對這一行數(shù)據(jù)進(jìn)行修改,故該行數(shù)據(jù)對事務(wù)A不可見。若這一行數(shù)據(jù)的tx_id,在min_id和max_id之間,那么就判斷這個tx_id是不是在ids中,即對這行數(shù)據(jù)進(jìn)行修改的那個事務(wù),還在不在活躍的事務(wù)列表中,若在,說明修改這行數(shù)據(jù)的事務(wù)還沒提交,這行數(shù)據(jù)還沒持久化,故不可見,反之,說明這行數(shù)據(jù)的修改已經(jīng)持久化,故可見。
RC隔離級別下,在一個事務(wù)中,每次讀取數(shù)據(jù)都會新建一個ReadView。所以可能會產(chǎn)生不可重復(fù)讀的問題,因?yàn)樵趦纱巫x之間,有其他事務(wù)對數(shù)據(jù)進(jìn)行了修改,而兩次讀時都新建了ReadView,故第二次讀的時候,修改后的數(shù)據(jù)是可見的。
RR隔離級別下,在一個事務(wù)中,第一次讀取時會新建一個ReadView,后序讀取都使用這個ReadView。所以哪怕在兩次讀之間,有其他事務(wù)修改了數(shù)據(jù),也不會產(chǎn)生不可重復(fù)讀的問題。因?yàn)榈诙巫x,并沒有新建ReadView,而是使用了一開始創(chuàng)建的那個ReadView,所以數(shù)據(jù)可見性和第一次是一樣的。
MVCC中,讀操作分為兩類:快照讀,當(dāng)前讀
- 快照讀(一致性非鎖定讀)
讀取的時記錄的可見版本(可能是歷史版本),不加鎖。
當(dāng)某一行被一個事務(wù)A加了X鎖時,另一個事務(wù)B仍然可以讀取該行,只不過讀取的是歷史版本。
-- 簡單select SELECT * FROM product;
- 當(dāng)前讀
讀取的是記錄的最新版本,當(dāng)前讀返回的記錄,會加鎖,保證了其他并發(fā)事務(wù)不能修改當(dāng)前記錄
SELECT * FROM product lock in share mode; SELECT * FROM product for update; insert .... update .... delete ....
LBCC
LCC
Lock-Based Concurrency Control
讀加讀鎖,寫加寫鎖。讀讀不互斥,讀寫,寫寫互斥。Serilizable的隔離級別是通過LBCC實(shí)現(xiàn)的
以上就是MySQL學(xué)習(xí)之事務(wù)與并發(fā)控制的詳細(xì)內(nèi)容,更多關(guān)于MySQL事務(wù)與并發(fā)控制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mysql load data infile 的用法(40w數(shù)據(jù) 用了3-5秒導(dǎo)進(jìn)mysql)
測試數(shù)據(jù)的時候,生成txt文件應(yīng)該快點(diǎn),再用這種方式導(dǎo)入到mysql 速度上快點(diǎn)。40w數(shù)據(jù) 用了3-5秒導(dǎo)進(jìn)mysql,牛逼毀了2013-01-01mysql5.7.19 winx64安裝配置方法圖文教程(win10)
這篇文章主要為大家詳細(xì)介紹了mysql5.7.19 winx64安裝配置教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07Mysql查看數(shù)據(jù)庫時區(qū)并設(shè)置時區(qū)的方法
這篇文章主要介紹了Mysql查看數(shù)據(jù)庫時區(qū)并設(shè)置時區(qū)的方法,設(shè)置時區(qū)的方式可以通過mysql命令行模式下動態(tài)修改以及通過修改配置文件來修改時區(qū),需要的朋友可以參考下2024-02-02mysql內(nèi)連接,連續(xù)兩次使用同一張表,自連接方式
這篇文章主要介紹了mysql內(nèi)連接,連續(xù)兩次使用同一張表,自連接方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12解決MySQL報錯Error 3948 (42000): Loading loc
在執(zhí)行MySQL項(xiàng)目過程中意外出現(xiàn)的報錯,之前也沒有遇到過,報錯信息如下,Error 3948 (42000): Loading local data is disabled; this must be enabled on both the client an,本文小編就給大家介紹一下解決報錯的方法,需要的朋友可以參考下2023-09-09Mysql中文漢字轉(zhuǎn)拼音的實(shí)現(xiàn)(每個漢字轉(zhuǎn)換全拼)
這篇文章主要介紹了Mysql中文漢字轉(zhuǎn)拼音的實(shí)現(xiàn),并且每個漢字會轉(zhuǎn)換全拼,使用Mysql自定義函數(shù)實(shí)現(xiàn),需要的朋友可以參考下2014-06-06