Navicat for MySQL

Navicat?for?MySQL是一套專為MySQL設(shè)計(jì)的強(qiáng)大數(shù)據(jù)庫(kù)管理及開(kāi)發(fā)工具。它可以用于任何3.21或以上的MySQL數(shù)據(jù)庫(kù)服務(wù)器,并支持大部份MySQL最新版本的功能,包括觸發(fā)器、存儲(chǔ)過(guò)程、函數(shù)、事件、檢索、權(quán)限管理等等。

Navicat for MySQL基本信息

中文名稱 數(shù)據(jù)庫(kù)管理和開(kāi)發(fā)工具 外文名稱 Navicat for MySQL

Navicat for MySQL造價(jià)信息

市場(chǎng)價(jià) 信息價(jià) 詢價(jià)
材料名稱 規(guī)格/型號(hào) 市場(chǎng)價(jià)
(除稅)
工程建議價(jià)
(除稅)
行情 品牌 單位 稅率 供應(yīng)商 報(bào)價(jià)日期
數(shù)據(jù)庫(kù)軟件 MySQL 查看價(jià)格 查看價(jià)格

13% 珠海派諾科技股份有限公司
智能網(wǎng)關(guān) 操作系統(tǒng):嵌入式Linux 處理器:4核 1.3GHz 存儲(chǔ)空間:8GB 校時(shí)服務(wù):NTP服務(wù)器 內(nèi)置數(shù)據(jù)庫(kù):MySQL 操作系統(tǒng):Linu 查看價(jià)格 查看價(jià)格

臺(tái) 13% 深圳市簡(jiǎn)測(cè)智能技術(shù)有限公司
門禁控制器 獨(dú)有特點(diǎn):軟件采用C++語(yǔ)言速度更快、MYSQL數(shù)據(jù)庫(kù)(國(guó)內(nèi)獨(dú)有)、通過(guò)歐盟ISO9001及ISO14000認(rèn)證;1.國(guó)內(nèi)首創(chuàng)三種通訊方式( 查看價(jià)格 查看價(jià)格

開(kāi)爾瑞

個(gè) 13% 長(zhǎng)春門禁智能有限公司
門禁控制器 獨(dú)有特點(diǎn):軟件采用C++語(yǔ)言速度更快、MYSQL數(shù)據(jù)庫(kù)(國(guó)內(nèi)獨(dú)有)、通過(guò)歐盟ISO9001及ISO14000認(rèn)證;1.國(guó)內(nèi)首創(chuàng)三種通訊方式( 查看價(jià)格 查看價(jià)格

開(kāi)爾瑞

個(gè) 13% 長(zhǎng)春門禁智能有限公司
門禁控制器 獨(dú)有特點(diǎn):軟件采用C++語(yǔ)言速度更快、MYSQL數(shù)據(jù)庫(kù)(國(guó)內(nèi)獨(dú)有)、通過(guò)歐盟ISO9001及ISO14000認(rèn)證;1.國(guó)內(nèi)首創(chuàng)三種通訊方式( 查看價(jià)格 查看價(jià)格

開(kāi)爾瑞

個(gè) 13% 長(zhǎng)春門禁智能有限公司
系統(tǒng)軟件WebCTPL 1、基于開(kāi)放標(biāo)準(zhǔn)的Web技術(shù)開(kāi)發(fā),系統(tǒng)為B/S架構(gòu)模式,無(wú)需任何特殊的軟件或插件; 2、可采用"時(shí)光機(jī)(Time-Lapse)"功能,以圖像趨勢(shì)和多種報(bào)警形式對(duì)樓宇設(shè)備進(jìn)行診斷; 3、采用先進(jìn)安全 查看價(jià)格 查看價(jià)格

13% 北京精誠(chéng)智深科技有限公司
材料名稱 規(guī)格/型號(hào) 除稅
信息價(jià)
含稅
信息價(jià)
行情 品牌 單位 稅率 地區(qū)/時(shí)間
暫無(wú)數(shù)據(jù)
材料名稱 規(guī)格/需求量 報(bào)價(jià)數(shù) 最新報(bào)價(jià)
(元)
供應(yīng)商 報(bào)價(jià)地區(qū) 最新報(bào)價(jià)時(shí)間
MYSQL數(shù)據(jù)庫(kù) MySQL標(biāo)準(zhǔn)版|1套 3 查看價(jià)格 廣州五洋計(jì)算機(jī)信息有限公司 廣東   2020-04-10
MySQL數(shù)據(jù)庫(kù)軟件 -|47套 1 查看價(jià)格 深圳市瑞爾時(shí)代科技有限公司    2014-11-07
數(shù)據(jù)庫(kù) mysql|1套 1 查看價(jià)格 廣州市熹尚科技設(shè)備有限公司 全國(guó)   2019-08-09
數(shù)據(jù)庫(kù)軟件 MySQL|1套 3 查看價(jià)格 深圳市諾達(dá)自動(dòng)化技術(shù)有限公司 全國(guó)   2018-07-16
數(shù)據(jù)庫(kù) MySQL|1套 3 查看價(jià)格 廣州中浩控制技術(shù)有限公司 全國(guó)   2017-09-27
數(shù)據(jù)庫(kù)軟件 MySQL|1套 1 查看價(jià)格 珠海派諾科技股份有限公司 廣東   2020-12-08
數(shù)據(jù)庫(kù)軟件 MySQL|1套 3 查看價(jià)格 深圳市中電電力技術(shù)股份有限公司 廣東  深圳市 2018-08-30
數(shù)據(jù)庫(kù) MySQL|1套 -14 查看價(jià)格 珠海派諾科技股份有限公司 廣東  陽(yáng)江市 2017-08-15

Navicat for MySQL常見(jiàn)問(wèn)題

  • mysql定時(shí)器該如何設(shè)置

    清楚了.基于簡(jiǎn)單php頁(yè)面的:方法1掃描的時(shí)候發(fā)個(gè)請(qǐng)求到php頁(yè)面(麻煩),php頁(yè)面判斷是否時(shí)間到了.方法2在客戶端瀏覽器使用定時(shí)器,請(qǐng)求php頁(yè)面,php頁(yè)面判斷是否時(shí)間到了.基于復(fù)雜php頁(yè)面的...

  • mysql 建表時(shí)的價(jià)格用什么類型定義?

    MySQL中可以用來(lái)做價(jià)格的字段一般有float、double、decimal如定義一個(gè)money字段類型為float(9,2),則money字段最大長(zhǎng)度為9,整數(shù)位是6位,小數(shù)位是2位。以下摘自My...

  • 大神,求助!?。槭裁磎ysql安裝時(shí)安裝進(jìn)度不動(dòng)了

    你之前是否安裝過(guò)mysql? 圖片顯示停止在安裝mysql服務(wù)上,如果你之前安裝過(guò),沒(méi)有卸載干凈,安裝服務(wù)會(huì)安裝不上去的

Navicat for MySQL文獻(xiàn)

PAVIRO廣播說(shuō)明 PAVIRO廣播說(shuō)明

格式:pdf

大小:726KB

頁(yè)數(shù): 13頁(yè)

評(píng)分: 4.7

v1.0 可編輯可修改 1 - 1 - BOSCH PRVIRO 公共廣播系統(tǒng)解決方案 v1.0 可編輯可修改 2 - 2 - 目 錄 一、項(xiàng)目概述 ...................................................................................................................................... 3 二、項(xiàng)目需求分析 ................................................................

立即下載
DPNAVIGI斷路器 DPNAVIGI斷路器

格式:pdf

大小:726KB

頁(yè)數(shù): 3頁(yè)

評(píng)分: 4.7

DPNAVIGI斷路器

立即下載

很早之前我寫(xiě)過(guò)幾篇關(guān)于MySQL死鎖的分析,比如

但是感覺(jué)不過(guò)癮,而且分析的都是一些特定的場(chǎng)景,好像還缺少一些舉一反三的感覺(jué),所以今天就補(bǔ)上這一波。

MySQL里的鎖兼容列表大體是這樣的關(guān)系,如果第一次看會(huì)有些暈,感覺(jué)抓不住重點(diǎn),其實(shí)有一點(diǎn)小技巧。

首先InnoDB實(shí)現(xiàn)了兩種類似的行鎖,即S(共享鎖)和X(排他鎖),而InnoDB層面的表級(jí)意向鎖有IS(意向共享鎖)和IX9意向排他鎖),意向鎖之間是互相兼容的,這句話很重要,按照這個(gè)思路里面一半的內(nèi)容就明確了。而另外一部分則是S和X的兼容性。帶著S鎖和X鎖的組合都是互相排斥,只有一類場(chǎng)景例外,那就是都是S鎖,是兼容的。所以這個(gè)圖按照這個(gè)思路幾乎不用記就能基本理解了。

看起來(lái)S鎖的組合是很柔和的,從這種場(chǎng)景來(lái)看保持兼容,那么出死鎖的概率應(yīng)該很低吧,其實(shí)在RR,RC隔離級(jí)別下我們可以逐步擴(kuò)展然后舉一反三。

如果S鎖的組合在兩個(gè)會(huì)話中是互相兼容,那么接下來(lái)的X鎖的組合就是互相排斥的。

那么在兩個(gè)會(huì)話并發(fā)的場(chǎng)景下,死鎖的步驟如下:

mysql> create table dt1 (id int unique

Query OK, 0 rows affected (0.03 sec)

會(huì)話1:

begin;

select *from dt1 lock in share mode; --顯式共享鎖

會(huì)話2:

begin;

select *from dt1 lock in share mode; --顯式共享鎖

會(huì)話1:

insert into dt1 values(1); --阻塞

會(huì)話2:

insert into dt1 values(2); --觸發(fā)死鎖

所以上面的語(yǔ)句特點(diǎn)很明顯,插入的數(shù)據(jù)分別是1和2,看起來(lái)互補(bǔ)沖突也不行。

我們進(jìn)度稍快一些,我們可能很少看到直接聲明share mode的方式,但是有很多時(shí)候由其他的場(chǎng)景會(huì)觸發(fā),其中的一個(gè)主要原因就在于對(duì)于duplicate數(shù)據(jù)的檢查會(huì)開(kāi)啟S鎖。這是比較特別的一點(diǎn),需要注意。

按照這一點(diǎn)來(lái)擴(kuò)展,很容易就可以擴(kuò)展到3個(gè)會(huì)話中。

會(huì)話1只是負(fù)責(zé)插入一條數(shù)據(jù),會(huì)話2,3也緊接著插入一條記錄(會(huì)話2,3自動(dòng)提交),但是因?yàn)槲ㄒ恍运饕臋z查,會(huì)導(dǎo)致會(huì)話2和會(huì)話3都開(kāi)啟了S鎖,因?yàn)榧嫒荩詴簳r(shí)還沒(méi)影響。如果會(huì)話1正常提交,會(huì)話2,3的檢查會(huì)生效,導(dǎo)致數(shù)據(jù)插入不了,違反唯一性約束,但是我們反其道而行,就可以用一個(gè)rollback來(lái)釋放鎖,緊接著會(huì)話2和會(huì)話3都會(huì)獲得S鎖成功,緊接著獲得X鎖,細(xì)節(jié)算法就不說(shuō)了。這個(gè)時(shí)候互相阻塞,導(dǎo)致會(huì)話3產(chǎn)生死鎖,會(huì)話2的數(shù)據(jù)插入依然會(huì)成功。

會(huì)話1:

begin;

insert into dt1 values(1);

會(huì)話2:

insert into dt1 values(1);

會(huì)話3:

insert into dt1 values(1);

會(huì)話1:

rollback;

看起來(lái)很精巧的小測(cè)試,但是里面蘊(yùn)含這大道理,比如按照這個(gè)思路,如果后面的兩個(gè)語(yǔ)句都是delete,也會(huì)觸發(fā)死鎖。有的時(shí)候我們可以正面來(lái)圖例,或者通過(guò)死鎖日志來(lái)推理。給我的一個(gè)啟發(fā)是太極。

放在鎖的角度來(lái)理解就會(huì)好很多。

用一張不太形象的圖表示就是,左邊的部分是insert操作在會(huì)話1中,右邊的是在會(huì)話2和會(huì)話3中,都持有S鎖,然后會(huì)因?yàn)橥瑯拥脑蚴聞?wù)回滾后,他們的S鎖會(huì)升級(jí)為X鎖導(dǎo)致死鎖發(fā)生。

按照這個(gè)思路,我們可以繼續(xù)擴(kuò)展出幾個(gè)場(chǎng)景。比如delete的方式。

按照這樣的思路,可以構(gòu)建出很多的死鎖場(chǎng)景來(lái)。

頁(yè)級(jí):引擎 BDB。

表級(jí):引擎 MyISAM , 理解為鎖住整個(gè)表,可以同時(shí)讀,寫(xiě)不行

行級(jí):引擎 INNODB , 單獨(dú)的一行記錄加鎖

表級(jí),直接鎖定整張表,在你鎖定期間,其它進(jìn)程無(wú)法對(duì)該表進(jìn)行寫(xiě)操作。如果你是寫(xiě)鎖,則其它進(jìn)程則讀也不允許

行級(jí),,僅對(duì)指定的記錄進(jìn)行加鎖,這樣其它進(jìn)程還是可以對(duì)同一個(gè)表中的其它記錄進(jìn)行操作。

頁(yè)級(jí),表級(jí)鎖速度快,但沖突多,行級(jí)沖突少,但速度慢。所以取了折衷的頁(yè)級(jí),一次鎖定相鄰的一組記錄。

MySQL 5.1支持對(duì)MyISAM和MEMORY表進(jìn)行表級(jí)鎖定,對(duì)BDB表進(jìn)行頁(yè)級(jí)鎖定,對(duì)InnoDB表進(jìn)行行級(jí)鎖定。

對(duì)WRITE,MySQL使用的表鎖定方法原理如下:

如果在表上沒(méi)有鎖,在它上面放一個(gè)寫(xiě)鎖。

否則,把鎖定請(qǐng)求放在寫(xiě)鎖定隊(duì)列中。

對(duì)READ,MySQL使用的鎖定方法原理如下:

如果在表上沒(méi)有寫(xiě)鎖定,把一個(gè)讀鎖定放在它上面

否則,把鎖請(qǐng)求放在讀鎖定隊(duì)列中。

InnoDB使用行鎖定,BDB使用頁(yè)鎖定。對(duì)于這兩種存儲(chǔ)引擎,都可能存在死鎖。這是因?yàn)?,在SQL語(yǔ)句處理期間,InnoDB自動(dòng)獲得行鎖定和BDB獲得頁(yè)鎖定,而不是在事務(wù)啟動(dòng)時(shí)獲得。

行級(jí)鎖定的優(yōu)點(diǎn):

· 當(dāng)在許多線程中訪問(wèn)不同的行時(shí)只存在少量鎖定沖突。

· 回滾時(shí)只有少量的更改。

· 可以長(zhǎng)時(shí)間鎖定單一的行。

行級(jí)鎖定的缺點(diǎn):

· 比頁(yè)級(jí)或表級(jí)鎖定占用更多的內(nèi)存。

· 當(dāng)在表的大部分中使用時(shí),比頁(yè)級(jí)或表級(jí)鎖定速度慢,因?yàn)槟惚仨毇@取更多的鎖。

· 如果你在大部分?jǐn)?shù)據(jù)上經(jīng)常進(jìn)行GROUP BY操作或者必須經(jīng)常掃描整個(gè)表,比其它鎖定明顯慢很多。

· 用高級(jí)別鎖定,通過(guò)支持不同的類型鎖定,你也可以很容易地調(diào)節(jié)應(yīng)用程序,因?yàn)槠滏i成本小于行級(jí)鎖定。

在以下情況下,表鎖定優(yōu)先于頁(yè)級(jí)或行級(jí)鎖定:

· 表的大部分語(yǔ)句用于讀取。

· 對(duì)嚴(yán)格的關(guān)鍵字進(jìn)行讀取和更新,你可以更新或刪除可以用單一的讀取的關(guān)鍵字來(lái)提取的一行:

· UPDATE tbl_name SET column=value WHERE unique_key_col=key_value;

· DELETE FROM tbl_name WHERE unique_key_col=key_value;

· SELECT 結(jié)合并行的INSERT語(yǔ)句,并且只有很少的UPDATE或DELETE語(yǔ)句。

· 在整個(gè)表上有許多掃描或GROUP BY操作,沒(méi)有任何寫(xiě)操作。

/* ========================= mysql 鎖表類型和解鎖語(yǔ)句 ========================= */

如果想要在一個(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;

行級(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)存。

鎖是計(jì)算機(jī)協(xié)調(diào)多個(gè)進(jìn)程或線程并發(fā)訪問(wèn)某一資源的機(jī)制,不同的數(shù)據(jù)庫(kù)的鎖機(jī)制大同小異。由于數(shù)據(jù)庫(kù)資源是一種供許多用戶共享的資源,所以如何保證數(shù)據(jù)并發(fā)訪問(wèn)的一致性、有效性是所有數(shù)據(jù)庫(kù)必須解決的一個(gè)問(wèn)題,鎖沖突也是影響數(shù)據(jù)庫(kù)并發(fā)訪問(wèn)性能的一個(gè)重要因素。了解鎖機(jī)制不僅可以使我們更有效的開(kāi)發(fā)利用數(shù)據(jù)庫(kù)資源,也使我們能夠更好地維護(hù)數(shù)據(jù)庫(kù),從而提高數(shù)據(jù)庫(kù)的性能。

MySQL的鎖機(jī)制比較簡(jiǎn)單,其最顯著的特點(diǎn)是不同的存儲(chǔ)引擎支持不同的鎖機(jī)制。

例如,MyISAM和MEMORY存儲(chǔ)引擎采用的是表級(jí)鎖(table-level-locking);BDB存儲(chǔ)引擎采用的是頁(yè)面鎖(page-level-locking),同時(shí)也支持表級(jí)鎖;InnoDB存儲(chǔ)引擎既支持行級(jí)鎖,也支持表級(jí)鎖,默認(rèn)情況下是采用行級(jí)鎖。

上述三種鎖的特性可大致歸納如下:

1) 表級(jí)鎖:開(kāi)銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低。

2) 行級(jí)鎖:開(kāi)銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度也最高。

3) 頁(yè)面鎖:開(kāi)銷和加鎖時(shí)間界于表鎖和行鎖之間;會(huì)出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般。

三種鎖各有各的特點(diǎn),若僅從鎖的角度來(lái)說(shuō),表級(jí)鎖更適合于以查詢?yōu)橹?,只有少量按索引條件更新數(shù)據(jù)的應(yīng)用,如WEB應(yīng)用;行級(jí)鎖更適合于有大量按索引條件并發(fā)更新少量不同數(shù)據(jù),同時(shí)又有并發(fā)查詢的應(yīng)用,如一些在線事務(wù)處理(OLTP)系統(tǒng)。

MySQL表級(jí)鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨(dú)占寫(xiě)鎖(Table Write Lock)。什么意思呢,就是說(shuō)對(duì)MyISAM表進(jìn)行讀操作時(shí),它不會(huì)阻塞其他用戶對(duì)同一表的讀請(qǐng)求,但會(huì)阻塞 對(duì)同一表的寫(xiě)操作;而對(duì)MyISAM表的寫(xiě)操作,則會(huì)阻塞其他用戶對(duì)同一表的讀和寫(xiě)操作。

MyISAM表的讀和寫(xiě)是串行的,即在進(jìn)行讀操作時(shí)不能進(jìn)行寫(xiě)操作,反之也是一樣。但在一定條件下MyISAM表也支持查詢和插入的操作的并發(fā)進(jìn)行,其機(jī)制是通過(guò)控制一個(gè)系統(tǒng)變量(concurrent_insert)來(lái)進(jìn)行的,當(dāng)其值設(shè)置為0時(shí),不允許并發(fā)插入;當(dāng)其值設(shè)置為1 時(shí),如果MyISAM表中沒(méi)有空洞(即表中沒(méi)有被刪除的行),MyISAM允許在一個(gè)進(jìn)程讀表的同時(shí),另一個(gè)進(jìn)程從表尾插入記錄;當(dāng)其值設(shè)置為2時(shí),無(wú)論MyISAM表中有沒(méi)有空洞,都允許在表尾并發(fā)插入記錄。

MyISAM鎖調(diào)度是如何實(shí)現(xiàn)的呢,這也是一個(gè)很關(guān)鍵的問(wèn)題。例如,當(dāng)一個(gè)進(jìn)程請(qǐng)求某個(gè)MyISAM表的讀鎖,同時(shí)另一個(gè)進(jìn)程也請(qǐng)求同一表的寫(xiě)鎖,此時(shí)MySQL將會(huì)如優(yōu)先處理進(jìn)程呢?通過(guò)研究表明,寫(xiě)進(jìn)程將先獲得鎖(即使讀請(qǐng)求先到鎖等待隊(duì)列)。但這也造成一個(gè)很大的缺陷,即大量的寫(xiě)操作會(huì)造成查詢操作很難獲得讀鎖,從而可能造成永遠(yuǎn)阻塞。所幸我們可以通過(guò)一些設(shè)置來(lái)調(diào)節(jié)MyISAM的調(diào)度行為。我們可通過(guò)指定參數(shù)low-priority-updates,使MyISAM默認(rèn)引擎給予讀請(qǐng)求以優(yōu)先的權(quán)利,設(shè)置其值為1(set low_priority_updates=1),使優(yōu)先級(jí)降低。

InnoDB鎖與MyISAM鎖的最大不同在于:一是支持事務(wù)(TRANCSACTION),二是采用了行級(jí)鎖。我們知道事務(wù)是由一組SQL語(yǔ)句組成的邏輯處理單元,其有四個(gè)屬性(簡(jiǎn)稱ACID屬性),分別為:

原子性(Atomicity):事務(wù)是一個(gè)原子操作單元,其對(duì)數(shù)據(jù)的修改,要么全部執(zhí)行,要么全都不執(zhí)行;

一致性(Consistent):在事務(wù)開(kāi)始和完成時(shí),數(shù)據(jù)都必須保持一致?tīng)顟B(tài);

隔離性(Isolation):數(shù)據(jù)庫(kù)系統(tǒng)提供一定的隔離機(jī)制,保證事務(wù)在不受外部并發(fā)操作影響的“獨(dú)立”環(huán)境執(zhí)行;

持久性(Durable):事務(wù)完成之后,它對(duì)于數(shù)據(jù)的修改是永久性的,即使出現(xiàn)系統(tǒng)故障也能夠保持。

InnoDB有兩種模式的行鎖:

1)共享鎖:允許一個(gè)事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖。

( Select * from table_name where ......lock in share mode)

2)排他鎖:允許獲得排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)取得相同數(shù)據(jù)集的共享讀鎖和 排他寫(xiě)鎖。(select * from table_name where.....for update)

為了允許行鎖和表鎖共存,實(shí)現(xiàn)多粒度鎖機(jī)制;同時(shí)還有兩種內(nèi)部使用的意向鎖(都是表鎖),分別為意向共享鎖和意向排他鎖。

InnoDB行鎖是通過(guò)給索引項(xiàng)加鎖來(lái)實(shí)現(xiàn)的,即只有通過(guò)索引條件檢索數(shù)據(jù),InnoDB才使用行級(jí)鎖,否則將使用表鎖!

另外:插入,更新性能優(yōu)化的幾個(gè)重要參數(shù)

代碼如下:

bulk_insert_buffer_size

批量插入緩存大小, 這個(gè)參數(shù)是針對(duì)MyISAM存儲(chǔ)引擎來(lái)說(shuō)的.適用于在一次性插入100-1000+條記錄時(shí), 提高效率.默認(rèn)值是8M.可以針對(duì)數(shù)據(jù)量的大小,翻倍增加.

concurrent_insert

并發(fā)插入, 當(dāng)表沒(méi)有空洞(刪除過(guò)記錄), 在某進(jìn)程獲取讀鎖的情況下,其他進(jìn)程可以在表尾部進(jìn)行插入.

值可以設(shè)0不允許并發(fā)插入, 1當(dāng)表沒(méi)有空洞時(shí), 執(zhí)行并發(fā)插入, 2不管是否有空洞都執(zhí)行并發(fā)插入.

默認(rèn)是1 針對(duì)表的刪除頻率來(lái)設(shè)置.

delay_key_write

針對(duì)MyISAM存儲(chǔ)引擎,延遲更新索引.意思是說(shuō),update記錄時(shí),先將數(shù)據(jù)up到磁盤(pán),但不up索引,將索引存在內(nèi)存里,當(dāng)表關(guān)閉時(shí),將內(nèi)存索引,寫(xiě)到磁盤(pán). 值為 0不開(kāi)啟, 1開(kāi)啟. 默認(rèn)開(kāi)啟.

delayed_insert_limit, delayed_insert_timeout, delayed_queue_size

延遲插入, 將數(shù)據(jù)先交給內(nèi)存隊(duì)列, 然后慢慢地插入.但是這些配置,不是所有的存儲(chǔ)引擎都支持, 目前來(lái)看, 常用的InnoDB不支持, MyISAM支持. 根據(jù)實(shí)際情況調(diào)大, 一般默認(rèn)夠用了

/* ==================== MySQL InnoDB 鎖表與鎖行 ======================== */

由于InnoDB預(yù)設(shè)是Row-Level Lock,所以只有「明確」的指定主鍵,MySQL才會(huì)執(zhí)行Row lock (只鎖住被選取的資料例) ,否則MySQL將會(huì)執(zhí)行Table Lock (將整個(gè)資料表單給鎖住)。

舉個(gè)例子: 假設(shè)有個(gè)表單products ,里面有id跟name二個(gè)欄位,id是主鍵。

例1: (明確指定主鍵,并且有此筆資料,row lock)

代碼如下:

SELECT * FROM products WHERE id='3' FOR UPDATE;

SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE;

例2: (明確指定主鍵,若查無(wú)此筆資料,無(wú)lock)

代碼如下:

SELECT * FROM products WHERE id='-1' FOR UPDATE;

例3: (無(wú)主鍵,table lock)

代碼如下:

SELECT * FROM products WHERE name='Mouse' FOR UPDATE;

例4: (主鍵不明確,table lock)

代碼如下:

SELECT * FROM products WHERE id<>'3' FOR UPDATE;

例5: (主鍵不明確,table lock)

代碼如下:

SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;

注1: FOR UPDATE僅適用于InnoDB,且必須在交易區(qū)塊(BEGIN/COMMIT)中才能生效。

注2: 要測(cè)試鎖定的狀況,可以利用MySQL的Command Mode ,開(kāi)二個(gè)視窗來(lái)做測(cè)試。

在MySql 5.0中測(cè)試確實(shí)是這樣的

另外:MyAsim 只支持表級(jí)鎖,InnerDB支持行級(jí)鎖

添加了(行級(jí)鎖/表級(jí)鎖)鎖的數(shù)據(jù)不能被其它事務(wù)再鎖定,也不被其它事務(wù)修改(修改、刪除)

是表級(jí)鎖時(shí),不管是否查詢到記錄,都會(huì)鎖定表

此外,如果A與B都對(duì)表id進(jìn)行查詢但查詢不到記錄,則A與B在查詢上不會(huì)進(jìn)行row鎖,但A與B都會(huì)獲取排它鎖,此時(shí)A再插入一條記錄的話則會(huì)因?yàn)锽已經(jīng)有鎖而處于等待中,此時(shí)B再插入一條同樣的數(shù)據(jù)則會(huì)拋出Deadlock found when trying to get lock; try restarting transaction然后釋放鎖,此時(shí)A就獲得了鎖而插入成功

鎖,在現(xiàn)實(shí)生活中是為我們想要隱藏于外界所使用的一種工具。在計(jì)算機(jī)中,是協(xié)調(diào)多個(gè)進(jìn)程或縣城并發(fā)訪問(wèn)某一資源的一種機(jī)制。在數(shù)據(jù)庫(kù)當(dāng)中,除了傳統(tǒng)的計(jì)算資源(CPU、RAM、I/O等等)的爭(zhēng)用之外,數(shù)據(jù)也是一種供許多用戶共享訪問(wèn)的資源。如何保證數(shù)據(jù)并發(fā)訪問(wèn)的一致性、有效性,是所有數(shù)據(jù)庫(kù)必須解決的一個(gè)問(wèn)題,鎖的沖突也是影響數(shù)據(jù)庫(kù)并發(fā)訪問(wèn)性能的一個(gè)重要因素。從這一角度來(lái)說(shuō),鎖對(duì)于數(shù)據(jù)庫(kù)而言就顯得尤為重要。

MySQL鎖

相對(duì)于其他的數(shù)據(jù)庫(kù)而言,MySQL的鎖機(jī)制比較簡(jiǎn)單,最顯著的特點(diǎn)就是不同的存儲(chǔ)引擎支持不同的鎖機(jī)制。根據(jù)不同的存儲(chǔ)引擎,MySQL中鎖的特性可以大致歸納如下:

行鎖 表鎖 頁(yè)鎖
MyISAM
BDB
InnoDB

開(kāi)銷、加鎖速度、死鎖、粒度、并發(fā)性能

表鎖:開(kāi)銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定力度大,發(fā)生鎖沖突概率高,并發(fā)度最低

行鎖:開(kāi)銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度小,發(fā)生鎖沖突的概率低,并發(fā)度高

頁(yè)鎖:開(kāi)銷和加鎖速度介于表鎖和行鎖之間;會(huì)出現(xiàn)死鎖;鎖定粒度介于表鎖和行鎖之間,并發(fā)度一般

從上述的特點(diǎn)課件,很難籠統(tǒng)的說(shuō)哪種鎖最好,只能根據(jù)具體應(yīng)用的特點(diǎn)來(lái)說(shuō)哪種鎖更加合適。僅僅從鎖的角度來(lái)說(shuō)的話:

表鎖更適用于以查詢?yōu)橹?,只有少量按索引條件更新數(shù)據(jù)的應(yīng)用;行鎖更適用于有大量按索引條件并發(fā)更新少量不同數(shù)據(jù),同時(shí)又有并發(fā)查詢的應(yīng)用。(PS:由于BDB已經(jīng)被InnoDB所取代,我們只討論MyISAM表鎖和InnoDB行鎖的問(wèn)題)

MyISAM表鎖

MyISAM存儲(chǔ)引擎只支持表鎖,這也是MySQL開(kāi)始幾個(gè)版本中唯一支持的鎖類型。隨著應(yīng)用對(duì)事務(wù)完整性和并發(fā)性要求的不斷提高,MySQL才開(kāi)始開(kāi)發(fā)基于事務(wù)的存儲(chǔ)引擎,后來(lái)慢慢出現(xiàn)了支持頁(yè)鎖的BDB存儲(chǔ)引擎和支持行鎖的InnoDB存儲(chǔ)引擎(實(shí)際 InnoDB是單獨(dú)的一個(gè)公司,現(xiàn)在已經(jīng)被Oracle公司收購(gòu))。但是MyISAM的表鎖依然是使用最為廣泛的鎖類型。本節(jié)將詳細(xì)介紹MyISAM表鎖的使用。

查詢表級(jí)鎖爭(zhēng)用情況

可以通過(guò)檢查table_locks_waited和table_locks_immediate狀態(tài)變量來(lái)分析系統(tǒng)上的表鎖定爭(zhēng)奪:

mysql> show status like 'table%';

+-----------------------+-------+

| Variable_name | Value |

+-----------------------+-------+

| Table_locks_immediate | 2979 |

| Table_locks_waited | 0 |

+-----------------------+-------+

2 rows in set (0.00 sec))

如果Table_locks_waited的值比較高,則說(shuō)明存在著較嚴(yán)重的表級(jí)鎖爭(zhēng)用情況。

MySQL表級(jí)鎖的鎖模式

MySQL的表級(jí)鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨(dú)占寫(xiě)鎖(Table Write Lock)。鎖模式的兼容性如下表所示。

MySQL中的表鎖兼容性

請(qǐng)求鎖模式

是否兼容

當(dāng)前鎖模式

None 讀鎖 寫(xiě)鎖
讀鎖
寫(xiě)鎖

可見(jiàn),對(duì)MyISAM表的讀操作,不會(huì)阻塞其他用戶對(duì)同一表的讀請(qǐng)求,但會(huì)阻塞對(duì)同一表的寫(xiě)請(qǐng)求;對(duì) MyISAM表的寫(xiě)操作,則會(huì)阻塞其他用戶對(duì)同一表的讀和寫(xiě)操作;MyISAM表的讀操作與寫(xiě)操作之間,以及寫(xiě)操作之間是串行的!根據(jù)如下表所示的例子可以知道,當(dāng)一個(gè)線程獲得對(duì)一個(gè)表的寫(xiě)鎖后,只有持有鎖的線程可以對(duì)表進(jìn)行更新操作。其他線程的讀、寫(xiě)操作都會(huì)等待,直到鎖被釋放為止。

MyISAM存儲(chǔ)引擎的寫(xiě)阻塞讀例子

session_1 session_2

獲得表film_text的WRITE鎖定

mysql> lock table film_text write;

Query OK, 0 rows affected (0.00 sec)

當(dāng)前session對(duì)鎖定表的查詢、更新、插入操作都可以執(zhí)行:

mysql> select film_id,title from film_text where film_id = 1001;

+---------+-------------+

| film_id | title |

+---------+-------------+

| 1001 | Update Test |

+---------+-------------+

1 row in set (0.00 sec)

mysql> insert into film_text (film_id,title) values(1003,'Test');

Query OK, 1 row affected (0.00 sec)

mysql> update film_text set title = 'Test' where film_id = 1001;

Query OK, 1 row affected (0.00 sec)

Rows matched: 1 Changed: 1 Warnings: 0

其他session對(duì)鎖定表的查詢被阻塞,需要等待鎖被釋放:

mysql> select film_id,title from film_text where film_id = 1001;

等待

釋放鎖:

mysql> unlock tables;

Query OK, 0 rows affected (0.00 sec)

等待

Session2獲得鎖,查詢返回:

mysql> select film_id,title from film_text where film_id = 1001;

+---------+-------+

| film_id | title |

+---------+-------+

| 1001 | Test |

+---------+-------+

1 row in set (57.59 sec)

如何加表鎖

MyISAM在執(zhí)行查詢語(yǔ)句(SELECT)前,會(huì)自動(dòng)給涉及的所有表加讀鎖,在執(zhí)行更新操作(UPDATE、DELETE、INSERT等)前,會(huì)自動(dòng)給涉及的表加寫(xiě)鎖,這個(gè)過(guò)程并不需要用戶干預(yù),因此,用戶一般不需要直接用LOCK TABLE命令給MyISAM表顯式加鎖。在本書(shū)的示例中,顯式加鎖基本上都是為了方便而已,并非必須如此。

給MyISAM表顯示加鎖,一般是為了在一定程度模擬事務(wù)操作,實(shí)現(xiàn)對(duì)某一時(shí)間點(diǎn)多個(gè)表的一致性讀取。例如,有一個(gè)訂單表orders,其中記錄有各訂單的總金額total,同時(shí)還有一個(gè)訂單明細(xì)表order_detail,其中記錄有各訂單每一產(chǎn)品的金額小計(jì) subtotal,假設(shè)我們需要檢查這兩個(gè)表的金額合計(jì)是否相符,可能就需要執(zhí)行如下兩條SQL:

Select sum(total) from orders;

Select sum(subtotal) from order_detail;

這時(shí),如果不先給兩個(gè)表加鎖,就可能產(chǎn)生錯(cuò)誤的結(jié)果,因?yàn)榈谝粭l語(yǔ)句執(zhí)行過(guò)程中,order_detail表可能已經(jīng)發(fā)生了改變。因此,正確的方法應(yīng)該是:

Lock tables orders read local, order_detail read local;

Select sum(total) from orders;

Select sum(subtotal) from order_detail;

Unlock tables;

要特別說(shuō)明以下兩點(diǎn)內(nèi)容。

上面的例子在LOCK TABLES時(shí)加了“l(fā)ocal”選項(xiàng),其作用就是在滿足MyISAM表并發(fā)插入條件的情況下,允許其他用戶在表尾并發(fā)插入記錄,有關(guān)MyISAM表的并發(fā)插入問(wèn)題,在后面的章節(jié)中還會(huì)進(jìn)一步介紹。

在用LOCK TABLES給表顯式加表鎖時(shí),必須同時(shí)取得所有涉及到表的鎖,并且MySQL不支持鎖升級(jí)。也就是說(shuō),在執(zhí)行LOCK TABLES后,只能訪問(wèn)顯式加鎖的這些表,不能訪問(wèn)未加鎖的表;同時(shí),如果加的是讀鎖,那么只能執(zhí)行查詢操作,而不能執(zhí)行更新操作。其實(shí),在自動(dòng)加鎖的情況下也基本如此,MyISAM總是一次獲得SQL語(yǔ)句所需要的全部鎖。這也正是MyISAM表不會(huì)出現(xiàn)死鎖(Deadlock Free)的原因。

在如下表所示的例子中,一個(gè)session使用LOCK TABLE命令給表film_text加了讀鎖,這個(gè)session可以查詢鎖定表中的記錄,但更新或訪問(wèn)其他表都會(huì)提示錯(cuò)誤;同時(shí),另外一個(gè)session可以查詢表中的記錄,但更新就會(huì)出現(xiàn)鎖等待。

MyISAM存儲(chǔ)引擎的讀阻塞寫(xiě)例子

session_1 session_2

獲得表film_text的READ鎖定

mysql> lock table film_text read;

Query OK, 0 rows affected (0.00 sec)

當(dāng)前session可以查詢?cè)摫碛涗?/p>

mysql> select film_id,title from film_text where film_id = 1001;

+---------+------------------+

| film_id | title |

+---------+------------------+

| 1001 | ACADEMY DINOSAUR |

+---------+------------------+

1 row in set (0.00 sec)

其他session也可以查詢?cè)摫淼挠涗?/p>

mysql> select film_id,title from film_text where film_id = 1001;

+---------+------------------+

| film_id | title |

+---------+------------------+

| 1001 | ACADEMY DINOSAUR |

+---------+------------------+

1 row in set (0.00 sec)

當(dāng)前session不能查詢沒(méi)有鎖定的表

mysql> select film_id,title from film where film_id = 1001;

ERROR 1100 (HY000): Table 'film' was not locked with LOCK TABLES

其他session可以查詢或者更新未鎖定的表

mysql> select film_id,title from film where film_id = 1001;

+---------+---------------+

| film_id | title |

+---------+---------------+

| 1001 | update record |

+---------+---------------+

1 row in set (0.00 sec)

mysql> update film set title = 'Test' where film_id = 1001;

Query OK, 1 row affected (0.04 sec)

Rows matched: 1 Changed: 1 Warnings: 0

當(dāng)前session中插入或者更新鎖定的表都會(huì)提示錯(cuò)誤:

mysql> insert into film_text (film_id,title) values(1002,'Test');

ERROR 1099 (HY000): Table 'film_text' was locked with a READ lock and can't be updated

mysql> update film_text set title = 'Test' where film_id = 1001;

ERROR 1099 (HY000): Table 'film_text' was locked with a READ lock and can't be updated

其他session更新鎖定表會(huì)等待獲得鎖:

mysql> update film_text set title = 'Test' where film_id = 1001;

等待

釋放鎖

mysql> unlock tables;

Query OK, 0 rows affected (0.00 sec)

等待

Session獲得鎖,更新操作完成:

mysql> update film_text set title = 'Test' where film_id = 1001;

Query OK, 1 row affected (1 min 0.71 sec)

Rows matched: 1 Changed: 1 Warnings: 0

注意,當(dāng)使用LOCK TABLES時(shí),不僅需要一次鎖定用到的所有表,而且,同一個(gè)表在SQL語(yǔ)句中出現(xiàn)多少次,就要通過(guò)與SQL語(yǔ)句中相同的別名鎖定多少次,否則也會(huì)出錯(cuò)!舉例說(shuō)明如下。

(1)對(duì)actor表獲得讀鎖:

mysql> lock table actor read;

Query OK, 0 rows affected (0.00 sec)

(2)但是通過(guò)別名訪問(wèn)會(huì)提示錯(cuò)誤:

mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom' and a.last_name <> b.last_name;

ERROR 1100 (HY000): Table 'a' was not locked with LOCK TABLES

(3)需要對(duì)別名分別鎖定:

mysql> lock table actor as a read,actor as b read;

Query OK, 0 rows affected (0.00 sec)

(4)按照別名的查詢可以正確執(zhí)行:

mysql> select a.first_name,a.last_name,b.first_name,b.last_name from actor a,actor b where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom' and a.last_name <> b.last_name;

+------------+-----------+------------+-----------+

| first_name | last_name | first_name | last_name |

+------------+-----------+------------+-----------+

| Lisa | Tom | LISA | MONROE |

+------------+-----------+------------+-----------+

1 row in set (0.00 sec)

并發(fā)插入(Concurrent Inserts)

上文提到過(guò)MyISAM表的讀和寫(xiě)是串行的,但這是就總體而言的。在一定條件下,MyISAM表也支持查詢和插入操作的并發(fā)進(jìn)行。

MyISAM存儲(chǔ)引擎有一個(gè)系統(tǒng)變量concurrent_insert,專門用以控制其并發(fā)插入的行為,其值分別可以為0、1或2。

當(dāng)concurrent_insert設(shè)置為0時(shí),不允許并發(fā)插入。

當(dāng)concurrent_insert設(shè)置為1時(shí),如果MyISAM表中沒(méi)有空洞(即表的中間沒(méi)有被刪除的行),MyISAM允許在一個(gè)進(jìn)程讀表的同時(shí),另一個(gè)進(jìn)程從表尾插入記錄。這也是MySQL的默認(rèn)設(shè)置。

當(dāng)concurrent_insert設(shè)置為2時(shí),無(wú)論MyISAM表中有沒(méi)有空洞,都允許在表尾并發(fā)插入記錄。

在如下表所示的例子中,session_1獲得了一個(gè)表的READ LOCAL鎖,該線程可以對(duì)表進(jìn)行查詢操作,但不能對(duì)表進(jìn)行更新操作;其他的線程(session_2),雖然不能對(duì)表進(jìn)行刪除和更新操作,但卻可以對(duì)該表進(jìn)行并發(fā)插入操作,這里假設(shè)該表中間不存在空洞。

MyISAM存儲(chǔ)引擎的讀寫(xiě)(INSERT)并發(fā)例子

session_1 session_2

獲得表film_text的READ LOCAL鎖定

mysql> lock table film_text read local;

Query OK, 0 rows affected (0.00 sec)

當(dāng)前session不能對(duì)鎖定表進(jìn)行更新或者插入操作:

mysql> insert into film_text (film_id,title) values(1002,'Test');

ERROR 1099 (HY000): Table 'film_text' was locked with a READ lock and can't be updated

mysql> update film_text set title = 'Test' where film_id = 1001;

ERROR 1099 (HY000): Table 'film_text' was locked with a READ lock and can't be updated

其他session可以進(jìn)行插入操作,但是更新會(huì)等待:

mysql> insert into film_text (film_id,title) values(1002,'Test');

Query OK, 1 row affected (0.00 sec)

mysql> update film_text set title = 'Update Test' where film_id = 1001;

等待

當(dāng)前session不能訪問(wèn)其他session插入的記錄:

mysql> select film_id,title from film_text where film_id = 1002;

Empty set (0.00 sec)

釋放鎖:

mysql> unlock tables;

Query OK, 0 rows affected (0.00 sec)

等待

當(dāng)前session解鎖后可以獲得其他session插入的記錄:

mysql> select film_id,title from film_text where film_id = 1002;

+---------+-------+

| film_id | title |

+---------+-------+

| 1002 | Test |

+---------+-------+

1 row in set (0.00 sec)

Session2獲得鎖,更新操作完成:

mysql> update film_text set title = 'Update Test' where film_id = 1001;

Query OK, 1 row affected (1 min 17.75 sec)

Rows matched: 1 Changed: 1 Warnings: 0

可以利用MyISAM存儲(chǔ)引擎的并發(fā)插入特性,來(lái)解決應(yīng)用中對(duì)同一表查詢和插入的鎖爭(zhēng)用。例如,將concurrent_insert系統(tǒng)變量設(shè)為2,總是允許并發(fā)插入;同時(shí),通過(guò)定期在系統(tǒng)空閑時(shí)段執(zhí)行 OPTIMIZE TABLE語(yǔ)句來(lái)整理空間碎片,收回因刪除記錄而產(chǎn)生的中間空洞。有關(guān)OPTIMIZE TABLE語(yǔ)句的詳細(xì)介紹,可以參見(jiàn)第18章中“兩個(gè)簡(jiǎn)單實(shí)用的優(yōu)化方法”一節(jié)的內(nèi)容。

MyISAM的鎖調(diào)度

前面講過(guò),MyISAM存儲(chǔ)引擎的讀鎖和寫(xiě)鎖是互斥的,讀寫(xiě)操作是串行的。那么,一個(gè)進(jìn)程請(qǐng)求某個(gè) MyISAM表的讀鎖,同時(shí)另一個(gè)進(jìn)程也請(qǐng)求同一表的寫(xiě)鎖,MySQL如何處理呢?答案是寫(xiě)進(jìn)程先獲得鎖。不僅如此,即使讀請(qǐng)求先到鎖等待隊(duì)列,寫(xiě)請(qǐng)求后到,寫(xiě)鎖也會(huì)插到讀鎖請(qǐng)求之前!這是因?yàn)镸ySQL認(rèn)為寫(xiě)請(qǐng)求一般比讀請(qǐng)求要重要。這也正是MyISAM表不太適合于有大量更新操作和查詢操作應(yīng)用的原因,因?yàn)?,大量的更新操作?huì)造成查詢操作很難獲得讀鎖,從而可能永遠(yuǎn)阻塞。這種情況有時(shí)可能會(huì)變得非常糟糕!幸好我們可以通過(guò)一些設(shè)置來(lái)調(diào)節(jié)MyISAM 的調(diào)度行為。

通過(guò)指定啟動(dòng)參數(shù)low-priority-updates,使MyISAM引擎默認(rèn)給予讀請(qǐng)求以優(yōu)先的權(quán)利。

通過(guò)執(zhí)行命令SET LOW_PRIORITY_UPDATES=1,使該連接發(fā)出的更新請(qǐng)求優(yōu)先級(jí)降低。

通過(guò)指定INSERT、UPDATE、DELETE語(yǔ)句的LOW_PRIORITY屬性,降低該語(yǔ)句的優(yōu)先級(jí)。

雖然上面3種方法都是要么更新優(yōu)先,要么查詢優(yōu)先的方法,但還是可以用其來(lái)解決查詢相對(duì)重要的應(yīng)用(如用戶登錄系統(tǒng))中,讀鎖等待嚴(yán)重的問(wèn)題。

另外,MySQL也提供了一種折中的辦法來(lái)調(diào)節(jié)讀寫(xiě)沖突,即給系統(tǒng)參數(shù)max_write_lock_count設(shè)置一個(gè)合適的值,當(dāng)一個(gè)表的讀鎖達(dá)到這個(gè)值后,MySQL就暫時(shí)將寫(xiě)請(qǐng)求的優(yōu)先級(jí)降低,給讀進(jìn)程一定獲得鎖的機(jī)會(huì)。

上面已經(jīng)討論了寫(xiě)優(yōu)先調(diào)度機(jī)制帶來(lái)的問(wèn)題和解決辦法。這里還要強(qiáng)調(diào)一點(diǎn):一些需要長(zhǎng)時(shí)間運(yùn)行的查詢操作,也會(huì)使寫(xiě)進(jìn)程“餓死”!因此,應(yīng)用中應(yīng)盡量避免出現(xiàn)長(zhǎng)時(shí)間運(yùn)行的查詢操作,不要總想用一條SELECT語(yǔ)句來(lái)解決問(wèn)題,因?yàn)檫@種看似巧妙的SQL語(yǔ)句,往往比較復(fù)雜,執(zhí)行時(shí)間較長(zhǎng),在可能的情況下可以通過(guò)使用中間表等措施對(duì)SQL語(yǔ)句做一定的“分解”,使每一步查詢都能在較短時(shí)間完成,從而減少鎖沖突。如果復(fù)雜查詢不可避免,應(yīng)盡量安排在數(shù)據(jù)庫(kù)空閑時(shí)段執(zhí)行,比如一些定期統(tǒng)計(jì)可以安排在夜間執(zhí)行。

InnoDB鎖問(wèn)題

InnoDB與MyISAM的最大不同有兩點(diǎn):一是支持事務(wù)(TRANSACTION);二是采用了行級(jí)鎖。行級(jí)鎖與表級(jí)鎖本來(lái)就有許多不同之處,另外,事務(wù)的引入也帶來(lái)了一些新問(wèn)題。下面我們先介紹一點(diǎn)背景知識(shí),然后詳細(xì)討論InnoDB的鎖問(wèn)題。

背景知識(shí)

1.事務(wù)(Transaction)及其ACID屬性

事務(wù)是由一組SQL語(yǔ)句組成的邏輯處理單元,事務(wù)具有以下4個(gè)屬性,通常簡(jiǎn)稱為事務(wù)的ACID屬性。

原子性(Atomicity):事務(wù)是一個(gè)原子操作單元,其對(duì)數(shù)據(jù)的修改,要么全都執(zhí)行,要么全都不執(zhí)行。

一致性(Consistent):在事務(wù)開(kāi)始和完成時(shí),數(shù)據(jù)都必須保持一致?tīng)顟B(tài)。這意味著所有相關(guān)的數(shù)據(jù)規(guī)則都必須應(yīng)用于事務(wù)的修改,以保持?jǐn)?shù)據(jù)的完整性;事務(wù)結(jié)束時(shí),所有的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(如B樹(shù)索引或雙向鏈表)也都必須是正確的。

隔離性(Isolation):數(shù)據(jù)庫(kù)系統(tǒng)提供一定的隔離機(jī)制,保證事務(wù)在不受外部并發(fā)操作影響的“獨(dú)立”環(huán)境執(zhí)行。這意味著事務(wù)處理過(guò)程中的中間狀態(tài)對(duì)外部是不可見(jiàn)的,反之亦然。

持久性(Durable):事務(wù)完成之后,它對(duì)于數(shù)據(jù)的修改是永久性的,即使出現(xiàn)系統(tǒng)故障也能夠保持。

銀行轉(zhuǎn)帳就是事務(wù)的一個(gè)典型例子。

2.并發(fā)事務(wù)處理帶來(lái)的問(wèn)題

相對(duì)于串行處理來(lái)說(shuō),并發(fā)事務(wù)處理能大大增加數(shù)據(jù)庫(kù)資源的利用率,提高數(shù)據(jù)庫(kù)系統(tǒng)的事務(wù)吞吐量,從而可以支持更多的用戶。但并發(fā)事務(wù)處理也會(huì)帶來(lái)一些問(wèn)題,主要包括以下幾種情況。

更新丟失(Lost Update):當(dāng)兩個(gè)或多個(gè)事務(wù)選擇同一行,然后基于最初選定的值更新該行時(shí),由于每個(gè)事務(wù)都不知道其他事務(wù)的存在,就會(huì)發(fā)生丟失更新問(wèn)題--最后的更新覆蓋了由其他事務(wù)所做的更新。例如,兩個(gè)編輯人員制作了同一文檔的電子副本。每個(gè)編輯人員獨(dú)立地更改其副本,然后保存更改后的副本,這樣就覆蓋了原始文檔。最后保存其更改副本的編輯人員覆蓋另一個(gè)編輯人員所做的更改。如果在一個(gè)編輯人員完成并提交事務(wù)之前,另一個(gè)編輯人員不能訪問(wèn)同一文件,則可避免此問(wèn)題。

臟讀(Dirty Reads):一個(gè)事務(wù)正在對(duì)一條記錄做修改,在這個(gè)事務(wù)完成并提交前,這條記錄的數(shù)據(jù)就處于不一致?tīng)顟B(tài);這時(shí),另一個(gè)事務(wù)也來(lái)讀取同一條記錄,如果不加控制,第二個(gè)事務(wù)讀取了這些“臟”數(shù)據(jù),并據(jù)此做進(jìn)一步的處理,就會(huì)產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系。這種現(xiàn)象被形象地叫做"臟讀"。

不可重復(fù)讀(Non-Repeatable Reads):一個(gè)事務(wù)在讀取某些數(shù)據(jù)后的某個(gè)時(shí)間,再次讀取以前讀過(guò)的數(shù)據(jù),卻發(fā)現(xiàn)其讀出的數(shù)據(jù)已經(jīng)發(fā)生了改變、或某些記錄已經(jīng)被刪除了!這種現(xiàn)象就叫做“不可重復(fù)讀”。

幻讀(Phantom Reads):一個(gè)事務(wù)按相同的查詢條件重新讀取以前檢索過(guò)的數(shù)據(jù),卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù),這種現(xiàn)象就稱為“幻讀”。

3.事務(wù)隔離級(jí)別

在上面講到的并發(fā)事務(wù)處理帶來(lái)的問(wèn)題中,“更新丟失”通常是應(yīng)該完全避免的。但防止更新丟失,并不能單靠數(shù)據(jù)庫(kù)事務(wù)控制器來(lái)解決,需要應(yīng)用程序?qū)σ碌臄?shù)據(jù)加必要的鎖來(lái)解決,因此,防止更新丟失應(yīng)該是應(yīng)用的責(zé)任。

“臟讀”、“不可重復(fù)讀”和“幻讀”,其實(shí)都是數(shù)據(jù)庫(kù)讀一致性問(wèn)題,必須由數(shù)據(jù)庫(kù)提供一定的事務(wù)隔離機(jī)制來(lái)解決。數(shù)據(jù)庫(kù)實(shí)現(xiàn)事務(wù)隔離的方式,基本上可分為以下兩種。

一種是在讀取數(shù)據(jù)前,對(duì)其加鎖,阻止其他事務(wù)對(duì)數(shù)據(jù)進(jìn)行修改。

另一種是不用加任何鎖,通過(guò)一定機(jī)制生成一個(gè)數(shù)據(jù)請(qǐng)求時(shí)間點(diǎn)的一致性數(shù)據(jù)快照(Snapshot),并用這個(gè)快照來(lái)提供一定級(jí)別(語(yǔ)句級(jí)或事務(wù)級(jí))的一致性讀取。從用戶的角度來(lái)看,好像是數(shù)據(jù)庫(kù)可以提供同一數(shù)據(jù)的多個(gè)版本,因此,這種技術(shù)叫做數(shù)據(jù)多版本并發(fā)控制(MultiVersion Concurrency Control,簡(jiǎn)稱MVCC或MCC),也經(jīng)常稱為多版本數(shù)據(jù)庫(kù)。

數(shù)據(jù)庫(kù)的事務(wù)隔離越嚴(yán)格,并發(fā)副作用越小,但付出的代價(jià)也就越大,因?yàn)槭聞?wù)隔離實(shí)質(zhì)上就是使事務(wù)在一定程度上 “串行化”進(jìn)行,這顯然與“并發(fā)”是矛盾的。同時(shí),不同的應(yīng)用對(duì)讀一致性和事務(wù)隔離程度的要求也是不同的,比如許多應(yīng)用對(duì)“不可重復(fù)讀”和“幻讀”并不敏感,可能更關(guān)心數(shù)據(jù)并發(fā)訪問(wèn)的能力。

為了解決“隔離”與“并發(fā)”的矛盾,ISO/ANSI SQL92定義了4個(gè)事務(wù)隔離級(jí)別,每個(gè)級(jí)別的隔離程度不同,允許出現(xiàn)的副作用也不同,應(yīng)用可以根據(jù)自己的業(yè)務(wù)邏輯要求,通過(guò)選擇不同的隔離級(jí)別來(lái)平衡 “隔離”與“并發(fā)”的矛盾。下表很好地概括了這4個(gè)隔離級(jí)別的特性。

4種隔離級(jí)別比較

讀數(shù)據(jù)一致性及允許的并發(fā)副作用

隔離級(jí)別

讀數(shù)據(jù)一致性 臟讀 不可重復(fù)讀 幻讀

未提交讀(Read uncommitted)

最低級(jí)別,只能保證不讀取物理上損壞的數(shù)據(jù)

已提交度(Read committed)

語(yǔ)句級(jí)

可重復(fù)讀(Repeatable read)

事務(wù)級(jí)

可序列化(Serializable)

最高級(jí)別,事務(wù)級(jí)

最后要說(shuō)明的是:各具體數(shù)據(jù)庫(kù)并不一定完全實(shí)現(xiàn)了上述4個(gè)隔離級(jí)別,例如,Oracle只提供Read committed和Serializable兩個(gè)標(biāo)準(zhǔn)隔離級(jí)別,另外還提供自己定義的Read only隔離級(jí)別;SQL Server除支持上述ISO/ANSI SQL92定義的4個(gè)隔離級(jí)別外,還支持一個(gè)叫做“快照”的隔離級(jí)別,但嚴(yán)格來(lái)說(shuō)它是一個(gè)用MVCC實(shí)現(xiàn)的Serializable隔離級(jí)別。MySQL 支持全部4個(gè)隔離級(jí)別,但在具體實(shí)現(xiàn)時(shí),有一些特點(diǎn),比如在一些隔離級(jí)別下是采用MVCC一致性讀,但某些情況下又不是,這些內(nèi)容在后面的章節(jié)中將會(huì)做進(jìn)一步介紹。

獲取InnoDB行鎖爭(zhēng)用情況

可以通過(guò)檢查InnoDB_row_lock狀態(tài)變量來(lái)分析系統(tǒng)上的行鎖的爭(zhēng)奪情況:

mysql> show status like 'innodb_row_lock%';

+-------------------------------+-------+

| Variable_name | Value |

+-------------------------------+-------+

| InnoDB_row_lock_current_waits | 0 |

| InnoDB_row_lock_time | 0 |

| InnoDB_row_lock_time_avg | 0 |

| InnoDB_row_lock_time_max | 0 |

| InnoDB_row_lock_waits | 0 |

+-------------------------------+-------+

5 rows in set (0.01 sec)

如果發(fā)現(xiàn)鎖爭(zhēng)用比較嚴(yán)重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比較高,還可以通過(guò)設(shè)置InnoDB Monitors來(lái)進(jìn)一步觀察發(fā)生鎖沖突的表、數(shù)據(jù)行等,并分析鎖爭(zhēng)用的原因。

具體方法如下:

mysql> CREATE TABLE innodb_monitor(a INT) ENGINE=INNODB;

Query OK, 0 rows affected (0.14 sec)

然后就可以用下面的語(yǔ)句來(lái)進(jìn)行查看:

mysql> Show innodb statusG;

*************************** 1. row ***************************

Type: InnoDB

Name:

Status:

------------

TRANSACTIONS

------------

Trx id counter 0 117472192

Purge done for trx's n:o < 0 117472190 undo n:o < 0 0

History list length 17

Total number of lock structs in row lock hash table 0

LIST OF TRANSACTIONS FOR EACH SESSION:

---TRANSACTION 0 117472185, not started, process no 11052, OS thread id 1158191456

MySQL thread id 200610, query id 291197 localhost root

---TRANSACTION 0 117472183, not started, process no 11052, OS thread id 1158723936

MySQL thread id 199285, query id 291199 localhost root

Show innodb status

監(jiān)視器可以通過(guò)發(fā)出下列語(yǔ)句來(lái)停止查看:

mysql> DROP TABLE innodb_monitor;

Query OK, 0 rows affected (0.05 sec)

設(shè)置監(jiān)視器后,在SHOW INNODB STATUS的顯示內(nèi)容中,會(huì)有詳細(xì)的當(dāng)前鎖等待的信息,包括表名、鎖類型、鎖定記錄的情況等,便于進(jìn)行進(jìn)一步的分析和問(wèn)題的確定。打開(kāi)監(jiān)視器以后,默認(rèn)情況下每15秒會(huì)向日志中記錄監(jiān)控的內(nèi)容,如果長(zhǎng)時(shí)間打開(kāi)會(huì)導(dǎo)致.err文件變得非常的巨大,所以用戶在確認(rèn)問(wèn)題原因之后,要記得刪除監(jiān)控表以關(guān)閉監(jiān)視器,或者通過(guò)使用“--console”選項(xiàng)來(lái)啟動(dòng)服務(wù)器以關(guān)閉寫(xiě)日志文件。

InnoDB的行鎖模式及加鎖方法

InnoDB實(shí)現(xiàn)了以下兩種類型的行鎖。

共享鎖(S):允許一個(gè)事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖。

排他鎖(X):允許獲得排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)取得相同數(shù)據(jù)集的共享讀鎖和排他寫(xiě)鎖。另外,為了允許行鎖和表鎖共存,實(shí)現(xiàn)多粒度鎖機(jī)制,InnoDB還有兩種內(nèi)部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。

意向共享鎖(IS):事務(wù)打算給數(shù)據(jù)行加行共享鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖。

意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加行排他鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖。

上述鎖模式的兼容情況具體如下表所示。

InnoDB行鎖模式兼容性列表

請(qǐng)求鎖模式

是否兼容

當(dāng)前鎖模式

X IX S IS
X 沖突 沖突 沖突 沖突
IX 沖突 兼容 沖突 兼容
S 沖突 沖突 兼容 兼容
IS 沖突 兼容 兼容 兼容

如果一個(gè)事務(wù)請(qǐng)求的鎖模式與當(dāng)前的鎖兼容,InnoDB就將請(qǐng)求的鎖授予該事務(wù);反之,如果兩者不兼容,該事務(wù)就要等待鎖釋放。

意向鎖是InnoDB自動(dòng)加的,不需用戶干預(yù)。對(duì)于UPDATE、DELETE和INSERT語(yǔ)句,InnoDB會(huì)自動(dòng)給涉及數(shù)據(jù)集加排他鎖(X);對(duì)于普通SELECT語(yǔ)句,InnoDB不會(huì)加任何鎖;事務(wù)可以通過(guò)以下語(yǔ)句顯示給記錄集加共享鎖或排他鎖。

共享鎖(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。

排他鎖(X):SELECT * FROM table_name WHERE ... FOR UPDATE。

用SELECT ... IN SHARE MODE獲得共享鎖,主要用在需要數(shù)據(jù)依存關(guān)系時(shí)來(lái)確認(rèn)某行記錄是否存在,并確保沒(méi)有人對(duì)這個(gè)記錄進(jìn)行UPDATE或者DELETE操作。但是如果當(dāng)前事務(wù)也需要對(duì)該記錄進(jìn)行更新操作,則很有可能造成死鎖,對(duì)于鎖定行記錄后需要進(jìn)行更新操作的應(yīng)用,應(yīng)該使用SELECT... FOR UPDATE方式獲得排他鎖。

在如下表所示的例子中,使用了SELECT ... IN SHARE MODE加鎖后再更新記錄,看看會(huì)出現(xiàn)什么情況,其中actor表的actor_id字段為主鍵。

InnoDB存儲(chǔ)引擎的共享鎖例子

session_1 session_2

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

+----------+------------+-----------+

| actor_id | first_name | last_name |

+----------+------------+-----------+

| 178 | LISA | MONROE |

+----------+------------+-----------+

1 row in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

+----------+------------+-----------+

| actor_id | first_name | last_name |

+----------+------------+-----------+

| 178 | LISA | MONROE |

+----------+------------+-----------+

1 row in set (0.00 sec)

當(dāng)前session對(duì)actor_id=178的記錄加share mode 的共享鎖:

mysql> select actor_id,first_name,last_name from actor where actor_id = 178lock in share mode;

+----------+------------+-----------+

| actor_id | first_name | last_name |

+----------+------------+-----------+

| 178 | LISA | MONROE |

+----------+------------+-----------+

1 row in set (0.01 sec)

其他session仍然可以查詢記錄,并也可以對(duì)該記錄加share mode的共享鎖:

mysql> select actor_id,first_name,last_name from actor where actor_id = 178lock in share mode;

+----------+------------+-----------+

| actor_id | first_name | last_name |

+----------+------------+-----------+

| 178 | LISA | MONROE |

+----------+------------+-----------+

1 row in set (0.01 sec)

當(dāng)前session對(duì)鎖定的記錄進(jìn)行更新操作,等待鎖:

mysql> update actor set last_name = 'MONROE T' where actor_id = 178;

等待

其他session也對(duì)該記錄進(jìn)行更新操作,則會(huì)導(dǎo)致死鎖退出:

mysql> update actor set last_name = 'MONROE T' where actor_id = 178;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

獲得鎖后,可以成功更新:

mysql> update actor set last_name = 'MONROE T' where actor_id = 178;

Query OK, 1 row affected (17.67 sec)

Rows matched: 1 Changed: 1 Warnings: 0

當(dāng)使用SELECT...FOR UPDATE加鎖后再更新記錄,出現(xiàn)如下表所示的情況。

InnoDB存儲(chǔ)引擎的排他鎖例子

session_1 session_2

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

+----------+------------+-----------+

| actor_id | first_name | last_name |

+----------+------------+-----------+

| 178 | LISA | MONROE |

+----------+------------+-----------+

1 row in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

+----------+------------+-----------+

| actor_id | first_name | last_name |

+----------+------------+-----------+

| 178 | LISA | MONROE |

+----------+------------+-----------+

1 row in set (0.00 sec)

當(dāng)前session對(duì)actor_id=178的記錄加for update的排它鎖:

mysql> select actor_id,first_name,last_name from actor where actor_id = 178 for update;

+----------+------------+-----------+

| actor_id | first_name | last_name |

+----------+------------+-----------+

| 178 | LISA | MONROE |

+----------+------------+-----------+

1 row in set (0.00 sec)

其他session可以查詢?cè)撚涗洠遣荒軐?duì)該記錄加共享鎖,會(huì)等待獲得鎖:

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

+----------+------------+-----------+

| actor_id | first_name | last_name |

+----------+------------+-----------+

| 178 | LISA | MONROE |

+----------+------------+-----------+

1 row in set (0.00 sec)

mysql> select actor_id,first_name,last_name from actor where actor_id = 178 for update;

等待

當(dāng)前session可以對(duì)鎖定的記錄進(jìn)行更新操作,更新后釋放鎖:

mysql> update actor set last_name = 'MONROE T' where actor_id = 178;

Query OK, 1 row affected (0.00 sec)

Rows matched: 1 Changed: 1 Warnings: 0

mysql> commit;

Query OK, 0 rows affected (0.01 sec)

其他session獲得鎖,得到其他session提交的記錄:

mysql> select actor_id,first_name,last_name from actor where actor_id = 178 for update;

+----------+------------+-----------+

| actor_id | first_name | last_name |

+----------+------------+-----------+

| 178 | LISA | MONROE T |

+----------+------------+-----------+

1 row in set (9.59 sec)

InnoDB行鎖實(shí)現(xiàn)方式

InnoDB行鎖是通過(guò)給索引上的索引項(xiàng)加鎖來(lái)實(shí)現(xiàn)的,這一點(diǎn)MySQL與Oracle不同,后者是通過(guò)在數(shù)據(jù)塊中對(duì)相應(yīng)數(shù)據(jù)行加鎖來(lái)實(shí)現(xiàn)的。InnoDB這種行鎖實(shí)現(xiàn)特點(diǎn)意味著:只有通過(guò)索引條件檢索數(shù)據(jù),InnoDB才使用行級(jí)鎖,否則,InnoDB將使用表鎖!

在實(shí)際應(yīng)用中,要特別注意InnoDB行鎖的這一特性,不然的話,可能導(dǎo)致大量的鎖沖突,從而影響并發(fā)性能。下面通過(guò)一些實(shí)際例子來(lái)加以說(shuō)明。

(1)在不通過(guò)索引條件查詢的時(shí)候,InnoDB確實(shí)使用的是表鎖,而不是行鎖。

在如下所示的例子中,開(kāi)始tab_no_index表沒(méi)有索引:

mysql> create table tab_no_index(id int,name varchar(10)) engine=innodb;

Query OK, 0 rows affected (0.15 sec)

mysql> insert into tab_no_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');

Query OK, 4 rows affected (0.00 sec)

Records: 4 Duplicates: 0 Warnings: 0

InnoDB存儲(chǔ)引擎的表在不使用索引時(shí)使用表鎖例子

session_1 session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_no_index where id = 1 ;

+------+------+

| id | name |

+------+------+

| 1 | 1 |

+------+------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_no_index where id = 2 ;

+------+------+

| id | name |

+------+------+

| 2 | 2 |

+------+------+

1 row in set (0.00 sec)

mysql> select * from tab_no_index where id = 1 for update;

+------+------+

| id | name |

+------+------+

| 1 | 1 |

+------+------+

1 row in set (0.00 sec)

mysql> select * from tab_no_index where id = 2 for update;

等待

在如上表所示的例子中,看起來(lái)session_1只給一行加了排他鎖,但session_2在請(qǐng)求其他行的排他鎖時(shí),卻出現(xiàn)了鎖等待!原因就是在沒(méi)有索引的情況下,InnoDB只能使用表鎖。當(dāng)我們給其增加一個(gè)索引后,InnoDB就只鎖定了符合條件的行,如下表所示。

創(chuàng)建tab_with_index表,id字段有普通索引:

mysql> create table tab_with_index(id int,name varchar(10)) engine=innodb;

Query OK, 0 rows affected (0.15 sec)

mysql> alter table tab_with_index add index id(id);

Query OK, 4 rows affected (0.24 sec)

Records: 4 Duplicates: 0 Warnings: 0

InnoDB存儲(chǔ)引擎的表在使用索引時(shí)使用行鎖例子

session_1 session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 1 ;

+------+------+

| id | name |

+------+------+

| 1 | 1 |

+------+------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 2 ;

+------+------+

| id | name |

+------+------+

| 2 | 2 |

+------+------+

1 row in set (0.00 sec)

mysql> select * from tab_with_index where id = 1 for update;

+------+------+

| id | name |

+------+------+

| 1 | 1 |

+------+------+

1 row in set (0.00 sec)

mysql> select * from tab_with_index where id = 2 for update;

+------+------+

| id | name |

+------+------+

| 2 | 2 |

+------+------+

1 row in set (0.00 sec)

(2)由于MySQL的行鎖是針對(duì)索引加的鎖,不是針對(duì)記錄加的鎖,所以雖然是訪問(wèn)不同行的記錄,但是如果是使用相同的索引鍵,是會(huì)出現(xiàn)鎖沖突的。應(yīng)用設(shè)計(jì)的時(shí)候要注意這一點(diǎn)。

在如下表所示的例子中,表tab_with_index的id字段有索引,name字段沒(méi)有索引:

mysql> alter table tab_with_index drop index name;

Query OK, 4 rows affected (0.22 sec)

Records: 4 Duplicates: 0 Warnings: 0

mysql> insert into tab_with_index values(1,'4');

Query OK, 1 row affected (0.00 sec)

mysql> select * from tab_with_index where id = 1;

+------+------+

| id | name |

+------+------+

| 1 | 1 |

| 1 | 4 |

+------+------+

2 rows in set (0.00 sec)

InnoDB存儲(chǔ)引擎使用相同索引鍵的阻塞例子

session_1 session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 1 and name = '1' for update;

+------+------+

| id | name |

+------+------+

| 1 | 1 |

+------+------+

1 row in set (0.00 sec)

雖然session_2訪問(wèn)的是和session_1不同的記錄,但是因?yàn)槭褂昧讼嗤乃饕?,所以需要等待鎖:

mysql> select * from tab_with_index where id = 1 and name = '4' for update;

等待

(3)當(dāng)表有多個(gè)索引的時(shí)候,不同的事務(wù)可以使用不同的索引鎖定不同的行,另外,不論是使用主鍵索引、唯一索引或普通索引,InnoDB都會(huì)使用行鎖來(lái)對(duì)數(shù)據(jù)加鎖。

在如下表所示的例子中,表tab_with_index的id字段有主鍵索引,name字段有普通索引:

mysql> alter table tab_with_index add index name(name);

Query OK, 5 rows affected (0.23 sec)

Records: 5 Duplicates: 0 Warnings: 0

InnoDB存儲(chǔ)引擎的表使用不同索引的阻塞例子

session_1 session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 1 for update;

+------+------+

| id | name |

+------+------+

| 1 | 1 |

| 1 | 4 |

+------+------+

2 rows in set (0.00 sec)

Session_2使用name的索引訪問(wèn)記錄,因?yàn)橛涗洓](méi)有被索引,所以可以獲得鎖:

mysql> select * from tab_with_index where name = '2' for update;

+------+------+

| id | name |

+------+------+

| 2 | 2 |

+------+------+

1 row in set (0.00 sec)

由于訪問(wèn)的記錄已經(jīng)被session_1鎖定,所以等待獲得鎖。:

mysql> select * from tab_with_index where name = '4' for update;

(4)即便在條件中使用了索引字段,但是否使用索引來(lái)檢索數(shù)據(jù)是由MySQL通過(guò)判斷不同執(zhí)行計(jì)劃的代價(jià)來(lái)決定的,如果MySQL認(rèn)為全表掃描效率更高,比如對(duì)一些很小的表,它就不會(huì)使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖沖突時(shí),別忘了檢查SQL的執(zhí)行計(jì)劃,以確認(rèn)是否真正使用了索引。

在下面的例子中,檢索值的數(shù)據(jù)類型與索引字段不同,雖然MySQL能夠進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換,但卻不會(huì)使用索引,從而導(dǎo)致InnoDB使用表鎖。通過(guò)用explain檢查兩條SQL的執(zhí)行計(jì)劃,我們可以清楚地看到了這一點(diǎn)。

例子中tab_with_index表的name字段有索引,但是name字段是varchar類型的,如果where條件中不是和varchar類型進(jìn)行比較,則會(huì)對(duì)name進(jìn)行類型轉(zhuǎn)換,而執(zhí)行的全表掃描。

mysql> alter table tab_no_index add index name(name);

Query OK, 4 rows affected (8.06 sec)

Records: 4 Duplicates: 0 Warnings: 0

mysql> explain select * from tab_with_index where name = 1 G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: tab_with_index

type: ALL

possible_keys: name

key: NULL

key_len: NULL

ref: NULL

rows: 4

Extra: Using where

1 row in set (0.00 sec)

mysql> explain select * from tab_with_index where name = '1' G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: tab_with_index

type: ref

possible_keys: name

key: name

key_len: 23

ref: const

rows: 1

Extra: Using where

1 row in set (0.00 sec)

間隙鎖(Next-Key鎖)

當(dāng)我們用范圍條件而不是相等條件檢索數(shù)據(jù),并請(qǐng)求共享或排他鎖時(shí),InnoDB會(huì)給符合條件的已有數(shù)據(jù)記錄的索引項(xiàng)加鎖;對(duì)于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做“間隙(GAP)”,InnoDB也會(huì)對(duì)這個(gè)“間隙”加鎖,這種鎖機(jī)制就是所謂的間隙鎖(Next-Key鎖)。

舉例來(lái)說(shuō),假如emp表中只有101條記錄,其empid的值分別是 1,2,...,100,101,下面的SQL:

Select * from emp where empid > 100 for update;

是一個(gè)范圍條件的檢索,InnoDB不僅會(huì)對(duì)符合條件的empid值為101的記錄加鎖,也會(huì)對(duì)empid大于101(這些記錄并不存在)的“間隙”加鎖。

InnoDB使用間隙鎖的目的,一方面是為了防止幻讀,以滿足相關(guān)隔離級(jí)別的要求,對(duì)于上面的例子,要是不使用間隙鎖,如果其他事務(wù)插入了empid大于100的任何記錄,那么本事務(wù)如果再次執(zhí)行上述語(yǔ)句,就會(huì)發(fā)生幻讀;另外一方面,是為了滿足其恢復(fù)和復(fù)制的需要。有關(guān)其恢復(fù)和復(fù)制對(duì)鎖機(jī)制的影響,以及不同隔離級(jí)別下InnoDB使用間隙鎖的情況,在后續(xù)的章節(jié)中會(huì)做進(jìn)一步介紹。

很顯然,在使用范圍條件檢索并鎖定記錄時(shí),InnoDB這種加鎖機(jī)制會(huì)阻塞符合條件范圍內(nèi)鍵值的并發(fā)插入,這往往會(huì)造成嚴(yán)重的鎖等待。因此,在實(shí)際應(yīng)用開(kāi)發(fā)中,尤其是并發(fā)插入比較多的應(yīng)用,我們要盡量?jī)?yōu)化業(yè)務(wù)邏輯,盡量使用相等條件來(lái)訪問(wèn)更新數(shù)據(jù),避免使用范圍條件。

還要特別說(shuō)明的是,InnoDB除了通過(guò)范圍條件加鎖時(shí)使用間隙鎖外,如果使用相等條件請(qǐng)求給一個(gè)不存在的記錄加鎖,InnoDB也會(huì)使用間隙鎖!

在如下表所示的例子中,假如emp表中只有101條記錄,其empid的值分別是1,2,......,100,101。

InnoDB存儲(chǔ)引擎的間隙鎖阻塞例子

session_1 session_2

mysql> select @@tx_isolation;

+-----------------+

| @@tx_isolation |

+-----------------+

| REPEATABLE-READ |

+-----------------+

1 row in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;

+-----------------+

| @@tx_isolation |

+-----------------+

| REPEATABLE-READ |

+-----------------+

1 row in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

當(dāng)前session對(duì)不存在的記錄加for update的鎖:

mysql> select * from emp where empid = 102 for update;

Empty set (0.00 sec)

這時(shí),如果其他session插入empid為102的記錄(注意:這條記錄并不存在),也會(huì)出現(xiàn)鎖等待:

mysql>insert into emp(empid,...) values(102,...);

阻塞等待

Session_1 執(zhí)行rollback:

mysql> rollback;

Query OK, 0 rows affected (13.04 sec)

由于其他session_1回退后釋放了Next-Key鎖,當(dāng)前session可以獲得鎖并成功插入記錄:

mysql>insert into emp(empid,...) values(102,...);

Query OK, 1 row affected (13.35 sec)

恢復(fù)和復(fù)制的需要,對(duì)InnoDB鎖機(jī)制的影響

MySQL通過(guò)BINLOG錄執(zhí)行成功的INSERT、UPDATE、DELETE等更新數(shù)據(jù)的SQL語(yǔ)句,并由此實(shí)現(xiàn)MySQL數(shù)據(jù)庫(kù)的恢復(fù)和主從復(fù)制(可以參見(jiàn)本書(shū)“管理篇”的介紹)。MySQL的恢復(fù)機(jī)制(復(fù)制其實(shí)就是在Slave Mysql不斷做基于BINLOG的恢復(fù))有以下特點(diǎn)。

l 一是MySQL的恢復(fù)是SQL語(yǔ)句級(jí)的,也就是重新執(zhí)行BINLOG中的SQL語(yǔ)句。這與Oracle數(shù)據(jù)庫(kù)不同,Oracle是基于數(shù)據(jù)庫(kù)文件塊的。

l 二是MySQL的Binlog是按照事務(wù)提交的先后順序記錄的,恢復(fù)也是按這個(gè)順序進(jìn)行的。這點(diǎn)也與Oralce不同,Oracle是按照系統(tǒng)更新號(hào)(System Change Number,SCN)來(lái)恢復(fù)數(shù)據(jù)的,每個(gè)事務(wù)開(kāi)始時(shí),Oracle都會(huì)分配一個(gè)全局唯一的SCN,SCN的順序與事務(wù)開(kāi)始的時(shí)間順序是一致的。

從上面兩點(diǎn)可知,MySQL的恢復(fù)機(jī)制要求:在一個(gè)事務(wù)未提交前,其他并發(fā)事務(wù)不能插入滿足其鎖定條件的任何記錄,也就是不允許出現(xiàn)幻讀,這已經(jīng)超過(guò)了ISO/ANSI SQL92“可重復(fù)讀”隔離級(jí)別的要求,實(shí)際上是要求事務(wù)要串行化。這也是許多情況下,InnoDB要用到間隙鎖的原因,比如在用范圍條件更新記錄時(shí),無(wú)論在Read Commited或是Repeatable Read隔離級(jí)別下,InnoDB都要使用間隙鎖,但這并不是隔離級(jí)別要求的,有關(guān)InnoDB在不同隔離級(jí)別下加鎖的差異在下一小節(jié)還會(huì)介紹。

另外,對(duì)于“insert into target_tab select * from source_tab where ...”和“create table new_tab ...select ... From source_tab where ...(CTAS)”這種SQL語(yǔ)句,用戶并沒(méi)有對(duì)source_tab做任何更新操作,但MySQL對(duì)這種SQL語(yǔ)句做了特別處理。先來(lái)看如下表的例子。

CTAS操作給原表加鎖例子

session_1 session_2

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from target_tab;

Empty set (0.00 sec)

mysql> select * from source_tab where name = '1';

+----+------+----+

| d1 | name | d2 |

+----+------+----+

| 4 | 1 | 1 |

| 5 | 1 | 1 |

| 6 | 1 | 1 |

| 7 | 1 | 1 |

| 8 | 1 | 1 |

+----+------+----+

5 rows in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from target_tab;

Empty set (0.00 sec)

mysql> select * from source_tab where name = '1';

+----+------+----+

| d1 | name | d2 |

+----+------+----+

| 4 | 1 | 1 |

| 5 | 1 | 1 |

| 6 | 1 | 1 |

| 7 | 1 | 1 |

| 8 | 1 | 1 |

+----+------+----+

5 rows in set (0.00 sec)

mysql> insert into target_tab select d1,name from source_tab where name = '1';

Query OK, 5 rows affected (0.00 sec)

Records: 5 Duplicates: 0 Warnings: 0

mysql> update source_tab set name = '1' where name = '8';

等待

commit;

返回結(jié)果

commit;

在上面的例子中,只是簡(jiǎn)單地讀 source_tab表的數(shù)據(jù),相當(dāng)于執(zhí)行一個(gè)普通的SELECT語(yǔ)句,用一致性讀就可以了。ORACLE正是這么做的,它通過(guò)MVCC技術(shù)實(shí)現(xiàn)的多版本數(shù)據(jù)來(lái)實(shí)現(xiàn)一致性讀,不需要給source_tab加任何鎖。我們知道InnoDB也實(shí)現(xiàn)了多版本數(shù)據(jù),對(duì)普通的SELECT一致性讀,也不需要加任何鎖;但這里InnoDB卻給source_tab加了共享鎖,并沒(méi)有使用多版本數(shù)據(jù)一致性讀技術(shù)!

MySQL為什么要這么做呢?其原因還是為了保證恢復(fù)和復(fù)制的正確性。因?yàn)椴患渔i的話,如果在上述語(yǔ)句執(zhí)行過(guò)程中,其他事務(wù)對(duì)source_tab做了更新操作,就可能導(dǎo)致數(shù)據(jù)恢復(fù)的結(jié)果錯(cuò)誤。為了演示這一點(diǎn),我們?cè)僦貜?fù)一下前面的例子,不同的是在session_1執(zhí)行事務(wù)前,先將系統(tǒng)變量 innodb_locks_unsafe_for_binlog的值設(shè)置為“on”(其默認(rèn)值為off),具體結(jié)果如下表所示。

CTAS操作不給原表加鎖帶來(lái)的安全問(wèn)題例子

session_1 session_2

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql>set innodb_locks_unsafe_for_binlog='on'

Query OK, 0 rows affected (0.00 sec)

mysql> select * from target_tab;

Empty set (0.00 sec)

mysql> select * from source_tab where name = '1';

+----+------+----+

| d1 | name | d2 |

+----+------+----+

| 4 | 1 | 1 |

| 5 | 1 | 1 |

| 6 | 1 | 1 |

| 7 | 1 | 1 |

| 8 | 1 | 1 |

+----+------+----+

5 rows in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from target_tab;

Empty set (0.00 sec)

mysql> select * from source_tab where name = '1';

+----+------+----+

| d1 | name | d2 |

+----+------+----+

| 4 | 1 | 1 |

| 5 | 1 | 1 |

| 6 | 1 | 1 |

| 7 | 1 | 1 |

| 8 | 1 | 1 |

+----+------+----+

5 rows in set (0.00 sec)

mysql> insert into target_tab select d1,name from source_tab where name = '1';

Query OK, 5 rows affected (0.00 sec)

Records: 5 Duplicates: 0 Warnings: 0

session_1未提交,可以對(duì)session_1的select的記錄進(jìn)行更新操作。

mysql> update source_tab set name = '8' where name = '1';

Query OK, 5 rows affected (0.00 sec)

Rows matched: 5 Changed: 5 Warnings: 0

mysql> select * from source_tab where name = '8';

+----+------+----+

| d1 | name | d2 |

+----+------+----+

| 4 | 8 | 1 |

| 5 | 8 | 1 |

| 6 | 8 | 1 |

| 7 | 8 | 1 |

| 8 | 8 | 1 |

+----+------+----+

5 rows in set (0.00 sec)

更新操作先提交

mysql> commit;

Query OK, 0 rows affected (0.05 sec)

插入操作后提交

mysql> commit;

Query OK, 0 rows affected (0.07 sec)

此時(shí)查看數(shù)據(jù),target_tab中可以插入source_tab更新前的結(jié)果,這符合應(yīng)用邏輯:

mysql> select * from source_tab where name = '8';

+----+------+----+

| d1 | name | d2 |

+----+------+----+

| 4 | 8 | 1 |

| 5 | 8 | 1 |

| 6 | 8 | 1 |

| 7 | 8 | 1 |

| 8 | 8 | 1 |

+----+------+----+

5 rows in set (0.00 sec)

mysql> select * from target_tab;

+------+------+

| id | name |

+------+------+

| 4 | 1.00 |

| 5 | 1.00 |

| 6 | 1.00 |

| 7 | 1.00 |

| 8 | 1.00 |

+------+------+

5 rows in set (0.00 sec)

mysql> select * from tt1 where name = '1';

Empty set (0.00 sec)

mysql> select * from source_tab where name = '8';

+----+------+----+

| d1 | name | d2 |

+----+------+----+

| 4 | 8 | 1 |

| 5 | 8 | 1 |

| 6 | 8 | 1 |

| 7 | 8 | 1 |

| 8 | 8 | 1 |

+----+------+----+

5 rows in set (0.00 sec)

mysql> select * from target_tab;

+------+------+

| id | name |

+------+------+

| 4 | 1.00 |

| 5 | 1.00 |

| 6 | 1.00 |

| 7 | 1.00 |

| 8 | 1.00 |

+------+------+

5 rows in set (0.00 sec)

從上可見(jiàn),設(shè)置系統(tǒng)變量innodb_locks_unsafe_for_binlog的值為“on”后,InnoDB不再對(duì)source_tab加鎖,結(jié)果也符合應(yīng)用邏輯,但是如果分析BINLOG的內(nèi)容:

......

SET TIMESTAMP=1169175130;

BEGIN;

# at 274

#070119 10:51:57 server id 1 end_log_pos 105 Query thread_id=1 exec_time=0 error_code=0

SET TIMESTAMP=1169175117;

update source_tab set name = '8' where name = '1';

# at 379

#070119 10:52:10 server id 1 end_log_pos 406 Xid = 5

COMMIT;

# at 406

#070119 10:52:14 server id 1 end_log_pos 474 Query thread_id=2 exec_time=0 error_code=0

SET TIMESTAMP=1169175134;

BEGIN;

# at 474

#070119 10:51:29 server id 1 end_log_pos 119 Query thread_id=2 exec_time=0 error_code=0

SET TIMESTAMP=1169175089;

insert into target_tab select d1,name from source_tab where name = '1';

# at 593

#070119 10:52:14 server id 1 end_log_pos 620 Xid = 7

COMMIT;

......

可以發(fā)現(xiàn),在BINLOG中,更新操作的位置在INSERT...SELECT之前,如果使用這個(gè)BINLOG進(jìn)行數(shù)據(jù)庫(kù)恢復(fù),恢復(fù)的結(jié)果與實(shí)際的應(yīng)用邏輯不符;如果進(jìn)行復(fù)制,就會(huì)導(dǎo)致主從數(shù)據(jù)庫(kù)不一致!

通過(guò)上面的例子,我們就不難理解為什么MySQL在處理“Insert into target_tab select * from source_tab where ...”和“create table new_tab ...select ... From source_tab where ...”時(shí)要給source_tab加鎖,而不是使用對(duì)并發(fā)影響最小的多版本數(shù)據(jù)來(lái)實(shí)現(xiàn)一致性讀。還要特別說(shuō)明的是,如果上述語(yǔ)句的SELECT是范圍條件,InnoDB還會(huì)給源表加間隙鎖(Next-Lock)。

因此,INSERT...SELECT...和 CREATE TABLE...SELECT...語(yǔ)句,可能會(huì)阻止對(duì)源表的并發(fā)更新,造成對(duì)源表鎖的等待。如果查詢比較復(fù)雜的話,會(huì)造成嚴(yán)重的性能問(wèn)題,我們?cè)趹?yīng)用中應(yīng)盡量避免使用。實(shí)際上,MySQL將這種SQL叫作不確定(non-deterministic)的SQL,不推薦使用。

如果應(yīng)用中一定要用這種SQL來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯,又不希望對(duì)源表的并發(fā)更新產(chǎn)生影響,可以采取以下兩種措施:

一是采取上面示例中的做法,將innodb_locks_unsafe_for_binlog的值設(shè)置為“on”,強(qiáng)制MySQL使用多版本數(shù)據(jù)一致性讀。但付出的代價(jià)是可能無(wú)法用binlog正確地恢復(fù)或復(fù)制數(shù)據(jù),因此,不推薦使用這種方式。

二是通過(guò)使用“select * from source_tab ... Into outfile”和“l(fā)oad data infile ...”語(yǔ)句組合來(lái)間接實(shí)現(xiàn),采用這種方式MySQL不會(huì)給source_tab加鎖。

InnoDB在不同隔離級(jí)別下的一致性讀及鎖的差異

前面講過(guò),鎖和多版本數(shù)據(jù)是InnoDB實(shí)現(xiàn)一致性讀和ISO/ANSI SQL92隔離級(jí)別的手段,因此,在不同的隔離級(jí)別下,InnoDB處理SQL時(shí)采用的一致性讀策略和需要的鎖是不同的。同時(shí),數(shù)據(jù)恢復(fù)和復(fù)制機(jī)制的特點(diǎn),也對(duì)一些SQL的一致性讀策略和鎖策略有很大影響。將這些特性歸納成如下表所示的內(nèi)容,以便讀者查閱。

InnoDB存儲(chǔ)引擎中不同SQL在不同隔離級(jí)別下鎖比較

隔離級(jí)別

一致性讀和鎖

SQL

Read Uncommited Read Commited Repeatable Read Serializable
SQL 條件
select 相等 None locks Consisten read/None lock Consisten read/None lock Share locks
范圍 None locks Consisten read/None lock Consisten read/None lock Share Next-Key
update 相等 exclusive locks exclusive locks exclusive locks Exclusive locks
范圍 exclusive next-key exclusive next-key exclusive next-key exclusive next-key
Insert N/A exclusive locks exclusive locks exclusive locks exclusive locks
replace 無(wú)鍵沖突 exclusive locks exclusive locks exclusive locks exclusive locks
鍵沖突 exclusive next-key exclusive next-key exclusive next-key exclusive next-key
delete 相等 exclusive locks exclusive locks exclusive locks exclusive locks
范圍 exclusive next-key exclusive next-key exclusive next-key exclusive next-key
Select ... from ... Lock in share mode 相等 Share locks Share locks Share locks Share locks
范圍 Share locks Share locks Share Next-Key Share Next-Key
Select * from ... For update 相等 exclusive locks exclusive locks exclusive locks exclusive locks
范圍 exclusive locks Share locks exclusive next-key exclusive next-key

Insert into ... Select ...

(指源表鎖)

innodb_locks_unsafe_for_binlog=off Share Next-Key Share Next-Key Share Next-Key Share Next-Key
innodb_locks_unsafe_for_binlog=on None locks Consisten read/None lock Consisten read/None lock Share Next-Key

create table ... Select ...

(指源表鎖)

innodb_locks_unsafe_for_binlog=off Share Next-Key Share Next-Key Share Next-Key Share Next-Key
innodb_locks_unsafe_for_binlog=on None locks Consisten read/None lock Consisten read/None lock Share Next-Key

從上表可以看出:對(duì)于許多SQL,隔離級(jí)別越高,InnoDB給記錄集加的鎖就越嚴(yán)格(尤其是使用范圍條件的時(shí)候),產(chǎn)生鎖沖突的可能性也就越高,從而對(duì)并發(fā)性事務(wù)處理性能的影響也就越大。因此,我們?cè)趹?yīng)用中,應(yīng)該盡量使用較低的隔離級(jí)別,以減少鎖爭(zhēng)用的機(jī)率。實(shí)際上,通過(guò)優(yōu)化事務(wù)邏輯,大部分應(yīng)用使用Read Commited隔離級(jí)別就足夠了。對(duì)于一些確實(shí)需要更高隔離級(jí)別的事務(wù),可以通過(guò)在程序中執(zhí)行SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ或SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE動(dòng)態(tài)改變隔離級(jí)別的方式滿足需求。

什么時(shí)候使用表鎖

對(duì)于InnoDB表,在絕大部分情況下都應(yīng)該使用行級(jí)鎖,因?yàn)槭聞?wù)和行鎖往往是我們之所以選擇InnoDB表的理由。但在個(gè)別特殊事務(wù)中,也可以考慮使用表級(jí)鎖。

第一種情況是:事務(wù)需要更新大部分或全部數(shù)據(jù),表又比較大,如果使用默認(rèn)的行鎖,不僅這個(gè)事務(wù)執(zhí)行效率低,而且可能造成其他事務(wù)長(zhǎng)時(shí)間鎖等待和鎖沖突,這種情況下可以考慮使用表鎖來(lái)提高該事務(wù)的執(zhí)行速度。

第二種情況是:事務(wù)涉及多個(gè)表,比較復(fù)雜,很可能引起死鎖,造成大量事務(wù)回滾。這種情況也可以考慮一次性鎖定事務(wù)涉及的表,從而避免死鎖、減少數(shù)據(jù)庫(kù)因事務(wù)回滾帶來(lái)的開(kāi)銷。

當(dāng)然,應(yīng)用中這兩種事務(wù)不能太多,否則,就應(yīng)該考慮使用MyISAM表了。

在InnoDB下,使用表鎖要注意以下兩點(diǎn)。

(1)使用LOCK TABLES雖然可以給InnoDB加表級(jí)鎖,但必須說(shuō)明的是,表鎖不是由InnoDB存儲(chǔ)引擎層管理的,而是由其上一層──MySQL Server負(fù)責(zé)的,僅當(dāng)autocommit=0、innodb_table_locks=1(默認(rèn)設(shè)置)時(shí),InnoDB層才能知道MySQL加的表鎖,MySQL Server也才能感知InnoDB加的行鎖,這種情況下,InnoDB才能自動(dòng)識(shí)別涉及表級(jí)鎖的死鎖;否則,InnoDB將無(wú)法自動(dòng)檢測(cè)并處理這種死鎖。有關(guān)死鎖,下一小節(jié)還會(huì)繼續(xù)討論。

(2)在用 LOCK TABLES對(duì)InnoDB表加鎖時(shí)要注意,要將AUTOCOMMIT設(shè)為0,否則MySQL不會(huì)給表加鎖;事務(wù)結(jié)束前,不要用UNLOCK TABLES釋放表鎖,因?yàn)閁NLOCK TABLES會(huì)隱含地提交事務(wù);COMMIT或ROLLBACK并不能釋放用LOCK TABLES加的表級(jí)鎖,必須用UNLOCK TABLES釋放表鎖。正確的方式見(jiàn)如下語(yǔ)句:

例如,如果需要寫(xiě)表t1并從表t讀,可以按如下做:

SET AUTOCOMMIT=0;

LOCK TABLES t1 WRITE, t2 READ, ...;

[do something with tables t1 and t2 here];

COMMIT;

UNLOCK TABLES;

關(guān)于死鎖

上文講過(guò),MyISAM表鎖是deadlock free的,這是因?yàn)镸yISAM總是一次獲得所需的全部鎖,要么全部滿足,要么等待,因此不會(huì)出現(xiàn)死鎖。但在InnoDB中,除單個(gè)SQL組成的事務(wù)外,鎖是逐步獲得的,這就決定了在InnoDB中發(fā)生死鎖是可能的。如下所示的就是一個(gè)發(fā)生死鎖的例子。

InnoDB存儲(chǔ)引擎中的死鎖例子

session_1 session_2

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from table_1 where where id=1 for update;

...

做一些其他處理...

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from table_2 where id=1 for update;

...

select * from table_2 where id =1 for update;

因session_2已取得排他鎖,等待

做一些其他處理...

mysql> select * from table_1 where where id=1 for update;

死鎖

在上面的例子中,兩個(gè)事務(wù)都需要獲得對(duì)方持有的排他鎖才能繼續(xù)完成事務(wù),這種循環(huán)鎖等待就是典型的死鎖。

發(fā)生死鎖后,InnoDB一般都能自動(dòng)檢測(cè)到,并使一個(gè)事務(wù)釋放鎖并回退,另一個(gè)事務(wù)獲得鎖,繼續(xù)完成事務(wù)。但在涉及外部鎖,或涉及表鎖的情況下,InnoDB并不能完全自動(dòng)檢測(cè)到死鎖,這需要通過(guò)設(shè)置鎖等待超時(shí)參數(shù) innodb_lock_wait_timeout來(lái)解決。需要說(shuō)明的是,這個(gè)參數(shù)并不是只用來(lái)解決死鎖問(wèn)題,在并發(fā)訪問(wèn)比較高的情況下,如果大量事務(wù)因無(wú)法立即獲得所需的鎖而掛起,會(huì)占用大量計(jì)算機(jī)資源,造成嚴(yán)重性能問(wèn)題,甚至拖跨數(shù)據(jù)庫(kù)。我們通過(guò)設(shè)置合適的鎖等待超時(shí)閾值,可以避免這種情況發(fā)生。

通常來(lái)說(shuō),死鎖都是應(yīng)用設(shè)計(jì)的問(wèn)題,通過(guò)調(diào)整業(yè)務(wù)流程、數(shù)據(jù)庫(kù)對(duì)象設(shè)計(jì)、事務(wù)大小,以及訪問(wèn)數(shù)據(jù)庫(kù)的SQL語(yǔ)句,絕大部分死鎖都可以避免。下面就通過(guò)實(shí)例來(lái)介紹幾種避免死鎖的常用方法。

(1)在應(yīng)用中,如果不同的程序會(huì)并發(fā)存取多個(gè)表,應(yīng)盡量約定以相同的順序來(lái)訪問(wèn)表,這樣可以大大降低產(chǎn)生死鎖的機(jī)會(huì)。在下面的例子中,由于兩個(gè)session訪問(wèn)兩個(gè)表的順序不同,發(fā)生死鎖的機(jī)會(huì)就非常高!但如果以相同的順序來(lái)訪問(wèn),死鎖就可以避免。

InnoDB存儲(chǔ)引擎中表順序造成的死鎖例子

session_1 session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select first_name,last_name from actor where actor_id = 1 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| PENELOPE | GUINESS |

+------------+-----------+

1 row in set (0.00 sec)

mysql> insert into country (country_id,country) values(110,'Test');

Query OK, 1 row affected (0.00 sec)

mysql> insert into country (country_id,country) values(110,'Test');

等待

mysql> select first_name,last_name from actor where actor_id = 1 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| PENELOPE | GUINESS |

+------------+-----------+

1 row in set (0.00 sec)

mysql> insert into country (country_id,country) values(110,'Test');

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

(2)在程序以批量方式處理數(shù)據(jù)的時(shí)候,如果事先對(duì)數(shù)據(jù)排序,保證每個(gè)線程按固定的順序來(lái)處理記錄,也可以大大降低出現(xiàn)死鎖的可能。

InnoDB存儲(chǔ)引擎中表數(shù)據(jù)操作順序不一致造成的死鎖例子

session_1 session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select first_name,last_name from actor where actor_id = 1 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| PENELOPE | GUINESS |

+------------+-----------+

1 row in set (0.00 sec)

mysql> select first_name,last_name from actor where actor_id = 3 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| ED | CHASE |

+------------+-----------+

1 row in set (0.00 sec)

mysql> select first_name,last_name from actor where actor_id = 3 for update;

等待

mysql> select first_name,last_name from actor where actor_id = 1 for update;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

mysql> select first_name,last_name from actor where actor_id = 3 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| ED | CHASE |

+------------+-----------+

1 row in set (4.71 sec)

(3)在事務(wù)中,如果要更新記錄,應(yīng)該直接申請(qǐng)足夠級(jí)別的鎖,即排他鎖,而不應(yīng)先申請(qǐng)共享鎖,更新時(shí)再申請(qǐng)排他鎖,因?yàn)楫?dāng)用戶申請(qǐng)排他鎖時(shí),其他事務(wù)可能又已經(jīng)獲得了相同記錄的共享鎖,從而造成鎖沖突,甚至死鎖。

(4)前面講過(guò),在REPEATABLE-READ隔離級(jí)別下,如果兩個(gè)線程同時(shí)對(duì)相同條件記錄用SELECT...FOR UPDATE加排他鎖,在沒(méi)有符合該條件記錄情況下,兩個(gè)線程都會(huì)加鎖成功。程序發(fā)現(xiàn)記錄尚不存在,就試圖插入一條新記錄,如果兩個(gè)線程都這么做,就會(huì)出現(xiàn)死鎖。這種情況下,將隔離級(jí)別改成READ COMMITTED,就可避免問(wèn)題,如下所示。

InnoDB存儲(chǔ)引擎中隔離級(jí)別引起的死鎖例子1

session_1 session_2

mysql> select @@tx_isolation;

+-----------------+

| @@tx_isolation |

+-----------------+

| REPEATABLE-READ |

+-----------------+

1 row in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;

+-----------------+

| @@tx_isolation |

+-----------------+

| REPEATABLE-READ |

+-----------------+

1 row in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

當(dāng)前session對(duì)不存在的記錄加for update的鎖:

mysql> select actor_id,first_name,last_name from actor where actor_id = 201 for update;

Empty set (0.00 sec)

其他session也可以對(duì)不存在的記錄加for update的鎖:

mysql> select actor_id,first_name,last_name from actor where actor_id = 201 for update;

Empty set (0.00 sec)

因?yàn)槠渌鹲ession也對(duì)該記錄加了鎖,所以當(dāng)前的插入會(huì)等待:

mysql> insert into actor (actor_id , first_name , last_name) values(201,'Lisa','Tom');

等待

因?yàn)槠渌鹲ession已經(jīng)對(duì)記錄進(jìn)行了更新,這時(shí)候再插入記錄就會(huì)提示死鎖并退出:

mysql> insert into actor (actor_id, first_name , last_name) values(201,'Lisa','Tom');

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

由于其他session已經(jīng)退出,當(dāng)前session可以獲得鎖并成功插入記錄:

mysql> insert into actor (actor_id , first_name , last_name) values(201,'Lisa','Tom');

Query OK, 1 row affected (13.35 sec)

(5)當(dāng)隔離級(jí)別為READ COMMITTED時(shí),如果兩個(gè)線程都先執(zhí)行SELECT...FOR UPDATE,判斷是否存在符合條件的記錄,如果沒(méi)有,就插入記錄。此時(shí),只有一個(gè)線程能插入成功,另一個(gè)線程會(huì)出現(xiàn)鎖等待,當(dāng)?shù)?個(gè)線程提交后,第2個(gè)線程會(huì)因主鍵重出錯(cuò),但雖然這個(gè)線程出錯(cuò)了,卻會(huì)獲得一個(gè)排他鎖!這時(shí)如果有第3個(gè)線程又來(lái)申請(qǐng)排他鎖,也會(huì)出現(xiàn)死鎖。

對(duì)于這種情況,可以直接做插入操作,然后再捕獲主鍵重異常,或者在遇到主鍵重錯(cuò)誤時(shí),總是執(zhí)行ROLLBACK釋放獲得的排他鎖,如下所示。

InnoDB存儲(chǔ)引擎中隔離級(jí)別引起的死鎖例子2

session_1 session_2 session_3

mysql> select @@tx_isolation;

+----------------+

| @@tx_isolation |

+----------------+

| READ-COMMITTED |

+----------------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.01 sec)

mysql> select @@tx_isolation;

+----------------+

| @@tx_isolation |

+----------------+

| READ-COMMITTED |

+----------------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.01 sec)

mysql> select @@tx_isolation;

+----------------+

| @@tx_isolation |

+----------------+

| READ-COMMITTED |

+----------------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.01 sec)

Session_1獲得for update的共享鎖:

mysql> select actor_id, first_name,last_name from actor where actor_id = 201 for update;

Empty set (0.00 sec)

由于記錄不存在,session_2也可以獲得for update的共享鎖:

mysql> select actor_id, first_name,last_name from actor where actor_id = 201 for update;

Empty set (0.00 sec)

Session_1可以成功插入記錄:

mysql> insert into actor (actor_id,first_name,last_name) values(201,'Lisa','Tom');

Query OK, 1 row affected (0.00 sec)

Session_2插入申請(qǐng)等待獲得鎖:

mysql> insert into actor (actor_id,first_name,last_name) values(201,'Lisa','Tom');

等待

Session_1成功提交:

mysql> commit;

Query OK, 0 rows affected (0.04 sec)

Session_2獲得鎖,發(fā)現(xiàn)插入記錄主鍵重,這個(gè)時(shí)候拋出了異常,但是并沒(méi)有釋放共享鎖:

mysql> insert into actor (actor_id,first_name,last_name) values(201,'Lisa','Tom');

ERROR 1062 (23000): Duplicate entry '201' for key 'PRIMARY'

Session_3申請(qǐng)獲得共享鎖,因?yàn)閟ession_2已經(jīng)鎖定該記錄,所以session_3需要等待:

mysql> select actor_id, first_name,last_name from actor where actor_id = 201 for update;

等待

這個(gè)時(shí)候,如果session_2直接對(duì)記錄進(jìn)行更新操作,則會(huì)拋出死鎖的異常:

mysql> update actor set last_name='Lan' where actor_id = 201;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

Session_2釋放鎖后,session_3獲得鎖:

mysql> select first_name, last_name from actor where actor_id = 201 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| Lisa | Tom |

+------------+-----------+

1 row in set (31.12 sec)

盡管通過(guò)上面介紹的設(shè)計(jì)和SQL優(yōu)化等措施,可以大大減少死鎖,但死鎖很難完全避免。因此,在程序設(shè)計(jì)中總是捕獲并處理死鎖異常是一個(gè)很好的編程習(xí)慣。

如果出現(xiàn)死鎖,可以用SHOW INNODB STATUS命令來(lái)確定最后一個(gè)死鎖產(chǎn)生的原因。返回結(jié)果中包括死鎖相關(guān)事務(wù)的詳細(xì)信息,如引發(fā)死鎖的SQL語(yǔ)句,事務(wù)已經(jīng)獲得的鎖,正在等待什么鎖,以及被回滾的事務(wù)等。據(jù)此可以分析死鎖產(chǎn)生的原因和改進(jìn)措施。下面是一段SHOW INNODB STATUS輸出的樣例:

mysql> show innodb status G

…….

------------------------

LATEST DETECTED DEADLOCK

------------------------

070710 14:05:16

*** (1) TRANSACTION:

TRANSACTION 0 117470078, ACTIVE 117 sec, process no 1468, OS thread id 1197328736 inserting

mysql tables in use 1, locked 1

LOCK WAIT 5 lock struct(s), heap size 1216

MySQL thread id 7521657, query id 673468054 localhost root update

insert into country (country_id,country) values(110,'Test')

………

*** (2) TRANSACTION:

TRANSACTION 0 117470079, ACTIVE 39 sec, process no 1468, OS thread id 1164048736 starting index read, thread declared inside InnoDB 500

mysql tables in use 1, locked 1

4 lock struct(s), heap size 1216, undo log entries 1

MySQL thread id 7521664, query id 673468058 localhost root statistics

select first_name,last_name from actor where actor_id = 1 for update

*** (2) HOLDS THE LOCK(S):

………

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

………

*** WE ROLL BACK TRANSACTION (1)

……

Navicat for MySQL相關(guān)推薦
  • 相關(guān)百科
  • 相關(guān)知識(shí)
  • 相關(guān)專欄

最新詞條

安徽省政采項(xiàng)目管理咨詢有限公司 數(shù)字景楓科技發(fā)展(南京)有限公司 懷化市人民政府電子政務(wù)管理辦公室 河北省高速公路京德臨時(shí)籌建處 中石化華東石油工程有限公司工程技術(shù)分公司 手持無(wú)線POS機(jī) 廣東合正采購(gòu)招標(biāo)有限公司 上海城建信息科技有限公司 甘肅鑫禾國(guó)際招標(biāo)有限公司 燒結(jié)金屬材料 齒輪計(jì)量泵 廣州采陽(yáng)招標(biāo)代理有限公司河源分公司 高鋁碳化硅磚 博洛尼智能科技(青島)有限公司 燒結(jié)剛玉磚 深圳市東海國(guó)際招標(biāo)有限公司 搭建香蕉育苗大棚 SF計(jì)量單位 福建省中億通招標(biāo)咨詢有限公司 泛海三江 威海鼠尾草 Excel 數(shù)據(jù)處理與分析應(yīng)用大全 廣東國(guó)咨招標(biāo)有限公司 甘肅中泰博瑞工程項(xiàng)目管理咨詢有限公司 山東創(chuàng)盈項(xiàng)目管理有限公司 當(dāng)代建筑大師 廣西北纜電纜有限公司 拆邊機(jī) 大山檳榔 上海地鐵維護(hù)保障有限公司通號(hào)分公司 甘肅中維國(guó)際招標(biāo)有限公司 舌花雛菊 湖北鑫宇陽(yáng)光工程咨詢有限公司 GB8163標(biāo)準(zhǔn)無(wú)縫鋼管 中國(guó)石油煉化工程建設(shè)項(xiàng)目部 華潤(rùn)燃?xì)猓ㄉ虾#┯邢薰? 韶關(guān)市優(yōu)采招標(biāo)代理有限公司 莎草目 建設(shè)部關(guān)于開(kāi)展城市規(guī)劃動(dòng)態(tài)監(jiān)測(cè)工作的通知 電梯平層準(zhǔn)確度 廣州利好來(lái)電氣有限公司 四川中澤盛世招標(biāo)代理有限公司