IN子句中的MySQL多列

Posted

技术标签:

【中文标题】IN子句中的MySQL多列【英文标题】:MySQL multiple columns in IN clause 【发布时间】:2017-11-26 03:13:27 【问题描述】:

我有一个数据库,其中有四列对应于地理坐标 x,y 的开始和结束位置。列是:

x0 y0 x1 y1

我有这四列的索引,序列为 x0, y0, x1, y1。

我有一个包含大约一百个地理对组合的列表。我将如何有效地查询这些数据?

我想按照this SO answer 的建议做这样的事情,但它只适用于 Oracle 数据库,不适用于 mysql

SELECT * FROM my_table WHERE (x0, y0, x1, y1) IN ((4, 3, 5, 6), ... ,(9, 3, 2, 1));

我在想也许可以用索引做点什么?什么是最好的方法(即:最快的查询)?感谢您的帮助!

注意事项:

我无法更改数据库的架构 我有大约 100'000'000 行

编辑: 原样的代码实际上是可以工作的,但是它非常慢并且没有利用索引(因为我们有一个旧版本的 MySQL v5.6.27)。

【问题讨论】:

这在 MySQL 中应该可以正常工作,你试过了吗?我看到的第一条评论说你链接的问题是 5 年前的。 您知道,您可以在 MySQL 中执行此操作。查看我的测试:sqlfiddle.com/#!9/7b5c1/1 【参考方案1】:

为了有效利用索引,可以重写IN谓词

(x0, y0, x1, y1) IN ((4, 3, 5, 6),(9, 3, 2, 1))

像这样:

(  ( x0 = 4 AND y0 = 3 AND x1 = 5 AND y1 = 6 ) 
OR ( x0 = 9 AND y0 = 3 AND x1 = 2 AND y1 = 1 )
)

【讨论】:

您的解决方案要快得多。使用我们的 MySQL 版本,单个查询(100'000'000 行,列表中的 10 个元素)使用您的解决方案需要 3.14 秒,而使用 IN 语法需要 1427 秒。 @GordonLinoff 建议的查询模式可能更快,将单独的 SELECT 语句的结果与 UNION ALL 集合运算符连接起来。对此的解释可能会显示带有“ref”和“const”的查询,而不是“range”。该模式肯定会使用索引。不能保证它会更快,但值得测试。 仅通过一次性测试,GordonLinoff 的解决方案比您的解决方案稍慢(3.96 对 3.14 秒)。到目前为止,这不是一个严格的测试,但至少,这两个选项都使用了索引。 不是in 相当于有索引的orin 对于mysql 的情况甚至更好,因为在mysql 中有对in 的优化?? @lily:使用 EXPLAIN 查看执行计划的差异,是否正在使用索引。是的,这两种形式在语义上是等效的 ( foo = 1 or foo = 2 )foo in (1,2)。在这个特定示例中,MySQL 5.6 优化器使用元组比较(a,b,c) IN ((1,1,1),(2,2,2)) 对表单使用效率较低的执行计划。从理论上讲,是的,优化器应该能够将其扩展为this answer中所示的形式,然后提出使用索引的执行计划。注意:在 MySQL 5.6 中观察到的行为,Bill Karwin 指出这已在 5.7 中修复【参考方案2】:

我不明白你的意思。以下查询是有效的 MySQL 语法:

SELECT *
FROM my_table
WHERE (x0, y0, x1, y1) IN ((4, 3, 5, 6), ... ,(9, 3, 2, 1));

我希望 MySQL 使用您描述的复合索引。但是,如果没有,您可以这样做:

SELECT *
FROM my_table
WHERE x0 = 4 AND y0 = 3 AND x1 = 5 AND y1 = 6
UNION ALL
. . .
SELECT *
FROM my_table
WHERE x0 = 9 AND y0 = 3 AND x1 = 2 AND y1 = 1

WHERE 子句中的相等比较利用索引。

【讨论】:

确实,它是一种有效的语法,但执行起来需要很长时间。看来我们使用的 MySQL 版本没有利用索引。【参考方案3】:

MySQL 允许像您展示的那样进行行构造函数比较,但优化器直到 MySQL 5.7 才知道如何使用索引来提高性能。

https://dev.mysql.com/doc/refman/5.7/en/row-constructor-optimization.html

【讨论】:

我明白了,我们有一个旧版本的 MySQL,它让一切变得非常缓慢。该代码实际上可以工作,但速度很慢。 @spencer 回答已修复。【参考方案4】:

您可以将concatenate 四个值放入一个字符串中并像这样检查它们:

SELECT * 
FROM my_table 
WHERE CONCAT_WS(',', x0, y0, x1, y1) IN ('4,3,5,6', ..., '9,3,2,1');

【讨论】:

MySQL 需要为表中的 每一 行评估 CONCAT_WS 函数。这可能会使用索引,但它会完全扫描索引,所有 100,000,000 行。【参考方案5】:

您正在做的方式是在我机器上的 mysql 版本中给出正确的结果。我正在使用v5.5.55。也许您正在使用旧的。请检查一下。

如果您仍想在自己的版本中解决此问题,或者上述解决方案不起作用,请阅读下一个解决方案。

我仍然不清楚这里所有列的数据类型和范围。所以我假设数据类型是整数,范围在 0 到 9 之间。如果是这种情况,您可以按照下面给出的方式轻松执行此操作。

select * from s1 where x0+10*x1+100*y1+1000*y2 in (4356,..., 9321);

【讨论】:

使用这种方法,MySQL 将无法对索引(x0,x1,y1,y2) 使用范围扫描操作。 MySQL 将在 where 子句中为表中 100,000,000 行中的每一行评估该表达式。

以上是关于IN子句中的MySQL多列的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Eloquent 多列 IN 子句

使用 sqlalchemy 查询使用多列 where in 子句

子查询(嵌套子查询)

SQL中的Group By的查询过程多列分组的查询过程是怎样的?

MySQL必知必会 第7-9章

具有多列的Order by子句的语法不正确