MySQL邏輯架構(gòu)
并發(fā)控制
-
讀寫(xiě)鎖
- sharelock共享鎖,exclusivelock排他鎖
-
鎖粒度
-
table lock
盡管存儲(chǔ)引擎可以管理自己的鎖,MySQL本身還是會(huì)使用各種有效的表鎖來(lái)實(shí)現(xiàn)不同的目的。例如,ALTER TABLE之類(lèi)的語(yǔ)句會(huì)使用表鎖,而忽略引擎的鎖機(jī)制
-
row lock
縮小鎖的粒度,提高并發(fā)性能,InnoDB還有XtraDB
-
事務(wù)
一組原子性的SQL查詢,或者說(shuō)一個(gè)獨(dú)立的工作單元。
ACID
銀行通常是解釋事務(wù)必要性的一個(gè)例子。假設(shè)一個(gè)銀行的數(shù)據(jù)系統(tǒng)有兩張表checking支票表和saving儲(chǔ)蓄表,現(xiàn)在要從用戶Jane的支票賬戶轉(zhuǎn)200美元到她的儲(chǔ)蓄賬戶,至少需要三個(gè)步驟:
- 檢查支票賬戶余額高于200美元
- 從支票賬戶余額中減少200美元
- 在儲(chǔ)蓄賬戶中增加200美元
三個(gè)步驟必須包含在一個(gè)事務(wù)中,任何一個(gè)步驟失敗都會(huì)回滾所有的步驟
START TRANSACTION;
SELECT ...
UPDATE ...
UPDATE ...
COMMIT;
原子性Atomicity:一個(gè)事務(wù)必須被視為一個(gè)不可劃分的最小工作單元,整個(gè)事務(wù)中的所有操作要么全部提交成功,要么全部失敗回滾
一致性Consistency:系統(tǒng)總是從一個(gè)正確狀態(tài)到另一個(gè)正確的狀態(tài)。其實(shí)是AID賦予的性質(zhì),一般正確的狀態(tài)指的是業(yè)務(wù)的正確狀態(tài)。例如銀行總的錢(qián)數(shù)不變
隔離型Isolation:通常來(lái)說(shuō),一個(gè)事務(wù)提交之前的操作對(duì)其他事務(wù)不可見(jiàn)。
持久性Durability:一旦事務(wù)提交,則其所做的操作就會(huì)永久保存在數(shù)據(jù)庫(kù)中。即使系統(tǒng)崩潰,修改的數(shù)據(jù)也不會(huì)丟失。不可能有能做到100%持久性保證策略(如果數(shù)據(jù)庫(kù)本身的持久性能夠保障,那么備份又怎么能增加持久性?)
選擇非事務(wù)型的引擎,可以獲得更高的性能,即使存儲(chǔ)引擎不支持事務(wù),也可以通過(guò)LOCK TABLES語(yǔ)句為Application提供一定程序的保護(hù)。
隔離級(jí)別
READ UNCOMMITED(讀未提交):事務(wù)中的修改,即使未提交,也能被讀取到,容易產(chǎn)生Dirty Read臟讀。從性能來(lái)說(shuō),它不會(huì)比其他級(jí)別好很多。
READ COMMITED(讀提交):大多數(shù)數(shù)據(jù)庫(kù)系統(tǒng)都是該默認(rèn)級(jí)別(MySQL不是),容易造成不可重復(fù)讀,兩次讀取的數(shù)據(jù)不一致。
REPEATABLE READ(可重復(fù)讀):解決了臟讀Dirty Read的問(wèn)題,保證同一事務(wù)多次讀取同樣的數(shù)據(jù)一致。理論上,還是無(wú)法解決幻讀Phantom Read的問(wèn)題。當(dāng)一個(gè)事務(wù)在讀取某個(gè)范圍的記錄,另一個(gè)事務(wù)在該范圍插入了新的記錄,當(dāng)之前事務(wù)再次讀取,就會(huì)出現(xiàn)幻行Phantom Row。InnoDB和XtraDB利用MVCC解決了該問(wèn)題
SERIALIZABLE (可串行化):強(qiáng)制事務(wù)串行執(zhí)行,but實(shí)際上為了提高并發(fā)性能,多個(gè)可串行化事務(wù)經(jīng)調(diào)度后可同時(shí)執(zhí)行
死鎖
兩個(gè)或多個(gè)事務(wù)在同一資源上互相占用,并請(qǐng)求鎖定對(duì)方占用的資源,從而導(dǎo)致惡性循環(huán)的現(xiàn)象。
例子:
兩個(gè)事務(wù)同時(shí)對(duì)StockPrice表操作,即使InnoDB存在RowLock
InnoDB在檢測(cè)到死鎖的循環(huán)依賴,會(huì)立即返回一個(gè)錯(cuò)誤。
當(dāng)查詢時(shí)間達(dá)到鎖等待的超時(shí)設(shè)定后放棄鎖請(qǐng)求,不太友好。
InnoDB目前處理死鎖:將持有最少行級(jí)排他鎖的事務(wù)進(jìn)行回滾。
鎖的行為和順序跟存儲(chǔ)引擎有關(guān),相同的語(yǔ)句在不同的引擎不同
雙重原因:
- 數(shù)據(jù)沖突
- 存儲(chǔ)引擎的實(shí)現(xiàn)方式
死鎖發(fā)生后,只有部分或者完全回滾其中一個(gè)事務(wù),才能打破死鎖。事務(wù)型系統(tǒng)是無(wú)法避免,所以application設(shè)計(jì)時(shí)候必須考慮如何處理死鎖,大多數(shù)情況下只需要重新執(zhí)行死鎖回滾的事務(wù)即可。
事務(wù)日志
事務(wù)日志能夠提高事務(wù)的效率,采用WAL write ahead logging,先寫(xiě)事務(wù)日志,再刷盤(pán)。
事務(wù)日志是順序?qū)懙模簿褪莂ppend-only,實(shí)際上有研究表明,順序?qū)懘疟P(pán)有可能會(huì)比內(nèi)存隨機(jī)讀寫(xiě)更快。
具有crash-safe能力。
MySQL中的事務(wù)
支持事務(wù)的引擎:InnoDB和NDB cluster,第三方的XtraDB和PBXT。
自動(dòng)提交AUTO COMMIT
MySQL默認(rèn)采用自動(dòng)提交,如果不是顯式開(kāi)啟一個(gè)事務(wù),每個(gè)查詢都會(huì)被當(dāng)作一個(gè)事務(wù)執(zhí)行提交操作。
當(dāng)前連接中,通過(guò)設(shè)置AUTO COMMIT變量來(lái)啟用或禁用“自動(dòng)提交”:
show variables like 'AUTOCOMMIT';
SET AUTOCOMMIT = 1;
注意事項(xiàng):
當(dāng)AUTOCOMMIT=0時(shí)候,所有查詢都是在一個(gè)事務(wù)中,知道顯式commit或者rollback。
修改AUTOCOMMIT對(duì)非事務(wù)型的表,比如MyISAM或者內(nèi)存表,不會(huì)有任何影響
有的命令會(huì)在執(zhí)行前強(qiáng)制執(zhí)行COMMIT提交當(dāng)前事務(wù)。DDL中,如果是導(dǎo)致大量數(shù)據(jù)改變的操作,如ALTER TABLE,還有LOCK TABLES等,需要可查詢
-
MySQL可通過(guò)SET TRANSACTION ISOLATION LEVEL來(lái)設(shè)置隔離級(jí)別,新的隔離級(jí)別會(huì)在下一個(gè)事務(wù)開(kāi)始生效,也可在配置文件設(shè)置整個(gè)數(shù)據(jù)庫(kù)的隔離級(jí)別。
設(shè)置當(dāng)前會(huì)話的隔離級(jí)別:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
在事務(wù)中混合使用存儲(chǔ)引擎
MySQL的Server不管理事務(wù),存儲(chǔ)引擎去管。所以,不要在同一個(gè)事務(wù)中使用多個(gè)存儲(chǔ)引擎。
如果混用了事務(wù)型和非事務(wù)型,在正常提交的狀況不會(huì)有問(wèn)題。
但如果需要回滾,非事務(wù)型的表上的變更就無(wú)法撤銷(xiāo),導(dǎo)致數(shù)據(jù)處于不一致的數(shù)據(jù)狀態(tài)。
而且在非事務(wù)型的表上執(zhí)行事務(wù)相關(guān)操作,MySQL通常不會(huì)發(fā)提醒,也不會(huì)報(bào)錯(cuò),回滾時(shí)候可能會(huì)發(fā)警告。
隱式和顯式鎖定
InnoDB采用的是兩階段鎖定協(xié)議(two phase locking protocol),事務(wù)執(zhí)行過(guò)程隨時(shí)可以執(zhí)行鎖定,但是解鎖會(huì)在commit或者rollback同一時(shí)刻釋放。InnoDB會(huì)根據(jù)隔離級(jí)別在需要情況下隱式鎖定。
InnoDB支持顯式鎖定
SELECT ... LOCK IN SHARE MODE
SELECT ... FOR UPDATE
MySQL也支持LOCK TABLES 和UNLOCK TABLES,但是在SERVER層實(shí)現(xiàn)的,不能替代性能。
如果從MyISAM遷移到InnoDB就不要再使用LOCK TABLES,嚴(yán)重影響性能
- LOCK TABLES和事務(wù)之間相互影響很麻煩,除了事務(wù)禁用了AUTOCOMMIT可以使用LOCK TABLES之外,建立不要用了,不管什么引擎
多版本并發(fā)控制MVCC
MySQL大多數(shù)的事務(wù)型引擎都不是簡(jiǎn)單的行級(jí)鎖,一般實(shí)現(xiàn)MVCC
MVCC可認(rèn)為是行級(jí)鎖的變種,很多情況避免了加鎖,提高并發(fā)性能。
MVCC實(shí)現(xiàn),通過(guò)保存數(shù)據(jù)在某一個(gè)時(shí)間點(diǎn)的快照來(lái)實(shí)現(xiàn)。不管事務(wù)執(zhí)行多長(zhǎng)時(shí)間,每個(gè)事務(wù)看到的數(shù)據(jù)都是一致的。
不同存儲(chǔ)引擎的MVCC實(shí)現(xiàn)會(huì)不一樣,典型有樂(lè)觀optimistic并發(fā)控制還有悲觀pessimistic并發(fā)控制。
InnoDB的實(shí)現(xiàn):
- 每行記錄后面保存兩個(gè)隱藏的列,一個(gè)是行的創(chuàng)建時(shí)間,一個(gè)是行的刪除時(shí)間(或過(guò)期時(shí)間),時(shí)間其實(shí)是指系統(tǒng)版本號(hào)。每開(kāi)始一個(gè)新的事務(wù),系統(tǒng)版本號(hào)+1,開(kāi)始時(shí)候的系統(tǒng)版本號(hào)作為事務(wù)的版本號(hào)。
SELECT:
- InnoDB只查找版本號(hào)早于當(dāng)前版本的數(shù)據(jù)行(小于或等于),事務(wù)開(kāi)始已經(jīng)存在或者自身修改或插入
- 行的刪除版本未定義,要么大于當(dāng)前事務(wù)版本號(hào),事務(wù)開(kāi)始之前未被刪除
INSERT:
- 為新插入的每一行保存當(dāng)前系統(tǒng)版本號(hào)
DELETE:
- 為刪除的每一行保存當(dāng)前系統(tǒng)版本號(hào)作為標(biāo)識(shí)
UPDATE:
- 為插入一條新記錄,保存當(dāng)前版本號(hào)作為行版本號(hào),同時(shí)保存當(dāng)前系統(tǒng)版本號(hào)到原來(lái)的行作為刪除標(biāo)識(shí)
優(yōu)點(diǎn):提高了性能
缺點(diǎn):增大了存儲(chǔ)