一、为什么需要评估未创建索引的 Cardinality?
在数据库优化中,Cardinality(基数) 是决定是否创建索引、以及如何排列联合索引列顺序的核心指标。它表示索引列中不重复值的数量。
核心矛盾 :索引尚未创建时,数据库不会为其维护统计信息。我们无法直接 SHOW INDEX 查看 Cardinality,必须手动估算或模拟。
关键认知:联合索引的 Cardinality ≠ 单列 Cardinality 的简单叠加,而是取决于列之间的组合唯一性。
二、方法一:手动计算(最准确)
2.1 基础计算查询
sql
-- 假设你想测试 (col1, col2) 的联合索引 Cardinality
-- 计算唯一组合数
SELECT
COUNT(*) AS total_rows,
COUNT(DISTINCT col1) AS col1_cardinality,
COUNT(DISTINCT col2) AS col2_cardinality,
COUNT(DISTINCT CONCAT(col1, '-', col2)) AS combined_cardinality
FROM your_table;
2.2 示例输出解读
+------------+------------------+------------------+-------------------+
| total_rows | col1_cardinality | col2_cardinality | combined_cardinality|
+------------+------------------+------------------+-------------------+
| 1000000 | 50000 | 100 | 800000|
+------------+------------------+------------------+-------------------+
分析逻辑:
| 指标 | 数值 | 含义 |
|---|---|---|
total_rows |
1,000,000 | 表总行数 |
col1_cardinality |
50,000 | col1 单独的不重复值数 |
col2_cardinality |
100 | col2 单独的不重复值数 |
combined_cardinality |
800,000 | 两列组合的不重复值数 |
判断标准:
combined_cardinality(80万)越接近total_rows(100万),联合索引效果越好- 如果
combined_cardinality≈col1_cardinality(5万),说明 col2 几乎没有额外区分度,不建议将 col2 加入联合索引
三、方法二:计算选择性(Selectivity)
选择性是 Cardinality 的"标准化"指标,消除了表大小差异的影响,更适合跨表比较。
3.1 选择性计算公式
sql
-- 联合索引选择性 = 唯一组合数 / 总行数
-- 越接近 1 越好
SELECT
COUNT(DISTINCT CONCAT(col1, '-', col2)) / COUNT(*) AS selectivity,
CASE
WHEN COUNT(DISTINCT CONCAT(col1, '-', col2)) / COUNT(*) > 0.1 THEN '适合建索引'
WHEN COUNT(DISTINCT CONCAT(col1, '-', col2)) / COUNT(*) > 0.01 THEN '效果一般'
ELSE '不建议建索引'
END AS suggestion
FROM your_table;
3.2 选择性决策矩阵
| 选择性范围 | 建议 | 说明 |
|---|---|---|
| > 0.1 (10%) | 强烈推荐建联合索引 | 区分度极高,索引收益明显 |
| 0.01 ~ 0.1 (1%~10%) | 效果一般 | 根据查询频率和写操作比例决定 |
| < 0.01 (1%) | 不建议建索引 | 区分度太低,索引维护成本高,考虑其他优化方案 |
四、方法三:对比不同列顺序
联合索引的列顺序直接影响查询性能。最左前缀原则要求:将 Cardinality 高的列放在前面。
4.1 列顺序对比查询
sql
-- 测试 (a,b) vs (b,a) 哪个更好
SELECT
'a_first' AS order_type,
COUNT(DISTINCT CONCAT(a, '-', b)) AS cardinality
FROM your_table
UNION ALL
SELECT
'b_first' AS order_type,
COUNT(DISTINCT CONCAT(b, '-', a)) AS cardinality
FROM your_table;
4.2 列顺序决策原则
┌─────────────────────────────────────────────────────────┐
│ 原则:将 Cardinality 高的列放在联合索引的左侧(前面) │
│ │
│ 原因: │
│ 1. 最左前缀原则要求查询必须从索引最左列开始匹配 │
│ 2. 高 Cardinality 列在前,能更快缩小扫描范围 │
│ 3. 避免索引"失效"------跳过最左列后,后续列无法使用索引 │
└─────────────────────────────────────────────────────────┘
示例场景:
- 订单表:
user_id(10万 Cardinality)+status(5 Cardinality) - 推荐索引:
(user_id, status)------ 先按用户过滤,再按状态过滤 - 不推荐:
(status, user_id)------ 先按状态(只有5种)过滤,效率极低
五、方法四:使用 EXPLAIN 模拟(MySQL 8.0)
虽然无法直接查看未创建索引的 Cardinality,但可以通过创建临时索引模拟查询效果。
5.1 临时索引测试流程
sql
-- Step 1: 创建临时索引(仅用于测试,数据量大时慎用)
ALTER TABLE your_table ADD INDEX idx_test (col1, col2);
-- Step 2: 查看优化器是否选择该索引
EXPLAIN SELECT * FROM your_table WHERE col1 = 'xxx' AND col2 = 'yyy';
-- Step 3: 查看实际 Cardinality
SHOW INDEX FROM your_table WHERE Key_name = 'idx_test';
-- Step 4: 测试完成后立即删除(避免影响线上性能)
ALTER TABLE your_table DROP INDEX idx_test;
5.2 注意事项
生产环境慎用 :
ALTER TABLE会锁表,大表操作可能导致长时间阻塞。建议在:
- 低峰期执行
- 或先在测试环境(相同数据量)验证
- 或使用 MySQL 8.0 的
INSTANT ADD COLUMN(部分场景支持在线DDL)
六、方法五:快速估算(大表优化)
对于千万级甚至亿级大表,COUNT(DISTINCT) 全表扫描非常慢,可以采用采样估算策略。
6.1 随机采样估算
sql
-- 随机采样 10000 条估算整体 Cardinality
SELECT
COUNT(DISTINCT CONCAT(col1, '-', col2)) *
(SELECT COUNT(*) FROM your_table) / 10000 AS estimated_cardinality,
COUNT(DISTINCT CONCAT(col1, '-', col2)) *
(SELECT COUNT(*) FROM your_table) / 10000 /
(SELECT COUNT(*) FROM your_table) AS estimated_selectivity
FROM (
SELECT col1, col2
FROM your_table
ORDER BY RAND()
LIMIT 10000
) AS sample;
6.2 采样策略优化
| 表大小 | 采样行数 | 误差范围 | 执行时间预估 |
|---|---|---|---|
| < 100万 | 全表计算 | 0% | < 5秒 |
| 100万 ~ 1000万 | 10,000 | ±5% | 2-10秒 |
| 1000万 ~ 1亿 | 50,000 | ±3% | 10-30秒 |
| > 1亿 | 100,000 | ±2% | 30-60秒 |
提示 :
ORDER BY RAND()在超大表上也很慢,可以改用主键范围采样:
sql-- 更高效的采样方式(基于主键范围) SELECT col1, col2 FROM your_table WHERE id BETWEEN FLOOR(RAND() * (SELECT MAX(id) FROM your_table)) AND FLOOR(RAND() * (SELECT MAX(id) FROM your_table)) + 10000 LIMIT 10000;
七、综合评估报告模板
7.1 多组候选联合索引对比
sql
-- 综合评估报告:分析多组候选联合索引
SELECT
'idx_a_b' AS index_name,
COUNT(DISTINCT CONCAT(a, '-', b)) AS cardinality,
COUNT(DISTINCT CONCAT(a, '-', b)) / COUNT(*) AS selectivity,
COUNT(DISTINCT a) AS first_col_card,
COUNT(DISTINCT b) AS second_col_card
FROM your_table
UNION ALL
SELECT
'idx_b_a' AS index_name,
COUNT(DISTINCT CONCAT(b, '-', a)) AS cardinality,
COUNT(DISTINCT CONCAT(b, '-', a)) / COUNT(*) AS selectivity,
COUNT(DISTINCT b) AS first_col_card,
COUNT(DISTINCT a) AS second_col_card
FROM your_table
UNION ALL
SELECT
'idx_a_c' AS index_name,
COUNT(DISTINCT CONCAT(a, '-', c)) AS cardinality,
COUNT(DISTINCT CONCAT(a, '-', c)) / COUNT(*) AS selectivity,
COUNT(DISTINCT a) AS first_col_card,
COUNT(DISTINCT c) AS second_col_card
FROM your_table;
7.2 输出示例与解读
+-----------+------------+------------+----------------+-----------------+
| index_name| cardinality| selectivity| first_col_card | second_col_card |
+-----------+------------+------------+----------------+-----------------+
| idx_a_b | 850000 | 0.8500 | 100000 | 200 |
| idx_b_a | 850000 | 0.8500 | 200 | 100000 |
| idx_a_c | 120000 | 0.1200 | 100000 | 5000 |
+-----------+------------+------------+----------------+-----------------+
决策分析:
| 候选索引 | 选择性 | 推荐列顺序 | 结论 |
|---|---|---|---|
idx_a_b |
0.85 | a 在前 |
优秀,高选择性 |
idx_b_a |
0.85 | b 在前 |
选择性相同,但 b 的 Cardinality 低,不适合放前面 |
idx_a_c |
0.12 | a 在前 |
中等,根据查询频率决定 |
最终推荐 :创建 INDEX idx_a_b (a, b),因为:
- 选择性高达 0.85,远超 0.1 阈值
a的 Cardinality(10万)远高于b(200),符合"高 Cardinality 列在前"原则
八、已创建索引的 Cardinality 查看
如果你已经创建了索引,可以直接查看数据库维护的统计信息:
sql
-- 查看表的索引统计信息
SHOW INDEX FROM your_table;
-- 或查询 information_schema
SELECT
TABLE_NAME,
INDEX_NAME,
COLUMN_NAME,
CARDINALITY,
ROUND(CARDINALITY / TABLE_ROWS, 4) AS selectivity
FROM information_schema.STATISTICS s
JOIN information_schema.TABLES t
ON s.TABLE_SCHEMA = t.TABLE_SCHEMA
AND s.TABLE_NAME = t.TABLE_NAME
WHERE s.TABLE_NAME = 'your_table';
注意 :
information_schema.STATISTICS中的CARDINALITY是估算值,基于采样统计,可能与实际值有偏差。对于关键决策,建议用本文的方法重新计算。
九、决策流程图
开始评估联合索引 Cardinality
│
▼
┌─────────────────────┐
│ 表数据量 < 100万? │
└─────────────────────┘
│ │
是 否
│ │
▼ ▼
全表 COUNT(DISTINCT) 采样估算(10,000~100,000条)
│ │
└──────┬───────┘
▼
┌─────────────────────────────┐
│ 计算 combined_cardinality │
│ 和 selectivity │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ selectivity > 0.1 ? │
└─────────────────────────────┘
│ │
是 否
│ │
▼ ▼
┌──────────┐ ┌─────────────────────────┐
│ 强烈推荐 │ │ selectivity > 0.01 ? │
│ 建索引 │ └─────────────────────────┘
└──────────┘ │ │
是 否
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ 效果一般 │ │ 不建议 │
│ 根据查询 │ │ 建索引 │
│ 频率决定 │ │ │
└──────────┘ └──────────┘
十、核心结论与公式
未创建的联合索引没有现成的 Cardinality 统计 ,但你可以通过
COUNT(DISTINCT CONCAT(col1, '-', col2))来计算唯一组合数,这就是联合索引的实际 Cardinality。
核心公式
联合索引 Cardinality = COUNT(DISTINCT CONCAT(col1, '-', col2, '-', ...))
联合索引选择性 = 联合索引 Cardinality / 表总行数
黄金法则
| 法则 | 说明 |
|---|---|
| 值越大越好 | Cardinality 越接近总行数,索引效果越好 |
| 高列在前 | 联合索引中,Cardinality 高的列放在左侧 |
| 选择性 > 0.1 | 强烈推荐建索引 |
| 选择性 < 0.01 | 维护成本高于收益,不建议建索引 |
十一、快速参考卡片
sql
-- 一键评估联合索引 (col1, col2)
SELECT
COUNT(*) AS total_rows,
COUNT(DISTINCT col1) AS col1_card,
COUNT(DISTINCT col2) AS col2_card,
COUNT(DISTINCT CONCAT(col1, '-', col2)) AS combined_card,
ROUND(COUNT(DISTINCT CONCAT(col1, '-', col2)) / COUNT(*), 4) AS selectivity,
CASE
WHEN COUNT(DISTINCT CONCAT(col1, '-', col2)) / COUNT(*) > 0.1 THEN '强烈推荐'
WHEN COUNT(DISTINCT CONCAT(col1, '-', col2)) / COUNT(*) > 0.01 THEN '效果一般'
ELSE '不建议'
END AS suggestion
FROM your_table;
记住:Cardinality 评估是索引优化的"地基",地基不稳,后续的 EXPLAIN 分析、SQL 改写都是空中楼阁。在创建任何联合索引前,先用本文的方法算一算,避免盲目建索引带来的性能反噬。