处理 MySQL 语句很慢,如何让它表现得更好?
Very slow processing MySQL statement, how to make it perform better?
我有以下 MySQL 语句,当要处理的产品数量变大时,它的执行速度非常慢。当前语句如下所示:
SELECT DISTINCT products.*, colors.value FROM products
LEFT OUTER JOIN product_fields colors ON colors.product_id = products.id AND colors.name = 'color'
GROUP BY products.id
ORDER BY
CASE WHEN merchant IN ('Merchant 1') THEN -1 ELSE RAND(1617116433) END,
CASE WHEN category IN ('Category 1', 'Category 2') THEN -1 ELSE RAND(1617116433) END
LIMIT 0, 30
说清楚:有一个product
和一个product_fields
table。对于每个 product
,product_fields
table 中有零个或多个记录。 product_fields
table 中的一个字段是一个字段:name
,其值为 color
,这是我在结果中唯一需要的字段值。
创建ORDER BY
语句是因为我想先展示某个商户的所有产品,然后再随机展示其他商户的其余产品。
接下来,来自特定类别(类别 1 和类别 2)的所有产品必须首先显示在结果中。之后随机显示其他类别的其余产品。
对于随机数,我已经使用固定数字来随机化所有内容。不必每次都不同。我只想在显示商家 1 和类别 1、类别 2 产品之后随机列出其余产品。
目前语句 工作正常,虽然在大数据集上很慢,但我认为 ORDER BY
让它变慢了,但我不知道如何解决这个问题。希望有人能指出我正确的方向。
编辑>>
对于上面的语句,我现在有 运行 EXPLAIN
,结果是:
+----+-------------+--------------+------+---------------+-------------+---------+----------------------------------------------+-------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+------+---------------+-------------+---------+----------------------------------------------+-------+---------------------------------+
| 1 | SIMPLE | products | ALL | NULL | NULL | NULL | NULL | 10402 | Using temporary; Using filesort |
| 1 | SIMPLE | colors | ref | product_key | product_key | 767 | dbname.products.id | 1 | |
+----+-------------+--------------+------+---------------+-------------+---------+----------------------------------------------+-------+---------------------------------+
2 rows in set (0.02 sec)
编辑 2>> 进一步澄清:
使用 RAND 只是因为我希望在显示特定 'merchant' 和 'category' 的所有产品后随机显示每个产品。但是下次用户访问该站点时,顺序可能是相同的,我不在乎。我只希望所有 other 产品不按特定商家或类别分类。这就是 RAND
的意义所在。
感谢@spencer7593 的出色回答,我认为这一切都归结为使用 Using filesort
选项对整个结果集进行排序(参见上面的 EXPLAIN)。那么现在我该如何解决这个问题,并按照上一段中的说明保留一种随机化结果的方法。
尝试在 select
前使用 explain
关键字 运行 您的查询。它会告诉您使用了什么索引(如果有的话)。
索引是在 MySQL 中取得良好性能的关键。
在这种情况下,您似乎需要颜色索引 (product_id, name).
即便如此,这将始终 运行 对产品进行全面 table 扫描。您应该尝试在查询中添加限制性 where
语句。
表达式 RAND(1617116433)
每次计算时都会 return 相同的常量值。每一行都将 return 编辑相同的确切值。也就是说,用大于 -1 的文字数值替换该表达式会产生等效的结果。
如果您真的想为每一行分配伪随机值,则需要从函数中删除种子值。您需要使用 RAND()
为每一行获取不同的值。
作为演示,比较以下结果:
SELECT RAND(1617116433), RAND(1617116433), RAND(1617116433) ;
SELECT RAND(1617116433), RAND(), RAND() ;
(请注意,每次运行时,第二个语句将 return 相同的 值序列 。RAND()
是一个 伪随机 数字生成器,不是真正随机的。)
无论哪种方式,在您的查询中,都会针对 每一 行评估该函数,然后对整个结果集进行排序。 (EXPLAIN 将显示 "Using filesort")。
最后应用 LIMIT
子句,对整个结果集进行排序,然后从排序的集合中,前 30 行被 returned。 (这样可以避免 return 处理大量行,但 MySQL 服务器仍会准备整组数据。)
这可能是您查询 "slow" 的最大原因。
DISTINCT
的用法有点奇怪,您已经有了一个 GROUP BY
子句来确保产品中的 id
是唯一的。规范模式是在 GROUP BY
子句中包含 colors.value
。
此外,您是否需要 return products
table 中的每个 列?我们更希望在 SELECT 列表中看到 return 列的列表,而不是依赖于 *
.
product_fields
table 上的适当索引可能会提高连接操作的性能。
... ON `product_fields` (`product_id`, `name`, `value`)
(我们希望 EXPLAIN 输出应该显示 "Using index" table。)
但这并不能解决需要访问 products
table 中的每一行并为每个不同值计算 RAND()
函数(两次)的需要products.id
.
(products
table 上的覆盖索引也可能带来一点好处,但我希望它可以忽略不计。)
我会这样写查询,但这并不能解决 "big rock" 性能问题:
SELECT p.id
, p.???
, p.???
, c.value
FROM (SELECT RAND(1617116433)) i
CROSS
JOIN products p
LEFT
JOIN product_fields c
ON c.product_id = p.id
AND c.name = 'color'
GROUP BY p.id, c.value
ORDER
BY CASE WHEN p.merchant IN ('Merchant 1') THEN -1 ELSE RAND() END
, CASE WHEN p.category IN ('Category 1', 'Category 2') THEN -1 ELSE RAND() END
LIMIT 0, 30
我有以下 MySQL 语句,当要处理的产品数量变大时,它的执行速度非常慢。当前语句如下所示:
SELECT DISTINCT products.*, colors.value FROM products
LEFT OUTER JOIN product_fields colors ON colors.product_id = products.id AND colors.name = 'color'
GROUP BY products.id
ORDER BY
CASE WHEN merchant IN ('Merchant 1') THEN -1 ELSE RAND(1617116433) END,
CASE WHEN category IN ('Category 1', 'Category 2') THEN -1 ELSE RAND(1617116433) END
LIMIT 0, 30
说清楚:有一个product
和一个product_fields
table。对于每个 product
,product_fields
table 中有零个或多个记录。 product_fields
table 中的一个字段是一个字段:name
,其值为 color
,这是我在结果中唯一需要的字段值。
创建ORDER BY
语句是因为我想先展示某个商户的所有产品,然后再随机展示其他商户的其余产品。
接下来,来自特定类别(类别 1 和类别 2)的所有产品必须首先显示在结果中。之后随机显示其他类别的其余产品。
对于随机数,我已经使用固定数字来随机化所有内容。不必每次都不同。我只想在显示商家 1 和类别 1、类别 2 产品之后随机列出其余产品。
目前语句 工作正常,虽然在大数据集上很慢,但我认为 ORDER BY
让它变慢了,但我不知道如何解决这个问题。希望有人能指出我正确的方向。
编辑>>
对于上面的语句,我现在有 运行 EXPLAIN
,结果是:
+----+-------------+--------------+------+---------------+-------------+---------+----------------------------------------------+-------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+------+---------------+-------------+---------+----------------------------------------------+-------+---------------------------------+
| 1 | SIMPLE | products | ALL | NULL | NULL | NULL | NULL | 10402 | Using temporary; Using filesort |
| 1 | SIMPLE | colors | ref | product_key | product_key | 767 | dbname.products.id | 1 | |
+----+-------------+--------------+------+---------------+-------------+---------+----------------------------------------------+-------+---------------------------------+
2 rows in set (0.02 sec)
编辑 2>> 进一步澄清:
使用 RAND 只是因为我希望在显示特定 'merchant' 和 'category' 的所有产品后随机显示每个产品。但是下次用户访问该站点时,顺序可能是相同的,我不在乎。我只希望所有 other 产品不按特定商家或类别分类。这就是 RAND
的意义所在。
感谢@spencer7593 的出色回答,我认为这一切都归结为使用 Using filesort
选项对整个结果集进行排序(参见上面的 EXPLAIN)。那么现在我该如何解决这个问题,并按照上一段中的说明保留一种随机化结果的方法。
尝试在 select
前使用 explain
关键字 运行 您的查询。它会告诉您使用了什么索引(如果有的话)。
索引是在 MySQL 中取得良好性能的关键。 在这种情况下,您似乎需要颜色索引 (product_id, name).
即便如此,这将始终 运行 对产品进行全面 table 扫描。您应该尝试在查询中添加限制性 where
语句。
表达式 RAND(1617116433)
每次计算时都会 return 相同的常量值。每一行都将 return 编辑相同的确切值。也就是说,用大于 -1 的文字数值替换该表达式会产生等效的结果。
如果您真的想为每一行分配伪随机值,则需要从函数中删除种子值。您需要使用 RAND()
为每一行获取不同的值。
作为演示,比较以下结果:
SELECT RAND(1617116433), RAND(1617116433), RAND(1617116433) ;
SELECT RAND(1617116433), RAND(), RAND() ;
(请注意,每次运行时,第二个语句将 return 相同的 值序列 。RAND()
是一个 伪随机 数字生成器,不是真正随机的。)
无论哪种方式,在您的查询中,都会针对 每一 行评估该函数,然后对整个结果集进行排序。 (EXPLAIN 将显示 "Using filesort")。
最后应用 LIMIT
子句,对整个结果集进行排序,然后从排序的集合中,前 30 行被 returned。 (这样可以避免 return 处理大量行,但 MySQL 服务器仍会准备整组数据。)
这可能是您查询 "slow" 的最大原因。
DISTINCT
的用法有点奇怪,您已经有了一个 GROUP BY
子句来确保产品中的 id
是唯一的。规范模式是在 GROUP BY
子句中包含 colors.value
。
此外,您是否需要 return products
table 中的每个 列?我们更希望在 SELECT 列表中看到 return 列的列表,而不是依赖于 *
.
product_fields
table 上的适当索引可能会提高连接操作的性能。
... ON `product_fields` (`product_id`, `name`, `value`)
(我们希望 EXPLAIN 输出应该显示 "Using index" table。)
但这并不能解决需要访问 products
table 中的每一行并为每个不同值计算 RAND()
函数(两次)的需要products.id
.
(products
table 上的覆盖索引也可能带来一点好处,但我希望它可以忽略不计。)
我会这样写查询,但这并不能解决 "big rock" 性能问题:
SELECT p.id
, p.???
, p.???
, c.value
FROM (SELECT RAND(1617116433)) i
CROSS
JOIN products p
LEFT
JOIN product_fields c
ON c.product_id = p.id
AND c.name = 'color'
GROUP BY p.id, c.value
ORDER
BY CASE WHEN p.merchant IN ('Merchant 1') THEN -1 ELSE RAND() END
, CASE WHEN p.category IN ('Category 1', 'Category 2') THEN -1 ELSE RAND() END
LIMIT 0, 30