【问题标题】:mysql index selection on large table大表上的mysql索引选择
【发布时间】: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


【解决方案1】:

为了最有效地对两个不同字段的多个值使用复合索引,您需要使用连接而不是简单的 where 条件来指定值。因此,假设您选择从 2022-12-01 到 2022-12-03 的日期和 (1,2,3) 中的 entity_id,请执行以下操作:

select ...
from (select date('2022-12-01') date union all select date('2022-12-02') union all select date('2022-12-03')) dates
join Entities on Entities.id in (1,2,3)
join Events on Events.entity_id=Entities.id and Events.date=dates.date

如果您预先创建一个日期表,其中包含从 0000-01-01 到 9999-12-31 的所有日期,那么您可以执行以下操作:

select ...
from dates
join Entities on Entities.id in (1,2,3)
join Events on Events.entity_id=Entities.id and Events.date=dates.date
where dates.date between @start_date and @end_date

【讨论】:

    猜你喜欢
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 2011-10-16
    • 2015-08-13
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    相关资源
    最近更新 更多