考虑以下查询:
onActivityResult
我想知道begin
;with
t1 as (
select top(10) x from tableX
),
t2 as (
select * from t1
),
t3 as (
select * from t1
)
-- --------------------------
select *
from t2
join t3 on t3.x=t2.x
end
go
是否被两次调用,因此tableX被两次调用(这意味着t1
就像表一样)?
还是仅一次将其行保存在整个查询中的t1中(就像编程lang中的变量一样)?
只是试图弄清楚tsql引擎如何对此进行优化。要知道这一点很重要,因为如果t1
有数百万行,并且在整个查询中被多次调用以生成相同的结果,那么应该有一种更好的方法。
答案 0 :(得分:3)
只需创建表:
CREATE TABLE tableX
(
x int PRIMARY KEY
);
INSERT INTO tableX
VALUES (1)
,(2)
打开执行计划生成并执行查询。您将得到如下内容:
是的,该表被查询两次。如果您使用复杂的公用表表达式并且要处理大量数据,我建议将结果存储在临时表中。
有时候,对于过去运行良好的复杂CTE,我的执行计划非常糟糕。此外,您还可以在临时表上定义索引并进一步提高性能。
答案 1 :(得分:2)
说实话,没有答案……唯一的答案是Race your horses (Eric Lippert)。
编写查询的方式不会告诉您引擎如何将其置于执行状态。这取决于很多很多的影响...
您告诉引擎您想获得什么,引擎决定如何来获取。
相同的调用之间甚至可能有所不同,具体取决于统计信息,当前正在运行的查询,现有的缓存结果等。
仅作为提示,请尝试以下操作:
USE master;
GO
CREATE DATABASE testDB;
GO
USE testDB;
GO
-我创建了一个具有1.000.000行的物理测试表
CREATE TABLE testTbl(ID INT IDENTITY PRIMARY KEY, SomeValue VARCHAR(100));
WITH MioRows(Nr) AS (SELECT TOP 1000000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values v1 CROSS JOIN master..spt_values v2 CROSS JOIN master..spt_values v3)
INSERT INTO testTbl(SomeValue)
SELECT CONCAT('Test',Nr)
FROM MioRows;
-现在我们可以开始对此进行测试
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @dt DATETIME2 = SYSUTCDATETIME();
-您与CTE接触的方法
;with t1 as (select * from testTbl)
,t2 as (select * from t1)
,t3 as (select * from t1)
select t2.ID AS t2_ID,t2.SomeValue AS t2_SomeValue,t3.ID AS t3_ID,t3.SomeValue AS t3_SomeValue INTO target1
from t2
join t3 on t3.ID=t2.ID;
SELECT 'Final CTE',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @dt DATETIME2 = SYSUTCDATETIME();
-将中间结果写入物理表
SELECT * INTO test1 FROM testTbl;
SELECT 'Write into test1',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
select t2.ID AS t2_ID,t2.SomeValue AS t2_SomeValue,t3.ID AS t3_ID,t3.SomeValue AS t3_SomeValue INTO target2
from test1 t2
join test1 t3 on t3.ID=t2.ID
SELECT 'Final physical table',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @dt DATETIME2 = SYSUTCDATETIME();
-与以前相同,但在中间表上具有主键
SELECT * INTO test2 FROM testTbl;
SELECT 'Write into test2',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
ALTER TABLE test2 ADD PRIMARY KEY (ID);
SELECT 'Add PK',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
select t2.ID AS t2_ID,t2.SomeValue AS t2_SomeValue,t3.ID AS t3_ID,t3.SomeValue AS t3_SomeValue INTO target3
from test2 t2
join test2 t3 on t3.ID=t2.ID
SELECT 'Final physical tabel with PK',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
-清理(小心真实数据!!!)
GO
USE master;
GO
--DROP DATABASE testDB;
GO
在我的系统上
test1
时为297)和test2
的时间为285毫秒,用于创建索引的时间为650毫秒。尽管查询执行了两次,但引擎可以利用缓存的结果。
引擎真的很聪明...不要试图变得更聪明...
如果表将包含很多列,每行包含更多数据,则整个测试可能会返回其他内容...
如果您的CTE(子查询)涉及具有连接,视图,函数等复杂得多的数据,则引擎可能会遇到麻烦,无法找到最佳方法。
如果性能很重要,您可以赛马进行测试。 One hint: I sometimes used a TABLE HINT quite successfully: FORCE ORDER
。这将按照查询中指定的顺序执行联接。
答案 2 :(得分:0)
这是测试理论的简单示例: 首先,通过仅调用此事件的临时表。
declare @r1 table (id int, v uniqueidentifier);
insert into @r1
SELECT * FROM
(
select id=1, NewId() as 'v' union
select id=2, NewId()
) t
-- -----------
begin
;with
t1 as (
select * from @r1
),
t2 as (
select * from t1
),
t3 as (
select * from t1
)
-- ----------------
select * from t2
union all select * from t3
end
go
另一方面,如果我们将问题放到t1
而不是临时表中,它将被调用两次。
t1 as (
select id=1, NewId() as 'v' union
select id=2, NewId()
)
因此,我的结论是使用临时表,而不对cached
结果进行回复。
另外,我在一次仅两次调用“ matter”的大规模查询中实现了这一点,并将其移至临时表后,执行时间几乎缩短了一半!