阿里云產(chǎn)品通用代金券,最高可領(lǐng)1888分享一波阿里云紅包. 阿里云的購(gòu)買入口
為什么要加鎖
多核計(jì)算機(jī)的出現(xiàn),計(jì)算機(jī)實(shí)現(xiàn)真正并行計(jì)算,可以在同一時(shí)刻,執(zhí)行多個(gè)任務(wù)伶氢。在多線程編程中,因?yàn)榫€程執(zhí)行順序不可控導(dǎo)致的數(shù)據(jù)錯(cuò)誤瘪吏。比如癣防,多線程的理想狀態(tài)是這樣的
但是實(shí)際情況是這樣的:
在網(wǎng)絡(luò)編程中,在同一時(shí)刻肪虎,多個(gè)客戶端同時(shí)請(qǐng)求同一個(gè)資源劣砍,如果不做控制,也會(huì)帶來數(shù)據(jù)錯(cuò)誤扇救。比如在同一時(shí)間有10000人去搶10張火車票刑枝,10張火車票有可能會(huì)買給100個(gè)人,這顯然是不符合要求的迅腔。
在多線程編程中,為了解決線程執(zhí)行不可控帶來的問題,通常情況下都是通過加鎖來實(shí)現(xiàn)數(shù)據(jù)同步的装畅。在網(wǎng)絡(luò)編程中,也可以通過加鎖機(jī)制來控制沧烈。
在網(wǎng)絡(luò)編程中掠兄,可以通過給數(shù)據(jù)庫(kù)加鎖,達(dá)到控制并發(fā)的目的。在php開發(fā)時(shí)蚂夕,基本都是使用mysql作為數(shù)據(jù)庫(kù)迅诬。所以,就會(huì)給mysql加鎖控制網(wǎng)絡(luò)并發(fā)引起數(shù)據(jù)錯(cuò)誤問題婿牍。
MySQL的存儲(chǔ)引擎
不是要說MySQL的鎖嗎,怎么說上存儲(chǔ)引擎了?因?yàn)镸ySQL存儲(chǔ)引擎不同,鎖也會(huì)不同侈贷。MySQL有MyISAM 和InnoDB兩種存儲(chǔ)引擎,現(xiàn)在主要使用InnoDB,所以主要介紹InnoDB下鎖的使用等脂。
InnoDB引擎支持事務(wù)操作俏蛮,使用事務(wù)可以保證多條sql語(yǔ)句執(zhí)行的完整性(要不都成功,要不都失敗)
事務(wù)是由一組SQL語(yǔ)句組成的邏輯處理單元,事務(wù)具有4屬性
- 原子性(Actomicity):事務(wù)是一個(gè)原子操作單元上遥,其對(duì)數(shù)據(jù)的修改搏屑,要么全都執(zhí)行,要么全都不執(zhí)行粉楚。
- 一致性(Consistent):在事務(wù)開始和完成時(shí)辣恋,數(shù)據(jù)都必須保持一致狀態(tài)。這意味著所有相關(guān)的數(shù)據(jù)規(guī)則都必須應(yīng)用于事務(wù)的修改解幼,以操持完整性抑党;事務(wù)結(jié)束時(shí),所有的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(如B樹索引或雙向鏈表)也都必須是正確的撵摆。
- 隔離性(Isolation):數(shù)據(jù)庫(kù)系統(tǒng)提供一定的隔離機(jī)制底靠,保證事務(wù)在不受外部并發(fā)操作影響的“獨(dú)立”環(huán)境執(zhí)行。這意味著事務(wù)處理過程中的中間狀態(tài)對(duì)外部是不可見的特铝,反之亦然暑中。
- 持久性(Durable):事務(wù)完成之后,它對(duì)于數(shù)據(jù)的修改是永久性的鲫剿,即使出現(xiàn)系統(tǒng)故障也能夠保持鳄逾。
多個(gè)事務(wù)并發(fā)執(zhí)行會(huì)帶來新的問題
- 更新丟失(Lost Update):當(dāng)兩個(gè)或多個(gè)事務(wù)選擇同一行,然后基于最初選定的值更新該行時(shí)灵莲,由于每個(gè) 事務(wù)都不知道其他事務(wù)的存在雕凹,就會(huì)發(fā)生丟失更新問題——最后的更新覆蓋了其他事務(wù)所做的更新。例如政冻,兩個(gè)編輯人員制作了同一文檔的電子副本枚抵。每個(gè)編輯人員獨(dú)立地更改其副本,然后保存更改后的副本明场,這樣就覆蓋了原始文檔汽摹。最后保存其更改保存其更改副本的編輯人員覆蓋另一個(gè)編輯人員所做的修改。如果在一個(gè)編輯人員完成并提交事務(wù)之前苦锨,另一個(gè)編輯人員不能訪問同一文件逼泣,則可避免此問題
- 臟讀(Dirty Reads):一個(gè)事務(wù)正在對(duì)一條記錄做修改趴泌,在這個(gè)事務(wù)并提交前,這條記錄的數(shù)據(jù)就處于不一致狀態(tài)拉庶;這時(shí)嗜憔,另一個(gè)事務(wù)也來讀取同一條記錄,如果不加控制砍的,第二個(gè)事務(wù)讀取了這些“臟”的數(shù)據(jù)痹筛,并據(jù)此做進(jìn)一步的處理,就會(huì)產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系廓鞠。這種現(xiàn)象被形象地叫做“臟讀”。
- 不可重復(fù)讀(Non-Repeatable Reads):一個(gè)事務(wù)在讀取某些數(shù)據(jù)已經(jīng)發(fā)生了改變谣旁、或某些記錄已經(jīng)被刪除了床佳!這種現(xiàn)象叫做“不可重復(fù)讀”。
- 幻讀(Phantom Reads):一個(gè)事務(wù)按相同的查詢條件重新讀取以前檢索過的數(shù)據(jù)榄审,卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù)砌们,這種現(xiàn)象就稱為“幻讀”。
曾經(jīng)年少無(wú)知的我,以為使用事務(wù)就能保證并發(fā)情況下數(shù)據(jù)同步問題,后來的一次慘痛經(jīng)歷才明白了,事務(wù)不能保證并發(fā)情況的數(shù)據(jù)同步問題,需要事務(wù)和鎖同時(shí)使用才能保證搁进。
鎖的種類
- 樂觀鎖 機(jī)制采取了更加寬松的加鎖機(jī)制浪感。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫(kù)的鎖機(jī)制實(shí)現(xiàn),以保證操作最大程度的獨(dú)占性饼问。但隨之而來的就是數(shù)據(jù)庫(kù)性能的大量開銷影兽,特別是對(duì)長(zhǎng)事務(wù)而言,這樣的開銷往往無(wú)法承受莱革。相對(duì)悲觀鎖而言峻堰,樂觀鎖更傾向于開發(fā)運(yùn)用。
- 悲觀鎖 具有強(qiáng)烈的獨(dú)占和排他特性盅视。它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù)捐名,以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此闹击,在整個(gè)數(shù)據(jù)處理過程中镶蹋,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實(shí)現(xiàn)赏半,往往依靠數(shù)據(jù)庫(kù)提供的鎖機(jī)制(也只有數(shù)據(jù)庫(kù)層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性贺归,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制除破,也無(wú)法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))牧氮。
MySQL中鎖的種類
樂觀鎖和悲觀鎖是一種思想,不是具體實(shí)現(xiàn),在MySQL中,有鎖的具體的實(shí)現(xiàn)方式
(文中的線程在MySQL中可以視作MySQL的連接)
- 共享鎖 一個(gè)線程在持有鎖時(shí),其他的線程可以查詢被鎖的數(shù)據(jù),但是不能修改,不能刪除。實(shí)現(xiàn)方式
SELECT * FROM table_name WHERE id =? lock in share mode;
- 排它鎖 一個(gè)線程在持有鎖時(shí),其他的線程不能查詢,不能更新,不能刪除被鎖的數(shù)據(jù),直到鎖被釋放.
SELECT * FROM table_name WHERE id =? for update
- 總結(jié)一下:共享鎖類似于java中的讀鎖,一個(gè)線程在持有樂觀鎖的時(shí)候,其他的線程也可以對(duì)被鎖的數(shù)據(jù)進(jìn)行讀操作,但是不能對(duì)被鎖的數(shù)據(jù)進(jìn)行刪除和更新操作;排他鎖類似于java的寫鎖,一個(gè)線程持有寫鎖的時(shí)候,其他的線程不能再對(duì)被鎖的數(shù)據(jù)進(jìn)行任何查詢,更新,刪除操作瑰枫。
- 重點(diǎn) InnoDB的行鎖是基于索引實(shí)現(xiàn)的,如果在查詢中不使用索引,會(huì)鎖表踱葛。
MySQL鎖粒度
- 表級(jí)鎖 是MySQL中鎖定粒度最大的一種鎖丹莲,表示對(duì)當(dāng)前操作的整張表加鎖,它實(shí)現(xiàn)簡(jiǎn)單尸诽,資源消耗較少甥材,被大部分MySQL引擎支持。最常使用的MyISAM與InnoDB都支持表級(jí)鎖定性含。表級(jí)鎖分為表共享讀鎖與表獨(dú)占寫鎖洲赵。
- 行級(jí)鎖 是Mysql中鎖定粒度最細(xì)的一種鎖,表示只針對(duì)當(dāng)前操作的行進(jìn)行加鎖商蕴。行級(jí)鎖能大大減少數(shù)據(jù)庫(kù)操作的沖突叠萍。其加鎖粒度最小天吓,但加鎖的開銷也最大咽斧。行級(jí)鎖分為共享鎖 和 排他鎖其馏。
共享鎖的使用
注意: 下面的操作,都是行鎖操作,MySQL為InnoDB引擎,id為自增主鍵
先創(chuàng)建一個(gè)測(cè)試表
CREATE TABLE `test` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`number` bigint(0) NOT NULL,
`age` int(0) NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
看一下隨便插入的幾條數(shù)據(jù)
沒有問題,使用共享鎖看一下效果:
- 開啟事務(wù)
begin;
- 給id為1的數(shù)據(jù)加共享鎖
mysql> select * from test where id=1 lock in share mode;
分別使用加鎖和不加鎖查詢id為1 的數(shù)據(jù)
都可以查詢到數(shù)據(jù)
修改id為1 的數(shù)據(jù)看看
sql語(yǔ)句會(huì)一直停在這里,直到超時(shí)或者鎖釋放(事務(wù)提交或者回滾)
左邊的事務(wù)提交后,右邊的sql會(huì)執(zhí)行,完成更新操作拆宛。
同樣,刪除操作也會(huì)等待鎖釋放才能操作,這里就不演示了茂嗓。
再看一下另一種情況,左邊鎖住id為5的數(shù)據(jù),右邊更新id為1 的數(shù)據(jù),不受影響缠劝。這就是行級(jí)鎖舷蒲,只會(huì)鎖住相關(guān)的一行數(shù)據(jù)
排他鎖的使用
還是使用test這張表
- 開啟事務(wù)
begin;
- 給id1的數(shù)據(jù)加排他鎖
select * from test where id = 1 for update;
在右邊查詢id為1的數(shù)據(jù)
查詢語(yǔ)句會(huì)一直等待,直到超時(shí)或者鎖釋放(左邊commit或者rollback)
左邊commit后
使用排它鎖對(duì)id為5的數(shù)據(jù)加鎖后,更新id為5的數(shù)據(jù)
sql語(yǔ)句同樣會(huì)等待,直到超時(shí)或者鎖釋放,刪除操作也是一樣
看一下對(duì)id為1的數(shù)據(jù)加鎖,然后操作id不為1的數(shù)據(jù)的情況
沒有問題,MySQL只是鎖住了id為5的數(shù)據(jù),其他的數(shù)據(jù)都可以操作蹬昌。
看一下InnoDB引擎鎖表的情況
我們常常說InnoDB是行鎖例书,但是這里介紹一下它鎖表的情況锣尉。因?yàn)閚ame列沒有索引,所以,在加行鎖的時(shí)候,MySQL不能加正常加行鎖,會(huì)鎖住整張表。
InnoDB行鎖是通過索引上的索引項(xiàng)來實(shí)現(xiàn)的决采,這一點(diǎn)MySQL與Oracle不同自沧,后者是通過在數(shù)據(jù)中對(duì)相應(yīng)數(shù)據(jù)行加鎖來實(shí)現(xiàn)的。InnoDB這種行鎖實(shí)現(xiàn)特點(diǎn)意味者:只有通過索引條件檢索數(shù)據(jù)织狐,InnoDB才會(huì)使用行級(jí)鎖暂幼,否則,InnoDB將使用表鎖移迫!
在實(shí)際應(yīng)用中旺嬉,要特別注意InnoDB行鎖的這一特性,不然的話厨埋,可能導(dǎo)致大量的鎖沖突邪媳,從而影響并發(fā)性能。
再看另一種情況
使用排它鎖對(duì)id為1的數(shù)據(jù)加鎖時(shí),使用不加鎖的查詢和沒有約束的查詢時(shí),一樣可以立刻查詢到數(shù)據(jù)荡陷。只有使用加鎖的查詢或者更新和刪除時(shí)才會(huì)等待鎖釋放雨效。
總結(jié)
- InnoDB的鎖配合事務(wù)使用
- MySQL有共享鎖和排它鎖
- 使用共享鎖時(shí),其他線程(連接)可以查詢數(shù)據(jù),但是不能更新和刪除數(shù)據(jù),使用排它鎖時(shí),不能查詢數(shù)據(jù)不能更新數(shù)據(jù),不能刪除數(shù)據(jù)
- MySQL的InnoDB引擎支持行級(jí)鎖和表級(jí)鎖,行級(jí)鎖
- InnoDB的行級(jí)鎖是基于索引的,加鎖是對(duì)索引加鎖,加鎖時(shí)沒有索引時(shí)會(huì)鎖住整張表
以上是我對(duì)MySQL鎖的理解,文中如果有不正確的地方,還請(qǐng)各位大哥批評(píng)指正。