數(shù)據(jù)庫(kù)事務(wù)是指作為單個(gè)邏輯工作單元執(zhí)行的一系列操作窖张,要么完全執(zhí)行诈胜,要么完全地不執(zhí)行豹障。
事務(wù)必須具備ACID四個(gè)特性:
原子性是指事務(wù)包含的所有操作要么全部成功,要么全部失敗回滾焦匈。
一致性是指事務(wù)必須使數(shù)據(jù)庫(kù)從一個(gè)一致的狀態(tài)變到另外一個(gè)一致的狀態(tài)血公,也就是執(zhí)行事務(wù)之前和之后的狀態(tài)都必須處于一致的狀態(tài)。
隔離性是指當(dāng)多個(gè)用戶并發(fā)訪問數(shù)據(jù)庫(kù)時(shí)缓熟,比如操作同一張表時(shí)累魔,數(shù)據(jù)庫(kù)為每一個(gè)用戶開啟的事務(wù)摔笤,不能被其他事務(wù)的操作所干擾,多個(gè)并發(fā)事務(wù)之間要相互隔離垦写。
持久性是指一個(gè)事務(wù)一旦被提交了吕世,那么對(duì)于數(shù)據(jù)庫(kù)中的數(shù)據(jù)改變就是永久性的,即便是在數(shù)據(jù)庫(kù)系統(tǒng)遭遇到故障的情況下也不會(huì)丟失提交事務(wù)的操作梯投。
提交事務(wù):
>set autocommit = 0 禁止自動(dòng)提交
>start transaction;
>update accout set money=money+100 where name="Jason";
>commit;
回滾事務(wù):
>set autocommit = 0 禁止自動(dòng)提交
>start transaction;
>update account set money=money-100 where name="justin";
>rollback;
1命辖、事務(wù)的隔離級(jí)別
mysql修改事務(wù)的隔離級(jí)別
>set? [global | session]? transaction isolation level 隔離級(jí)別名稱;
>set tx_isolation=’隔離級(jí)別名稱;’
隔離級(jí)別:Serializable | Repeatable read | Read committed |Read uncommitted
設(shè)置默認(rèn)級(jí)別是指當(dāng)前session的下一個(gè)事務(wù)
設(shè)置session級(jí)別是指當(dāng)前session以后的所有事務(wù)
設(shè)置global級(jí)別是指對(duì)之后的所有session,不包括當(dāng)前session
四種隔離級(jí)別和可能產(chǎn)生的問題
不可重復(fù)讀:Read committed讀已提交事務(wù)的數(shù)據(jù)分蓖,可能是兩次查詢過程中間插入了一個(gè)事務(wù)更新的原有的數(shù)據(jù)尔艇,導(dǎo)致出現(xiàn)同一事務(wù)的不同實(shí)例先后讀取的內(nèi)容不同。在 Repeatable read通過數(shù)據(jù)庫(kù)事務(wù)版本號(hào)方式解決么鹤。
事務(wù)1:
mysql> begin;
Query OK, 0 rows affected
mysql> select * from student where stu_id = 2;
+----+------+--------+
| id | name | stu_id |
+----+------+--------+
|? 2 | 生物 |????? 2 |
+----+------+--------+
1 row in set
事務(wù)2:
mysql> begin;
Query OK, 0 rows affected
mysql> update student set name = '地理' where stu_id = 2;
Query OK, 1 row affected
Rows matched: 1? Changed: 1? Warnings: 0
mysql> commit;
Query OK, 0 rows affected
接下來事務(wù)1再次查詢:
mysql> select * from student where stu_id = 2;
+----+------+--------+
| id | name | stu_id |
+----+------+--------+
|? 2 | 地理 |????? 2 |
+----+------+--------+
1 row in set
上述過程可見终娃,帶事務(wù)1的一個(gè)事務(wù)中,兩次請(qǐng)求得到了不同的結(jié)果蒸甜,就導(dǎo)致了不可重復(fù)讀的現(xiàn)象棠耕。
幻讀:在一個(gè)事務(wù)的兩次查詢中數(shù)據(jù)筆數(shù)不一致,例如有一個(gè)事務(wù)查詢了幾列(Row)數(shù)據(jù)迅皇,而另一個(gè)事務(wù)卻在此時(shí)插入了新的幾列數(shù)據(jù)昧辽,先前的事務(wù)在接下來的查詢中,就會(huì)發(fā)現(xiàn)有幾列數(shù)據(jù)是它先前所沒有的登颓。
數(shù)據(jù)庫(kù)默認(rèn)隔離級(jí)別是 Repeatable read搅荞。
2、鎖與事務(wù)
數(shù)據(jù)庫(kù)采用兩段鎖協(xié)議(Two-Phase Locking――2PL)
在對(duì)任何數(shù)據(jù)進(jìn)行讀框咙、寫操作之前咕痛,首先要申請(qǐng)并獲得對(duì)該數(shù)據(jù)的封鎖,即擴(kuò)展階段喇嘱。
在釋放一個(gè)封鎖之后茉贡,事務(wù)不再申請(qǐng)和獲得其它任何封鎖,即收縮階段者铜。
若所有事務(wù)均遵守兩段鎖協(xié)議腔丧,則這些事務(wù)的所有交叉調(diào)度都是可串行化的。
mysql的讀操作作烟,通常是不會(huì)加鎖的(和隔離機(jī)制有關(guān))愉粤,也就是說通常的讀操作是不加鎖的,而是通過mvcc去解決的拿撩,對(duì)于通常的寫請(qǐng)求衣厘,insert、update、delete通常會(huì)加行鎖影暴、間隙鎖或表鎖(這和索引是有關(guān)系的)错邦,這些鎖通常是排他的,會(huì)阻塞其他的事務(wù)寫事務(wù)型宙。具體的情況需要結(jié)合隔離機(jī)制撬呢。
(1)、鎖的類型:
共享鎖(S Lock)和排他鎖(X Lock)
意向共享鎖(IS Lock):事務(wù)想要獲取一張表中的某幾行共享鎖早歇。
意向排他鎖(IX Lock):事務(wù)想要獲取一張表中的某幾行的排它鎖倾芝。
表鎖:
對(duì)一整張表加鎖,并發(fā)能力低下(即使有分讀鎖箭跳、寫鎖)晨另,一般在DDL處理時(shí)使用。
行鎖:
只鎖住特定行的數(shù)據(jù)谱姓,并發(fā)能力強(qiáng)借尿,MySQL一般都是用行鎖來處理并發(fā)事務(wù)。
在更新記錄的時(shí)候會(huì)對(duì)此記錄加行鎖屉来,在事務(wù)沒有commit之前不會(huì)釋放鎖路翻,所以事務(wù)2的更新會(huì)阻塞等待事務(wù)1的排它鎖,當(dāng)事務(wù)1Commit后茄靠,行鎖釋放事務(wù)2獲得行鎖,更新成功掉冶。
Gap鎖(間隙鎖):
是MySQL使用索引對(duì)行鎖兩邊的區(qū)間進(jìn)行加鎖战秋,避免其他事務(wù)在這兩個(gè)區(qū)間insert的一種鎖癣蟋。
如圖所示:數(shù)據(jù)庫(kù)中存在值5,30尝哆。那么數(shù)據(jù)庫(kù)會(huì)將數(shù)據(jù)段切分以下幾個(gè)區(qū)間:
(negative infinity, 5],(5,30],(30,positive infinity)
select * from table where number=30 for update琐馆;
當(dāng)對(duì)值為30這一行加行鎖的時(shí)候歧胁,會(huì)同時(shí)對(duì)(5,30]和(30,positive infinity)加GAP鎖。這樣其他事務(wù)如果想在這兩個(gè)區(qū)間進(jìn)行insert操作的時(shí)候,需要等待本次事務(wù)完成。如果對(duì)不存在的數(shù)據(jù)進(jìn)行更新,比如更新20(不存在)對(duì)應(yīng)數(shù)據(jù)行,那么數(shù)據(jù)庫(kù)也會(huì)對(duì)其存在的區(qū)間(5,30]加GAP鎖。
Next-Key鎖:
Next-Key鎖是行鎖和GAP鎖的合并(MySQL使用它來避免幻讀)。
當(dāng)按照id(非唯一索引,不是主鍵)進(jìn)行更新或刪除的時(shí)候會(huì)先對(duì)id索引進(jìn)行next_key鎖瓦糟,防止幻讀,因?yàn)樾略黾拥挠涗浿荒茉?0的左邊和30的右邊或者就是30陆淀。那么鎖住范圍后就能保證防止“幻讀”。
注意:如果用到無索引的字段衅胀,那么MySQL會(huì)在存儲(chǔ)引擎層面將所有的記錄加鎖,然后由MySQL Server過濾哀九,如果不滿足會(huì)調(diào)用unlock_row把不滿足條件的記錄釋放鎖(這里違背了二段鎖協(xié)議)剿配。
(2)、Mvcc(多版本并發(fā)控制):
Mvcc是多版本的并發(fā)控制協(xié)議阅束,Innodb中的樂觀鎖實(shí)現(xiàn)呼胚。讀不加鎖,讀寫不沖突息裸。通過它提高M(jìn)ySQL的讀取操作的性能蝇更。并能解決MySQL的重復(fù)讀問題。
MVVC在每一行記錄的后面加兩個(gè)隱含列(記錄創(chuàng)建版本號(hào)和刪除版本號(hào))呼盆。這里的版本號(hào)指的是事務(wù)的版本號(hào)(每個(gè)事務(wù)啟動(dòng)的時(shí)候年扩,都有一個(gè)遞增的版本號(hào))。在更新時(shí)進(jìn)行版本號(hào)的遞增访圃,插入時(shí)新建一個(gè)版本號(hào)厨幻,同時(shí)舊版本數(shù)據(jù)存儲(chǔ)在undo日志中。
? ● 比如插入一條記錄(事務(wù)id為1)
? ● 如果把這條記錄name更新為dog
在更新操作的時(shí)候腿时,采用的是先標(biāo)記舊的那行記錄為已刪除况脆,并且刪除版本號(hào)是事務(wù)版本號(hào),然后插入一行新的記錄的方式批糟。
? ● 刪除這條記錄時(shí)
刪除操作的時(shí)候格了,就把事務(wù)版本號(hào)作為刪除版本號(hào)
執(zhí)行查詢操作需要符合如下規(guī)則才能被查出來
1) 刪除版本號(hào) 大于 當(dāng)前事務(wù)版本號(hào),就是說刪除操作是在當(dāng)前事務(wù)啟動(dòng)之后做的徽鼎。
2) 創(chuàng)建版本號(hào) 小于或者等于 當(dāng)前事務(wù)版本號(hào) 盛末,就是說記錄創(chuàng)建是在事務(wù)中(等于的情況)或者事務(wù)啟動(dòng)之前弹惦。
mvcc分為快照讀和當(dāng)前讀。
快照讀只是針對(duì)于目標(biāo)數(shù)據(jù)的版本小于等于當(dāng)前事務(wù)的版本號(hào)悄但,也就是說讀數(shù)據(jù)的時(shí)候可能讀到舊的數(shù)據(jù)肤频,但是這種快照讀不需要加鎖,性能很高算墨。
當(dāng)前讀是讀取當(dāng)前數(shù)據(jù)的最新版本,但是更新等操作會(huì)對(duì)數(shù)據(jù)進(jìn)行加鎖汁雷,所以當(dāng)前讀需要獲取記錄的行鎖净嘀,存在鎖爭(zhēng)用的問題。
下面是快照讀和當(dāng)前讀的常見操作:
快照讀:就是select * from table ....;
當(dāng)前讀:特殊的讀操作(加共享鎖或排他鎖)侠讯,插入/更新/刪除操作挖藏,需要加鎖。
select from table where ? lock in share mode;
select from table where ? for update;
其實(shí)Mysql實(shí)現(xiàn)的Mvcc并不純粹厢漩,因?yàn)樵诋?dāng)前讀的時(shí)候需要對(duì)記錄進(jìn)行加鎖膜眠,而不是多版本競(jìng)爭(zhēng)。下面是具體操作時(shí)的Mvcc機(jī)制:
? 1. SELECT時(shí)溜嗜,讀取創(chuàng)建版本號(hào)<=當(dāng)前事務(wù)版本號(hào)宵膨,刪除版本號(hào)為空或>當(dāng)前事務(wù)版本號(hào)。
? 2. INSERT時(shí)炸宵,保存當(dāng)前事務(wù)版本號(hào)為行的創(chuàng)建版本號(hào)
? 3. DELETE時(shí)辟躏,保存當(dāng)前事務(wù)版本號(hào)為行的刪除版本號(hào)
? 4. UPDATE時(shí),插入一條新紀(jì)錄土全,保存當(dāng)前事務(wù)版本號(hào)為行創(chuàng)建版本號(hào)捎琐,同時(shí)保存當(dāng)前事務(wù)版本號(hào)到原來刪除的行
總結(jié):
RC提交讀,采用行鎖和MVCC的當(dāng)前讀裹匙,有幻讀和不可重復(fù)讀問題瑞凑。RR可重讀,采用Next-key(默認(rèn))和MVCC的快照讀概页,無幻讀和可重復(fù)讀籽御。
mysql的鎖機(jī)制和事務(wù)隔離級(jí)別有關(guān)。并不是說所有的讀操作都不加鎖绰沥,寫操作加鎖篱蝇,加什么鎖也和索引類型、有無索引有關(guān)徽曲。
感謝分享:
http://www.reibang.com/p/bcc614524024
http://www.reibang.com/p/edbe22beaecb