MySQL 联合索引创建效果评估

一、为什么需要评估未创建索引的 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_cardinalitycol1_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),因为:

  1. 选择性高达 0.85,远超 0.1 阈值
  2. 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 改写都是空中楼阁。在创建任何联合索引前,先用本文的方法算一算,避免盲目建索引带来的性能反噬。

相关推荐
逸Y 仙X1 小时前
文章二十五:ElasticSearch 分页查询
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
今天长肉了吗1 小时前
风控指标平台实战:大数据量下如何设计分批处理
开发语言·数据库·python
2301_782040451 小时前
JavaScript中丢失的this:回调函数中指向改变的对策
jvm·数据库·python
2301_818008441 小时前
MySQL从库出现数据同步异常中断_重新获取binlog坐标同步
jvm·数据库·python
四维迁跃2 小时前
MySQL如何优雅处理数据库连接池耗尽_HikariCP与连接数调优
jvm·数据库·python
X56612 小时前
Go语言如何做Helm Chart_Go语言Helm打包部署教程【收藏】
jvm·数据库·python
szccyw02 小时前
如何阻止 HTML 页面在 JavaScript 执行完成前渲染
jvm·数据库·python
1.14(java)2 小时前
Spring事务和事务传播机制
java·数据库·spring
forEverPlume2 小时前
Go语言怎么做链路追踪_Go语言分布式链路追踪教程【精选】
jvm·数据库·python