MySQL 索引不工作(用例特定场景)

MySQL Index not working ( Use Case specific scenario)

到目前为止,以下是我的场景:

参数 由用户控制:(这些参数由仪表板控制,但出于测试目的,我创建了 sql 参数以更改它们的值)

    SET @device_param := "all devices";
    SET @date_param_start_bar_chart := '2016-09-01';
    SET @date_param_end_bar_chart := '2016-09-19';
    SET @country_param := "US";
    SET @channel_param := "all channels";

在后端运行的查询

SELECT 
  country_code,
  channel_report_tag,
  SUM(count_more_then_30_min_play) AS '>30 minutes',
  SUM(count_15_30_min_play) AS '15-30 Minutes',
  SUM(count_0_15_min_play) AS '0-15 Minutes' 
FROM
  channel_play_times_cleaned 
WHERE IFNULL(country_code, '') = 
  CASE
    WHEN @country_param = "all countries" 
    THEN IFNULL(country_code, '') 
    ELSE @country_param 
  END 
  AND IFNULL(channel_report_tag, '') = 
  CASE
    WHEN @channel_param = "all channels" 
    THEN IFNULL(channel_report_tag, '') 
    ELSE @channel_param 
  END 
  AND iFnull(device_report_tag, '') = 
  CASE
    WHEN @device_param = "all devices" 
    THEN iFnull(device_report_tag, '') 
    ELSE @device_param 
  END 
  AND playing_date BETWEEN @date_param_start_bar_chart 
  AND @date_param_end_bar_chart 
GROUP BY channel_report_tag 
ORDER BY SUM(count_more_then_30_min_play) DESC 
limit 10 ;

我申请的index

CREATE INDEX my_index 
ON channel_play_times_cleaned (
  country_code,
  channel_report_tag,
  device_report_tag,
  playing_date,
  channel_report_tag
)

我已经按照这个 link : My SQL Index Cook-Book Guide 创建了我的索引。

但是执行上述查询时 EXPLAIN 关键字告诉我没有使用索引。

我想知道我这里做错了什么?

  1. 你在前3个where条件中使用了函数和case表达式。不能使用简单的字段索引来加速此类查找。

  2. MySQL 可能会为 playing_date 条件使用索引,但该字段不是引用索引中的最左边,因此引用索引不适合该字段要么。

如果我是你,我会从 where 条件中删除逻辑并将其移至应用程序层,方法是构建这样一个 sql 语句,该语句解决了 case 条件并仅发出必要的 sql.

您在 WHERE 子句中的 CASE 表达式强制执行完整 table 扫描。很明显,他们必须去……但是怎么办?

您必须像优化器一样思考并记住它的工作是尽可能避免工作。

考虑这个查询:

SELECT * FROM users
 WHERE first_name LIKE '%a%';

必须读取每一行以找到包含字母 'a' 的所有 first_name 值。很慢。

现在,这个:

SELECT * FROM users
 WHERE first_name LIKE '%a%'
   AND 2 < 1;

对于每一行,您要求服务器再次检查 first_name 并仅包含 2 小于 1 的行。

是慢还是快?

它非常快,因为优化器检测到 Impossible WHERE。扫描行没有意义,因为 2 < 1 总是错误的。

现在,使用此逻辑告诉优化器您真正想要什么:

不是这个:

  WHERE IFNULL(country_code, '') = 
   CASE
     WHEN @country_param = "all countries" 
     THEN IFNULL(country_code, '') 
     ELSE @country_param 
   END 
  AND

但是这个:

 WHERE
  (
    (
      @country_param = "all countries" 
    )
    OR
    (
      @country_param != "all countries"
      AND
      country_code = @country_param
    )
  )
  AND ...

区别应该很明显。如果 @country_param = "all countries" 则不需要第二个测试,否则,只需要具有匹配国家/地区的行并且 WHERE 子句的这一部分对于所有其他子句根据定义为 false行,允许使用 country_param 上的索引。

这些 OR'ed 表达式中的一个或另一个 总是 假,并且那个将被优化掉,尽早 - 永远不会对每一行进行评估。表达式 @country_param != "all countries" 的处理方式应与表达式 2 < 12 > 1 的处理方式相同。它不会根据行中的数据改变其真实性,因此只需要在开始时评估一次。

对另一个 CASE 重复。您几乎不应该将列作为参数传递给 WHERE 子句中的函数,因为优化器无法 "look backwards through" 函数并形成智能查询计划。

其他答案已经解释了您的查询速度慢的原因。我会解释你应该做什么。

将代码写入 "construct" 查询。如果用户说 "all countries",它要么省略 country_code 的测试,要么添加 AND country_code = "US"。没有@variables,没有CASE,等等

那么,除了少数情况外,一个 5 列索引将不起作用。相反,了解用户的需求,然后构建一些 2 列索引来涵盖常见情况。