日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

MySQL 避免行锁升级为表锁——使用高效的索引

發(fā)布時(shí)間:2025/1/21 数据库 61 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MySQL 避免行锁升级为表锁——使用高效的索引 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

眾所周知,MySQL 的 InnoDB 存儲(chǔ)引擎支持事務(wù),支持行級(jí)鎖(innodb的行鎖是通過給索引項(xiàng)加鎖實(shí)現(xiàn)的)。得益于這些特性,數(shù)據(jù)庫支持高并發(fā)。如果 InnoDB 更新數(shù)據(jù)使用的不是行鎖,而是表鎖呢?是的,InnoDB 其實(shí)很容易就升級(jí)為表鎖,屆時(shí)并發(fā)性將大打折扣了。

經(jīng)過我操作驗(yàn)證,得出行鎖升級(jí)為表鎖的原因之一是: SQL 語句中未使用到索引,或者說使用的索引未被數(shù)據(jù)庫認(rèn)可(相當(dāng)于沒有使用索引)。

我相信,MySQL InnoDB 存儲(chǔ)引擎引發(fā)表鎖的原因肯定不止一個(gè)因素,針對(duì)其解決方法也不是只有一種。

據(jù)掘金上另一位作者【Blink-前端】,提出行鎖升級(jí)為表鎖與 事務(wù)的隔離級(jí)別 有關(guān),并給出了事例。當(dāng)然,我同意這個(gè)說法,因?yàn)槭聞?wù)的隔離性是靠加鎖來實(shí)現(xiàn)的,而加鎖勢(shì)必會(huì)影響并發(fā)。本篇只針對(duì) 索引影響并發(fā) 作出說明,并特別希望有朋友能提出質(zhì)疑并給出獨(dú)特見解,萬分感謝。

普通索引

既然談及索引是影響并發(fā)的決定因素之一,那我們就來了解一下索引這位主角。

常用的索引有三類:主鍵、唯一索引、普通索引。主鍵 不由分說,自帶最高效的索引屬性;唯一索引指的是該屬性值重復(fù)率為0,一般可作為業(yè)務(wù)主鍵,例如學(xué)號(hào);普通索引 與前者不同的是,屬性值的重復(fù)率大于0,不能作為唯一指定條件,例如學(xué)生姓名。接下來我要說明是 “普通索引對(duì)并發(fā)的影響”。

為什么我會(huì)想到 “普通索引對(duì)并發(fā)有影響”?這源自【掘金】微信群拋出的一個(gè)問題:

mysql 5.6 在 update 和 delete 的時(shí)候,where 條件如果不存在索引字段,那么這個(gè)事務(wù)是否會(huì)導(dǎo)致表鎖?

有人回答:

只有主鍵和唯一索引才是行鎖,普通索引是表鎖。

我針對(duì) “普通索引是表鎖” 進(jìn)行了驗(yàn)證,結(jié)果發(fā)現(xiàn)普通索引并不一定會(huì)引發(fā)表鎖,在普通索引中,是否引發(fā)表鎖取決于普通索引的高效程度。

上文提及的“高效”是相對(duì)主鍵和唯一索引而言,也許“高效”并不是一個(gè)很好的解釋,明白在一般i情況下,“普通索引”效率低于其他兩者即可。

屬性值重復(fù)率高

為了突出效果,我將“普通索引”建立在一個(gè)“值重復(fù)率”高的屬性下。以相對(duì)極端的方式,擴(kuò)大對(duì)結(jié)果的影響。

我會(huì)創(chuàng)建一張“分?jǐn)?shù)等級(jí)表”,屬性有“id”、“score(分?jǐn)?shù))”、“l(fā)evel(等級(jí))”,模擬一個(gè)半自動(dòng)的業(yè)務(wù)——“分?jǐn)?shù)”已被自動(dòng)導(dǎo)入,而“等級(jí)”需要手工更新。

操作步驟如下:

  • 取消 MySQL 的 事務(wù)自動(dòng)提交
  • 建表,id自增,并給“score(分?jǐn)?shù))”創(chuàng)建普通索引
  • 插入分?jǐn)?shù)值,等級(jí)為 null
  • 開啟兩個(gè)事務(wù) session_1、session_2,兩個(gè)事務(wù)以“score”為條件指定不同值,鎖定數(shù)據(jù)
  • session_1 和 session_2 先后更新各自事務(wù)鎖定內(nèi)容的“l(fā)evel”
  • 觀察數(shù)據(jù)庫對(duì)兩個(gè)事務(wù)的響應(yīng)
  • 取消 事務(wù)自動(dòng)提交

    mysql> set autocommit = off;Query OK, 0 rows affected (0.02 sec)mysql> show variables like "autocommit";+--------------------------+-------+| Variable_name | Value |+--------------------------+-------+| autocommit | OFF |+--------------------------+-------+1 rows in set (0.01 sec)

    建表、創(chuàng)建索引、插入數(shù)據(jù):

    DROP TABLE IF EXISTS `test1`;CREATE TABLE `test1` (`ID` int(5) NOT NULL AUTO_INCREMENT ,`SCORE` int(3) NOT NULL ,`LEVEL` int(2) NULL DEFAULT NULL ,PRIMARY KEY (`ID`))ENGINE=InnoDB DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci;ALTER TABLE `test2` ADD INDEX index_name ( `SCORE` );INSERT INTO `test1`(`SCORE`) VALUE (100);……INSERT INTO `test1`(`SCORE`) VALUE (0);……

    “SCORE” 屬性的“值重復(fù)率”奇高,達(dá)到了 50%,劍走偏鋒:

    mysql> select * from test1;+----+-------+-------+| ID | SCORE | LEVEL |+----+-------+-------+| 1 | 100 | NULL || 2 | 0 | NULL || 5 | 100 | NULL || 6 | 100 | NULL || 7 | 100 | NULL || 8 | 100 | NULL || 9 | 100 | NULL || 10 | 100 | NULL || 11 | 100 | NULL || 12 | 100 | NULL || 13 | 100 | NULL || 14 | 0 | NULL || 15 | 0 | NULL || 16 | 0 | NULL || 17 | 0 | NULL || 18 | 0 | NULL || 19 | 0 | NULL || 20 | 0 | NULL || 21 | 0 | NULL || 22 | 0 | NULL || 23 | 0 | NULL || 24 | 100 | NULL || 25 | 0 | NULL || 26 | 100 | NULL || 27 | 0 | NULL |+----+-------+-------+25 rows in set

    開啟兩個(gè)事務(wù)(一個(gè)窗口對(duì)應(yīng)一個(gè)事務(wù)),并選定數(shù)據(jù):

    -- SESSION_1,選定 SCORE = 100 的數(shù)據(jù)mysql> BEGIN;SELECT t.* FROM `test1` t WHERE t.`SCORE` = 100 FOR UPDATE;Query OK, 0 rows affected+----+-------+-------+| ID | SCORE | LEVEL |+----+-------+-------+| 1 | 100 | NULL || 5 | 100 | NULL || 6 | 100 | NULL || 7 | 100 | NULL || 8 | 100 | NULL || 9 | 100 | NULL || 10 | 100 | NULL || 11 | 100 | NULL || 12 | 100 | NULL || 13 | 100 | NULL || 24 | 100 | NULL || 26 | 100 | NULL |+----+-------+-------+12 rows in set

    再打開一個(gè)窗口:

    -- SESSION_2,選定 SCORE = 0 的數(shù)據(jù)mysql> BEGIN;SELECT t.* FROM `test1` t WHERE t.`SCORE` = 0 FOR UPDATE;Query OK, 0 rows affected+----+-------+-------+| ID | SCORE | LEVEL |+----+-------+-------+| 2 | 0 | NULL || 14 | 0 | NULL || 15 | 0 | NULL || 16 | 0 | NULL || 17 | 0 | NULL || 18 | 0 | NULL || 19 | 0 | NULL || 20 | 0 | NULL || 21 | 0 | NULL || 22 | 0 | NULL || 23 | 0 | NULL || 25 | 0 | NULL || 27 | 0 | NULL |+----+-------+-------+13 rows in set

    session_1 窗口,更新“LEVEL”失敗:

    mysql> UPDATE `test1` SET `LEVEL` = 1 WHERE `SCORE` = 100;1205 - Lock wait timeout exceeded; try restarting transaction

    在之前的操作中,session_1 選擇了 SCORE = 100 的數(shù)據(jù),session_2 選擇了 SCORE = 0 的數(shù)據(jù),看似兩個(gè)事務(wù)井水不犯河水,但是在 session_1 事務(wù)中更新自己鎖定的數(shù)據(jù)失敗,只能說明在此時(shí)引發(fā)了表鎖。別著急,剛剛走向了一個(gè)極端——索引屬性值重復(fù)性奇高,接下來走向另一個(gè)極端。

    屬性值重復(fù)率低

    還是同一張表,將數(shù)據(jù)刪除只剩下兩條,“SCORE” 的 “值重復(fù)率” 為 0:

    mysql> delete from test1 where id > 2;Query OK, 23 rows affectedmysql> select * from test1;+----+-------+-------+| ID | SCORE | LEVEL |+----+-------+-------+| 1 | 100 | NULL || 2 | 0 | NULL |+----+-------+-------+2 rows in set

    關(guān)閉兩個(gè)事務(wù)操作窗口,重新開啟 session_1 和 session_2,并選擇各自需要的數(shù)據(jù):

    -- SESSION_1,選定 SCORE = 100 的數(shù)據(jù)mysql> BEGIN;SELECT t.* FROM `test1` t WHERE t.`SCORE` = 100 FOR UPDATE;Query OK, 0 rows affected+----+-------+-------+| ID | SCORE | LEVEL |+----+-------+-------+| 1 | 100 | NULL |+----+-------+-------+1 row in set-- -----------------新窗口----------------- ---- SESSION_2,選定 SCORE = 0 的數(shù)據(jù)mysql> BEGIN;SELECT t.* FROM `test1` t WHERE t.`SCORE` = 0 FOR UPDATE;Query OK, 0 rows affected+----+-------+-------+| ID | SCORE | LEVEL |+----+-------+-------+| 2 | 0 | NULL |+----+-------+-------+1 row in set

    session_1 更新數(shù)據(jù)成功:

    mysql> UPDATE `test1` SET `LEVEL` = 1 WHERE `SCORE` = 100;Query OK, 1 row affectedRows matched: 1 Changed: 1 Warnings: 0

    相同的表結(jié)構(gòu),相同的操作,兩個(gè)不同的結(jié)果讓人出乎意料。第一個(gè)結(jié)果讓人覺得“普通索引”引發(fā)表鎖,第二個(gè)結(jié)果推翻了前者,兩個(gè)操作中,唯一不同的是索引屬性的“值重復(fù)率”。根據(jù) 單一變量 證明法,可以得出結(jié)論:當(dāng)“值重復(fù)率”低時(shí),甚至接近主鍵或者唯一索引的效果,“普通索引”依然是行鎖;當(dāng)“值重復(fù)率”高時(shí),MySQL 不會(huì)把這個(gè)“普通索引”當(dāng)做索引,即造成了一個(gè)沒有索引的 SQL,此時(shí)引發(fā)表鎖。

    小結(jié)

    索引不是越多越好,索引存在一個(gè)和這個(gè)表相關(guān)的文件里,占用硬盤空間,寧缺勿濫,每個(gè)表都有主鍵(id),操作能使用主鍵盡量使用主鍵。

    同 JVM 自動(dòng)優(yōu)化 java 代碼一樣,MySQL 也具有自動(dòng)優(yōu)化 SQL 的功能。低效的索引將被忽略,這也就倒逼開發(fā)者使用正確且高效的索引。

    總結(jié)

    以上是生活随笔為你收集整理的MySQL 避免行锁升级为表锁——使用高效的索引的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。