【发布时间】:2012-03-29 19:30:52
【问题描述】:
我使用 Microsoft SQL Server 2008 (SP1, x64)。我有两个查询执行相同的操作,或者我认为是这样,但它们的查询计划和性能完全不同。
查询 1:
SELECT c_pk
FROM table_c
WHERE c_b_id IN (SELECT b_id FROM table_b WHERE b_z = 1)
OR c_a_id IN (SELECT a_id FROM table_a WHERE a_z = 1)
查询 2:
SELECT c_pk
FROM table_c
LEFT JOIN (SELECT b_id FROM table_b WHERE b_z = 1) AS b ON c_b_id = b_id
LEFT JOIN (SELECT a_id FROM table_a WHERE a_z = 1) AS a ON c_a_id = a_id
WHERE b_id IS NOT NULL
OR a_id IS NOT NULL
查询 1 和我预期的一样快,而查询 2 非常慢。 query plans 看起来完全不同。
我希望查询 2 与查询 1 一样快。我有使用查询 2 的软件,但我无法将其更改为查询 1。我可以更改数据库。
一些问题:
- 为什么查询计划不同?
- 我能否以某种方式“教”SQL Server 查询 2 等于查询 1?
所有表在所有列上都有(聚集的)主键和正确的索引:
CREATE TABLE table_a (
a_pk int NOT NULL PRIMARY KEY,
a_id int NOT NULL UNIQUE,
a_z int
)
GO
CREATE INDEX IX_table_a_z ON table_a (a_z)
GO
CREATE TABLE table_b (
b_pk int NOT NULL PRIMARY KEY,
b_id int NOT NULL UNIQUE,
b_z int
)
GO
CREATE INDEX IX_table_b_z ON table_b (b_z)
GO
CREATE TABLE table_c (
c_pk int NOT NULL PRIMARY KEY,
c_a_id int,
c_b_id int
)
GO
CREATE INDEX IX_table_c_a_id ON table_c (c_a_id)
GO
CREATE INDEX IX_table_c_b_id ON table_c (c_b_id)
GO
表格在最初填充后不会被修改。我是唯一一个询问他们的人。它们包含数百万条记录(table_a:5M,table_b:4M,table_c:12M),但仅使用 1% 会产生类似的结果。
编辑:我尝试为c_a_id 和c_b_id 添加外键,但这只会使查询1 变慢...
我希望有人可以看看query plans并解释其中的区别。
【问题讨论】:
-
这样做的动机是什么?
IN/EXISTS在 SQL Server 中通常比OUTER JOIN ... NULL更有效,而且第一个查询对我来说似乎更清晰,那么为什么不直接使用第一个呢? -
@Martin "我有使用查询 2 的软件,我无法更改"
-
一般来说,查询是不一样的,因为 Join 可以带来重复的行,而 semi join 不会。虽然尚未检查您是否有任何限制阻止此操作。
-
@Martin
a_id和b_id是唯一的,因此连接不会重复行。 -
我 99% 确信在这种情况下它们确实具有相同的语义。但这并不意味着 QO 具有将一种转换为另一种的必要转换规则。查询的编写方式经常会影响计划。您是否尝试过在查询中使用计划 quide(带有
USE PLAN提示)来尝试让第二个使用第一个的计划?
标签: sql-server sql-server-2008 database-performance sql-execution-plan