看唐漢明等人2008年出版的的《深入淺出MySQL》一書,第277頁“InnoDB行鎖實作方式”中講到,行鎖是通過給索引上的索引項加鎖來實作的,并且創建了一個沒有索引的表做示例;
# 建表陳述句
CREATE TABLE `tab_no_index` (
`id` INT(11) NULL DEFAULT NULL,
`name` VARCHAR(10) NULL DEFAULT NULL
)
ENGINE=InnoDB;
# 插如(1, '1')、(2, '2')、(3, '3')、(4, '4')四條資料的SQL省略
但是我在驗證時發現,SQL陳述句必須顯式地帶for update才能出現書中所說的效果:
# 會話1
set autocommit=0;
select * from tab_no_index a where id = 1 for update;
# 不提交
# 會話2
set autocommit=0;
# 下面的陳述句無法執行,需等待會話1提交。
select * from tab_no_index a where id = 2 for update;
如果直接按下面這種寫法就沒有效果:
# 會話1
set autocommit=0;
update tab_no_index a
set a.name = 'abc'
where a.id = 1;
# 不提交
# 會話2
set autocommit=0;
# 下面這個update陳述句可以執行
update tab_no_index a
set a.name = 'abc'
where a.id = 2;
而在書中第274也第二段指出:“對于UPDATE、DELETE和INSERT陳述句,InnoDB會自動給涉及資料集加排他鎖”,也就意味著第二種方式效果應該跟第一種方式是一樣的,為什么結果出現差異呢?請教各位大神。
uj5u.com熱心網友回復:
我在5.6版本上做了一個實驗,不管是for update,還是直接update都會阻塞住:
#會話1
mysql> SET autocommit=0;
QUERY OK, 0 ROWS affected (0.03 sec)
#1
mysql> SELECT * FROM tab_no_index a WHERE id = 1 FOR UPDATE;
+------+------+
| id | NAME |
+------+------+
| 1 | 1 |
+------+------+
1 ROW IN SET (0.03 sec)
mysql> COMMIT;
QUERY OK, 0 ROWS affected (0.03 sec)
#2
mysql> UPDATE tab_no_index a
-> SET a.name = 'abc'
-> WHERE a.id = 1;
QUERY OK, 1 ROW affected (6.13 sec)
ROWS matched: 1 CHANGED: 1 WARNINGS: 0
#會話2
mysql> SET autocommit=0;
QUERY OK, 0 ROWS affected (0.03 sec)
#1
mysql> SELECT * FROM tab_no_index a WHERE id = 2 FOR UPDATE;
ERROR 1205 (HY000): LOCK WAIT timeout exceeded; try restarting TRANSACTION
mysql> COMMIT ;
QUERY OK, 0 ROWS affected (0.03 sec)
#2
mysql> UPDATE tab_no_index a
-> SET a.name = 'abc'
-> WHERE a.id = 2;
ERROR 1205 (HY000): LOCK WAIT timeout exceeded; try restarting TRANSACTION
mysql>
uj5u.com熱心網友回復:
可能我沒表達清楚,書上的意思是說沒有索引,行鎖就失效了,就變成了表鎖。這樣雖然是更新id=1的列,但是會影響更新id=2的操作,對其造成阻塞。
uj5u.com熱心網友回復:
貼出 show index from tab_no_indexuj5u.com熱心網友回復:
那肯定的,不僅是mysql,sql server也是如此。
要修改資料,首先要找到資料,而如果沒有索引,就會直接鎖住表的資料,就會鎖住其他的會話。
所以,如果你看innodb的最佳實踐里,一定會要你創建主鍵索引,如果你沒有主鍵,可以用一個代理鍵,也就是自增列實作。
uj5u.com熱心網友回復:
樓主的疑問解決了嗎?我也遇到了同樣的問題。有點想不明白。為什么for update會鎖表,而單獨執行update則沒有問題呢?uj5u.com熱心網友回復:
不同的版本上,實作有差異吧mysql> select @@version;
+------------+
| @@version |
+------------+
| 5.7.18-log |
+------------+
1 row in set (0.00 sec)
mysql> show create table tab_no_index\G
*************************** 1. row ***************************
Table: tab_no_index
Create Table: CREATE TABLE `tab_no_index` (
`id` int(11) DEFAULT NULL,
`name` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> select * from tab_no_index;
+------+------+
| id | name |
+------+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
+------+------+
4 rows in set (0.00 sec)
-----------------------------------------------------------------------------------
測驗 UPDATE 發現確實鎖了,查詢鎖的資訊
SELECT TRX.trx_isolation_level as transaction_level, LX.lock_mode, LX.lock_type, LX.lock_table, LX.lock_index
FROM information_schema.innodb_trx TRX
INNER JOIN information_schema.innodb_locks LX
ON LX.lock_trx_id = TRX.trx_id
結果,行鎖(SELECT FOR UPDATE一樣):
----------------------------------------------------------------------------------------------------------------------
REPEATABLE READ X RECORD `tempdb`.`tab_no_index` GEN_CLUST_INDEX
REPEATABLE READ X RECORD `tempdb`.`tab_no_index` GEN_CLUST_INDEX
uj5u.com熱心網友回復:
sql server 里面不一定是表鎖,表級別通常只有意向鎖,真正鎖定的通常只是更新的資料,除非資料量大導致鎖升級, 可以用 profiler 跟蹤 Lock:Acquired / Lock:Released 兩個事件驗證加鎖和釋放鎖uj5u.com熱心網友回復:
你的mysql隔離級別應該是repeatable-read,所以才會鎖。當你把隔離級別調成READ-COMMITTED的時候,兩個會話都是for update會鎖掉。但是兩個會話是update陳述句的時候就不會鎖 。
uj5u.com熱心網友回復:
-- 一樣鎖的, 在查鎖的查詢中,第1列就是當前的事務隔離級別( mysql 默認是 repeatable-read,所以通常不特別說明都是基于這個隔離級別)READ COMMITTED X RECORD `tempdb`.`tab_no_index` GEN_CLUST_INDEX
READ COMMITTED X RECORD `tempdb`.`tab_no_index` GEN_CLUST_INDEX
uj5u.com熱心網友回復:
對于滿足條件的記錄,更新 或者 FOR UPDATE 跟事務隔離級別沒什么關系,滿足條件的記錄始侄訓加 X 鎖,不同的是加鎖范圍的在不同的隔離級別下可能會有差異轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/110878.html
標籤:MySQL
