MySQL 用了索引還是很慢的原因分析及解決方案
即使查詢(xún)使用了索引,仍然可能很慢,主要原因可以歸納為 6 大類(lèi):
| 類(lèi)別 | 具體原因 | 影響 |
|---|---|---|
| 索引設(shè)計(jì)問(wèn)題 | 索引選擇性差、索引列順序不當(dāng)、索引冗余 | 掃描行數(shù)過(guò)多 |
| SQL 寫(xiě)法問(wèn)題 | SELECT *、LIKE '%xxx'、函數(shù)操作、類(lèi)型轉(zhuǎn)換 | 索引失效或回表過(guò)多 |
| 數(shù)據(jù)分布問(wèn)題 | 數(shù)據(jù)量過(guò)大、數(shù)據(jù)傾斜嚴(yán)重、熱點(diǎn)數(shù)據(jù)集中 | 即使走索引也慢 |
| 表結(jié)構(gòu)問(wèn)題 | 字段過(guò)大、行過(guò)長(zhǎng)、未分區(qū) | I/O 開(kāi)銷(xiāo)大 |
| 數(shù)據(jù)庫(kù)配置問(wèn)題 | 緩沖池過(guò)小、連接數(shù)不足、參數(shù)配置不當(dāng) | 整體性能下降 |
| 硬件資源瓶頸 | 磁盤(pán) I/O、內(nèi)存不足、CPU 瓶頸 | 物理層面慢 |
一句話(huà)總結(jié):索引只是加速查詢(xún)的必要條件而非充分條件,需要結(jié)合索引設(shè)計(jì)、SQL 優(yōu)化、表結(jié)構(gòu)設(shè)計(jì)、數(shù)據(jù)庫(kù)配置和硬件資源進(jìn)行系統(tǒng)性?xún)?yōu)化。
一、索引設(shè)計(jì)問(wèn)題
1. 索引選擇性差
索引選擇性對(duì)查詢(xún)性能的影響。選擇性越高,索引的效果越好;選擇性越低,索引的效果越差。
- 高選擇性:例如用戶(hù)表的
user_id字段,100 萬(wàn)用戶(hù)有 100 萬(wàn)個(gè)不同的user_id,選擇性為 1.0,查詢(xún)時(shí)可以快速定位到唯一行。 - 低選擇性:例如性別字段
gender,只有 "男" 和 "女" 兩個(gè)值,100 萬(wàn)行數(shù)據(jù)的選擇性?xún)H為 0.000002,查詢(xún) "男" 性用戶(hù)時(shí)需要掃描約 50 萬(wàn)行,數(shù)據(jù)庫(kù)優(yōu)化器可能直接選擇全表掃描。
判斷標(biāo)準(zhǔn):一般建議索引選擇性大于 0.1,可以通過(guò)以下 SQL 計(jì)算:
?
-- 計(jì)算字段的選擇性
SELECT
COUNT(DISTINCT column_name) / COUNT(*) AS selectivity
FROM table_name;
-- 示例:gender 字段的選擇性
SELECT
COUNT(DISTINCT gender) / COUNT(*) AS selectivity
FROM users;
-- 結(jié)果:0.000002(非常低,不適合單獨(dú)建索引)
?2. 索引列順序不當(dāng)(最左前綴原則)
-- 創(chuàng)建聯(lián)合索引 CREATE INDEX idx_name_age_gender ON users(name, age, gender); -- ? 走索引:符合最左前綴 SELECT * FROM users WHERE name = '張三'; SELECT * FROM users WHERE name = '張三' AND age = 25; SELECT * FROM users WHERE name = '張三' AND age = 25 AND gender = '男'; -- ? 不走索引:違反最左前綴 SELECT * FROM users WHERE age = 25; -- 缺少 name SELECT * FROM users WHERE gender = '男'; -- 缺少 name 和 age SELECT * FROM users WHERE age = 25 AND gender = '男'; -- 缺少 name -- ?? 部分走索引:只有 name 走索引,后面的列無(wú)法利用索引排序 SELECT * FROM users WHERE name = '張三' AND gender = '男'; -- age 跳過(guò)了
核心原則:聯(lián)合索引要遵循 "最左前綴原則",索引列的順序非常重要。將區(qū)分度高、經(jīng)常用于查詢(xún)條件的列放在左邊。
3. 索引冗余
-- ? 冗余索引示例 CREATE INDEX idx_name ON users(name); -- 單列索引 CREATE INDEX idx_name_age ON users(name, age); -- 聯(lián)合索引 -- idx_name 是冗余的,因?yàn)?idx_name_age 可以覆蓋 name 單列查詢(xún) -- ? 優(yōu)化后:刪除冗余索引 DROP INDEX idx_name ON users; -- 只保留聯(lián)合索引 idx_name_age
二、SQL 寫(xiě)法問(wèn)題
1.SELECT *導(dǎo)致大量回表
SELECT * 查詢(xún)的回表過(guò)程。如果查詢(xún)只需要部分字段,但使用了 SELECT *,就會(huì)導(dǎo)致不必要的回表操作,嚴(yán)重影響性能。
- 回表過(guò)程:先在二級(jí)索引(如
name索引)中找到滿(mǎn)足條件的主鍵 ID,然后根據(jù)主鍵 ID 到聚簇索引中查找完整的行數(shù)據(jù)。 - 性能影響:如果查詢(xún)返回 1000 行數(shù)據(jù),就需要進(jìn)行 1000 次回表操作,每次回表都是一次隨機(jī) I/O,性能開(kāi)銷(xiāo)巨大。
優(yōu)化方案:使用覆蓋索引,只查詢(xún)索引列,避免回表。
-- ? 慢查詢(xún):需要回表 SELECT * FROM users WHERE name = '張三'; -- ? 優(yōu)化:使用覆蓋索引,無(wú)需回表 -- 假設(shè)有索引 idx_name_age(name, age) SELECT name, age FROM users WHERE name = '張三';
2.LIKE查詢(xún)導(dǎo)致索引失效
-- ? 走索引:前綴匹配
SELECT * FROM users WHERE name LIKE '張%';
-- ? 不走索引:后綴匹配或包含匹配
SELECT * FROM users WHERE name LIKE '%張'; -- 后綴匹配
SELECT * FROM users WHERE name LIKE '%張%'; -- 包含匹配
-- ? 優(yōu)化方案 1:使用覆蓋索引
-- 即使 LIKE '%張%' 不走索引,如果只查詢(xún)索引列,可能走索引掃描
SELECT name FROM users WHERE name LIKE '%張%';
-- ? 優(yōu)化方案 2:使用全文索引
ALTER TABLE users ADD FULLTEXT INDEX ft_name(name);
SELECT * FROM users WHERE MATCH(name) AGAINST('張');
-- ? 優(yōu)化方案 3:使用搜索引擎(如 Elasticsearch)3. 對(duì)索引列使用函數(shù)或計(jì)算
-- ? 不走索引:對(duì)索引列使用函數(shù)
SELECT * FROM users WHERE DATE(create_time) = '2024-01-01';
SELECT * FROM users WHERE YEAR(create_time) = 2024;
SELECT * FROM users WHERE SUBSTRING(name, 1, 1) = '張';
-- ? 走索引:使用范圍查詢(xún)或等值查詢(xún)
SELECT * FROM users WHERE create_time >= '2024-01-01'
AND create_time < '2024-01-02';
-- ? 不走索引:索引列參與計(jì)算
SELECT * FROM users WHERE age + 1 = 26;
-- ? 走索引:調(diào)整計(jì)算方式
SELECT * FROM users WHERE age = 25;4. 隱式類(lèi)型轉(zhuǎn)換
-- 假設(shè) user_id 是 VARCHAR 類(lèi)型 CREATE INDEX idx_user_id ON users(user_id); -- ? 不走索引:字符串與數(shù)字比較,發(fā)生隱式類(lèi)型轉(zhuǎn)換 SELECT * FROM users WHERE user_id = 123; -- 等價(jià)于:SELECT * FROM users WHERE CAST(user_id AS SIGNED) = 123; -- ? 走索引:類(lèi)型一致 SELECT * FROM users WHERE user_id = '123';
核心原則:索引列的類(lèi)型必須與查詢(xún)條件的類(lèi)型完全一致,否則會(huì)發(fā)生隱式類(lèi)型轉(zhuǎn)換,導(dǎo)致索引失效。
三、數(shù)據(jù)分布問(wèn)題
1. 數(shù)據(jù)量過(guò)大
-- 查詢(xún)表的總行數(shù)和大小
SELECT
table_name,
table_rows,
ROUND(data_length / 1024 / 1024, 2) AS data_size_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_size_mb
FROM information_schema.tables
WHERE table_schema = 'your_database';優(yōu)化方案:
- 分區(qū)表:按時(shí)間或范圍分區(qū),減少單次查詢(xún)掃描的數(shù)據(jù)量
- 分表分庫(kù):將大表拆分為多個(gè)小表
- 歸檔歷史數(shù)據(jù):將歷史數(shù)據(jù)遷移到歸檔表
- 使用覆蓋索引:減少回表次數(shù)
2. 數(shù)據(jù)傾斜嚴(yán)重
-- 查看數(shù)據(jù)分布 SELECT gender, COUNT(*) as count FROM users GROUP BY gender; -- 假設(shè)結(jié)果: -- gender | count -- -------|-------- -- 男 | 999000 -- 女 | 1000 -- 其他 | 0 -- 查詢(xún) "男" 性用戶(hù)時(shí),即使有索引,也可能走全表掃描 -- 因?yàn)閮?yōu)化器認(rèn)為掃描 999000 行和全表掃描差不多
優(yōu)化方案:
- 對(duì)于極端傾斜的數(shù)據(jù),考慮不建索引或使用復(fù)合索引
- 使用
FORCE INDEX強(qiáng)制走索引(謹(jǐn)慎使用)
-- 強(qiáng)制使用索引 SELECT * FROM users FORCE INDEX(idx_gender) WHERE gender = '男';
四、表結(jié)構(gòu)問(wèn)題
1. 字段過(guò)大
-- ? 慢查詢(xún):大字段導(dǎo)致行過(guò)長(zhǎng)
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(255),
content TEXT, -- 大字段
author VARCHAR(100),
create_time DATETIME
);
-- ? 優(yōu)化:將大字段拆分到單獨(dú)的表
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(255),
author VARCHAR(100),
create_time DATETIME
);
CREATE TABLE article_contents (
article_id INT PRIMARY KEY,
content LONGTEXT,
FOREIGN KEY (article_id) REFERENCES articles(id)
);2. 未使用合適的字段類(lèi)型
-- ? 不合理:使用 VARCHAR 存儲(chǔ) IP 地址
CREATE TABLE logs (
id INT PRIMARY KEY,
ip VARCHAR(15)
);
-- ? 優(yōu)化:使用 INT UNSIGNED 存儲(chǔ) IP 地址
CREATE TABLE logs (
id INT PRIMARY KEY,
ip INT UNSIGNED
);
-- 插入時(shí)轉(zhuǎn)換
INSERT INTO logs (ip) VALUES (INET_ATON('192.168.1.1'));
-- 查詢(xún)時(shí)轉(zhuǎn)換
SELECT INET_NTOA(ip) FROM logs;五、數(shù)據(jù)庫(kù)配置問(wèn)題
1. 緩沖池配置不當(dāng)
-- 查看當(dāng)前 InnoDB 緩沖池大小 SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; -- 建議設(shè)置為服務(wù)器內(nèi)存的 70%-80% -- 例如:16GB 內(nèi)存的服務(wù)器,設(shè)置為 12GB SET GLOBAL innodb_buffer_pool_size = 12884901888; -- 12GB
2. 其他重要參數(shù)
-- 查看當(dāng)前配置 SHOW VARIABLES LIKE 'innodb_io_capacity'; -- 磁盤(pán) I/O 能力 SHOW VARIABLES LIKE 'innodb_read_io_threads'; -- 讀線(xiàn)程數(shù) SHOW VARIABLES LIKE 'innodb_write_io_threads'; -- 寫(xiě)線(xiàn)程數(shù) SHOW VARIABLES LIKE 'max_connections'; -- 最大連接數(shù) -- 根據(jù)服務(wù)器配置調(diào)整 -- SSD 硬盤(pán)可以設(shè)置更高的 innodb_io_capacity
六、使用 EXPLAIN 分析執(zhí)行計(jì)劃
-- 查看執(zhí)行計(jì)劃 EXPLAIN SELECT * FROM users WHERE name = '張三'; -- 關(guān)鍵字段解讀 -- id: 查詢(xún)標(biāo)識(shí)符 -- select_type: 查詢(xún)類(lèi)型(SIMPLE, PRIMARY, SUBQUERY 等) -- table: 訪(fǎng)問(wèn)的表 -- type: 訪(fǎng)問(wèn)類(lèi)型(從好到差:system > const > eq_ref > ref > range > index > ALL) -- possible_keys: 可能使用的索引 -- key: 實(shí)際使用的索引 -- key_len: 使用的索引長(zhǎng)度 -- rows: 預(yù)估掃描的行數(shù) -- Extra: 額外信息(Using index, Using where, Using filesort 等)
重點(diǎn)關(guān)注:
type字段:如果是ALL,說(shuō)明是全表掃描;如果是index,說(shuō)明是索引掃描rows字段:預(yù)估掃描的行數(shù),越大越慢Extra字段:Using filesort(文件排序)、Using temporary(臨時(shí)表)都是性能殺手
到此這篇關(guān)于MySQL 用了索引還是很慢,可能是什么原因?的文章就介紹到這了,更多相關(guān)mysql索引還是很慢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- mysql添加索引反而速度變慢的問(wèn)題
- mysql?in索引慢查詢(xún)優(yōu)化實(shí)現(xiàn)步驟解析
- MySQL數(shù)據(jù)庫(kù)的索引原理與慢SQL優(yōu)化的5大原則
- MySQL全文索引like模糊匹配查詢(xún)慢解決方法
- mysql?or走索引加索引及慢查詢(xún)的作用
- 為什么Mysql?數(shù)據(jù)庫(kù)表中有索引還是查詢(xún)慢
- 美團(tuán)網(wǎng)技術(shù)團(tuán)隊(duì)分享的MySQL索引及慢查詢(xún)優(yōu)化教程
- MySQL前綴索引導(dǎo)致的慢查詢(xún)分析總結(jié)
- mysql中索引使用不當(dāng)速度比沒(méi)加索引還慢的測(cè)試
相關(guān)文章
關(guān)于mysql中時(shí)間日期類(lèi)型和字符串類(lèi)型的選擇
大家好,本篇文章主要講的是關(guān)于mysql中時(shí)間日期類(lèi)型和字符串類(lèi)型的選擇,感興趣的朋友趕快來(lái)看一看吧,希望對(duì)你有幫助2021-11-11
MySql8.0對(duì)應(yīng)驅(qū)動(dòng)包匹配注意點(diǎn)+SpringBoot連接MySQL+多數(shù)據(jù)源配置教程
MySql8.0更新后,應(yīng)用程序需要更新對(duì)應(yīng)驅(qū)動(dòng)包,Maven配置、JDBC配置、Mybatis集成、多數(shù)據(jù)源配置等細(xì)節(jié)注意事項(xiàng),需注意驅(qū)動(dòng)包版本匹配、數(shù)據(jù)庫(kù)配置、類(lèi)文件創(chuàng)建及配置等,并解決了項(xiàng)目啟動(dòng)過(guò)程中遇到的報(bào)錯(cuò)信息2026-03-03
MySQL錯(cuò)誤代碼1862 your password has expired的解決方法
這篇文章主要為大家詳細(xì)介紹了MySQL錯(cuò)誤代碼1862 your password has expired的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08
在Mysql上創(chuàng)建數(shù)據(jù)表實(shí)例代碼
這篇文章主要介紹了如何在Mysql上創(chuàng)建數(shù)據(jù)表,需要的朋友可以參考下2014-03-03
MySQL及SQL注入詳細(xì)說(shuō)明(附預(yù)防措施)
所謂SQL注入,就是通過(guò)把SQL命令插入到Web表單遞交或輸入域名或頁(yè)面請(qǐng)求的查詢(xún)字符串,最終達(dá)到欺騙服務(wù)器執(zhí)行惡意的SQL命令,這篇文章主要介紹了MySQL及SQL注入的相關(guān)資料,需要的朋友可以參考下2025-11-11
Linux(Ubuntu)下mysql5.7.17安裝配置方法圖文教程
這篇文章主要為大家詳細(xì)介紹了Linux下mysql5.7.17安裝配置方法圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01

