MySQL 鎖與事務(wù)

1.支持

MYSQL鎖 官方文檔

首先需要明確的是,對于MySQL而言,不同的engine所支持的鎖類型不一樣:MyISAM僅支持表鎖,BDB支持表鎖和頁鎖,InnoDB支持表鎖和行鎖;

| Engine| 表鎖|行鎖|頁鎖|
|-|-|-|
|MyISAM|√|ㄨ|ㄨ|
|BDB|√|ㄨ|√|
|InnoDB|√|√|ㄨ|

需要額外說明的是,對數(shù)據(jù)庫的連接丟失或斷開將會丟失該進(jìn)程生成的所有鎖,當(dāng)進(jìn)程重連時(shí)也不會生效.

If the connection for a client session terminates, whether normally or abnormally, the server implicitly releases all table locks held by the session (transactional and nontransactional). If the client reconnects, the locks will no longer be in effect.

2.表鎖

表鎖指的是:
在單個(gè)進(jìn)程中鎖表后,若鎖定模式為WRITE,則允許該進(jìn)程讀寫表,但是只允許其他進(jìn)程的讀表操作,寫表操作將會被阻塞,直到該鎖被釋放;
若鎖定模式為READ,則允許所有進(jìn)程的讀表操作,當(dāng)前進(jìn)程的寫表操作將被拒絕,其他進(jìn)程的寫表操作將會被阻塞,直到該鎖被釋放.

write-lock demo:

// 進(jìn)程1
mysql> lock table `test` write;
// 大約十秒后釋放寫鎖
mysql> unlock tables;
// 進(jìn)程2
mysql> insert into `test`(`id`) values ('9');
Query OK, 1 row affected (10.74 sec)

read-lock demo:

// 進(jìn)程1
mysql> lock table `test` read;
// 此時(shí)進(jìn)行寫表將會被拒絕
mysql> insert into `test`(`id`) values ('7');
ERROR 1099 (HY000): Table 'test' was locked with a READ lock and can't be updated

// 同上,大約十秒后釋放讀鎖
mysql> unlock tables;
// 進(jìn)程2
mysql> insert into `test`(`id`) values ('4');
Query OK, 1 row affected (10.33 sec)

3.事務(wù)

官方文檔

事務(wù)是一組連續(xù)的操作集合,它需要SET AUTOCOMMIT = 0;(默認(rèn)為1),以START TRANSACTION;BEGIN;開始,以COMMIT;結(jié)束一次事務(wù),或者以ROLLBACK;回滾事務(wù)操作并結(jié)束事務(wù).

實(shí)際上,在AUTOCOMMIT為1的情況下,每一次請求都是一個(gè)獨(dú)立的事務(wù),即該次請求會被立即執(zhí)行;

需要注意的是,```SET AUTOCOMMITSTART TRANSACTION在功能上可能相同,但是在概念上完全不同.前者表示是否開啟自動提交(0 = 否, 1 = 是),后者表示開始一個(gè)事務(wù),這一點(diǎn)要區(qū)分開來,不能因?yàn)楣δ軡M足而混淆概念. 根據(jù)文檔說明,在SET AUTOCOMMIT = 1的前提下開啟事務(wù)時(shí),執(zhí)行COMMITROLLBACKAUTOCOMMIT``都會是默認(rèn)禁用狀態(tài),在事務(wù)結(jié)束后會恢復(fù)默認(rèn)狀態(tài):

With START TRANSACTION, autocommit remains disabled until you end the transaction with COMMIT or ROLLBACK. The autocommit mode then reverts to its previous state.

事務(wù)最顯著的特點(diǎn)是:擁有獨(dú)立的內(nèi)部邏輯,事務(wù)中的請求與操作在執(zhí)行COMMIT;前都不會對數(shù)據(jù)庫該事務(wù)外產(chǎn)生任何影響;

demo:

// 進(jìn)程1
// 開啟一個(gè)事務(wù),直到COMMIT前以下語句都不會在該事務(wù)外對數(shù)據(jù)庫產(chǎn)生影響
mysql> SET AUTOCOMMIT = 0;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO `test`(`id`) values ('1');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM `test`;
+----+---------------------+
| id | Created_time        |
+----+---------------------+
| 1  | 1970-01-01 08:00:00 |
+----+---------------------+
1 row in set (0.00 sec)
// 進(jìn)程2
mysql> SELECT * FROM `test`;
Empty set (0.00 sec)

我們可以看到,由于進(jìn)程1開啟了一個(gè)事務(wù),所以進(jìn)程1中的INSERT在其事務(wù)中可以查詢到改變,但是在事務(wù)外的進(jìn)程2中并不能.
那么我們把進(jìn)程1的事務(wù)COMMIT以后:

// 進(jìn)程1
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
// 進(jìn)程2
mysql> SELECT * FROM `test`;
+----+---------------------+
| id | Created_time        |
+----+---------------------+
| 1  | 1970-01-01 08:00:00 |
+----+---------------------+
1 row in set (0.00 sec)

這個(gè)處理流式有點(diǎn)類似于git,而ROLLBACK則可以理解為git checkout .git reset --hard上一版本.

需要額外說明的是,如果事務(wù)結(jié)束前,進(jìn)程斷開或丟失連接,事務(wù)將會被強(qiáng)制回滾,并且重連后進(jìn)程的AUTOCOMMIT將會是可用狀態(tài).

In addition, if the client had an active transaction, the server rolls back the transaction upon disconnect, and if reconnect occurs, the new session begins with autocommit enabled.

所以官方文檔建議可以適時(shí)關(guān)閉auto-reconnect功能以獲取錯(cuò)誤信息并及時(shí)修正原本的事務(wù);

With auto-reconnect disabled, if the connection drops, an error occurs for the next statement issued. The client can detect the error and take appropriate action such as reacquiring the locks or redoing the transaction.

自動重連行為控制官方文檔

4.行鎖

之所以把行鎖放到事務(wù)后面說明,是因?yàn)樾墟i引用到一些事務(wù)的概念.

行鎖與表鎖不同的是,行鎖在Mysql中不是針對數(shù)據(jù)加鎖,而是針對索引加的鎖,所以在不同的訪問請求中如果使用了相同的索引,還是會出現(xiàn)沖突.

我們在單次事務(wù)中,可能需要在本次事務(wù)運(yùn)行過程中阻止其它事務(wù)對目標(biāo)數(shù)據(jù)的修改,但是使用表鎖會鎖定整個(gè)表,阻塞一大堆不相干的事務(wù)和進(jìn)程,這時(shí)就需要針對目標(biāo)數(shù)據(jù)進(jìn)行鎖定.InnoDB提供了for update來做行鎖;

DEMO:

// 事務(wù)1
// 開始執(zhí)行一個(gè)事務(wù)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

// 鎖定id = 1的數(shù)據(jù)行,有效期為本事務(wù)生命周期
mysql> select * from `test` where `id`='1' for update;
+----+---------------------+
| id | Created_time        |
+----+---------------------+
| 1  | 1970-01-01 08:00:00 |
+----+---------------------+
1 row in set (0.00 sec)

// 執(zhí)行所需的操作
mysql> update `test` set `id`='9' where `id`='1';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
// 不結(jié)束事務(wù),此時(shí)觀察其它事務(wù)的情況
// 事務(wù)2
mysql> update `test` set `id`='9' where `id`='1';

發(fā)現(xiàn)為事務(wù)1未結(jié)束前,事務(wù)2的update操作一直處于被阻塞的狀態(tài).
并且事務(wù)2在長時(shí)間的Lock等待后,會直接拋出錯(cuò)誤信息

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

那么問題來了,事務(wù)1更新了數(shù)據(jù)后是否會影響到事務(wù)2的索引結(jié)果呢?
我們把事務(wù)1COMMIT后觀察一下:

// 事務(wù)1
COMMIT;
// 事務(wù)2
mysql> update `test` set `id`='5' where `id`='1';
Query OK, 0 rows affected (8.20 sec)
Rows matched: 0  Changed: 0  Warnings: 0
mysql> SELECT * FROM `test` where `id`='1';
+----+---------------------+
| id | Created_time        |
+----+---------------------+
| 9  | 1970-01-01 08:00:00 |
+----+---------------------+
1 row in set (0.00 sec)

顯然,事務(wù)2被事務(wù)1的行鎖阻塞解除后重新進(jìn)行了一次索引,因此沒有匹配到任何值,這可以有效避免高并發(fā)情況下多事務(wù)處理時(shí)可能出現(xiàn)的臟讀.

關(guān)于數(shù)據(jù)庫事務(wù)隔離級別

臟讀(Dirty reads (Uncommitted Dependency))
當(dāng)一個(gè)事務(wù)允許讀取另外一個(gè)事務(wù)修改但未提交的數(shù)據(jù)時(shí),就可能發(fā)生臟讀(dirty reads)。

臟讀(dirty reads)和不可重復(fù)讀(non-repeatable reads)類似。事務(wù)2沒有提交造成事務(wù)1的語句1兩次執(zhí)行得到不同的結(jié)果集。在未提交讀(READ UNCOMMITTED)隔離級別唯一禁止的是更新混亂,即早期的更新可能出現(xiàn)在后來更新之前的結(jié)果集中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容