深入理解MySQL——鎖、事務(wù)與并發(fā)控制
mysql服務(wù)器邏輯架構(gòu)
每個(gè)連接都會(huì)在mysql服務(wù)端產(chǎn)生一個(gè)線(xiàn)程(內(nèi)部通過(guò)線(xiàn)程池管理線(xiàn)程),比如一個(gè)select語(yǔ)句進(jìn)入,mysql首先會(huì)在查詢(xún)緩存中查找是否緩存了這個(gè)select的結(jié)果集,如果沒(méi)有則繼續(xù)執(zhí)行 解析、優(yōu)化、執(zhí)行的過(guò)程;否則會(huì)之間從緩存中獲取結(jié)果集。
mysql并發(fā)控制——共享鎖、排他鎖
共享鎖
共享鎖也稱(chēng)為讀鎖,讀鎖允許多個(gè)連接可以同一時(shí)刻并發(fā)的讀取同一資源,互不干擾;
排他鎖
排他鎖也稱(chēng)為寫(xiě)鎖,一個(gè)寫(xiě)鎖會(huì)阻塞其他的寫(xiě)鎖或讀鎖,保證同一時(shí)刻只有一個(gè)連接可以寫(xiě)入數(shù)據(jù),同時(shí)防止其他用戶(hù)對(duì)這個(gè)數(shù)據(jù)的讀寫(xiě)。
鎖策略
鎖的開(kāi)銷(xiāo)是較為昂貴的,鎖策略其實(shí)就是保證了線(xiàn)程安全的同時(shí)獲取最大的性能之間的平衡策略。
-
mysql鎖策略:talbe lock(表鎖)
表鎖是mysql最基本的鎖策略,也是開(kāi)銷(xiāo)最小的鎖,它會(huì)鎖定整個(gè)表;
具體情況是:若一個(gè)用戶(hù)正在執(zhí)行寫(xiě)操作,會(huì)獲取排他的“寫(xiě)鎖”,這可能會(huì)鎖定整個(gè)表,阻塞其他用戶(hù)的讀、寫(xiě)操作;
若一個(gè)用戶(hù)正在執(zhí)行讀操作,會(huì)先獲取共享鎖“讀鎖”,這個(gè)鎖運(yùn)行其他讀鎖并發(fā)的對(duì)這個(gè)表進(jìn)行讀取,互不干擾。只要沒(méi)有寫(xiě)鎖的進(jìn)入,讀鎖可以是并發(fā)讀取統(tǒng)一資源的。
通常發(fā)生在DDL語(yǔ)句\DML不走索引的語(yǔ)句中,比如這個(gè)DML update table set columnA=”A” where columnB=“B”.
如果columnB字段不存在索引(或者不是組合索引前綴),會(huì)鎖住所有記錄也就是鎖表。如果語(yǔ)句的執(zhí)行能夠執(zhí)行一個(gè)columnB字段的索引,那么會(huì)鎖住滿(mǎn)足where的行(行鎖)。
-
mysql鎖策略:row lock(行鎖)
行鎖可以最大限度的支持并發(fā)處理,當(dāng)然也帶來(lái)了最大開(kāi)銷(xiāo),顧名思義,行鎖的粒度實(shí)在每一條行數(shù)據(jù)。
事務(wù)
事務(wù)就是一組原子性的sql,或者說(shuō)一個(gè)獨(dú)立的工作單元。
事務(wù)就是說(shuō),要么mysql引擎會(huì)全部執(zhí)行這一組sql語(yǔ)句,要么全部都不執(zhí)行(比如其中一條語(yǔ)句失敗的話(huà))。
比如,tim要給bill轉(zhuǎn)賬100塊錢(qián):
1.檢查tim的賬戶(hù)余額是否大于100塊;
2.tim的賬戶(hù)減少100塊;
3.bill的賬戶(hù)增加100塊;
這三個(gè)操作就是一個(gè)事務(wù),必須打包執(zhí)行,要么全部成功,要么全部不執(zhí)行,其中任何一個(gè)操作的失敗都會(huì)導(dǎo)致所有三個(gè)操作“不執(zhí)行”——回滾。
- CREATE DATABASE IF NOT EXISTS employees;
- USE employees;
- CREATE TABLE `employees`.`account` (
- `id` BIGINT (11) NOT NULL AUTO_INCREMENT,
- `p_name` VARCHAR (4),
- `p_money` DECIMAL (10, 2) NOT NULL DEFAULT 0,
- PRIMARY KEY (`id`)
- ) ;
- INSERT INTO `employees`.`account` (`id`, `p_name`, `p_money`) VALUES ('1', 'tim', '200');
- INSERT INTO `employees`.`account` (`id`, `p_name`, `p_money`) VALUES ('2', 'bill', '200');
- START TRANSACTION;
- SELECT p_money FROM account WHERE p_name="tim";-- step1
- UPDATE account SET p_money=p_money-100 WHERE p_name="tim";-- step2
- UPDATE account SET p_money=p_money+100 WHERE p_name="bill";-- step3
- COMMIT;
一個(gè)良好的事務(wù)系統(tǒng),必須滿(mǎn)足ACID特點(diǎn):
事務(wù)的ACID
-
A:atomiciy原子性
一個(gè)事務(wù)必須保證其中的操作要么全部執(zhí)行,要么全部回滾,不可能存在只執(zhí)行了一部分這種情況出現(xiàn)。 -
C:consistency一致性
數(shù)據(jù)必須保證從一種一致性的狀態(tài)轉(zhuǎn)換為另一種一致性狀態(tài)。
比如上一個(gè)事務(wù)中執(zhí)行了第二步時(shí)系統(tǒng)崩潰了,數(shù)據(jù)也不會(huì)出現(xiàn)bill的賬戶(hù)少了100塊,但是tim的賬戶(hù)沒(méi)變的情況。要么維持原裝(全部回滾),要么bill少了100塊同時(shí)tim多了100塊,只有這兩種一致性狀態(tài)的 -
I:isolation隔離性
在一個(gè)事務(wù)未執(zhí)行完畢時(shí),通常會(huì)保證其他Session 無(wú)法看到這個(gè)事務(wù)的執(zhí)行結(jié)果 -
D:durability持久性
事務(wù)一旦commit,則數(shù)據(jù)就會(huì)保存下來(lái),即使提交完之后系統(tǒng)崩潰,數(shù)據(jù)也不會(huì)丟失。
隔離級(jí)別
查看系統(tǒng)隔離級(jí)別: select @@global.tx_isolation; 查看當(dāng)前會(huì)話(huà)隔離級(jí)別 select @@tx_isolation; 設(shè)置當(dāng)前會(huì)話(huà)隔離級(jí)別 SET session TRANSACTION ISOLATION LEVEL serializable; 設(shè)置全局系統(tǒng)隔離級(jí)別 SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
READ UNCOMMITTED(未提交讀,可臟讀)
事務(wù)中的修改,即使沒(méi)有提交,對(duì)其他會(huì)話(huà)也是可見(jiàn)的。
可以讀取未提交的數(shù)據(jù)——臟讀。臟讀會(huì)導(dǎo)致很多問(wèn)題,一般不適用這個(gè)隔離級(jí)別。
實(shí)例:
- -- ------------------------- read-uncommitted實(shí)例 ------------------------------
- -- 設(shè)置全局系統(tǒng)隔離級(jí)別
- SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
- -- Session A
- START TRANSACTION;
- SELECT * FROM USER;
- UPDATE USER SET NAME="READ UNCOMMITTED";
- -- commit;
- -- Session B
- SELECT * FROM USER;
- //SessionB Console 可以看到Session A未提交的事物處理,在另一個(gè)Session 中也看到了,這就是所謂的臟讀
- id name
- 2 READ UNCOMMITTED
- 34 READ UNCOMMITTED
READ COMMITTED(提交讀或不可重復(fù)讀,幻讀)
一般數(shù)據(jù)庫(kù)都默認(rèn)使用這個(gè)隔離級(jí)別(mysql不是),這個(gè)隔離級(jí)別保證了一個(gè)事務(wù)如果沒(méi)有完全成功(commit執(zhí)行完),事務(wù)中的操作對(duì)其他會(huì)話(huà)是不可見(jiàn)的。
- -- ------------------------- read-cmmitted實(shí)例 ------------------------------
- -- 設(shè)置全局系統(tǒng)隔離級(jí)別
- SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
- -- Session A
- START TRANSACTION;
- SELECT * FROM USER;
- UPDATE USER SET NAME="READ COMMITTED";
- -- COMMIT;
- -- Session B
- SELECT * FROM USER;
- //Console OUTPUT:
- id name
- 2 READ UNCOMMITTED
- 34 READ UNCOMMITTED
- ---------------------------------------------------
- -- 當(dāng) Session A執(zhí)行了commit,Session B得到如下結(jié)果:
- id name
- 2 READ COMMITTED
- 34 READ COMMITTED
也就驗(yàn)證了read committed級(jí)別在事物未完成commit操作之前修改的數(shù)據(jù)對(duì)其他Session 不可見(jiàn),執(zhí)行了commit之后才會(huì)對(duì)其他Session 可見(jiàn)。
我們可以看到Session B兩次查詢(xún)得到了不同的數(shù)據(jù)。
read committed隔離級(jí)別解決了臟讀的問(wèn)題,但是會(huì)對(duì)其他Session 產(chǎn)生兩次不一致的讀取結(jié)果(因?yàn)榱硪粋€(gè)Session 執(zhí)行了事務(wù),一致性變化)。
REPEATABLE READ(可重復(fù)讀)
一個(gè)事務(wù)中多次執(zhí)行統(tǒng)一讀SQL,返回結(jié)果一樣。
這個(gè)隔離級(jí)別解決了臟讀的問(wèn)題,幻讀問(wèn)題。這里指的是innodb的rr級(jí)別,innodb中使用next-key鎖對(duì)”當(dāng)前讀”進(jìn)行加鎖,鎖住行以及可能產(chǎn)生幻讀的插入位置,阻止新的數(shù)據(jù)插入產(chǎn)生幻行。
下文中詳細(xì)分析。
具體請(qǐng)參考mysql手冊(cè)
https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html
SERIALIZABLE(可串行化)
最強(qiáng)的隔離級(jí)別,通過(guò)給事務(wù)中每次讀取的行加鎖,寫(xiě)加寫(xiě)鎖,保證不產(chǎn)生幻讀問(wèn)題,但是會(huì)導(dǎo)致大量超時(shí)以及鎖爭(zhēng)用問(wèn)題。
多版本并發(fā)控制-MVCC
MVCC(multiple-version-concurrency-control)是個(gè)行級(jí)鎖的變種,它在普通讀情況下避免了加鎖操作,因此開(kāi)銷(xiāo)更低。
雖然實(shí)現(xiàn)不同,但通常都是實(shí)現(xiàn)非阻塞讀,對(duì)于寫(xiě)操作只鎖定必要的行。
-
一致性讀 (就是讀取快照)
select * from table ….; -
當(dāng)前讀(就是讀取實(shí)際的持久化的數(shù)據(jù))
特殊的讀操作,插入/更新/刪除操作,屬于當(dāng)前讀,處理的都是當(dāng)前的數(shù)據(jù),需要加鎖。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update ;
delete;
注意:select …… from where…… (沒(méi)有額外加鎖后綴)使用MVCC,保證了讀快照(mysql稱(chēng)為consistent read),所謂一致性讀或者讀快照就是讀取當(dāng)前事務(wù)開(kāi)始之前的數(shù)據(jù)快照,在這個(gè)事務(wù)開(kāi)始之后的更新不會(huì)被讀到。詳細(xì)情況下文select的詳述。
對(duì)于加鎖讀SELECT with FOR UPDATE(排他鎖) or LOCK IN SHARE MODE(共享鎖)、update、delete語(yǔ)句,要考慮是否是唯一索引的等值查詢(xún)。
寫(xiě)鎖-recordLock,gapLock,next key lock
對(duì)于使用到唯一索引 等值查詢(xún):比如,where columnA=”…” ,如果columnA上的索引被使用到,
那么會(huì)在滿(mǎn)足where的記錄上加行鎖(for update是排他鎖,lock in shared 是共享鎖,其他寫(xiě)操作加排他鎖)。這里是行級(jí)鎖,record lock。
對(duì)于范圍查詢(xún)(使用非唯一的索引):
比如(做范圍查詢(xún)):where columnA between 10 and 30 ,會(huì)導(dǎo)致其他會(huì)話(huà)中10以后的數(shù)據(jù)都無(wú)法插入(next key lock),從而解決了幻讀問(wèn)題。
這里是next key lock 會(huì)包括涉及到的所有行。
next key lock=recordLock+gapLock,不僅鎖住相關(guān)數(shù)據(jù),而且鎖住邊界,從而徹底避免幻讀。
對(duì)于沒(méi)有索引
鎖表
通常發(fā)生在DDL語(yǔ)句\DML不走索引的語(yǔ)句中,比如這個(gè)DML update table set columnA=”A” where columnB=“B”.
如果columnB字段不存在索引(或者不是組合索引前綴),會(huì)鎖住所有記錄也就是鎖表。如果語(yǔ)句的執(zhí)行能夠執(zhí)行一個(gè)columnB字段的索引,那么會(huì)鎖住滿(mǎn)足where的行(行鎖)。
INNODB的MVCC通常是通過(guò)在每行數(shù)據(jù)后邊保存兩個(gè)隱藏的列來(lái)實(shí)現(xiàn)(其實(shí)是三列,第三列是用于事務(wù)回滾,此處略去),
一個(gè)保存了行的創(chuàng)建版本號(hào),另一個(gè)保存了行的更新版本號(hào)(上一次被更新數(shù)據(jù)的版本號(hào))
這個(gè)版本號(hào)是每個(gè)事務(wù)的版本號(hào),遞增的。
這樣保證了innodb對(duì)讀操作不需要加鎖也能保證正確讀取數(shù)據(jù)。
MVCC select無(wú)鎖操作 與 維護(hù)版本號(hào)
下邊在mysql默認(rèn)的Repeatable Read隔離級(jí)別下,具體看看MVCC操作:
-
Select(快照讀,所謂讀快照就是讀取當(dāng)前事務(wù)之前的數(shù)據(jù)。):
a.InnoDB只select查找版本號(hào)早于當(dāng)前版本號(hào)的數(shù)據(jù)行,這樣保證了讀取的數(shù)據(jù)要么是在這個(gè)事務(wù)開(kāi)始之前就已經(jīng)commit了的(早于當(dāng)前版本號(hào)),要么是在這個(gè)事務(wù)自身中執(zhí)行創(chuàng)建操作的數(shù)據(jù)(等于當(dāng)前版本號(hào))。b.查找行的更新版本號(hào)要么未定義,要么大于當(dāng)前的版本號(hào)(為了保證事務(wù)可以讀到老數(shù)據(jù)),這樣保證了事務(wù)讀取到在當(dāng)前事務(wù)開(kāi)始之后未被更新的數(shù)據(jù)。
注意: 這里的select不能有for update、lock in share 語(yǔ)句。
總之要只返回滿(mǎn)足以下條件的行數(shù)據(jù),達(dá)到了快照讀的效果:
(行創(chuàng)建版本號(hào)< =當(dāng)前版本號(hào) && (行更新版本號(hào)==null or 行更新版本號(hào)>當(dāng)前版本號(hào) ) )
-
Insert
InnoDB為這個(gè)事務(wù)中新插入的行,保存當(dāng)前事務(wù)版本號(hào)的行作為行的行創(chuàng)建版本號(hào)。
-
Delete
InnoDB為每一個(gè)刪除的行保存當(dāng)前事務(wù)版本號(hào),作為行的刪除標(biāo)記。 -
Update
將存在兩條數(shù)據(jù),保持當(dāng)前版本號(hào)作為更新后的數(shù)據(jù)的新增版本號(hào),同時(shí)保存當(dāng)前版本號(hào)作為老數(shù)據(jù)行的更新版本號(hào)。
當(dāng)前版本號(hào)—寫(xiě)—>新數(shù)據(jù)行創(chuàng)建版本號(hào) && 當(dāng)前版本號(hào)—寫(xiě)—>老數(shù)據(jù)更新版本號(hào)();
臟讀 vs 幻讀 vs 不可重復(fù)讀
臟讀:一事務(wù)未提交的中間狀態(tài)的更新數(shù)據(jù) 被其他會(huì)話(huà)讀取到。 當(dāng)一個(gè)事務(wù)正在訪(fǎng)問(wèn)數(shù)據(jù),并且對(duì)數(shù)據(jù)進(jìn)行了修改,而這種修改還沒(méi)有 提交到數(shù)據(jù)庫(kù)中(commit未執(zhí)行),這時(shí),另外會(huì)話(huà)也訪(fǎng)問(wèn)這個(gè)數(shù)據(jù),因?yàn)檫@個(gè)數(shù)據(jù)是還沒(méi)有提交, 那么另外一個(gè)會(huì)話(huà)讀到的這個(gè)數(shù)據(jù)是臟數(shù)據(jù),依據(jù)臟數(shù)據(jù)所做的操作也可能是不正確的。
不可重復(fù)讀:簡(jiǎn)單來(lái)說(shuō)就是在一個(gè)事務(wù)中讀取的數(shù)據(jù)可能產(chǎn)生變化,ReadCommitted也稱(chēng)為不可重復(fù)讀。
在同一事務(wù)中,多次讀取同一數(shù)據(jù)返回的結(jié)果有所不同。換句話(huà)說(shuō)就是,后續(xù)讀取可以讀到另一會(huì)話(huà)事務(wù)已提交的更新數(shù)據(jù)。 相反,“可重復(fù)讀”在同一事務(wù)中多次讀取數(shù)據(jù)時(shí),能夠保證所讀數(shù)據(jù)一樣,也就是,后續(xù)讀取不能讀到另一會(huì)話(huà)事務(wù)已提交的更新數(shù)據(jù)。
幻讀:會(huì)話(huà)T1事務(wù)中執(zhí)行一次查詢(xún),然后會(huì)話(huà)T2新插入一行記錄,這行記錄恰好可以滿(mǎn)足T1所使用的查詢(xún)的條件。然后T1又使用相同 的查詢(xún)?cè)俅螌?duì)表進(jìn)行檢索,但是此時(shí)卻看到了事務(wù)T2剛才插入的新行。這個(gè)新行就稱(chēng)為“幻像”,因?yàn)閷?duì)T1來(lái)說(shuō)這一行就像突然 出現(xiàn)的一樣。
innoDB的RR級(jí)別無(wú)法做到完全避免幻讀,下文詳細(xì)分析。
- ----------------------------------前置準(zhǔn)備----------------------------------------
- prerequisite:
- -- 創(chuàng)建表
- mysql>
- CREATE TABLE `t_bitfly` (
- `id` bigint(20) NOT NULL DEFAULT '0',
- `value` varchar(32) DEFAULT NULL,
- PRIMARY KEY (`id`)
- )
- -- 確保當(dāng)前隔離級(jí)別為默認(rèn)的RR級(jí)別
- mysql> select @@global.tx_isolation, @@tx_isolation;
- +-----------------------+-----------------+
- | @@global.tx_isolation | @@tx_isolation |
- +-----------------------+-----------------+
- | REPEATABLE-READ | REPEATABLE-READ |
- +-----------------------+-----------------+
- 1 row in set (0.00 sec)
- ---------------------------------------開(kāi)始---------------------------------------------
- session A | session B
- |
- |
- mysql> START TRANSACTION; | mysql> START TRANSACTION;
- Query OK, 0 rows affected (0.00 sec) | Query OK, 0 rows affected (0.00 sec)
- |
- |
- mysql> SELECT * FROM test.t_bitfly; | mysql> SELECT * FROM test.t_bitfly;
- Empty set (0.00 sec) | Empty set (0.00 sec)
- |
- | mysql> INSERT INTO t_bitfly VALUES (1, 'test');
- | Query OK, 1 row affected (0.00 sec)
- |
- |
- mysql> SELECT * FROM test.t_bitfly; |
- Empty set (0.00 sec) |
- |
- | mysql> commit;
- | Query OK, 0 rows affected (0.01 sec)
- mysql> SELECT * FROM test.t_bitfly; |
- Empty set (0.00 sec) |
- -- 可以看到雖然兩次執(zhí)行結(jié)果返回的數(shù)據(jù)一致, |
- -- 但是不能說(shuō)明沒(méi)有幻讀。接著看: |
- |
- mysql> INSERT INTO t_bitfly VALUES (1, 'test'); |
- ERROR 1062 (23000): |
- Duplicate entry '1' for key 'PRIMARY' |
- |
- -- 明明為空的表,為什么說(shuō)主鍵重復(fù)?——幻讀出現(xiàn) !!! |
如何保證rr級(jí)別絕對(duì)不產(chǎn)生幻讀?
在使用的select …where語(yǔ)句中加入 for update(排他鎖) 或者 lock in share mode(共享鎖)語(yǔ)句來(lái)實(shí)現(xiàn)。其實(shí)就是鎖住了可能造成幻讀的數(shù)據(jù),阻止數(shù)據(jù)的寫(xiě)入操作。
其實(shí)是因?yàn)閿?shù)據(jù)的寫(xiě)入操作(insert 、update)需要先獲取寫(xiě)鎖,由于可能產(chǎn)生幻讀的部分,已經(jīng)獲取到了某種鎖,所以要在另外一個(gè)會(huì)話(huà)中獲取寫(xiě)鎖的前提是當(dāng)前會(huì)話(huà)中釋放所有因加鎖語(yǔ)句產(chǎn)生的鎖。
mysql死鎖問(wèn)題
死鎖,就是產(chǎn)生了循環(huán)等待鏈條,我等待你的資源,你卻等待我的資源,我們都相互等待,誰(shuí)也不釋放自己占有的資源,導(dǎo)致無(wú)線(xiàn)等待下去。
比如:
- //Session A
- START TRANSACTION;
- UPDATE account SET p_money=p_money-100 WHERE p_name="tim";
- UPDATE account SET p_money=p_money+100 WHERE p_name="bill";
- COMMIT;
- //Thread B
- START TRANSACTION;
- UPDATE account SET p_money=p_money+100 WHERE p_name="bill";
- UPDATE account SET p_money=p_money-100 WHERE p_name="tim";
- COMMIT;
當(dāng)線(xiàn)程A執(zhí)行到第一條語(yǔ)句UPDATE account SET p_money=p_money-100 WHERE p_name=”tim”;鎖定了p_name=”tim”的行數(shù)據(jù);并且試圖獲取p_name=”bill”的數(shù)據(jù);
,此時(shí),恰好,線(xiàn)程B也執(zhí)行到第一條語(yǔ)句:UPDATE account SET p_money=p_money+100 WHERE p_name=”bill”;
鎖定了 p_name=”bill”的數(shù)據(jù),同時(shí)試圖獲取p_name=”tim”的數(shù)據(jù);
此時(shí),兩個(gè)線(xiàn)程就進(jìn)入了死鎖,誰(shuí)也無(wú)法獲取自己想要獲取的資源,進(jìn)入無(wú)線(xiàn)等待中,直到超時(shí)!
innodb_lock_wait_timeout 等待鎖超時(shí)回滾事務(wù):
直觀(guān)方法是在兩個(gè)事務(wù)相互等待時(shí),當(dāng)一個(gè)等待時(shí)間超過(guò)設(shè)置的某一閥值時(shí),對(duì)其中一個(gè)事務(wù)進(jìn)行回滾,另一個(gè)事務(wù)就能繼續(xù)執(zhí)行。這種方法簡(jiǎn)單有效,在innodb中,參數(shù)innodb_lock_wait_timeout用來(lái)設(shè)置超時(shí)時(shí)間。
wait-for graph算法來(lái)主動(dòng)進(jìn)行死鎖檢測(cè):
innodb還提供了wait-for graph算法來(lái)主動(dòng)進(jìn)行死鎖檢測(cè),每當(dāng)加鎖請(qǐng)求無(wú)法立即滿(mǎn)足需要并進(jìn)入等待時(shí),wait-for graph算法都會(huì)被觸發(fā)。
如何盡可能避免死鎖
1)以固定的順序訪(fǎng)問(wèn)表和行。比如兩個(gè)更新數(shù)據(jù)的事務(wù),事務(wù)A 更新數(shù)據(jù)的順序 為1,2;事務(wù)B更新數(shù)據(jù)的順序?yàn)?,1。這樣更可能會(huì)造成死鎖。
2)大事務(wù)拆小。大事務(wù)更傾向于死鎖,如果業(yè)務(wù)允許,將大事務(wù)拆小。
3)在同一個(gè)事務(wù)中,盡可能做到一次鎖定所需要的所有資源,減少死鎖概率。
4)降低隔離級(jí)別。如果業(yè)務(wù)允許,將隔離級(jí)別調(diào)低也是較好的選擇,比如將隔離級(jí)別從RR調(diào)整為RC,可以避免掉很多因?yàn)間ap鎖造成的死鎖。
5)為表添加合理的索引。可以看到如果不走索引將會(huì)為表的每一行記錄添加上鎖,死鎖的概率大大增大。
顯式鎖 與 隱式鎖
隱式鎖:我們上文說(shuō)的鎖都屬于不需要額外語(yǔ)句加鎖的隱式鎖。
顯示鎖:
SELECT ... LOCK IN SHARE MODE(加共享鎖); SELECT ... FOR UPDATE(加排他鎖);
詳情上文已經(jīng)說(shuō)過(guò)。
通過(guò)如下sql可以查看等待鎖的情況
- select * from information_schema.innodb_trx where trx_state="lock wait";
或
- show engine innodb status;
mysql中的事務(wù)
- show variables like "autocommit";
- set autocommit=0; //0表示AutoCommit關(guān)閉
- set autocommit=1; //1表示AutoCommit開(kāi)啟
-
自動(dòng)提交(AutoCommit,mysql默認(rèn))
mysql默認(rèn)采用AutoCommit模式,也就是每個(gè)sql都是一個(gè)事務(wù),并不需要顯示的執(zhí)行事務(wù)。
如果autoCommit關(guān)閉,那么每個(gè)sql都默認(rèn)開(kāi)啟一個(gè)事務(wù),只有顯式的執(zhí)行“commit”后這個(gè)事務(wù)才會(huì)被提交。