在 MySQL SELECT 语句中使用 OR 子句通常是个坏主意吗?
Is using OR clause in MySQL SELECT statement a generally bad idea?
前几天我遇到了查询问题。大型数据集大约需要 10 秒。查询看起来像这样:
SELECT a.* from Document as a
LEFT JOIN Waybill as b on a.waybill = b.id
WHERE a.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
OR b.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
这 运行 速度很慢。但是,然后我将其更改为:
SELECT a.* from Document as a
LEFT JOIN Waybill as b on a.waybill = b.id
WHERE a.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
UNION ALL
SELECT a.* from Document as a
LEFT JOIN Waybill as b on a.waybill = b.id
WHERE b.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
这大约用了 0.01 秒,虽然这两个查询产生的结果基本相同!我查找了官方 MySQL 文档,发现了一个有趣的评论 here:
Indices lose their speed advantage when using them in OR-situations
(4.1.10):
SELECT * FROM a WHERE index1 = 'foo' UNION SELECT * FROM a WHERE
index2 = 'baar';
is much faster than
SELECT * FROM a WHERE index1 = 'foo' OR index2 = 'bar';
所以,我的问题有 3 个部分:
- 在生产系统(即数据集非常大)的 select 查询中使用 OR 子句真的不好吗?
- 这个 OR 查询可以通过索引以某种方式进行调整吗?现在,我用于过滤的查询中的两列实际上都已编入索引。我可以创建一些棘手的复合索引以使 OR 与 UNION ALL 一样快吗?
- 这是特定于供应商的问题,还是我在使用 Oracle 或 Postgresql 时会遇到同样的问题?
OR
本身 并不坏。与 SQL 中的几乎所有其他构造一样,它可能是也可能不是一个好主意。
您发现优化器存在问题。 . .和许多数据库通用的一种。当您的 OR
条件来自不同的表时,优化器很难利用索引。
您改进的解决方案之所以有效,是因为每个子查询都可以利用索引。
您可能会发现以下版本比第一个版本好但比第二个版本差:
SELECT d.*
FROM Document d
WHERE d.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d' OR
(EXISTS (SELECT 1
FROM Waybill b
WHERE d.waybill = b.id AND
b.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
)
);
这是一个与优化器相关的问题,因此 Engine/Version/Table 统计数据等可能会有所不同。
实际上你不能说全 table 扫描总是比两次索引扫描然后整理结果(即 union
运算符)差。这取决于指数的选择性。不过你还是要非常小心OR
,这是真的。
前几天我遇到了查询问题。大型数据集大约需要 10 秒。查询看起来像这样:
SELECT a.* from Document as a
LEFT JOIN Waybill as b on a.waybill = b.id
WHERE a.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
OR b.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
这 运行 速度很慢。但是,然后我将其更改为:
SELECT a.* from Document as a
LEFT JOIN Waybill as b on a.waybill = b.id
WHERE a.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
UNION ALL
SELECT a.* from Document as a
LEFT JOIN Waybill as b on a.waybill = b.id
WHERE b.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
这大约用了 0.01 秒,虽然这两个查询产生的结果基本相同!我查找了官方 MySQL 文档,发现了一个有趣的评论 here:
Indices lose their speed advantage when using them in OR-situations (4.1.10):
SELECT * FROM a WHERE index1 = 'foo' UNION SELECT * FROM a WHERE index2 = 'baar';
is much faster than
SELECT * FROM a WHERE index1 = 'foo' OR index2 = 'bar';
所以,我的问题有 3 个部分:
- 在生产系统(即数据集非常大)的 select 查询中使用 OR 子句真的不好吗?
- 这个 OR 查询可以通过索引以某种方式进行调整吗?现在,我用于过滤的查询中的两列实际上都已编入索引。我可以创建一些棘手的复合索引以使 OR 与 UNION ALL 一样快吗?
- 这是特定于供应商的问题,还是我在使用 Oracle 或 Postgresql 时会遇到同样的问题?
OR
本身 并不坏。与 SQL 中的几乎所有其他构造一样,它可能是也可能不是一个好主意。
您发现优化器存在问题。 . .和许多数据库通用的一种。当您的 OR
条件来自不同的表时,优化器很难利用索引。
您改进的解决方案之所以有效,是因为每个子查询都可以利用索引。
您可能会发现以下版本比第一个版本好但比第二个版本差:
SELECT d.*
FROM Document d
WHERE d.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d' OR
(EXISTS (SELECT 1
FROM Waybill b
WHERE d.waybill = b.id AND
b.enterpriseGuid = '763a3ac3-a3c7-4379-9735-2a4a96e87e5d'
)
);
这是一个与优化器相关的问题,因此 Engine/Version/Table 统计数据等可能会有所不同。
实际上你不能说全 table 扫描总是比两次索引扫描然后整理结果(即 union
运算符)差。这取决于指数的选择性。不过你还是要非常小心OR
,这是真的。