Liz*_*eir 3 sql-server best-practices query-performance
我使用的应用程序使用 SQL Server 数据库,其中包含许多保存单行配置数据的表,有时在针对更传统的多行表的查询中需要这些表。我见过的大多数代码在处理单个查询时都通过联接访问这些表,但在最近的代码审查中,我看到了一种使用标量子查询的方法,大致如下:
Select T.Id
From dbo.SomeTable T
Where T.SomeValue > (Select Tolerance From dbo.Settings)
Run Code Online (Sandbox Code Playgroud)
虽然它显然有效,但我最初的反应是假设这是对我们标准实践的冒险违反,但我对表单进行了一些实验,发现“子查询返回超过 1 个值。当子查询遵循时,这是不允许的=、!=、<、<=、>、>= 或当子查询用作表达式时”错误。这使得这看起来可以避免意外 1:n 连接带来的不良行为的风险。(在实践中,这不应该是这些单行表的问题,它们相当强大,但我已经看到它出现在系统的其他地方。)
除了(可能非常便宜的)流聚合和断言之外,我的简单测试用例的执行计划看起来非常相似,我认为它们负责查询引擎在多行情况下识别和抛出错误的能力。
使用这种表是否有普遍接受的最佳实践?选择方法时我应该注意哪些主要优点和缺点?
(我知道使用变量来保存数据也是一种选择,但在我们的某些代码中这样做并不总是可行,因此我想重点关注这两种方法和/或任何其他方式的比较将其折叠到单个查询中。)
我创建了一组演示表来看看这在实践中是如何运作的。
USE tempdb;
GO
DROP TABLE IF EXISTS dbo.Settings;
CREATE TABLE dbo.Settings
(
Id int IDENTITY(1,1) NOT NULL,
Tolerance int NOT NULL,
CONSTRAINT PK_Settings PRIMARY KEY (Id)
);
GO
INSERT INTO dbo.Settings (Tolerance) VALUES (1073741823);
CREATE TABLE dbo.SomeTable
(
Id int IDENTITY(1,1) NOT NULL,
SomeValue int NOT NULL,
CONSTRAINT PK_SomeTable PRIMARY KEY (Id)
);
CREATE NONCLUSTERED INDEX IX_SomeValue
ON dbo.SomeTable (SomeValue);
GO
INSERT INTO dbo.SomeTable
(SomeValue)
SELECT
SomeValue = v1.number
FROM master.dbo.spt_values v1
CROSS JOIN master.dbo.spt_values v2;
GO
Run Code Online (Sandbox Code Playgroud)
这会将约 6,000,000 行放入 SomeTable 表中,其中约 15,000 行比 Settings 表中的行 (1073741823) 大。
OP 中的标量子查询版本
SELECT st.Id
FROM dbo.SomeTable st
WHERE st.SomeValue > (SELECT s.Tolerance FROM dbo.Settings s);
Run Code Online (Sandbox Code Playgroud)
这会导致一种奇怪的查询计划。估计值相差几个数量级。这导致选择并行计划。然而,由于连接的上部输入上只有一行,因此来自下部输入的所有行最终都在一个线程上 - 导致并行性严重倾斜(并且完全无用)。
我没有很好地解释为什么这里的估计如此糟糕,但这似乎是这种方法的一个坏兆头,具体取决于您的表大小和数据分布。
内连接版本
SELECT st.Id
FROM dbo.SomeTable st
INNER JOIN dbo.Settings s
ON st.SomeValue > s.Tolerance;
Run Code Online (Sandbox Code Playgroud)
这得到了更好的估计(仅下降了 2 倍),并且不保证并行性。
实际标量子查询
TOP (1)
我们可以通过添加到内部查询来强制子查询为标量:
SELECT st.Id
FROM dbo.SomeTable st
WHERE st.SomeValue > (SELECT TOP (1) s.Tolerance FROM dbo.Settings s);
Run Code Online (Sandbox Code Playgroud)
这会产生与 INNER JOIN 版本非常相似的查询计划,其中 TOP 强制我们只从 Settings 表中获取一行。
使用参数代替
你提到要避免这种方法,但我很好奇。如果您的应用程序代码可以缓存/检索该值,并将查询传递给它(可能在存储过程中,我在这里使用 sp_executesql):
EXEC sys.sp_executesql
N'SELECT st.Id FROM dbo.SomeTable st WHERE st.SomeValue > @SomeValue',
N'@SomeValue int',
@SomeValue = 1073741823;
Run Code Online (Sandbox Code Playgroud)
这会产生良好的估计,并且通常会产生相当有效的执行计划,并且不必触及“设置”表。
注意:以上所有项目都取决于 SomeTable 表的 SomeValue 列上是否有索引。
基于所有这些,我同意京东的回答,即该INNER JOIN
方法可能更可靠。如果您被迫使用子查询方法,请查看添加是否TOP (1)
有帮助。
我想补充一点,如果你可以使用参数,那就更好了,但听起来这不可行。
归档时间: |
|
查看次数: |
678 次 |
最近记录: |