MYSQL--三种锁
數(shù)據(jù)庫鎖設(shè)計(jì)的初衷是處理并發(fā)問題。作為多用戶共享的資源,當(dāng)出現(xiàn)并發(fā)訪問的時(shí)候,數(shù)據(jù)庫需要合理地控制資源的訪問規(guī)則。而鎖就是用來實(shí)現(xiàn)這些訪問規(guī)則的重要數(shù)據(jù)結(jié)構(gòu)。根據(jù)加鎖的范圍,MySQL 里面的鎖大致可以分成全局鎖、表級(jí)鎖和行鎖三類。
全局鎖:加上鎖之后整個(gè)數(shù)據(jù)庫都無法訪問。MySQL 提供了一個(gè)加全局讀鎖的方法,命令是 Flush tables with read lock (FTWRL)。
全局鎖的典型使用場景是,做全庫邏輯備份。也就是把整庫每個(gè)表都 select 出來存成文本。
不加鎖的話,備份系統(tǒng)備份的得到的庫不是一個(gè)邏輯時(shí)間點(diǎn),這個(gè)視圖是邏輯不一致的。
之前講過,有一個(gè)辦法可以拿到一致性視圖,那就是在可重復(fù)讀隔離級(jí)別下開啟一個(gè)事務(wù)。
官方自帶的邏輯備份工具是 mysqldump。當(dāng) mysqldump 使用參數(shù)–single-transaction 的時(shí)候,導(dǎo)數(shù)據(jù)之前就會(huì)啟動(dòng)一個(gè)事務(wù),來確保拿到一致性視圖。而由于 MVCC 的支持,這個(gè)過程中數(shù)據(jù)是可以正常更新的。
為什么有了single-transaction還要FTWRL?
因?yàn)橛械臄?shù)據(jù)存儲(chǔ)引擎不支持事務(wù)。
比如,對(duì)于 MyISAM 這種不支持事務(wù)的引擎,如果備份過程中有更新,總是只能取到最新的數(shù)據(jù),那么就破壞了備份的一致性。這時(shí),我們就需要使用 FTWRL 命令了。所以,single-transaction 方法只適用于所有的表使用事務(wù)引擎的庫。如果有的表使用了不支持事務(wù)的引擎,那么備份就只能通過 FTWRL 方法。
既然要全庫只讀,為什么不使用 set global readonly=true 的方式呢?
1.readonly這個(gè)參數(shù)可以被用來做其他邏輯判斷
2.readonly設(shè)為true,之后數(shù)據(jù)庫連接異常斷開,這樣會(huì)導(dǎo)致整個(gè)庫長時(shí)間處于不可寫狀態(tài),風(fēng)險(xiǎn)較高。如果是FTWRL方式,那么異常斷開后,那么 MySQL 會(huì)自動(dòng)釋放這個(gè)全局鎖,整個(gè)庫回到可以正常更新的狀態(tài)。
業(yè)務(wù)的更新不只是增刪改數(shù)據(jù)(DML),還有可能是加字段等修改表結(jié)構(gòu)的操作(DDL)。不論是哪種方法,一個(gè)庫被全局鎖上以后,你要對(duì)里面任何一個(gè)表做加字段操作,都是會(huì)被鎖住的。
表級(jí)鎖:表鎖,元數(shù)據(jù)鎖(meta data lock,MDL)
表鎖的語法是 lock tables … read/write。與 FTWRL 類似,可以用 unlock tables 主動(dòng)釋放鎖,也可以在客戶端斷開的時(shí)候自動(dòng)釋放。需要注意,lock tables 語法除了會(huì)限制別的線程的讀寫外,也限定了本線程接下來的操作對(duì)象。
舉個(gè)例子, 如果在某個(gè)線程 A 中執(zhí)行 lock tables t1 read, t2 write; 這個(gè)語句,則其他線程寫 t1、讀寫 t2 的語句都會(huì)被阻塞。同時(shí),線程 A 在執(zhí)行 unlock tables 之前,也只能執(zhí)行讀 t1、讀寫 t2 的操作。連寫 t1 都不允許,自然也不能訪問其他表。在還沒有出現(xiàn)更細(xì)粒度的鎖的時(shí)候,表鎖是最常用的處理并發(fā)的方式。
而對(duì)于 InnoDB 這種支持行鎖的引擎,一般不使用 lock tables 命令來控制并發(fā),畢竟鎖住整個(gè)表的影響面還是太大。另一類表級(jí)的鎖是 MDL(metadata lock)。MDL 不需要顯式使用,在訪問一個(gè)表的時(shí)候會(huì)被自動(dòng)加上。MDL 的作用是,保證讀寫的正確性。你可以想象一下,如果一個(gè)查詢正在遍歷一個(gè)表中的數(shù)據(jù),而執(zhí)行期間另一個(gè)線程對(duì)這個(gè)表結(jié)構(gòu)做變更,刪了一列,那么查詢線程拿到的結(jié)果跟表結(jié)構(gòu)對(duì)不上,肯定是不行的。因此,在 MySQL 5.5 版本中引入了 MDL,當(dāng)對(duì)一個(gè)表做增刪改查操作的時(shí)候,加 MDL 讀鎖;當(dāng)要對(duì)表做結(jié)構(gòu)變更操作的時(shí)候,加 MDL 寫鎖。讀鎖之間不互斥,因此你可以有多個(gè)線程同時(shí)對(duì)一張表增刪改查。讀寫鎖之間、寫鎖之間是互斥的,用來保證變更表結(jié)構(gòu)操作的安全性。因此,如果有兩個(gè)線程要同時(shí)給一個(gè)表加字段,其中一個(gè)要等另一個(gè)執(zhí)行完才能開始執(zhí)行。雖然 MDL 鎖是系統(tǒng)默認(rèn)會(huì)加的,但卻是你不能忽略的一個(gè)機(jī)制。比如下面這個(gè)例子,我經(jīng)常看到有人掉到這個(gè)坑里:給一個(gè)小表加個(gè)字段,導(dǎo)致整個(gè)庫掛了。
?
如何安全地給小表加字段?首先我們要解決長事務(wù),事務(wù)不提交,就會(huì)一直占著 MDL 鎖。在 MySQL 的 information_schema 庫的 innodb_trx 表中,你可以查到當(dāng)前執(zhí)行中的事務(wù)。如果你要做 DDL 變更的表剛好有長事務(wù)在執(zhí)行,要考慮先暫停 DDL,或者 kill 掉這個(gè)長事務(wù)。但考慮一下這個(gè)場景。如果你要變更的表是一個(gè)熱點(diǎn)表,雖然數(shù)據(jù)量不大,但是上面的請求很頻繁,而你不得不加個(gè)字段,你該怎么做呢?這時(shí)候 kill 可能未必管用,因?yàn)樾碌恼埱篑R上就來了。比較理想的機(jī)制是,在 alter table 語句里面設(shè)定等待時(shí)間,如果在這個(gè)指定的等待時(shí)間里面能夠拿到 MDL 寫鎖最好,拿不到也不要阻塞后面的業(yè)務(wù)語句,先放棄。之后開發(fā)人員或者 DBA 再通過重試命令重復(fù)這個(gè)過程。
?
行鎖:行鎖就是針對(duì)數(shù)據(jù)表中行記錄的鎖。這很好理解,比如事務(wù) A 更新了一行,而這時(shí)候事務(wù) B 也要更新同一行,則必須等事務(wù) A 的操作完成后才能進(jìn)行更新。
在 InnoDB 事務(wù)中,行鎖是在需要的時(shí)候才加上的,但并不是不需要了就立刻釋放,而是要等到事務(wù)結(jié)束時(shí)才釋放。這個(gè)就是兩階段鎖協(xié)議。知道了這個(gè)設(shè)定,對(duì)我們使用事務(wù)有什么幫助呢?那就是,如果你的事務(wù)中需要鎖多個(gè)行,要把最可能造成鎖沖突、最可能影響并發(fā)度的鎖盡量往后放。
如果往后放還是會(huì)造成沖突怎么辦呢?
死鎖和死鎖檢測
當(dāng)出現(xiàn)死鎖以后,有兩種策略:一種策略是,直接進(jìn)入等待,直到超時(shí)。這個(gè)超時(shí)時(shí)間可以通過參數(shù) innodb_lock_wait_timeout 來設(shè)置。另一種策略是,發(fā)起死鎖檢測,發(fā)現(xiàn)死鎖后,主動(dòng)回滾死鎖鏈條中的某一個(gè)事務(wù),讓其他事務(wù)得以繼續(xù)執(zhí)行。將參數(shù) innodb_deadlock_detect 設(shè)置為 on,表示開啟這個(gè)邏輯。
第一種方法如果時(shí)間太長,會(huì)死鎖,太短可能誤傷,因?yàn)榭赡苤皇擎i等待,就認(rèn)定是死鎖
推薦第二種方法
但第二種會(huì)出現(xiàn)額外負(fù)擔(dān),每個(gè)新來的被堵住的線程,都要判斷會(huì)不會(huì)由于自己的加入導(dǎo)致了死鎖,這是一個(gè)時(shí)間復(fù)雜度是 O(n) 的操作。
怎么解決這種負(fù)擔(dān)呢?
一種頭痛醫(yī)頭的方法,就是如果你能確保這個(gè)業(yè)務(wù)一定不會(huì)出現(xiàn)死鎖,可以臨時(shí)把死鎖檢測關(guān)掉。但是這種操作本身帶有一定的風(fēng)險(xiǎn)。
另一種是控制并發(fā)度。
控制并發(fā)度的一個(gè)小技巧:
可以考慮通過將一行改成邏輯上的多行來減少鎖沖突。還是以影院賬戶為例,可以考慮放在多條記錄上,比如 10 個(gè)記錄,影院的賬戶總額等于這 10 個(gè)記錄的值的總和。這樣每次要給影院賬戶加金額的時(shí)候,隨機(jī)選其中一條記錄來加。這樣每次沖突概率變成原來的 1/10,可以減少鎖等待個(gè)數(shù),也就減少了死鎖檢測的 CPU 消耗。這個(gè)方案看上去是無損的,但其實(shí)這類方案需要根據(jù)業(yè)務(wù)邏輯做詳細(xì)設(shè)計(jì)。如果賬戶余額可能會(huì)減少,比如退票邏輯,那么這時(shí)候就需要考慮當(dāng)一部分行記錄變成 0 的時(shí)候,代碼要有特殊處理。
總結(jié)
以上是生活随笔為你收集整理的MYSQL--三种锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第一章 计算机网络 4 性能指标 [计算
- 下一篇: 删除数据清理oracle表空间,orac