mysql 經過測試'for update',深刻了解行鎖、表鎖、索引

mysql 經過測試'for update',深刻了解行鎖、表鎖、索引

參考:https://www.cnblogs.com/wangshiwen/p/9837408.htmlhtml

條件

FOR UPDATE 僅適用於InnoDB存儲引擎,且必須在事務區塊(BEGIN/COMMIT)中才能生效。mysql

mysql默認狀況下每一個sql都是單獨的一個事務,而且是自動提交事務。

測試以前須要設置成非自動提交事務,否則沒法模擬併發訪問:sql

mysql> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 1 | +--------------+ 1 row in set (0.00 sec) mysql> set autocommit = 0; Query OK, 0 rows affected (0.00 sec) mysql> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 0 | +--------------+ 1 row in set (0.00 sec) 

此修改只針對當前窗口有效,從新打開的新窗口依然是自動提交事務的shell

因此要就須要兩個窗口,窗口a:非自動提交事務,用於for update操做;
窗口b:用於普通update操做。數據庫


測試

咱們有一數據庫 test1,有一張表testa ,有自增主鍵ID,name,id_card

表中有兩條數據ruby

mysql> select * from testa;
+----+-------+--------------------+
| id | name | id_card | +----+-------+--------------------+ | 1 | wangb | 322343256564545754 | | 2 | shuna | 320990348823998792 | +----+-------+--------------------+ 2 rows in set (0.00 sec) mysql> desc testa; +---------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(10) | NO | | NULL | | | id_card | varchar(18) | YES | UNI | NULL | | +---------+-------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec) 

1.只明確主鍵

  • 有數據

在a窗口進行開啓事務,對id爲1的數據進行 for update,此時並無commit;bash

mysql> begin; Query OK, 0 rows affected (0.00 sec)  mysql> select * from testa where id = 1 for update; +----+------+--------------------+ | id | name | id_card | +----+------+--------------------+ | 1 | wang | 322343256564545754 | +----+------+--------------------+ 1 row in set (0.00 sec)  mysql>

在b窗口對id=1的數據進行update name操做,發現失敗:等待鎖釋放超時markdown

mysql> update testa set name = "wangwang" where id = 1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

再對id=2的數據進行update name操做,發現成功併發

mysql> update testa set name = "shunshun" where id = 2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 

a窗口commit;以後,b窗口update操做都顯示正常post

  • 無數據

a窗口 select for update 無數據

mysql> begin; Query OK, 0 rows affected (0.00 sec)  mysql> select * from testa where id = 3 -> ; Empty set (0.00 sec)  mysql>

b窗口,對兩條數據update操做都是成功

mysql> update testa set name = "wanga" where id = 1; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0  mysql> update testa set name = "shun" where id = 2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0

得出結論

明確主鍵而且有數據的狀況下:mysql -> row lock;

明確主鍵無數據的狀況下:mysql -> no lock;

2.明確主鍵和一個普通字段

  • 有數據

將數據還原以後,

在a窗口進行開啓事務,對id=1,name='wang'的數據進行 for update,此時並無commit;

mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from testa where id=1 and name = 'wang' for update -> ; +----+------+--------------------+ | id | name | id_card | +----+------+--------------------+ | 1 | wang | 322343256564545754 | +----+------+--------------------+ 1 row in set (0.03 sec) mysql>

b窗口,對進行for update的那條數據的update操做無效(等待鎖釋放超時),其餘的行的update操做正常

mysql> update testa set name = "wanga" where id = 1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction  mysql> update testa set name = "shunshun" where id = 2; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0

a窗口commit;以後,b窗口update操做都顯示成功

  • 無數據

同第一種狀況的無數據測試

得出結論

明確主鍵和一個普通字段有數據的狀況下:mysql -> row lock;

明確主鍵和一個普通字段無數據的狀況下:mysql -> no lock;

3.明確一個普通字段

  • 有數據
    將數據還原以後,

    在a窗口進行開啓事務,對name='wang'的數據進行 for update,此時並無commit;
mysql> begin; Query OK, 0 rows affected (0.00 sec)  mysql> select * from testa where name = 'wang' for update; +----+------+--------------------+ | id | name | id_card | +----+------+--------------------+ | 1 | wang | 322343256564545754 | +----+------+--------------------+ 1 row in set (0.00 sec)  mysql>

b窗口,對進行for update的那條數據的update操做失敗(等待鎖釋放超時),其餘的行的update操做也顯示失敗(等待鎖釋放超時)

mysql> update testa set id_card = '222' where id = 1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> update testa set id_card = '333' where id = 2; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 

a窗口commit;以後,b窗口update操做都顯示成功

  • 無數據

同第一種狀況的無數據測試

得出結論

只明確一個普通字段有數據的狀況下:mysql -> table lock;

只明確一個普通字段無數據的狀況下:mysql -> no lock;

4.明確一個unique字段

  • 有數據

將數據還原以後,

在a窗口進行開啓事務,對id_card='111'的數據進行 for update,此時並無commit;

mysql> begin; Query OK, 0 rows affected (0.00 sec)  mysql> select * from testa where id_card='111' for update; +----+------+---------+ | id | name | id_card | +----+------+---------+ | 1 | wang | 111 | +----+------+---------+ 1 row in set (0.00 sec)  mysql>

b窗口,對進行for update的那條數據的update操做失敗(等待鎖釋放超時),其餘的行的update操做顯示正常!!

mysql> update testa set id_card = '222' where id = 1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction  mysql> update testa set id_card = '333' where id = 2; Query OK, 1 row affected (0.00 sec) 
  • 無數據

同第一種狀況的無數據測試

得出結論

只明確一個unique字段有數據的狀況下:mysql -> row lock;

只明確一個unique字段無數據的狀況下:mysql -> no lock;


思考

爲何對主鍵和unique字段進行for update操做的時候,mysql進行的是row lock;而對普通字段for update操做的時候進行的是table lock,是根據什麼判斷呢?

primary key和unique的共同特色是mysql會自動爲其建立索引,他們都有索引,那把name字段建立索引,是否是就進行row lock呢?

查看錶中的索引:

mysql> show keys from testa\G; *************************** 1. row ***************************  Table: testa  Non_unique: 0  Key_name: PRIMARY  Seq_in_index: 1  Column_name: id  Collation: A  Cardinality: 2  Sub_part: NULL  Packed: NULL  Null:  Index_type: BTREE  Comment: Index_comment: *************************** 2. row ***************************  Table: testa  Non_unique: 0  Key_name: id_card  Seq_in_index: 1  Column_name: id_card  Collation: A  Cardinality: 2  Sub_part: NULL  Packed: NULL  Null: YES  Index_type: BTREE  Comment: Index_comment: 2 rows in set (0.00 sec) ERROR: No query specified 

發現testa表中的索引只包含了id,id_card

添加name字段的索引

mysql> alter table testa add index index_name (name);
Query OK, 0 rows affected (0.03 sec) Records: 0 Duplicates: 0 Warnings: 0 

查看建表語句:

mysql> show create table testa \G;
*************************** 1. row ***************************  Table: testa Create Table: CREATE TABLE `testa` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(10) NOT NULL, `id_card` varchar(18) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `id_card` (`id_card`), KEY `index_name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 1 row in set (0.00 sec) ERROR: No query specified

發現name字段已經建立了普通索引index_name

在a窗口,對name字段再進行一次for update測試,不commit 

mysql> begin; Query OK, 0 rows affected (0.00 sec)  mysql> select * from testa where name = 'wang' for update; +----+------+---------+ | id | name | id_card | +----+------+---------+ | 1 | wang | 222 | +----+------+---------+ 1 row in set (0.01 sec)  mysql>

在b窗口 對進行for update的數據進行update操做失敗(鎖釋放等待超時)

mysql> update testa set id_card = '111' where id = 1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

在b窗口 對其餘行數據進行update操做,成功!!!

mysql> update testa set id_card = '4353' where id = 2; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0

a窗口commit以後,在b敞口操做正常

總結

select .... for update; 操做

未獲取到數據的時候,mysql不進行鎖 (no lock)

獲取到數據的時候,進行對約束字段進行判斷,存在有索引的字段則進行row lock
不然進行 table lock

注意

當使用 '<>','like'等關鍵字時,進行for update操做時,mysql進行的是table lock

網上其餘博客說是由於主鍵不明確形成的,其實並不是如此;

mysql進行row lock仍是table lock只取決因而否能使用索引,而 使用'<>','like'等操做時,索引會失效,天然進行的是table lock;

什麼狀況索引會失效:

1.負向條件查詢不能使用索引

負向條件有:!=、<>、not in、not exists、not like 等。

2.索引列不容許爲null

單列索引不存null值,複合索引不存全爲null的值,若是列容許爲 null,可能會獲得不符合預期的結果集。

3.避免使用or來鏈接條件

應該儘可能避免在 where 子句中使用 or 來鏈接條件,由於這會致使索引失效而進行全表掃描,雖然新版的MySQL可以命中索引,但查詢優化耗費的 CPU比in多。

4.模糊查詢

前導模糊查詢不能使用索引,非前導查詢能夠。

以上狀況索引都會失效,因此進行for update的時候,會進行table lock

參考:https://juejin.im/post/5b14e0fd6fb9a01e8c5fc663

再思考

爲何存在索引,mysql進行row lock,不存在索引,mysql進行table lock?

這是存儲引擎InnoDB特性決定的:

InnoDB這種行鎖實現特色意味者:只有經過索引條件檢索數據,InnoDB纔會使用行級鎖,不然,InnoDB將使用表鎖!

再總結

在上述例子中 ,咱們使用給name字段加索引的方法,使表鎖降級爲行鎖,不幸的是這種方法只針對 屬性值重複率低 的狀況。當屬性值重複率很高的時候,索引就變得低效,MySQL 也具備自動優化 SQL 的功能。低效的索引將被忽略。就會使用表鎖了。參考:http://zhoupq.com/MySQL-%E9%81%BF%E5%85%8D%E8%A1%8C%E9%94%81%E5%8D%87%E7%BA%A7%E4%B8%BA%E8%A1%A8%E9%94%81%E2%80%94%E2%80%94%E4%BD%BF%E7%94%A8%E9%AB%98%E6%95%88%E7%9A%84%E7%B4%A2%E5%BC%95/

相關文章
相關標籤/搜索