-- 计算字段的选择性 SELECT COUNT(DISTINCT column_name) /COUNT(*) AS selectivity FROM table_name;
-- 示例:gender 字段的选择性 SELECT COUNT(DISTINCT gender) /COUNT(*) AS selectivity FROM users; -- 结果:0.000002(非常低,不适合单独建索引)
2. 索引列顺序不当(最左前缀原则)
-- 创建联合索引 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 =25AND gender ='男';
-- ❌ 不走索引:违反最左前缀 SELECT*FROM users WHERE age =25; -- 缺少 name SELECT*FROM users WHERE gender ='男'; -- 缺少 name 和 age SELECT*FROM users WHERE age =25AND gender ='男'; -- 缺少 name
-- ⚠️ 部分走索引:只有 name 走索引,后面的列无法利用索引排序 SELECT*FROM users WHERE name ='张三'AND gender ='男'; -- age 跳过了
-- ❌ 冗余索引示例 CREATE INDEX idx_name ON users(name); -- 单列索引 CREATE INDEX idx_name_age ON users(name, age); -- 联合索引 -- idx_name 是冗余的,因为 idx_name_age 可以覆盖 name 单列查询
-- ✅ 优化后:删除冗余索引 DROP INDEX idx_name ON users; -- 只保留联合索引 idx_name_age
二、SQL 写法问题
1. SELECT * 导致大量回表
flowchart LR
Q["查询:SELECT * FROM users WHERE name = '张三'"] --> SEC["非聚簇索引(二级索引)<br/>name + 主键 ID<br/>张三 → 1001,李四 → 1002,王五 → 1003"]
SEC -->|"1. 找到主键 ID = 1001"| CLU["聚簇索引(主键索引)<br/>主键 ID + 所有字段"]
CLU -->|"2. 回表:用 ID=1001 查完整行数据<br/>3. 返回所有字段"| R["返回结果"]
R --> W["注意:如果返回 1000 行,就需要回表 1000 次"]
-- ✅ 走索引:使用范围查询或等值查询 SELECT*FROM users WHERE create_time >='2024-01-01' AND create_time <'2024-01-02';
-- ❌ 不走索引:索引列参与计算 SELECT*FROM users WHERE age +1=26;
-- ✅ 走索引:调整计算方式 SELECT*FROM users WHERE age =25;
4. 隐式类型转换
-- 假设 user_id 是 VARCHAR 类型 CREATE INDEX idx_user_id ON users(user_id);
-- ❌ 不走索引:字符串与数字比较,发生隐式类型转换 SELECT*FROM users WHERE user_id =123; -- 等价于:SELECT * FROM users WHERE CAST(user_id AS SIGNED) = 123;
-- ✅ 走索引:类型一致 SELECT*FROM users WHERE user_id ='123';
核心原则:索引列的类型必须与查询条件的类型完全一致,否则会发生隐式类型转换,导致索引失效。
三、数据分布问题
1. 数据量过大
-- 查询表的总行数和大小 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';
优化方案:
分区表:按时间或范围分区,减少单次查询扫描的数据量
分表分库:将大表拆分为多个小表
归档历史数据:将历史数据迁移到归档表
使用覆盖索引:减少回表次数
2. 数据倾斜严重
-- 查看数据分布 SELECT gender, COUNT(*) as count FROM users GROUPBY gender;