加了個(gè)索引,SQL性能竟然慢了十倍!
前言
最近星球中有小伙伴問我:加了索引,SQL查詢效率一定會(huì)提升嗎?
答案是否定的。
讓我想起了幾年前查詢訂單的場(chǎng)景。
優(yōu)化前下面這條SQL的查詢耗時(shí)是0.5s。
SELECT * FROM orders WHERE user_id = 10086;
添加了下面的索引:
CREATE INDEX idx_user ON orders(user_id);
再次執(zhí)行上面的查詢SQL語(yǔ)句,此時(shí)的耗時(shí)卻是5.2s。
出現(xiàn)了非常神奇的一幕:加了索引,SQL查詢性能反而慢了10倍。
作為一個(gè)踩過無數(shù)數(shù)據(jù)庫(kù)性能坑的老司機(jī),今天跟大家一起聊聊那些“加了索引反而更慢”的詭異場(chǎng)景。
1. 索引失效
加了索引之后,你以為它在工作,其實(shí)它在摸魚,因?yàn)樗赡芤呀?jīng)失效了。
1.1 最左前綴原則
如果查詢條件的順序不對(duì),努力白費(fèi)。
復(fù)合索引 (a, b, c)
生效的關(guān)鍵在于最左前綴匹配。
用戶表創(chuàng)建了名稱、年齡和城市這三個(gè)字段的復(fù)合索引:
CREATE INDEX idx_user ON user(name, age, city);
? 有效:使用最左列 name
SELECT * FROM user WHERE name = '蘇三';
? 有效:使用最左前綴 (name, age)
SELECT * FROM user WHERE name = '蘇三' AND age = 30;
? 失效:跳過了最左列 name
SELECT * FROM user WHERE age = 30;
? 失效:未使用最左前綴
SELECT * FROM user WHERE city = '北京';
原理:復(fù)合索引的存儲(chǔ)結(jié)構(gòu)類似于電話簿(先按姓排序,再按名排序)。
如果跳過“姓”直接查“名”,索引就失效了。
1.2 函數(shù)操作
函數(shù)操作會(huì)讓索引瞬間失憶。
對(duì)索引列做計(jì)算、函數(shù)轉(zhuǎn)換或類型轉(zhuǎn)換,會(huì)導(dǎo)致索引失效:
? 失效:對(duì)索引列使用函數(shù):
SELECT * FROM user WHERE YEAR(create_time) = 2023;
? 失效:隱式類型轉(zhuǎn)換 (phone 是 varchar)
SELECT * FROM user WHERE phone = 13800138000;
? 有效:避免函數(shù)操作
SELECT * FROM user WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';
可以使用BETWEEN...AND查詢時(shí)間范圍。
原理:索引存儲(chǔ)的是列的原始值。對(duì)值進(jìn)行修改后,數(shù)據(jù)庫(kù)無法在索引樹中定位原始值。
1.3 范圍查詢
范圍查詢(>
、<
、BETWEEN
)會(huì)截?cái)?/span>復(fù)合索引中后續(xù)列的匹配:
索引 (age, salary)
SELECT * FROM employee
WHERE age > 25 -- ? age 范圍查詢
AND salary = 10000; -- ? salary 無法使用索引
會(huì)導(dǎo)致salary的索引失效。
原理:age>25
匹配到的是一系列值(非精確值),數(shù)據(jù)庫(kù)無法高效地對(duì) salary
進(jìn)行索引過濾。
2. 索引維護(hù)成本
天下沒有免費(fèi)的午餐,索引是有額外的維護(hù)成本的。
2.1 DML 操作變慢
每一次寫入都可能在負(fù)重前行。
每次 INSERT
、UPDATE
、DELETE
操作,數(shù)據(jù)庫(kù)不僅要修改數(shù)據(jù),還要維護(hù)相關(guān)索引。
無索引表插入 100w 行:1.2 秒。
有 5 個(gè)索引的表插入 100w 行:15.8 秒 (實(shí)測(cè)差距 10 倍以上)。
場(chǎng)景:在寫多讀少的高并發(fā)場(chǎng)景下(如流水記錄),索引反而會(huì)成為性能瓶頸。
2.2 索引占用空間
索引可能會(huì)給磁盤和內(nèi)存帶來雙重壓力。
每個(gè)索引都是一棵 B+ 樹,存儲(chǔ)完整的索引列值(或組合值)。大表的索引輕松占據(jù)幾十GB空間。
MySQL中可以通過下面的SQL查看表索引大?。?/span>
SELECT
table_name AS `Table`,
index_name AS `Index`,
ROUND(stat_value * @@innodb_page_size / 1024 / 1024, 2) `Size(MB)`
FROM mysql.innodb_index_stats
WHERE table_name = 'your_table';
后果:索引過大導(dǎo)致內(nèi)存中緩存命中率降低,物理 I/O 增加。
3. 優(yōu)化器的“錯(cuò)誤”選擇
有時(shí)候,可能會(huì)出現(xiàn)聰明反被聰明誤,加了索引可能會(huì)導(dǎo)致性能變慢。
3.1 統(tǒng)計(jì)信息過時(shí)
導(dǎo)航用了舊地圖。
優(yōu)化器依賴統(tǒng)計(jì)信息(如索引區(qū)分度、數(shù)據(jù)分布)來選擇索引。
如果統(tǒng)計(jì)信息過期,優(yōu)化器可能選擇性能更差的索引。
MySQL中我們可以通過下面的命令強(qiáng)制更新表統(tǒng)計(jì)信息:
ANALYZE TABLE user;
場(chǎng)景:當(dāng)表中數(shù)據(jù)發(fā)生劇烈變化(如大批量刪除/導(dǎo)入)后,統(tǒng)計(jì)信息未及時(shí)更新。
3.2 回表代價(jià)高昂
索引再好也怕“繞路”。
假如給user表給age創(chuàng)建了索引:
SELECT name, email FROM user
WHERE age > 25;
即使 age
索引被使用,數(shù)據(jù)庫(kù)仍需根據(jù)索引中的主鍵 ID 回表查詢name
、email
字段。
當(dāng)符合條件的數(shù)據(jù)量很大時(shí),回表 I/O 可能遠(yuǎn)超索引掃描本身。
優(yōu)化方案:使用覆蓋索引(Covering Index),讓索引包含查詢所需的所有列。
創(chuàng)建覆蓋索引 (包含 age, name, email):
CREATE INDEX idx_age_covering ON user(age, name, email);
查詢可直接從索引獲取數(shù)據(jù),無需回表
SELECT name, email FROM user WHERE age > 25;
4. 索引過多
當(dāng)表上存在多個(gè)索引時(shí),優(yōu)化器需要評(píng)估每個(gè)索引的成本,選擇越多,決策時(shí)間越長(zhǎng)。
下面的查詢可能使用索引 A 或索引 B
SELECT * FROM orders
WHERE user_id = 1001
AND status = 'completed';
優(yōu)化建議:
- 刪除重復(fù)或冗余索引
- 合并可組合的索引
- 使用工具分析索引使用率(如
sys.schema_unused_indexes
)
5. 鎖與并發(fā)
鎖的競(jìng)爭(zhēng)是一個(gè)看不見的戰(zhàn)場(chǎng)。
5.1 行鎖升級(jí)
在事務(wù)中通過索引檢索并鎖定行時(shí),如果鎖數(shù)量過多(超過閾值),數(shù)據(jù)庫(kù)可能將鎖升級(jí)為表鎖,嚴(yán)重降低并發(fā)性能。
場(chǎng)景:全表更新或刪除大量數(shù)據(jù)時(shí),索引的存在可能導(dǎo)致鎖升級(jí)。
5.2 索引分裂的阻塞
B+樹索引在插入數(shù)據(jù)時(shí)可能發(fā)生頁(yè)分裂。
這個(gè)過程需要加鎖,在高并發(fā)寫入場(chǎng)景下可能導(dǎo)致短暫阻塞。
總結(jié)
索引是把雙刃劍,用對(duì)場(chǎng)景是關(guān)鍵。
- 理解原理:掌握最左前綴、索引失效條件、覆蓋索引等核心機(jī)制
- 權(quán)衡成本:在寫密集場(chǎng)景謹(jǐn)慎添加索引,評(píng)估維護(hù)代價(jià)
- 精準(zhǔn)設(shè)計(jì):按實(shí)際查詢模式設(shè)計(jì)復(fù)合索引,避免冗余
- 關(guān)注統(tǒng)計(jì)信息:定期更新統(tǒng)計(jì)信息,確保優(yōu)化器決策準(zhǔn)確
- 監(jiān)控分析:使用
EXPLAIN
、慢查詢?nèi)罩镜裙ぞ叱掷m(xù)跟蹤索引效果
某電商平臺(tái)曾因在流水表上盲目添加索引,導(dǎo)致高峰時(shí)段寫入延遲飆升。
后經(jīng)分析,移除兩個(gè)非核心索引,寫入速度提升8倍,而相關(guān)查詢僅增加20毫秒——這正是索引取舍的藝術(shù)。
記?。?/span>索引不是越多越好,而是越準(zhǔn)越好。
真正的高手,懂得在索引的利刃上優(yōu)雅行走。