【问题标题】:Poor performance with BETWEEN queryBETWEEN 查询性能不佳
【发布时间】:2013-09-25 14:51:44
【问题描述】:

我正在尝试使用此查询在多个时期之间查找个人的考试结果:

SELECT * FROM RESULTS AS R, Define_Times AS T 
WHERE R.PERSONID = T.PERSONID AND ( 
(R.DATE BETWEEN T.Previous_Month_Start AND T.Previous_Month_End) OR 
(R.DATE BETWEEN T.Next_Month_Start AND T.Next_Month_End) OR 
(R.DATE BETWEEN T.Six_Month_Start AND T.Six_Month_End) OR 
(R.DATE BETWEEN T.One_Year_Start AND T.One_Year_End) OR 
(R.DATE BETWEEN T.Two_Year_Start AND T.Two_Year_End) OR 
(R.DATE BETWEEN T.Three_Year_Start AND T.Three_Year_End) OR 
(R.DATE BETWEEN T.Four_Year_Start AND T.Four_Year_End) )

Previous/Next/One_Year 等因人而异。

解释给出:

| id | select_type | table | type | possible_keys | key  | key_len | ref             | rows  | Extra       |
|  1 | SIMPLE      | T     | ALL  | PEOPLE        | NULL | NULL    | NULL            | 75775 |             |
|  1 | SIMPLE      | R     | ref  | IDX3,IDX2     | IDX3 | 5       | T.PERSONID      |  3550 | Using where |

结果表有大约 3 亿行。 Define_Times 有 75,000 个。

需要 AGES 时间。

我看到第一种类型是 ALL,这很糟糕。但如果情况如此糟糕,为什么不使用它认为可能的 PERSONID(称为 PEOPLE)上的索引?我能做些什么来改善这一点?

我也无法使用日期索引来查看它 - R.DATE 上有一个。 (它是 IDX2 索引中 5 序列中的第一个。)

抱歉有任何错别字 - 我的键盘坏了,提前致谢。

【问题讨论】:

  • 您是否尝试过使用join 来选择这两个表?
  • 确实使用了连接,只是语义不同。
  • Define_Times 中的每一行在 Results 中是否都有对应的人?
  • 你能重组数据库以规范Define_Times吗?
  • 第一行 R.DATE 是否正确,还是 R.ETRDATE 的错字?

标签: mysql sql performance indexing between


【解决方案1】:

问题在于您将所有条件都进行了 OR 运算。

如果可能,重组您的数据库,使 Define_Time 只有四列:

 CREATE TABLE Define_Times (
    PersonID INTEGER,
    PeriodType SomeType,
    StartDate DATE,
    EndDate DATE )

然后,每个人都会获得 7 条记录(或更多,如果您在示例中没有搜索更多期间),其中 PeriodType 指示日期指定的期间(您可以使用 PM、NM、SM 等文本值, 1Y, 2Y, 3Y, 4Y 或者您可以使用指向另一个表中的描述的整数值。

然后,像这样重写您的查询:

SELECT * FROM RESULTS AS R, Define_Times AS T 
WHERE R.PERSONID = T.PERSONID 
   AND R.DATE BETWEEN T.StartDate AND T.EndDate
   AND T.PeriodType IN (PM,NM,SM,1Y,2Y,3Y,4Y)

这个查询至少是可优化的

此查询将为每个人生成一个每个匹配期间的记录。如果您的期间不重叠,那很好(只会有一个匹配的记录)。如果您的期间确实重叠并且您只希望每个结果集中有一条记录,则您需要通过聚合结果集中的记录来使用 DISTINCT 或 GROUP BY 做一些额外的工作。

另外,请注意,如果您没有在 Define_Times 表中有任何额外的句点,那么您可以删除 WHERE 子句的 AND T.PeriodType 部分。

【讨论】:

  • 酷 - 我会尝试并告诉你。谢谢!我需要一个特定的索引来利用这个新查询吗?
  • Results 表上以 (PersonID, Date) 开头的索引是优化的最佳方式。我不是 MySQL 专家,所以不知道它将如何优化此查询,但至少它有可能进行优化。
【解决方案2】:

作为比较,你能运行这个等效的查询吗

SELECT * FROM Define_Times AS T 
INNER JOIN RESULTS AS R on
(R.PERSONID = T.PERSONID and 
  ( 
  (R.DATE BETWEEN T.Previous_Month_Start AND T.Previous_Month_End) OR 
  (R.DATE BETWEEN T.Next_Month_Start AND T.Next_Month_End) OR 
  (R.DATE BETWEEN T.Six_Month_Start AND T.Six_Month_End) OR 
  (R.DATE BETWEEN T.One_Year_Start AND T.One_Year_End) OR 
  (R.DATE BETWEEN T.Two_Year_Start AND T.Two_Year_End) OR 
  (R.DATE BETWEEN T.Three_Year_Start AND T.Three_Year_End) OR 
  (R.DATE BETWEEN T.Four_Year_Start AND T.Four_Year_End) 
  ) 
)

我发现优化器有时在这种形式下工作得更好。

此外,由于您对表达式之间的所有日期进行 OR,因此几乎无法使用日期索引,因为任何日期范围都可以满足 where 子句。

编辑——添加

如果你不想运行查询,至少尝试比较估计的执行计划

【讨论】:

  • 所以基本上把WHERE改成INNER JOIN? (只是为了确认我没有错过任何东西。)我可以尝试一下 - 但如果它没有改善,我们不会知道一周左右! ;-)
  • 刚刚看到您的编辑 - 按执行计划,您的意思是解释吗?如果是这样 - 完全一样。
  • 估计计划意味着查询并未实际运行,但它提供了实际执行计划的“最佳猜测”。您可能希望这样做来查询您不希望针对数据库运行的长时间运行的替代方案。
  • 抱歉,我将其读作 MSSQL,而不是 MySql。如果这是一个选项,请不要运行足够多的 MySql 来回忆 - 在工作中,没有 MySql 可用于仔细检查。
猜你喜欢
  • 2013-09-22
  • 2021-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-04
  • 2013-05-28
相关资源
最近更新 更多