激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|數據庫技術|

服務器之家 - 數據庫 - Mysql - 15個必知的 MySQL 索引失效場景

15個必知的 MySQL 索引失效場景

2022-02-28 23:07程序新視界二師兄 Mysql

本篇文章為大家總結了15個常見的索引失效的場景,由于不同的Mysql版本,索引失效策略也有所不同。大多數索引失效情況都是明確的,有少部分索引失效會因Mysql的版本不同而有所不同。

背景

無論你是技術大佬,還是剛入行的小白,時不時都會踩到Mysql數據庫不走索引的坑。常見的現象就是:明明在字段上添加了索引,但卻并未生效。

前些天就遇到一個稍微特殊的場景,同一條SQL語句,在某些參數下生效,在某些參數下不生效,這是為什么呢?

另外,無論是面試或是日常,Mysql索引失效的通常情況都應該了解和學習。

為了方便學習和記憶,這篇文件將常見的15種不走索引情況進行匯總,并以實例展示,幫助大家更好地避免踩坑。建議收藏,以備不時之需。

數據庫及索引準備

創建表結構

為了逐項驗證索引的使用情況,我們先準備一張表t_user:

CREATE TABLE `t_user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', `id_no` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '身份編號', `username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用戶名', `age` int(11) DEFAULT NULL COMMENT '年齡', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間', PRIMARY KEY (`id`), KEY `union_idx` (`id_no`,`username`,`age`), KEY `create_time_idx` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

在上述表結構中有三個索引:

  • id:為數據庫主鍵;
  • union_idx:為id_no、username、age構成的聯合索引;
  • create_time_idx:是由create_time構成的普通索引;

初始化數據

初始化數據分兩部分:基礎數據和批量導入數據。

基礎數據insert了4條數據,其中第4條數據的創建時間為未來的時間,用于后續特殊場景的驗證:

INSERT INTO `t_user` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1001', 'Tom1', 11, '2022-02-27 09:04:23'); INSERT INTO `t_user` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1002', 'Tom2', 12, '2022-02-26 09:04:23'); INSERT INTO `t_user` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1003', 'Tom3', 13, '2022-02-25 09:04:23'); INSERT INTO `t_user` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1004', 'Tom4', 14, '2023-02-25 09:04:23');

除了基礎數據,還有一條存儲過程及其調用的SQL,方便批量插入數據,用來驗證數據比較多的場景:

-- 刪除歷史存儲過程 DROP PROCEDURE IF EXISTS `insert_t_user` -- 創建存儲過程 delimiter $ CREATE PROCEDURE insert_t_user(IN limit_num int) BEGIN DECLARE i INT DEFAULT 10; DECLARE id_no varchar(18) ; DECLARE username varchar(32) ; DECLARE age TINYINT DEFAULT 1; WHILE i < limit_num DO SET id_no = CONCAT("NO", i); SET username = CONCAT("Tom",i); SET age = FLOOR(10 + RAND()*2); INSERT INTO `t_user` VALUES (NULL, id_no, username, age, NOW()); SET i = i + 1; END WHILE; END $ -- 調用存儲過程 call insert_t_user(100);

關于存儲過程的創建和存儲,可暫時不執行,當用到時再執行。

數據庫版本及執行計劃

查看當前數據庫的版本:

select version(); 8.0.18

上述為本人測試的數據庫版本:8.0.18。當然,以下的所有示例,大家可在其他版本進行執行驗證。

查看SQL語句執行計劃,一般我們都采用explain關鍵字,通過執行結果來判斷索引使用情況。

執行示例:

explain select * from t_user where id = 1;

執行結果:

15個必知的 MySQL 索引失效場景

explain

可以看到上述SQL語句使用了主鍵索引(PRIMARY),key_len為4;

其中key_len的含義為:表示索引使用的字節數,根據這個值可以判斷索引的使用情況,特別是在組合索引的時候,判斷該索引有多少部分被使用到非常重要。

做好以上數據及知識的準備,下面就開始講解具體索引失效的實例了。

1.聯合索引不滿足最左匹配原則

聯合索引遵從最左匹配原則,顧名思義,在聯合索引中,最左側的字段優先匹配。因此,在創建聯合索引時,where子句中使用最頻繁的字段放在組合索引的最左側。

而在查詢時,要想讓查詢條件走索引,則需滿足:最左邊的字段要出現在查詢條件中。

實例中,union_idx聯合索引組成:

KEY `union_idx` (`id_no`,`username`,`age`)

最左邊的字段為id_no,一般情況下,只要保證id_no出現在查詢條件中,則會走該聯合索引。

示例一:

explain select * from t_user where id_no = '1002';

explain結果:

15個必知的 MySQL 索引失效場景

explain-01

通過explain執行結果可以看出,上述SQL語句走了union_idx這條索引。

這里再普及一下key_len的計算:

  • id_no 類型為varchar(18),字符集為utf8mb4_bin,也就是使用4個字節來表示一個完整的UTF-8。此時,key_len = 18* 4 = 72;
  • 由于該字段類型varchar為變長數據類型,需要再額外添加2個字節。此時,key_len = 72 + 2 = 74;
  • 由于該字段運行為NULL(default NULL),需要再添加1個字節。此時,key_len = 74 + 1 = 75;

上面演示了key_len一種情況的計算過程,后續不再進行逐一推演,知道基本組成和原理即可,更多情況大家可自行查看。

示例二:

explain select * from t_user where id_no = '1002' and username = 'Tom2';

explain結果:

15個必知的 MySQL 索引失效場景

explain-02

很顯然,依舊走了union_idx索引,根據上面key_len的分析,大膽猜測,在使用索引時,不僅使用了id_no列,還使用了username列。

示例三:

explain select * from t_user where id_no = '1002' and age = 12;

explain結果:

15個必知的 MySQL 索引失效場景

explain-03

走了union_idx索引,但跟示例一一樣,只用到了id_no列。

當然,還有三列都在查詢條件中的情況,就不再舉例了。上面都是走索引的正向例子,也就是滿足最左匹配原則的例子,下面來看看,不滿足該原則的反向例子。

反向示例:

explain select * from t_user where username = 'Tom2' and age = 12;

explain結果:

15個必知的 MySQL 索引失效場景

explain-04

此時,可以看到未走任何索引,也就是說索引失效了。

同樣的,下面只要沒出現最左條件的組合,索引也是失效的:

explain select * from t_user where age = 12; explain select * from t_user where username = 'Tom2';

那么,第一種索引失效的場景就是:在聯合索引的場景下,查詢條件不滿足最左匹配原則。

2.使用了select *

在《阿里巴巴開發手冊》的ORM映射章節中有一條【強制】的規范:

【強制】在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明。說明:1)增加查詢分析器解析成本。2)增減字段容易與 resultMap 配置不一致。3)無用字段增加網絡 消耗,尤其是 text 類型的字段。

雖然在規范手冊中沒有提到索引方面的問題,但禁止使用select * 語句可能會帶來的附帶好處就是:某些情況下可以走覆蓋索引。

比如,在上面的聯合索引中,如果查詢條件是age或username,當使用了select * ,肯定是不會走索引的。

但如果希望根據username查詢出id_no、username、age這三個結果(均為索引字段),明確查詢結果字段,是可以走覆蓋索引的:

explain select id_no, username, age from t_user where username = 'Tom2'; explain select id_no, username, age from t_user where age = 12;

explain結果:

15個必知的 MySQL 索引失效場景

覆蓋索引

無論查詢條件是username還是age,都走了索引,根據key_len可以看出使用了索引的所有列。

第二種索引失效場景:在聯合索引下,盡量使用明確的查詢列來趨向于走覆蓋索引;

這一條不走索引的情況屬于優化項,如果業務場景滿足,則進來促使SQL語句走索引。至于阿里巴巴開發手冊中的規范,只不過是兩者撞到一起了,規范本身并不是為這條索引規則而定的。

3.索引列參與運算

直接來看示例:

explain select * from t_user where id + 1 = 2 ;

explain結果:

15個必知的 MySQL 索引失效場景

索引列計算

可以看到,即便id列有索引,由于進行了計算處理,導致無法正常走索引。

針對這種情況,其實不單單是索引的問題,還會增加數據庫的計算負擔。就以上述SQL語句為例,數據庫需要全表掃描出所有的id字段值,然后對其計算,計算之后再與參數值進行比較。如果每次執行都經歷上述步驟,性能損耗可想而知。

建議的使用方式是:先在內存中進行計算好預期的值,或者在SQL語句條件的右側進行參數值的計算。

針對上述示例的優化如下:

-- 內存計算,得知要查詢的id為1 explain select * from t_user where id = 1 ; -- 參數側計算 explain select * from t_user where id = 2 - 1 ;

第三種索引失效情況:索引列參與了運算,會導致全表掃描,索引失效。

4.索引列參使用了函數示例:

explain select * from t_user where SUBSTR(id_no,1,3) = '100';

explain結果:

15個必知的 MySQL 索引失效場景

索引-函數

上述示例中,索引列使用了函數(SUBSTR,字符串截取),導致索引失效。

此時,索引失效的原因與第三種情況一樣,都是因為數據庫要先進行全表掃描,獲得數據之后再進行截取、計算,導致索引索引失效。同時,還伴隨著性能問題。

示例中只列舉了SUBSTR函數,像CONCAT等類似的函數,也都會出現類似的情況。解決方案可參考第三種場景,可考慮先通過內存計算或其他方式減少數據庫來進行內容的處理。

第四種索引失效情況:索引列參與了函數處理,會導致全表掃描,索引失效。

5.錯誤的Like使用

示例:

explain select * from t_user where id_no like '%00%';

explain結果:

15個必知的 MySQL 索引失效場景

索引-like

針對like的使用非常頻繁,但使用不當往往會導致不走索引。常見的like使用方式有:

  • 方式一:like '%abc';
  • 方式二:like 'abc%';
  • 方式三:like '%abc%';

其中方式一和方式三,由于占位符出現在首部,導致無法走索引。這種情況不做索引的原因很容易理解,索引本身就相當于目錄,從左到右逐個排序。而條件的左側使用了占位符,導致無法按照正常的目錄進行匹配,導致索引失效就很正常了。

第五種索引失效情況:模糊查詢時(like語句),模糊匹配的占位符位于條件的首部。

6.類型隱式轉換

示例:

explain select * from t_user where id_no = 1002;

explain結果:

15個必知的 MySQL 索引失效場景

隱式轉換

id_no字段類型為varchar,但在SQL語句中使用了int類型,導致全表掃描。

出現索引失效的原因是:varchar和int是兩個種不同的類型。

解決方案就是將參數1002添加上單引號或雙引號。

第六種索引失效情況:參數類型與字段類型不匹配,導致類型發生了隱式轉換,索引失效。

這種情況還有一個特例,如果字段類型為int類型,而查詢條件添加了單引號或雙引號,則Mysql會參數轉化為int類型,雖然使用了單引號或雙引號:

explain select * from t_user where id = '2';

上述語句是依舊會走索引的。

7.使用OR操作

OR是日常使用最多的操作關鍵字了,但使用不當,也會導致索引失效。

示例:

explain select * from t_user where id = 2 or username = 'Tom2';

explain結果:

15個必知的 MySQL 索引失效場景

or-索引

看到上述執行結果是否是很驚奇啊,明明id字段是有索引的,由于使用or關鍵字,索引竟然失效了。

其實,換一個角度來想,如果單獨使用username字段作為條件很顯然是全表掃描,既然已經進行了全表掃描了,前面id的條件再走一次索引反而是浪費了。所以,在使用or關鍵字時,切記兩個條件都要添加索引,否則會導致索引失效。

但如果or兩邊同時使用“>”和“<”,則索引也會失效:

explain select * from t_user where id > 1 or id < 80;

explain結果:

15個必知的 MySQL 索引失效場景

or-范圍

第七種索引失效情況:查詢條件使用or關鍵字,其中一個字段沒有創建索引,則會導致整個查詢語句索引失效;or兩邊為“>”和“<”范圍查詢時,索引失效。

8.兩列做比較

如果兩個列數據都有索引,但在查詢條件中對兩列數據進行了對比操作,則會導致索引失效。

這里舉個不恰當的示例,比如age小于id這樣的兩列(真實場景可能是兩列同維度的數據比較,這里遷就現有表結構):

explain select * from t_user where id > age;

explain結果:

15個必知的 MySQL 索引失效場景

索引-兩列比較

這里雖然id有索引,age也可以創建索引,但當兩列做比較時,索引還是會失效的。

第八種索引失效情況:兩列數據做比較,即便兩列都創建了索引,索引也會失效。

9.不等于比較

示例:

explain select * from t_user where id_no <> '1002';

explain結果:

15個必知的 MySQL 索引失效場景

索引-不等

當查詢條件為字符串時,使用”<>“或”!=“作為條件查詢,有可能不走索引,但也不全是。

explain select * from t_user where create_time != '2022-02-27 09:56:42';

上述SQL中,由于“2022-02-27 09:56:42”是存儲過程在同一秒生成的,大量數據是這個時間。執行之后會發現,當查詢結果集占比比較小時,會走索引,占比比較大時不會走索引。此處與結果集與總體的占比有關。

需要注意的是:上述語句如果是id進行不等操作,則正常走索引。

explain select * from t_user where id != 2;

explain結果:

15個必知的 MySQL 索引失效場景

不等-ID

第九種索引失效情況:查詢條件使用不等進行比較時,需要慎重,普通索引會查詢結果集占比較大時索引會失效。

10.is not null

示例:

explain select * from t_user where id_no is not null;

explain結果:

15個必知的 MySQL 索引失效場景

索引-is not null

第十種索引失效情況:查詢條件使用is null時正常走索引,使用is not null時,不走索引。

11.not in和not exists

在日常中使用比較多的范圍查詢有in、exists、not in、not exists、between and等。

explain select * from t_user where id in (2,3); explain select * from t_user where id_no in ('1001','1002'); explain select * from t_user u1 where exists (select 1 from t_user u2 where u2.id = 2 and u2.id = u1.id); explain select * from t_user where id_no between '1002' and '1003';

上述四種語句執行時都會正常走索引,具體的explain結果就不再展示。主要看不走索引的情況:

explain select * from t_user where id_no not in('1002' , '1003');

explain結果:

15個必知的 MySQL 索引失效場景

索引-not in

當使用not in時,不走索引?把條件列換成主鍵試試:

explain select * from t_user where id not in (2,3);

explain結果:

15個必知的 MySQL 索引失效場景

主鍵-not in

如果是主鍵,則正常走索引。

第十一種索引失效情況:查詢條件使用not in時,如果是主鍵則走索引,如果是普通索引,則索引失效。

再來看看not exists:

explain select * from t_user u1 where not exists (select 1 from t_user u2 where u2.id = 2 and u2.id = u1.id);

explain結果:

15個必知的 MySQL 索引失效場景

索引-not in

當查詢條件使用not exists時,不走索引。

第十二種索引失效情況:查詢條件使用not exists時,索引失效。

12.order by導致索引失效

示例:

explain select * from t_user order by id_no ;

explain結果:

15個必知的 MySQL 索引失效場景

索引-order by

其實這種情況的索引失效很容易理解,畢竟需要對全表數據進行排序處理。

那么,添加刪limit關鍵字是否就走索引了呢?

explain select * from t_user order by id_no limit 10;

explain結果:

15個必知的 MySQL 索引失效場景

order by limit

結果依舊不走索引。在網絡上看到有說如果order by條件滿足最左匹配則會正常走索引, 在當前8.0.18版本中并未出現。所以,在基于order by和limit進行使用時,要特別留意。是否走索引不僅涉及到數據庫版本,還要看Mysql優化器是如何處理的。

這里還有一個特例,就是主鍵使用order by時,可以正常走索引。

explain select * from t_user order by id desc;

explain結果:

15個必知的 MySQL 索引失效場景

主鍵-order by

可以看出針對主鍵,還是order by可以正常走索引。

另外,筆者測試如下SQL語句:

explain select id from t_user order by age; explain select id , username from t_user order by age; explain select id_no from t_user order by id_no;

上述三條SQL語句都是走索引的,也就是說覆蓋索引的場景也是可以正常走索引的。

現在將id和id_no組合起來進行order by:

explain select * from t_user order by id,id_no desc; explain select * from t_user order by id,id_no desc limit 10; explain select * from t_user order by id_no desc,username desc;

explain結果:

15個必知的 MySQL 索引失效場景

orderby多索引

上述兩個SQL語句,都未走索引。

第十三種索引失效情況:當查詢條件涉及到order by、limit等條件時,是否走索引情況比較復雜,而且與Mysql版本有關,通常普通索引,如果未使用limit,則不會走索引。order by多個索引字段時,可能不會走索引。其他情況,建議在使用時進行expain驗證。

13.參數不同導致索引失效

此時,如果你還未執行最開始創建的存儲過程,建議你先執行一下存儲過程,然后執行如下SQL:

explain select * from t_user where create_time > '2023-02-24 09:04:23';

其中,時間是未來的時間,確保能夠查到數據。

explain結果:

15個必知的 MySQL 索引失效場景

索引-參數

可以看到,正常走索引。

隨后,我們將查詢條件的參數換個日期:

explain select * from t_user where create_time > '2022-02-27 09:04:23';

explain結果:

15個必知的 MySQL 索引失效場景

索引-參數

此時,進行了全表掃描。這也是最開始提到的奇怪的現象。

為什么同樣的查詢語句,只是查詢的參數值不同,卻會出現一個走索引,一個不走索引的情況呢?

答案很簡單:上述索引失效是因為DBMS發現全表掃描比走索引效率更高,因此就放棄了走索引。

也就是說,當Mysql發現通過索引掃描的行記錄數超過全表的10%-30%時,優化器可能會放棄走索引,自動變成全表掃描。某些場景下即便強制SQL語句走索引,也同樣會失效。

類似的問題,在進行范圍查詢(比如>、< 、>=、<=、in等條件)時往往會出現上述情況,而上面提到的臨界值根據場景不同也會有所不同。

第十四種索引失效情況:當查詢條件為大于等于、in等范圍查詢時,根據查詢結果占全表數據比例的不同,優化器有可能會放棄索引,進行全表掃描。

14 其他

當然,還有其他一些是否走索引的規則,這與索引的類型是B-tree索引還是位圖索引也有關系,就不再詳細展開。

這里要說的其他,可以總結為第十五種索引失效的情況:Mysql優化器的其他優化策略,比如優化器認為在某些情況下,全表掃描比走索引快,則它就會放棄索引。

針對這種情況,一般不用過多理會,當發現問題時再定點排查即可。

小結

本篇文章為大家總結了15個常見的索引失效的場景,由于不同的Mysql版本,索引失效策略也有所不同。大多數索引失效情況都是明確的,有少部分索引失效會因Mysql的版本不同而有所不同。

因此,建議收藏本文,當在實踐的過程中進行對照,如果沒辦法準確把握,則可直接執行explain進行驗證。

原文地址:https://mp.weixin.qq.com/s/iBPO4Y_Q5ANSZ9iETAdMvA

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25
主站蜘蛛池模板: 13一14毛片免费看 | 久草在线精品观看 | 国产毛片毛片 | 欧美日韩手机在线观看 | 精品亚洲综合 | av电影免费播放 | 久久久麻豆 | 国产激情精品一区二区三区 | 亚洲精品91 | 91 在线免费观看 | 少妇一级淫片高潮流水电影 | 欧美黄色三级视频 | 国产一级做a爱片在线看免 2019天天干夜夜操 | 日本一区二区免费在线观看 | 国产精品一区在线观看 | 精品一区二区三区日本 | 99久久久精品视频 | 日本在线不卡一区二区 | 午夜精品久久久久久中宇 | 亚洲乱搞 | 精品一区二区电影 | 亚洲精品无码不卡在线播放he | 久久午夜神器 | 美女被免费网站在线软件 | 奇米影视亚洲精品一区 | 久久久国产一区二区三区 | 一日本道久久久精品国产 | 97久久日一线二线三线 | 久久久aa | 国产精品99久久久久久久女警 | 成人在线视频在线观看 | 高清做爰免费无遮网站挡 | 欧美淫| 黄色网址在线免费 | 日日操夜夜操狠狠操 | 成人黄色小视频网站 | 成人勉费视频 | 一级做a爱片性色毛片 | 国产黄色一级大片 | 国产a级久久 | 国产精品久久久久久模特 |