一、为什么需要评估未创建索引的 Cardinality?
在数据库优化中,Cardinality(基数)是决定是否创建索引、以及如何排列联合索引列顺序的核心指标。它表示索引列中不重复值的数量。
核心矛盾:索引尚未创建时,数据库不会为其维护统计信息。我们无法直接SHOW INDEX查看 Cardinality,必须手动估算或模拟。
关键认知:联合索引的 Cardinality ≠ 单列 Cardinality 的简单叠加,而是取决于列之间的组合唯一性。
二、方法一:手动计算(最准确)
2.1 基础计算查询
-- 假设你想测试 (col1, col2) 的联合索引 Cardinality-- 计算唯一组合数SELECTCOUNT(*)AStotal_rows,COUNT(DISTINCTcol1)AScol1_cardinality,COUNT(DISTINCTcol2)AScol2_cardinality,COUNT(DISTINCTCONCAT(col1,'-',col2))AScombined_cardinalityFROMyour_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 选择性计算公式
-- 联合索引选择性 = 唯一组合数 / 总行数-- 越接近 1 越好SELECTCOUNT(DISTINCTCONCAT(col1,'-',col2))/COUNT(*)ASselectivity,CASEWHENCOUNT(DISTINCTCONCAT(col1,'-',col2))/COUNT(*)>0.1THEN'适合建索引'WHENCOUNT(DISTINCTCONCAT(col1,'-',col2))/COUNT(*)>0.01THEN'效果一般'ELSE'不建议建索引'ENDASsuggestionFROMyour_table;3.2 选择性决策矩阵
| 选择性范围 | 建议 | 说明 |
|---|---|---|
| > 0.1 (10%) | 强烈推荐建联合索引 | 区分度极高,索引收益明显 |
| 0.01 ~ 0.1 (1%~10%) | 效果一般 | 根据查询频率和写操作比例决定 |
| < 0.01 (1%) | 不建议建索引 | 区分度太低,索引维护成本高,考虑其他优化方案 |
四、方法三:对比不同列顺序
联合索引的列顺序直接影响查询性能。最左前缀原则要求:将 Cardinality 高的列放在前面。
4.1 列顺序对比查询
-- 测试 (a,b) vs (b,a) 哪个更好SELECT'a_first'ASorder_type,COUNT(DISTINCTCONCAT(a,'-',b))AScardinalityFROMyour_tableUNIONALLSELECT'b_first'ASorder_type,COUNT(DISTINCTCONCAT(b,'-',a))AScardinalityFROMyour_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 临时索引测试流程
-- Step 1: 创建临时索引(仅用于测试,数据量大时慎用)ALTERTABLEyour_tableADDINDEXidx_test(col1,col2);-- Step 2: 查看优化器是否选择该索引EXPLAINSELECT*FROMyour_tableWHEREcol1='xxx'ANDcol2='yyy';-- Step 3: 查看实际 CardinalitySHOWINDEXFROMyour_tableWHEREKey_name='idx_test';-- Step 4: 测试完成后立即删除(避免影响线上性能)ALTERTABLEyour_tableDROPINDEXidx_test;5.2 注意事项
生产环境慎用:
ALTER TABLE会锁表,大表操作可能导致长时间阻塞。建议在:
- 低峰期执行
- 或先在测试环境(相同数据量)验证
- 或使用 MySQL 8.0 的
INSTANT ADD COLUMN(部分场景支持在线DDL)
六、方法五:快速估算(大表优化)
对于千万级甚至亿级大表,COUNT(DISTINCT)全表扫描非常慢,可以采用采样估算策略。
6.1 随机采样估算
-- 随机采样 10000 条估算整体 CardinalitySELECTCOUNT(DISTINCTCONCAT(col1,'-',col2))*(SELECTCOUNT(*)FROMyour_table)/10000ASestimated_cardinality,COUNT(DISTINCTCONCAT(col1,'-',col2))*(SELECTCOUNT(*)FROMyour_table)/10000/(SELECTCOUNT(*)FROMyour_table)ASestimated_selectivityFROM(SELECTcol1,col2FROMyour_tableORDERBYRAND()LIMIT10000)ASsample;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()在超大表上也很慢,可以改用主键范围采样:-- 更高效的采样方式(基于主键范围)SELECTcol1,col2FROMyour_tableWHEREidBETWEENFLOOR(RAND()*(SELECTMAX(id)FROMyour_table))ANDFLOOR(RAND()*(SELECTMAX(id)FROMyour_table))+10000LIMIT10000;
七、综合评估报告模板
7.1 多组候选联合索引对比
-- 综合评估报告:分析多组候选联合索引SELECT'idx_a_b'ASindex_name,COUNT(DISTINCTCONCAT(a,'-',b))AScardinality,COUNT(DISTINCTCONCAT(a,'-',b))/COUNT(*)ASselectivity,COUNT(DISTINCTa)ASfirst_col_card,COUNT(DISTINCTb)ASsecond_col_cardFROMyour_tableUNIONALLSELECT'idx_b_a'ASindex_name,COUNT(DISTINCTCONCAT(b,'-',a))AScardinality,COUNT(DISTINCTCONCAT(b,'-',a))/COUNT(*)ASselectivity,COUNT(DISTINCTb)ASfirst_col_card,COUNT(DISTINCTa)ASsecond_col_cardFROMyour_tableUNIONALLSELECT'idx_a_c'ASindex_name,COUNT(DISTINCTCONCAT(a,'-',c))AScardinality,COUNT(DISTINCTCONCAT(a,'-',c))/COUNT(*)ASselectivity,COUNT(DISTINCTa)ASfirst_col_card,COUNT(DISTINCTc)ASsecond_col_cardFROMyour_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 查看
如果你已经创建了索引,可以直接查看数据库维护的统计信息:
-- 查看表的索引统计信息SHOWINDEXFROMyour_table;-- 或查询 information_schemaSELECTTABLE_NAME,INDEX_NAME,COLUMN_NAME,CARDINALITY,ROUND(CARDINALITY/TABLE_ROWS,4)ASselectivityFROMinformation_schema.STATISTICSsJOINinformation_schema.TABLEStONs.TABLE_SCHEMA=t.TABLE_SCHEMAANDs.TABLE_NAME=t.TABLE_NAMEWHEREs.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 | 维护成本高于收益,不建议建索引 |
十一、快速参考卡片
-- 一键评估联合索引 (col1, col2)SELECTCOUNT(*)AStotal_rows,COUNT(DISTINCTcol1)AScol1_card,COUNT(DISTINCTcol2)AScol2_card,COUNT(DISTINCTCONCAT(col1,'-',col2))AScombined_card,ROUND(COUNT(DISTINCTCONCAT(col1,'-',col2))/COUNT(*),4)ASselectivity,CASEWHENCOUNT(DISTINCTCONCAT(col1,'-',col2))/COUNT(*)>0.1THEN'强烈推荐'WHENCOUNT(DISTINCTCONCAT(col1,'-',col2))/COUNT(*)>0.01THEN'效果一般'ELSE'不建议'ENDASsuggestionFROMyour_table;记住:Cardinality 评估是索引优化的"地基",地基不稳,后续的 EXPLAIN 分析、SQL 改写都是空中楼阁。在创建任何联合索引前,先用本文的方法算一算,避免盲目建索引带来的性能反噬。