【发布时间】:2022-12-31 00:31:44
【问题描述】:
我有几个看起来像这样的表:
CREATE TABLE Entities (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(45) NOT NULL,
client_id INT NOT NULL,
display_name VARCHAR(45),
PRIMARY KEY (id)
)
CREATE TABLE Statuses (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(45) NOT NULL,
PRIMARY KEY (id)
)
CREATE TABLE EventTypes (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(45) NOT NULL,
PRIMARY KEY (id)
)
CREATE TABLE Events (
id INT NOT NULL AUTO_INCREMENT,
entity_id INT NOT NULL,
date DATE NOT NULL,
event_type_id INT NOT NULL,
status_id INT NOT NULL
)
事件很大 > 100,000,000 行
实体、状态和事件类型都很小,每行 < 300 行
我有几个关于事件的索引,但发挥作用的两个是 idx_events_date_ent_status_type(日期、entity_id、status_id、event_type_id) 和 idx_events_date_ent_status_type (entity_id, status_id, event_type_id)
我有一个大而复杂的查询,但我得到了同样慢的查询结果和一个更简单的查询结果,如下所示(注意,在实际查询中,我不使用 evt。*)
SELECT evt.*, ent.name AS ent_name, s.name AS stat_name, et.name AS type_name
FROM `Events` evt
JOIN `Entities` ent ON evt.entity_id = ent.id
JOIN `EventTypes` et ON evt.event_type_id = et.id
JOIN `Statuses` s ON evt.status_id = s.id
WHERE
evt.date BETWEEN @start_date AND @end_date AND
evt.entity_id IN ( 19 ) AND -- this in clause is built by code
evt.event_type_id = @type_id
出于某种原因,mysql 一直选择不涵盖 Events.date 的索引,查询需要 15 秒或更长时间并返回几千行。如果我将查询更改为:
SELECT evt.*, ent.name AS ent_name, s.name AS stat_name, et.name AS type_name
FROM `Events` evt force index (idx_events_date_ent_status_type)
JOIN `Entities` ent ON evt.entity_id = ent.id
JOIN `EventTypes` et ON evt.event_type_id = et.id
JOIN `Statuses` s ON evt.status_id = s.id
WHERE
evt.date BETWEEN @start_date AND @end_date AND
evt.entity_id IN ( 19 ) AND -- this in clause is built by code
evt.event_type_id = @type_id
查询需要 0.014 秒。
由于此查询是通过代码构建的,我宁愿不强制索引,但主要是,我想知道为什么它选择一个索引而不是另一个索引。是因为加入吗?
为了提供一些统计数据,事件表中有约 2500 个不同的日期和约 200 个实体。所以我想这可能就是它选择具有所有低基数列的索引的原因。
您认为将日期添加到 idx_events_date_ent_status_type 的末尾会有帮助吗?由于这是一张大表,添加索引需要很长时间。
我尝试添加一个额外的索引, ix_events_ent_date_status_et(entity_id,日期,status_id,event_type_id) 它实际上使查询变慢了。
我会做更多的实验,但我觉得我不确定优化器是如何做出决定的。
【问题讨论】:
-
请做“更多实验”,或开始阅读有关Optimization 的章节,或找到与此主题有关的 stackoverflow 上给出的任何答案。
-
“出于某种原因,mysql 一直选择不涵盖 Events.date 的索引”=>
start_date和end_date之间有多少条记录?如果是“很多”,那么 MySQL 将决定不使用索引。当只选择 1 天 (start_date=end_date) 或几天时,MySQL 可能最终决定使用索引 -
另外
status_id在您强制使用的索引中,但没有对该字段进行过滤。这也是不选择该索引的原因。 -
@Luuk - 我一直在试验和阅读有关索引优化的内容。与事件总数相比,开始日期和结束日期之间的记录数要少得多,尤其是在使用 entity_id 时。请注意,status_id 在两个索引中。不过,我确实有一些额外的信息,似乎与状态表的连接是导致选择没有日期的索引的原因。这就是让我困惑的地方。由于我没有按 status_id 过滤,为什么优化器不选择覆盖范围更大的索引(date、entity_id、status_id、event_type_id)
标签: mysql