一、索引是什么?為什么這么重要?
索引就像字典的目錄
想象一下,你要在一本1000頁的字典里找"程序員"這個詞,你會怎么做?
- 沒有目錄:從第1頁開始一頁一頁翻,可能要翻500頁才能找到
- 有目錄:直接翻到目錄,找到"程"字開頭的詞在第300頁,瞬間就找到了
數據庫索引就是這樣的"目錄",它能幫我們快速定位數據的位置。
索引的神奇效果
場景 | 無索引 | 有索引 | 性能提升 |
---|---|---|---|
100萬條數據查詢 | 掃描100萬行 | 掃描3-4行 | 提升25萬倍+ |
用戶登錄驗證 | 50ms | 1ms | 提升50倍 |
訂單查詢 | 200ms | 5ms | 提升40倍 |
真實的例子
-- 沒有索引的查詢(慢得要命)
SELECT * FROM users WHERE email = 'john@example.com';
-- 執行時間:1.2秒(掃描了50萬行數據)-- 給email字段添加索引后
CREATE INDEX idx_email ON users(email);
SELECT * FROM users WHERE email = 'john@example.com';
-- 執行時間:0.01秒(直接定位到1行數據)
看到了嗎?同樣的查詢,性能差了120倍!
二、索引的底層原理:B+樹的魔法
什么是B+樹?
?
B+樹的查找過程
讓我們用一個簡單例子來理解:
-- 假設我們要查找 id = 75 的用戶
SELECT * FROM users WHERE id = 75;
查找步驟:
- 第1步:從根節點開始,75在50-100之間,走中間分支
- 第2步:到達葉子節點,找到id=75的數據位置
- 第3步:根據位置直接獲取完整的用戶數據
整個過程只需要3次磁盤IO,而全表掃描可能需要幾萬次!
為什么B+樹這么快?
特點 | 優勢 | 實際效果 |
---|---|---|
多路平衡 | 樹的高度很低 | 減少磁盤訪問次數 |
葉子節點連接 | 支持范圍查詢 | ORDER BY、分頁查詢快 |
只在葉子存數據 | 內部節點小 | 更多索引數據放入內存 |
三、MySQL索引的類型詳解
1. 主鍵索引(Primary Key)
主鍵索引是最特殊的索引,它就像身份證號碼一樣:
-- 創建主鍵索引
CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT, -- 自動創建主鍵索引name VARCHAR(50),email VARCHAR(100)
);-- 主鍵查詢超級快
SELECT * FROM users WHERE id = 12345; -- 毫秒級響應
主鍵索引的特點:
- 唯一且不能為空
- 一個表只能有一個主鍵
- 查詢性能最好
- 數據按主鍵順序存儲
2. 唯一索引(Unique Index)
-- 給郵箱添加唯一索引
CREATE UNIQUE INDEX idx_email ON users(email);-- 插入重復郵箱會報錯
INSERT INTO users(name, email) VALUES('張三', 'test@qq.com'); -- 成功
INSERT INTO users(name, email) VALUES('李四', 'test@qq.com'); -- 失敗,郵箱重復
3. 普通索引(Normal Index)
最常用的索引類型:
-- 給姓名添加普通索引
CREATE INDEX idx_name ON users(name);-- 快速查找用戶
SELECT * FROM users WHERE name = '張三';
4. 復合索引(Composite Index)
多個字段組合的索引,功能更強大:
-- 創建復合索引
CREATE INDEX idx_name_age_city ON users(name, age, city);-- 這些查詢都能用到索引
SELECT * FROM users WHERE name = '張三'; -- ? 能用到
SELECT * FROM users WHERE name = '張三' AND age = 25; -- ? 能用到
SELECT * FROM users WHERE name = '張三' AND age = 25 AND city = '北京'; -- ? 能用到
SELECT * FROM users WHERE age = 25; -- ? 用不到
SELECT * FROM users WHERE city = '北京'; -- ? 用不到
復合索引的使用規則(最左前綴原則):
-- 索引:(name, age, city)
-- 可以理解為創建了三個索引:
-- 1. (name)
-- 2. (name, age)
-- 3. (name, age, city)
四、索引設計的黃金法則
法則1:為WHERE條件添加索引
-- 經常這樣查詢
SELECT * FROM orders WHERE user_id = 123;
SELECT * FROM orders WHERE status = 'paid';
SELECT * FROM orders WHERE create_time > '2024-01-01';-- 就應該創建這些索引
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_create_time ON orders(create_time);
法則2:為ORDER BY字段添加索引
-- 經常按創建時間排序
SELECT * FROM articles ORDER BY create_time DESC LIMIT 10;-- 創建索引讓排序飛快
CREATE INDEX idx_create_time ON articles(create_time);
法則3:復合索引的順序很關鍵
-- 如果經常這樣查詢
SELECT * FROM users WHERE city = '北京' AND age > 25 ORDER BY create_time;-- 索引字段順序應該是:過濾性強的字段在前
CREATE INDEX idx_city_age_create_time ON users(city, age, create_time);
法則4:覆蓋索引讓查詢更快
-- 如果只需要這幾個字段
SELECT id, name, email FROM users WHERE age = 25;-- 創建覆蓋索引,連回表都省了
CREATE INDEX idx_age_name_email ON users(age, name, email);
五、實戰案例:訂單系統優化
場景描述
假設我們有一個訂單表:
CREATE TABLE orders (id BIGINT PRIMARY KEY AUTO_INCREMENT,user_id BIGINT NOT NULL,order_no VARCHAR(50) NOT NULL,status ENUM('pending', 'paid', 'shipped', 'completed', 'cancelled'),total_amount DECIMAL(10,2),create_time DATETIME,update_time DATETIME
);
常見查詢場景及優化
場景1:用戶查看自己的訂單
-- 原始查詢(慢)
SELECT * FROM orders WHERE user_id = 12345 ORDER BY create_time DESC;-- 優化方案
CREATE INDEX idx_user_id_create_time ON orders(user_id, create_time);
優化效果:
- 優化前:掃描50萬行數據,耗時800ms
- 優化后:直接定位用戶訂單,耗時5ms
場景2:訂單狀態查詢
-- 查詢待支付訂單
SELECT * FROM orders WHERE status = 'pending' AND create_time > '2024-01-01';-- 優化方案
CREATE INDEX idx_status_create_time ON orders(status, create_time);
場景3:訂單號精確查找
-- 通過訂單號查找
SELECT * FROM orders WHERE order_no = 'ORD20240101001';-- 優化方案
CREATE UNIQUE INDEX idx_order_no ON orders(order_no);
優化前后對比
查詢類型 | 優化前耗時 | 優化后耗時 | 提升倍數 |
---|---|---|---|
用戶訂單查詢 | 800ms | 5ms | 160倍 |
狀態篩選 | 1200ms | 8ms | 150倍 |
訂單號查找 | 600ms | 2ms | 300倍 |
六、索引的注意事項:別踩這些坑
坑1:不要給小表建索引
-- 錯誤示例:給只有100行數據的字典表建索引
CREATE TABLE dict_status (id INT PRIMARY KEY,name VARCHAR(20)
);
-- 這個表數據量太小,建索引反而浪費空間
坑2:不要在區分度低的字段建索引
-- 錯誤示例:性別字段只有男/女兩個值
CREATE INDEX idx_gender ON users(gender); -- 沒意義,區分度太低
坑3:索引不是越多越好
-- 錯誤示例:給每個字段都建索引
CREATE INDEX idx_name ON users(name);
CREATE INDEX idx_age ON users(age);
CREATE INDEX idx_city ON users(city);
CREATE INDEX idx_phone ON users(phone);
CREATE INDEX idx_email ON users(email);
-- 太多索引會嚴重影響INSERT/UPDATE性能
坑4:復合索引的字段順序
-- 錯誤示例
CREATE INDEX idx_age_name ON users(age, name);
SELECT * FROM users WHERE name = '張三'; -- 用不到索引-- 正確示例
CREATE INDEX idx_name_age ON users(name, age);
SELECT * FROM users WHERE name = '張三'; -- 能用到索引
七、索引優化實戰技巧
技巧1:使用EXPLAIN分析查詢
-- 分析查詢是否使用了索引
EXPLAIN SELECT * FROM orders WHERE user_id = 12345;
EXPLAIN結果解讀:
字段 | 說明 | 好的值 | 壞的值 |
---|---|---|---|
type | 訪問類型 | const, eq_ref, ref | ALL, index |
key | 使用的索引 | 有具體索引名 | NULL |
rows | 掃描行數 | 越少越好 | 很大的數字 |
Extra | 額外信息 | Using index | Using filesort |
技巧2:監控慢查詢
-- 開啟慢查詢日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超過1秒的查詢記錄下來-- 查看慢查詢
SHOW VARIABLES LIKE 'slow_query_log_file';
技巧3:定期分析表統計信息
-- 更新表的統計信息,讓優化器做出更好的選擇
ANALYZE TABLE orders;
技巧4:合理使用前綴索引
-- 對于很長的字符串字段,使用前綴索引
CREATE INDEX idx_title_prefix ON articles(title(20)); -- 只索引前20個字符
八、高級索引特性
1. 函數索引(MySQL 8.0+)
-- 給計算字段創建索引
ALTER TABLE orders ADD INDEX idx_year ((YEAR(create_time)));-- 這個查詢能用到索引
SELECT * FROM orders WHERE YEAR(create_time) = 2024;
2. 降序索引(MySQL 8.0+)
-- 創建降序索引
CREATE INDEX idx_create_time_desc ON orders(create_time DESC);-- 降序排序更快
SELECT * FROM orders ORDER BY create_time DESC LIMIT 10;
3. 不可見索引
-- 創建不可見索引(用于測試)
CREATE INDEX idx_test ON orders(status) INVISIBLE;-- 測試性能后再設為可見
ALTER INDEX idx_test VISIBLE;
九、索引維護:讓索引保持最佳狀態
定期檢查索引使用情況
-- 查看索引使用統計
SELECT schema_name,table_name,index_name,rows_selected,rows_inserted,rows_updated,rows_deleted
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE schema_name = 'your_database';
刪除無用索引
-- 找出從未使用的索引
SELECT t.table_schema,t.table_name,t.index_name
FROM information_schema.statistics t
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage pON t.table_schema = p.object_schemaAND t.table_name = p.object_nameAND t.index_name = p.index_name
WHERE p.index_name IS NULLAND t.table_schema = 'your_database'AND t.index_name != 'PRIMARY';
重建碎片化的索引
-- 檢查索引碎片化程度
SHOW TABLE STATUS WHERE name = 'orders';-- 重建索引
ALTER TABLE orders ENGINE=InnoDB;
十、實際項目中的索引策略
電商系統索引設計
-- 商品表
CREATE TABLE products (id BIGINT PRIMARY KEY,category_id INT,name VARCHAR(200),price DECIMAL(10,2),stock INT,status TINYINT,create_time DATETIME,-- 核心索引INDEX idx_category_status_price (category_id, status, price),INDEX idx_name (name),INDEX idx_create_time (create_time)
);-- 訂單表
CREATE TABLE orders (id BIGINT PRIMARY KEY,user_id BIGINT,status TINYINT,total_amount DECIMAL(10,2),create_time DATETIME,-- 核心索引INDEX idx_user_id_create_time (user_id, create_time),INDEX idx_status_create_time (status, create_time)
);
社交系統索引設計
-- 用戶關注表
CREATE TABLE user_follows (id BIGINT PRIMARY KEY,follower_id BIGINT, -- 關注者following_id BIGINT, -- 被關注者create_time DATETIME,-- 核心索引INDEX idx_follower_id (follower_id), -- 查詢我關注的人INDEX idx_following_id (following_id), -- 查詢關注我的人UNIQUE KEY uk_follow (follower_id, following_id) -- 防止重復關注
);
十一、性能測試與優化案例
案例1:用戶登錄優化
場景:?用戶登錄驗證
-- 優化前的查詢
SELECT id, password_hash FROM users WHERE email = 'user@example.com';-- 性能測試結果
-- 數據量:100萬用戶
-- 查詢時間:平均 850ms
-- 掃描行數:平均 50萬行
優化方案:
-- 1. 創建郵箱唯一索引
CREATE UNIQUE INDEX idx_email ON users(email);-- 2. 創建覆蓋索引(避免回表)
CREATE INDEX idx_email_password ON users(email, password_hash);
優化效果:
指標 | 優化前 | 優化后 | 提升 |
---|---|---|---|
查詢時間 | 850ms | 2ms | 425倍 |
掃描行數 | 50萬行 | 1行 | 50萬倍 |
CPU使用率 | 85% | 5% | 17倍 |
案例2:分頁查詢優化
場景:?商品列表分頁查詢
-- 優化前:傳統分頁(深度分頁很慢)
SELECT * FROM products
WHERE category_id = 5
ORDER BY create_time DESC
LIMIT 50000, 20; -- 第2500頁,超級慢-- 優化后:游標分頁
SELECT * FROM products
WHERE category_id = 5 AND create_time < '2024-01-15 10:30:00'
ORDER BY create_time DESC
LIMIT 20;
性能對比:
頁數 | 傳統分頁 | 游標分頁 | 性能提升 |
---|---|---|---|
第1頁 | 5ms | 3ms | 1.7倍 |
第100頁 | 50ms | 3ms | 16.7倍 |
第1000頁 | 500ms | 3ms | 166.7倍 |
第5000頁 | 2500ms | 3ms | 833.3倍 |
十二、總結與最佳實踐
索引設計的黃金原則
1. 基礎原則:
- 主鍵索引是必須的
- 經常WHERE查詢的字段要建索引
- 經常ORDER BY的字段要建索引
- 區分度高的字段適合建索引
2. 復合索引原則:
- 遵循最左前綴原則
- 區分度高的字段放在前面
- 經常組合查詢的字段建復合索引
3. 性能原則:
- 索引不是越多越好
- 定期檢查和清理無用索引
- 監控慢查詢,及時優化
常見的索引使用誤區
誤區 | 說明 | 正確做法 |
---|---|---|
給所有字段建索引 | 浪費空間,影響寫性能 | 只給查詢頻繁的字段建索引 |
忽略復合索引順序 | 索引失效 | 按最左前綴原則設計 |
不監控索引使用情況 | 存在無用索引 | 定期檢查,清理無用索引 |
小表也建索引 | 得不償失 | 小表(<1000行)不建議建索引 |