Update和Insert操作與行鎖表鎖


概述:

Update和Insert是鎖表還是鎖行,會影響到程序中並發程序的設計。

總結:

(1)Update時,where中的過濾條件列,如果用索引,鎖行,無法用索引,鎖表。按照索引規則,如果能使用索引,鎖行,不能使用索引,鎖表。

(2)Insert時,可以並發執行,之間並不會相互影響。

一、Update操作

1. 實驗一

1)創建表和基礎數據,id是主鍵,如下圖:

2)在navicat中,新建一個查詢頁面,如下圖:

關閉自動提交,並更新第1條數據,執行上圖中的sql語句。

由於沒有使用commit;進行提交,所以id=1數據的age並沒有被更新為111。

3)在navicat中,再次新建一個查詢頁面,如下圖:

關閉自動提交,並修改id=1的數據的age值為1111,執行上圖中的sql語句,結果如下:

可以看到,無法對id=1的數據進行修改。但是,此時只知道id=1的數據無法修改,無法確定是鎖表還是鎖行。

4)在navicat中再次新建一個查詢頁面,如下圖:

關閉自動提交,並更新id=2的數據的age列為222,執行上圖中的sql語句,結果如下:

可以看到,id=2的數據的age被修改。也就是說,update時,where中使用id作為過濾條件時,只是鎖行,而不是鎖表。

2.實驗二

(1)將表中數據重置為初始值,如下圖:

 

(2)以上步驟,將過濾條件,由id改為name,例如:

由於沒有commit,所以,在執行之后,表中數據並沒有改變。

(3)接着,執行以下語句,如下圖:

(4)查看執行結果

執行失敗。如果只是鎖行,那么where name='陳二'的數據應該是可以修改的。所以這里是鎖表。

3.實驗三

(1)將表中數據重置為初始值,如下圖:

(2)給name列添加索引

ALTER TABLE `tb_user` ADD INDEX index_name ( `name` ) ;

(3)重新執行查詢4的語句,如下圖:

(4)接着,執行查詢5的語句,如下圖:

(5)查看結果

可以看到,where name='陳二'的數據被成功修改,意味着,當name有索引的時候,是鎖行。

4、結論

(1)實驗一和實驗二,實驗變量是過濾條件where中的列,實驗一是id列(主鍵,有索引),實驗二是name列(沒有索引)。結果是以id列為過濾條件,也就是使用有索引的列時,只是鎖行,而以name列為過濾條件,也就是沒有索引的列時,會鎖表。

(2)為驗證以上結果,實驗三給name列添加普通索引,實驗二和實驗三種,where的過濾條件都是name列,實驗變量是name是否有索引。結果是:有索引,鎖行;無索引,鎖表。同結論(1),驗證完成。

二、Insert操作

0.准備

注意,將索引刪除(並沒有影響,但是要減少變量),如下:

ALTER TABLE `tb_user` DROP INDEX index_name;

1. 實驗一

(1)關閉自動提交,insert一條新數據,如下圖:

查看tb_user表,發現並沒有插入。(原因是沒有執行commit。)

(2)再次insert一條新數據(這里並沒有關閉autocommit),如下圖:

結果如下:

 

可以看到,插入成功。也就是說id=5被鎖定。之后的insert語句並沒有受到影響。

2. 結論

insert的時候,可以並發執行,之間並不會相互影響。

三、深入探究:UPDATE能走索引還會鎖全表嗎

導讀

執行UPDATE時,WEHRE條件列雖已有索引,但還會鎖全表,腫么回事?

問題描述

  葉師傅有次上課過程中執行UPDATE測試案例時,發現雖然WHERE條件列已有索引,有時候能利用二級索引進行更新(且只鎖定相應必要的幾行記錄),但有時候卻變成了根據主鍵進行更新,且會鎖全表。我們先來看看下面的例子。

測試表 t1

CREATE TABLE `t1` (
  `c1` int(10) unsigned NOT NULL DEFAULT '0',
  `c2` int(10) unsigned NOT NULL DEFAULT '0',
  `c3` int(10) unsigned NOT NULL DEFAULT '0',
  `c4` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`c1`),
  KEY `c2` (`c2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

表中數據

+----+----+----+----+
| c1 | c2 | c3 | c4 |
+----+----+----+----+
|  0 |  0 |  0 |  0 |
|  1 |  1 |  1 |  0 |
|  3 |  3 |  3 |  0 |
|  4 |  2 |  2 |  0 |
|  6 |  8 |  5 |  0 |
|  7 |  6 |  6 | 10 |
| 10 | 10 |  4 |  0 |
+----+----+----+----+

case1:根據二級索引UPDATE,不鎖全表

先看執行計划

yejr@imysql.com [yejr]>desc update t1 set c4=123 where c2>=8\G
*************************** 1. row ***************************
           id: 1
  select_type: UPDATE
        table: t1
   partitions: NULL
         type: range
possible_keys: c2
          key: c2
      key_len: 4
          ref: const
         rows: 2
     filtered: 100.00
        Extra: Using where

啟動兩個session執行UPDATE測試

session1 session 2(后執行)
mysql> begin;
mysql> update t1 set c4=123 where c2>=8;

 

Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0

mysql> begin;
mysql> select * from t1 where c2 = 7 for update;

 


1 row in set (0.00 sec)
#直接可得到結果,不會被阻塞

case2:根據PRIMARY KEY更新,鎖全表

yejr@imysql.com [yejr]>desc update t1 set c4=123 where c2>=6\G
*************************** 1. row ***************************
           id: 1
  select_type: UPDATE
        table: t1
   partitions: NULL
         type: index
possible_keys: c2
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 7
     filtered: 100.00
        Extra: Using where

我們能看到本次執行計划是 根據主鍵索引進行更新,且會鎖全表。

同樣地,啟動兩個session執行UPDATE測試

session1 session2(后執行)
mysql> begin;
mysql> update t1 set c4=123 where c2>=6;

 

Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0

mysql> begin;
mysql> select * from t1 where c2 = 3 for update;
#無法得到結果,被阻塞了

mysql> rollback;
#執行rollback,釋放鎖

=============================================


1 row in set (4.23 sec)
#session1釋放鎖后才能得到結果

查看行鎖等待情況

yejr@imysql.com [yejr]>select * from sys.innodb_lock_waits\G
*************************** 1. row ***************************
                wait_started: 2017-08-15 15:20:20
                    wait_age: 00:00:17
               wait_age_secs: 17
                locked_table: `yejr`.`t1`
                locked_index: PRIMARY  <--主鍵上加鎖
                 locked_type: RECORD
              waiting_trx_id: 268350
         waiting_trx_started: 2017-08-15 15:20:20
             waiting_trx_age: 00:00:17
     waiting_trx_rows_locked: 2
   waiting_trx_rows_modified: 0
                 waiting_pid: 13
               waiting_query: select * from t1 where c2 = 3 for update
             waiting_lock_id: 268350:387:3:4
           waiting_lock_mode: X
             blocking_trx_id: 268349
                blocking_pid: 12
              blocking_query:  NULL
            blocking_lock_id: 268349:387:3:4
          blocking_lock_mode: X
        blocking_trx_started: 2017-08-15 15:20:18
            blocking_trx_age: 00:00:19
    blocking_trx_rows_locked: 8  <-- 所有記錄都被加鎖了
  blocking_trx_rows_modified: 3  <---持有鎖的事務更新了3行記錄
     sql_kill_blocking_query: KILL QUERY 12
sql_kill_blocking_connection: KILL 12

問題分析

好了,案例說完了,也該說原因了。

腎好的同學可能記得我說過一個結論:當MySQL預估掃描行數超過全表總數約 20% ~ 30% 時,即便有二級索引,也會直接升級為全表掃描。

這個結論的原因並不難理解,二級索引的順序和主鍵順序一般來說是不一樣的,根據二級索引的順序回表讀數據時,實際上對於主鍵則很可能是隨機掃描,因此當需要隨機掃描的數量超過一定比例時(一般是20% ~ 30%),則優化器會決定直接改成全表掃描。

上述說法出處:WHERE Clause Optimization

Each table index is queried, and the best index is used unless the optimizer believes 
that it is more efficient to use a table scan. At one time, a scan was used based on whether
the best index spanned more than 30% of the table, but a fixed percentage no longer determines
the choice between using an index or a scan. The optimizer now is more complex and bases its estimate
on additional factors such as table size, number of rows, and I/O block size.

不過,上面這個結論是針對讀數據的情況,UPDATE/DELETE修改數據時是否也這樣呢?

答案是肯定的,要不然上面的測試結果怎么解釋……

按照官方開發者的說法,當優化器評估根據二級索引更新行數超過約50%(從上面測試結果來看,其實20% ~ 30%就已經是這樣了,不過這個比例並不是固定值,會根據各種代價計算自動調整)就會改成走主鍵索引,並且鎖全表,這么做是既定的策略,原因和上面一樣,也是為了提高檢索效率。

總結

老調重彈,幾點建議:

  • 不管檢索還是更新,都盡可能利用索引;
  • 不要一次性檢索或更新大批量數據,建議分城多批次;
  • 事務盡快提交,降低行鎖持有時間及其影響。

 

 

參考文章:

https://blog.csdn.net/wodeshouji6/article/details/104323875

https://imysql.com/2020/07/14/why-update-rows-using-index-but-cause-row-lock.shtml


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM