我正在查看一个MS SQL Server数据库,该数据库是由数据库设计专家公司开发的(或者我被告知),我注意到了JOIN /索引的一种奇怪模式。它与我的工作完全颠倒了,所以我想知道它是否有一些性能优势(数据库相当大)。
表结构(简化的伪代码)是:
表JOBS(约1K行):
表JOB_HISTORY(约17M行):
注意两个表中server_id
所在的非规范化。
他们做的是:
select
t1.job_name, t2.job_start, t2.job_duration
from
JOBS t1
inner join
JOB_HISTORY t2 on (t1.job_id = t2.job_id and t1.server_id = t2.server_id)
where
t1.server_id = @param_server_id
and t2.job_start >= @param_from
and t2.job_start <= @param_to
他们有索引:
换句话说,当他们选择行时,他们首先从JOBS
表中过滤作业,然后查找相关的JOB_HISTORY
条目。由于索引,这就是DB被迫做的事情。
我会做的是自下而上的版本:
select
t1.job_name, t2.job_start, t2.job_duration
from
JOB_HISTORY t2
inner join
JOBS t1 on (t1.job_id = t2.job_id)
where
t2.server_id = @param_server_id
and t2.job_start >= @param_from
and t2.job_start <= @param_to
一个索引:
所以,基本上,我直接从大JOB_HISTORY
中选择相关行,然后只查找JOBS
表中的附加数据。
是否有理由偏爱另一个?
答案 0 :(得分:3)
好吧,我有点无聊,所以我想为你重新创造这个。首先设置(我使用数字表生成大约1K和17M行,当然,这是所有随机数据,并不代表您的系统:)我还假设它是一个聚簇索引在每张桌子上,即使你暗示你也没有。
USE TempDB;
GO
DROP TABLE IF EXISTS #Jobs;
DROP TABLE IF EXISTS #Job_History;
CREATE TABLE #Jobs
(
job_id INT IDENTITY PRIMARY KEY
,server_id INT
,job_name VARCHAR(50)
);
CREATE TABLE #Job_History
(
history_id INT IDENTITY PRIMARY KEY
,job_id INT
,server_id INT
,job_start DATETIME DEFAULT SYSDATETIME()
,job_duration INT DEFAULT ABS(CHECKSUM(NEWID())) % 5000
);
GO
INSERT INTO #Jobs
SELECT server_id = N.n
,job_name = CONVERT(VARCHAR(50), NEWID())
FROM DBA.Dim.Numbers N
WHERE n < 1000;
INSERT INTO #JOB_HISTORY
( job_id
,server_id
)
SELECT job_id = j1.job_id
,server_id = j1.server_id
FROM #Jobs j1
CROSS JOIN DBA.Dim.Numbers n
WHERE n < 17000;
现在,案例1(他们的方式)
DROP INDEX IF EXISTS Idx_Job_hist ON #Job_History;
CREATE NONCLUSTERED INDEX Idx_Job_Hist ON #Job_History (job_id, server_id, job_start);
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
DECLARE @param_server_id INT = 1234
DECLARE @param_from INT = 500
DECLARE @param_to INT = 1000
select
t1.job_name, t2.job_start, t2.job_duration
from
#JOBS t1
inner join
#JOB_HISTORY t2 on (t1.job_id = t2.job_id and t1.server_id = t2.server_id)
where
t1.server_id = @param_server_id
and t2.job_start >= @param_from
and t2.job_start <= @param_to;
案例2(你的方式)
DROP INDEX IF EXISTS Idx_Job_hist ON #Job_History;
CREATE NONCLUSTERED INDEX Idx_Job_Hist ON #Job_History (server_id, job_start);
select
t1.job_name, t2.job_start, t2.job_duration
from
#JOB_HISTORY t2
inner join
#JOBS t1 on (t1.job_id = t2.job_id)
where
t2.server_id = @param_server_id
and t2.job_start >= @param_from
and t2.job_start <= @param_to;
(完全没有结论,因为我的系统不是你的系统......)结果:
您的计划的成本总体上要高得多。
但是,这只是一个相当人为的练习来证明这一点 - 运行计划,答案是 - 这取决于。
(感谢借口玩这个,很有趣:)
答案 1 :(得分:1)
这里简短的回答是,你JOIN
表的顺序并不重要。 SQL是您告诉服务器您想要什么的语言之一,而不是您希望它做什么(**)。 (AKA是一个所谓的declarative language)。
我们看到两个版本的查询的不同查询计划的原因是它们不完全相同。在第一个表中,要求server_id
在两个表中都相同,而在第二个版本中则不再提及。 t1.server_id
可以是任何东西。如果您重新添加此要求,您会注意到查询计划将是相同的,并且服务器将在“引擎盖”下为任一查询执行完全相同的操作。
仅供参考:在Les H的回答的基础上,我冒昧地检查了MSSQL在这里建议的索引类型,而不是出乎意料地提出
CREATE NONCLUSTERED INDEX idx_test
ON [dbo].[Job_History] ([server_id],[job_start])
INCLUDE ([job_id],[job_duration])
仅供参考:
(**:是的,我知道你可以通过HINTS'指导'在幕后发生的事情,但经验表明,当QO不再有意义时,那些应该只是最后的手段在大多数情况下,当统计数据是最新的并且数据布局不是极具异国情调时,查询优化工具非常聪明地找到了获取所需数据的最佳方式。)