mysql - 为什么索引列上的 SELECT DISTINCT 不是瞬时的?
问题描述
我有一个这样的表,它存储正在运行的各种程序的配置。它看起来像这样:
+--------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------+------+-----+---------+-------+
| Date | date | YES | MUL | NULL | |
| Program | varchar(20) | YES | MUL | NULL | |
| ConfigFile | int(11) | YES | | NULL | |
| Parameter | varchar(20) | YES | | NULL | |
| Value | varchar(20) | YES | | NULL | |
+--------------+---------------+------+-----+---------+-------+
该ConfigFile
字段包含配置文件的编号——对于某些程序,可以选择多个配置文件。
它有几个索引,如下所示:
+-------+------------+----------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| lists | 1 | Date | 1 | Date | A | 1108060 | NULL | NULL | YES | BTREE | | |
| lists | 1 | Date | 2 | Program | A | 1108060 | NULL | NULL | YES | BTREE | | |
| lists | 1 | Date | 3 | Parameter | A | 1108060 | NULL | NULL | YES | BTREE | | |
| lists | 1 | Program | 1 | Program | A | 4676 | NULL | NULL | YES | BTREE | | |
| lists | 1 | Program | 2 | Parameter | A | 183706 | NULL | NULL | YES | BTREE | | |
+-------+------------+----------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
现在假设我想知道给定程序的参数是什么。看来我应该能够做这样的事情:
SELECT DISTINCT Parameter FROM params WHERE Program = 'MyProgram';
这有以下解释计划:
+----+-------------+--------+------------+------+----------------+---------+---------+-------+-----------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+----------------+---------+---------+-------+-----------+----------+--------------------------+
| 1 | SIMPLE | params | NULL | ref | Date,Program | Program | 23 | const | 137203382 | 100.00 | Using where; Using index |
+----+-------------+--------+------------+------+----------------+---------+---------+-------+-----------+----------+--------------------------+
有 15 种不同的选择,每个程序Program
可能有 10 到 100 个值。Parameter
根据我对数据库索引如何工作的理解,我希望这会立即完成。特别是,我希望底层数据结构是具有 15 个节点的二叉搜索树,我会搜索它以找到与我的程序相对应的节点;找到我的程序后,它会将我带到可能有 100 个或更少节点的第二个二叉搜索树,然后我会简单地遍历它。
但是,当我实际运行查询时,它最终需要几分钟。
对我来说,这表明二叉搜索树中可能存在相同值的多个副本,表的每个节点一个。这是正在发生的事情吗?如果是这样,我能做些什么来缓解这种情况?
我考虑过拥有一个具有唯一三元组(日期、程序、参数)的表并具有关系,但我不确定在这种情况下如何执行数据的批量插入。如果我错了为什么它这么慢那么当然这甚至没有帮助。
解决方案
InnoDB 的 B+Tree 二级索引不是这样形成的。这样想:
- 对于每一行,构造一个由 , , 组成
Program
的Parameter
字符串PK
。 - 对这些字符串进行排序。
- 将它们放在 BTree 中。
注意:没有任何分裂的暗示Program
。如果 99.9% 的项目都在项目 #5 中会怎样?那将是一个相当不平衡的 BTree。对于您的一个罕见的查询很方便,但对于大多数其他查询来说较慢。
使用平衡良好的 B+Tree,您的查询必须:
- 向下钻取 BTree 以找到第一个“行”
Program = 'MyProgram'
- 通过 B+Tree 的叶子节点向前走,使用“+”从一个叶子块步进到下一个叶子块。
- 走路时,跟踪每一个新的
Parameter
。 Program = 'MyProgram'
失败时退出。
笔记:
DISTINCT
通过了解物品的排序方式,在我的步骤 3 中很容易实现。- “使用索引”表示索引是“覆盖”——因为您只需要
Program
andParameter
(这些是 中的列INDEX
)。PK 也隐含地可用于“覆盖”。 - 您提供的 15 不同意“4676”的基数。但这只是指出,统计数据有时相差甚远。(统计信息对此查询的优化没有影响。)
我考虑过拥有一个具有唯一三元组(日期、程序、参数)的表
是的,拥有这样的表会使您的查询运行得更快。但是这样的维护值得吗?
该表可以让您做的另一件事是将这 3 列标准化为单个MEDIUMINT UNSIGNED
(仅 3 个字节),而不是现在在平均行上使用 30 个字节。再一次,等的复杂性会JOINs
超过好处吗?它可能会将磁盘占用空间缩小 50%。
推荐阅读
- c# - 未设置 Toast 通知组属性
- c++ - 从 const 限定类方法操作属于 std::unique_ptr 的 std::mutex 是否安全?
- streamlit - Warp10 和流线型集成?
- python - 使用 selenium (Python) 从 ui-datepicker-calendar 中选择生日
- ios - SwiftUI - 重音,前景色和色调之间的区别?
- html - 如何让我的表单有 2 个 div 始终居中在页面上
- node.js - heroku node js创建文件夹问题
- node.js - Express 路线在内部和 Postman 测试中工作,但在 Heroku 上出错了 500(大部分)
- java - 在 Java 密钥库中导入 pkcs12 (.p12) 客户端证书文件时,CertificateChain 为空
- excel - 使用数组删除工作表