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 关键字告诉我没有使用索引。
我想知道我这里做错了什么?
你在前3个where条件中使用了函数和case表达式。不能使用简单的字段索引来加速此类查找。
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 < 1
或 2 > 1
的处理方式相同。它不会根据行中的数据改变其真实性,因此只需要在开始时评估一次。
对另一个 CASE
重复。您几乎不应该将列作为参数传递给 WHERE
子句中的函数,因为优化器无法 "look backwards through" 函数并形成智能查询计划。
其他答案已经解释了您的查询速度慢的原因。我会解释你应该做什么。
将代码写入 "construct" 查询。如果用户说 "all countries",它要么省略 country_code
的测试,要么添加 AND country_code = "US"
。没有@variables,没有CASE,等等
那么,除了少数情况外,一个 5 列索引将不起作用。相反,了解用户的需求,然后构建一些 2 列索引来涵盖常见情况。
到目前为止,以下是我的场景:
参数 由用户控制:(这些参数由仪表板控制,但出于测试目的,我创建了 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 关键字告诉我没有使用索引。
我想知道我这里做错了什么?
你在前3个where条件中使用了函数和case表达式。不能使用简单的字段索引来加速此类查找。
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 < 1
或 2 > 1
的处理方式相同。它不会根据行中的数据改变其真实性,因此只需要在开始时评估一次。
对另一个 CASE
重复。您几乎不应该将列作为参数传递给 WHERE
子句中的函数,因为优化器无法 "look backwards through" 函数并形成智能查询计划。
其他答案已经解释了您的查询速度慢的原因。我会解释你应该做什么。
将代码写入 "construct" 查询。如果用户说 "all countries",它要么省略 country_code
的测试,要么添加 AND country_code = "US"
。没有@variables,没有CASE,等等
那么,除了少数情况外,一个 5 列索引将不起作用。相反,了解用户的需求,然后构建一些 2 列索引来涵盖常见情况。