2017-08-13 44 views
0

在我们的应用程序中,我们尝试为给定的一组参数找到最佳匹配。我们已将这些行分成不同的质量组,这些质量组与总参数集的一部分相匹配。为了匹配这些不同的组,我们有多个select查询,如果没有找到结果,我们随后会查询这些查询,现在我们决定使用UNION ALL和LIMIT 1一起加入它们。来自多个查询的第一个现有行

SET @size = 4, @price = 18, @category = 'NEW', @weight = 20, @origin = 'France'; 
(SELECT * FROM product_catalog WHERE quality = 'A1' AND size = @size AND price = @price AND category = @category AND weight = @weight AND origin = @origin LIMIT 1) 
UNION ALL 
(SELECT * FROM product_catalog WHERE quality = 'A2' AND size = @size AND price = @price AND category = @category AND origin = @origin LIMIT 1) 
UNION ALL 
(SELECT * FROM product_catalog WHERE quality = 'A3' AND price = @price AND category = @category AND weight = @weight AND origin = @origin LIMIT 1) 
UNION ALL 
(SELECT * FROM product_catalog WHERE quality = 'A4' AND price = @price AND category = @category AND origin = @origin LIMIT 1) 
UNION ALL 
... SOME MORE SELECTS ... 
LIMIT 1 

现在查询确实按预期运行,但它执行方式比我们当前的解决方案更差。我认为这与MySQL可能首先执行UNION语句然后意识到它只需要返回第一个语句有关。

你有什么建议可以帮助加快查询速度吗?你认为有可能将查询重写到一个存储过程,该存储过程将检查每个查询的结果,并在找到结果时立即返回该结果。这会加快查询速度吗?

+0

MySQL将评估所有部件。但有些想法:a)如果没有总体顺序,你的最后一个限制可以带任意联合的* ANY *未指定的随机行,所以它不等同于尝试第一个查询,并且只有在没有找到任何时才继续下一个查询。 b)如果为所有组合添加索引,则此查询应该在<0.2s内运行。不知道它是否已经这样做了,并且您只需要它以每分钟1000次的速度运行得更快,但除此之外,您应该首先检查索引。c)'或','按质量排序',只有一个限制可能会比'union'更快,这取决于索引。 – Solarflare

+0

谢谢你的回答。我担心你的观点a),但我不确定是否属于这种情况。此外,查询确实运行速度在0.2秒以下,事实上甚至更快,但正如您所猜测的,我总共需要运行该查询数百万次。我做了一些重写,现在我有一个解决方案,它使用一些'ORDER BY'和'(size = @size或size IS NULL)'魔法来结合查询。 –

回答

1

首先,一些问题...

  • UNION总是建立一个tmp目录表。 (如果可行的话,在MySQL 5.7.3和MariaDB 10.1中,这种低效率被消除了)。
  • 查询结果缺少ORDER BY - 此可能导致得到错误的答案。
  • 需要第二个tmp表来完成外部ORDER BY

现在一些建议的改进。在不了解数据的情况下,我不得不说这些数据可能会运行得更快,也可能不会运行得更快。

避免*

而不是做SELECT *的,只是SELECT id然后JOIN回到谈判桌,以获得其余列:

SELECT b.* 
    FROM (SELECT id ... UNION ALL ... LIMIT 1) AS a 
    JOIN product_quality AS b USING(id); 

多个索引:

INDEX(quality, size, price) 
INDEX(quality, price, category) 
... 

做一个表扫描;不需要索引。 (这需要quality值是有序的。):

SELECT * FROM ... 
    WHERE (quality = 'A1' AND size = @size AND price = @price ...) 
     OR (quality = 'A3' AND price = @price AND category = @category ...) 
    ORDER BY quality 
    LIMIT 1 

(通常情况下,我建议由UNION性能更换OR,但我认为您的使用案例工作的其他方式。)

CASE

您的前两个选择可以合并:

SELECT MIN(IF(weight = @weight, 'A1', 'A2')) AS quality 
    WHERE size = @size 
     AND price = @price 
     AND category = @category 
     AND origin = @origin) 
+0

UNION ALL也进行了优化,以避免在MySQL 5.7.3中尽可能使用临时表。 https://bugs.mysql.com/bug.php?id=50674 –

+0

感谢您提供详尽的答案。我确实使用了特定的列而不是使用*,但为了简单起见,我省略了列,也许我应该提到这一点。您的OR查询与我正在查找的内容很接近,但我认为数据库在返回最高质量的对象之前仍然会检查对应情况,对吗?如果A1质量匹配,是否有办法立即返回而不考虑其他情况? –

相关问题