7.3 鎖
7.3.1 鎖機(jī)制
當(dāng)前MySQL已經(jīng)支持 ISAM
, MyISAM
, MEMORY
(HEAP
) 類型表的表級(jí)鎖了,BDB
表支持頁(yè)級(jí)鎖,InnoDB
表支持行級(jí)鎖。
很多時(shí)候,可以通過(guò)經(jīng)驗(yàn)來(lái)猜測(cè)什么樣的鎖對(duì)應(yīng)用程序更合適,不過(guò)通常很難說(shuō)一個(gè)鎖比別的更好,這全都要依據(jù)應(yīng)用程序來(lái)決定,不同的地方可能需要不同的鎖。
想要決定是否需要采用一個(gè)支持行級(jí)鎖的存儲(chǔ)引擎,就要看看應(yīng)用程序都要做什么,其中的查詢、更新語(yǔ)句是怎么用的。例如,很多的web應(yīng)用程序大量的做查詢,很少刪除,主要是基于索引的更新,只往特定的表中插入記錄。采用基本的MySQL MyISAM
表就很合適了。
MySQL中對(duì)表級(jí)鎖的存儲(chǔ)引擎來(lái)說(shuō)是釋放死鎖的。避免死鎖可以這樣做到:在任何查詢之前先請(qǐng)求鎖,并且按照請(qǐng)求的順序鎖表。
MySQL中用于 WRITE(寫)
的表鎖的實(shí)現(xiàn)機(jī)制如下:
- 如果表沒有加鎖,那么就加一個(gè)寫鎖。
- 否則的話,將請(qǐng)求放到寫鎖隊(duì)列中。
MySQL中用于 READ(讀)
的表鎖的實(shí)現(xiàn)機(jī)制如下:
- 如果表沒有加寫鎖,那么就加一個(gè)讀鎖。
- 否則的話,將請(qǐng)求放到讀鎖隊(duì)列中。
當(dāng)鎖釋放后,寫鎖隊(duì)列中的線程可以用這個(gè)鎖資源,然后才輪到讀鎖隊(duì)列中的線程。
這就是說(shuō),如果表里有很多更新操作的話,那么 SELECT
必須等到所有的更新都完成了之后才能開始。
從 MySQL 3.23.33 開始,可以通過(guò)狀態(tài)變量 Table_locks_waited
和 Table_locks_immediate
來(lái)分析系統(tǒng)中的鎖表爭(zhēng)奪情況:
mysql> SHOW STATUS LIKE 'Table%'; +-----------------------+---------+ | Variable_name | Value | +-----------------------+---------+ | Table_locks_immediate | 1151552 | | Table_locks_waited | 15324 | +-----------------------+---------+
在 MySQL 3.23.7(在Windows上是3.23.25)以后,在 MyISAM
表中只要沒有沖突的 INSERT
操作,就可以無(wú)需使用鎖表自由地并行執(zhí)行 INSERT
和 SELECT
語(yǔ)句。也就是說(shuō),可以在其它客戶端正在讀取 MyISAM
表記錄的同時(shí)時(shí)插入新記錄。如果數(shù)據(jù)文件的中間沒有空余的磁盤塊的話,就不會(huì)發(fā)生沖突了,因?yàn)檫@種情況下所有的新記錄都會(huì)寫在數(shù)據(jù)文件的末尾(當(dāng)在表的中間做刪除或者更新操作時(shí),就可能導(dǎo)致空洞)。當(dāng)空洞被新數(shù)據(jù)填充后,并行插入特性就會(huì)自動(dòng)重新被啟用了。
如果想要在一個(gè)表上做大量的 INSERT
和 SELECT
操作,但是并行的插入?yún)s不可能時(shí),可以將記錄插入到臨時(shí)表中,然后定期將臨時(shí)表中的數(shù)據(jù)更新到實(shí)際的表里。可以用以下命令實(shí)現(xiàn):
mysql> LOCK TABLES real_table WRITE, insert_table WRITE; mysql> INSERT INTO real_table SELECT * FROM insert_table; mysql> TRUNCATE TABLE insert_table; mysql> UNLOCK TABLES;
InnoDB
使用行級(jí)鎖,BDB
使用頁(yè)級(jí)鎖。對(duì)于 InnoDB
和 BDB
存儲(chǔ)引擎來(lái)說(shuō),是可能產(chǎn)生死鎖的。這是因?yàn)?InnoDB
會(huì)自動(dòng)捕獲行鎖,BDB
會(huì)在執(zhí)行 SQL 語(yǔ)句時(shí)捕獲頁(yè)鎖的,而不是在事務(wù)的開始就這么做。
行級(jí)鎖的優(yōu)點(diǎn)有:
- 在很多線程請(qǐng)求不同記錄時(shí)減少?zèng)_突鎖。
- 事務(wù)回滾時(shí)減少改變數(shù)據(jù)。
- 使長(zhǎng)時(shí)間對(duì)單獨(dú)的一行記錄加鎖成為可能。
行級(jí)鎖的缺點(diǎn)有:
- 比頁(yè)級(jí)鎖和表級(jí)鎖消耗更多的內(nèi)存。
- 當(dāng)在大量表中使用時(shí),比頁(yè)級(jí)鎖和表級(jí)鎖更慢,因?yàn)樗枰?qǐng)求更多的所資源。
- 當(dāng)需要頻繁對(duì)大部分?jǐn)?shù)據(jù)做
GROUP BY
操作或者需要頻繁掃描整個(gè)表時(shí),就明顯的比其它鎖更糟糕。 - 使用更高層的鎖的話,就能更方便的支持各種不同的類型應(yīng)用程序,因?yàn)檫@種鎖的開銷比行級(jí)鎖小多了。
表級(jí)鎖在下列幾種情況下比頁(yè)級(jí)鎖和行級(jí)鎖更優(yōu)越:
- 很多操作都是讀表。
- 在嚴(yán)格條件的索引上讀取和更新,當(dāng)更新或者刪除可以用單獨(dú)的索引來(lái)讀取得到時(shí):
UPDATE tbl_name SET column=value WHERE unique_key_col=key_value; DELETE FROM tbl_name WHERE unique_key_col=key_value;
SELECT
和INSERT
語(yǔ)句并發(fā)的執(zhí)行,但是只有很少的UPDATE
和DELETE
語(yǔ)句。- 很多的掃描表和對(duì)全表的
GROUP BY
操作,但是沒有任何寫表。
表級(jí)鎖和行級(jí)鎖或頁(yè)級(jí)鎖之間的不同之處還在于:
將同時(shí)有一個(gè)寫和多個(gè)讀的地方做版本(例如在MySQL中的并發(fā)插入)。也就是說(shuō),數(shù)據(jù)庫(kù)/表支持根據(jù)開始訪問(wèn)數(shù)據(jù)時(shí)間點(diǎn)的不同支持各種不同的試圖。其它名有:時(shí)間行程,寫復(fù)制,或者是按需復(fù)制。
原文: Versioning (such as we use in MySQL for concurrent inserts) where you can have one writer at the same time as many readers. This means that the database/table supports different views for the data depending on when you started to access it. Other names for this are time travel, copy on write, or copy on demand.
按需復(fù)制在很多情況下比頁(yè)級(jí)鎖或行級(jí)鎖好多了。盡管如此,最壞情況時(shí)還是比其它正常鎖使用了更多的內(nèi)存。
可以用應(yīng)用程序級(jí)鎖來(lái)代替行級(jí)鎖,例如MySQL中的 GET_LOCK()
和 RELEASE_LOCK()
。但它們是勸告鎖(原文:These are advisory locks),因此只能用于安全可信的應(yīng)用程序中。
7.3.2 鎖表
為了能有快速的鎖,MySQL除了 InnoDB
和 BDB
這兩種存儲(chǔ)引擎外,所有的都是用表級(jí)鎖(而非頁(yè)、行、列級(jí)鎖)。
對(duì)于 InnoDB
和 BDB
表,MySQL只有在指定用 LOCK TABLES
鎖表時(shí)才使用表級(jí)鎖。在這兩種表中,建議最好不要使用 LOCK TABLES
,因?yàn)?InnoDB
自動(dòng)采用行級(jí)鎖,BDB
用頁(yè)級(jí)鎖來(lái)保證事務(wù)的隔離。
如果數(shù)據(jù)表很大,那么在大多數(shù)應(yīng)用中表級(jí)鎖會(huì)比行級(jí)鎖好多了,不過(guò)這有一些陷阱。
表級(jí)鎖讓很多線程可以同時(shí)從數(shù)據(jù)表中讀取數(shù)據(jù),但是如果另一個(gè)線程想要寫數(shù)據(jù)的話,就必須要先取得排他訪問(wèn)。正在更新數(shù)據(jù)時(shí),必須要等到更新完成了,其他線程才能訪問(wèn)這個(gè)表。
更新操作通常認(rèn)為比讀取更重要,因此它的優(yōu)先級(jí)更高。不過(guò)最好要先確認(rèn),數(shù)據(jù)表是否有很高的 SELECT
操作,而更新操作并非很‘急需’。
表鎖鎖在一個(gè)線程在等待,因?yàn)榇疟P空間滿了,但是卻需要有空余的磁盤空間,這個(gè)線程才能繼續(xù)處理時(shí)就有問(wèn)題了。這種情況下,所有要訪問(wèn)這個(gè)出問(wèn)題的表的線程都會(huì)被置為等待狀態(tài),直到有剩余磁盤空間了。
表鎖在以下設(shè)想情況中就不利了:
- 一個(gè)客戶端提交了一個(gè)需要長(zhǎng)時(shí)間運(yùn)行的
SELECT
操作。 - 其他客戶端對(duì)同一個(gè)表提交了
UPDATE
操作,這個(gè)客戶端就要等到SELECT
完成了才能開始執(zhí)行。 - 其他客戶端也對(duì)同一個(gè)表提交了
SELECT
請(qǐng)求。由于UPDATE
的優(yōu)先級(jí)高于SELECT
,所以SELECT
就會(huì)先等到UPDATE
完成了之后才開始執(zhí)行,它也在等待第一個(gè)SELECT
操作。
下列所述可以減少表鎖帶來(lái)的資源爭(zhēng)奪:
- 讓
SELECT
速度盡量快,這可能需要?jiǎng)?chuàng)建一些摘要表。 - 啟動(dòng)
mysqld
時(shí)使用參數(shù)--low-priority-updates
。這就會(huì)讓更新操作的優(yōu)先級(jí)低于SELECT
。這種情況下,在上面的假設(shè)中,第二個(gè)SELECT
就會(huì)在INSERT
之前執(zhí)行了,而且也無(wú)需等待第一個(gè)SELECT
了。 - 可以執(zhí)行
SET LOW_PRIORITY_UPDATES=1
命令,指定所有的更新操作都放到一個(gè)指定的鏈接中去完成。詳情請(qǐng)看“14.5.3.1SET
Syntax”。 - 用
LOW_PRIORITY
屬性來(lái)降低INSERT
,UPDATE
,DELETE
的優(yōu)先級(jí)。 - 用
HIGH_PRIORITY
來(lái)提高SELECT
語(yǔ)句的優(yōu)先級(jí)。詳情請(qǐng)看“14.1.7SELECT
Syntax”。 - 從MySQL 3.23.7 開始,可以在啟動(dòng)
mysqld
時(shí)指定系統(tǒng)變量max_write_lock_count
為一個(gè)比較低的值,它能強(qiáng)制臨時(shí)地提高表的插入數(shù)達(dá)到一個(gè)特定值后的所有SELECT
操作的優(yōu)先級(jí)。它允許在WRITE
鎖達(dá)到一定數(shù)量后有READ
鎖。 - 當(dāng)
INSERT
和SELECT
一起使用出現(xiàn)問(wèn)題時(shí),可以轉(zhuǎn)而采用MyISAM
表,它支持并發(fā)的SELECT
和INSERT
操作。 - 當(dāng)在同一個(gè)表上同時(shí)有插入和刪除操作時(shí),
INSERT DELAYED
可能會(huì)很有用。詳情請(qǐng)看“14.1.4.2INSERT DELAYED
Syntax”。 - 當(dāng)
SELECT
和DELETE
一起使用出現(xiàn)問(wèn)題時(shí),DELETE
的LIMIT
參數(shù)可能會(huì)很有用。詳情請(qǐng)看“14.1.1DELETE
Syntax” - 執(zhí)行
SELECT
時(shí)使用SQL_BUFFER_RESULT
有助于減短鎖表的持續(xù)時(shí)間.詳情請(qǐng)看“14.1.7SELECT
Syntax”。 - 可以修改源代碼 `mysys/thr_lock.c',只用一個(gè)所隊(duì)列。這種情況下,寫鎖和讀鎖的優(yōu)先級(jí)就一樣了,這對(duì)一些應(yīng)用可能有幫助。
以下是MySQL鎖的一些建議:
- 只要對(duì)同一個(gè)表沒有大量的更新和查詢操作混在一起,目前的用戶并不是問(wèn)題。
- 執(zhí)行
LOCK TABLES
來(lái)提高速度(很多更新操作放在一個(gè)鎖之中比沒有鎖的很多更新快多了)。將數(shù)據(jù)拆分開到多個(gè)表中可能也有幫助。 - 當(dāng)MySQL碰到由于鎖表引起的速度問(wèn)題時(shí),將表類型轉(zhuǎn)換成
InnoDB
或BDB
可能有助于提高性能。詳情請(qǐng)看“16 TheInnoDB
Storage Engine”和“15.4 TheBDB
(BerkeleyDB
) Storage Engine”。