【问题标题】:How to put all my selected columns into a dummy variable?如何将我选择的所有列放入一个虚拟变量中?
【发布时间】:2017-09-08 16:47:07
【问题描述】:

背景

这个问题是previous question 的后续问题。在这里也为您提供上下文,我想总结上一个问题:在我上一个问题中,我打算有一种方法来执行选择而不将其结果发送给客户。目标是在不消耗大量资源的情况下通过发送数百万数据来衡量性能。我只对执行这些查询所需的时间感兴趣,而不是它们将结果发送到客户端应用程序的时间,因为我打算优化查询,所以查询的结果根本不会改变,但方法会改变,我打算能够比较这些方法。

当前知识

在我的另一个问题中,提出了几个想法。一个想法是选择记录的数量并将其放入变量中。但是,这显着改变了查询计划,并且结果在性能方面并不准确。也提出了使用临时表的想法,但是如果我们不知道要测量的输入是什么查询并且还会引入大量白噪声,那么创建一个临时表并将其插入是很困难的,因此,即使这个想法很有创意,它不适合我的问题。最后,Vladimir Baranov 提出了一个想法,即创建与选择返回的列一样多的变量。这是一个好主意,但我通过创建一个 nvarchar(max) 变量并将我的所有列选择到其中来进一步改进它。这个想法很有效,除了一些问题。我有大部分问题的解决方案,但我想分享它们,所以,我无论如何都会描述它们,但不要误解我,我有一个问题。

问题1

如果我有一个@container 变量并且我在每个选择中都做了一个@container = columnname,那么我就会遇到转换问题。

解决方案1

我需要做一个@container = cast(columnname as nvarchar(max)),而不是仅仅做一个@container = columnname

问题2

对于选择中的每一列,我需要将<whatever> as something 转换为@container = cast(<whatever> as nvarchar(max)),但对于子选择,我需要一个通用的解决方案来处理case when 和括号,我不想有任何实例@container = 的任意位置,主选区左侧除外。

解决方案2

由于我对正则表达式一无所知,我可以通过迭代查询字符串来解决这个问题,直到我找到主查询的from,每次我找到一个括号,我什么都不做,直到那个括号关闭,找到应该放置@container = 和应该取出as [customname] 的索引,并在查询字符串中从右到左执行所有这些操作。这将是一个冗长而不优雅的代码。

问题

是否可以确保我的所有主要列都以@container = 开头,而以as [Customname] 结尾?

【问题讨论】:

  • 为确保执行计划不会改变,请考虑使用丢弃查询结果选项从 SSMS 或使用 ExecuteNonQuery 从 Powershell 执行数据库服务器上的 proc。尽管 SQL Server 仍会返回结果,但只会考虑 API 处理时间(不考虑网络、呈现或其他处理)。
  • @DanGuzman 如果我有 \@container = foo 而不是 foo 作为 [bar],查询计划是否可能更改?
  • 如果源和目标的数据类型不完全匹配,则需要进行转换(显式或隐式),这会影响性能。我不能说我遇到了具有匹配类型的不同计划的情况,但是当返回多行时,我很少使用 SELECT 进行变量赋值,由于行为未定义,它可能不适用于所有情况,或者行为可能会因 SQL 版本等而改变。这样做需要您自担风险。
  • @DanGuzman 如果我理解正确,您是说由于变量分配和转换,测量可能不准确,您建议执行 ExecuteNonQuery。我需要在 SQL Server 级别计算性能以及从那里返回的值。如果我调用 ExecuteNonQuery,那么我将需要在 Powershell 级别或应用程序级别进行测量。
  • 你是对的 - 我建议你在客户端 API 级别测量时间(没有客户端渲染/处理时间)。我知道你在问什么,但我认为这不可能可靠地完成。此外,考虑时间应该包括数据库引擎为填充 TDS 协议结果缓冲区所做的工作,这是从应用代码调用时将发生的事情的一部分。

标签: sql-server vb.net


【解决方案1】:

这对于评论来说太长了,但我想将我的 $.02 添加到其他答案中,并分享我用来测试建议方法的脚本。

我喜欢@MartinSmith 的 TOP 0 解决方案,但我担心它在某些情况下可能会导致不同的执行计划形状。我在运行的测试中没有看到这一点,但我认为您需要验证该计划与您测试的每个查询的不受干扰的查询大致相同。但是,我的测试结果表明,列数和/或数据类型可能会影响此方法的性能。

@VladimirBaranov 的答案中的 SQLCLR 方法应该提供应用程序代码生成的确切计划(假设测试的 SET 选项相同),SqlClient 在 SQLCLR 中使用结果仍然会有一些轻微的开销 (YMMV)。与将结果返回给调用应用程序相比,使用此方法将减少服务器开销。

我在第一条评论中建议的 SSMS 丢弃结果方法将产生比其他方法更多的开销,但确实包括 SQL Server 不仅在运行查询时执行的服务器端工作,而且还为返回的结果填充缓冲区。是否应考虑此额外的 SQL Server 工作取决于测试的目的。对于单元级性能测试,我更喜欢使用与应用代码相同的 API 来执行测试。

我使用@MartinSmith 的原始查询通过这 3 种方法捕获了服务器端性能。我的机器上平均 1,000 次迭代是:

test method     cpu_time      duration    logical_reads
SSMS discard   53031.000000  55358.844000  7190.512000
TOP 0          52374.000000  52432.936000  7190.527000
SQLCLR         49110.000000  48838.532000  7190.578000

我对从用户表返回 10,000 行和 2 列(intnvarchar(100))的简单查询做了同样的事情:

test method     cpu_time      duration    logical_reads
SSMS discard    4204.000000  9245.426000   402.004000
TOP 0           2641.000000  2752.695000   402.008000
SQLCLR          1921.000000  1878.579000   402.000000

重复相同的测试,但使用 varchar(100) 列而不是 nvarchar(100)

test method     cpu_time      duration    logical_reads
SSMS discard    3078.000000  5901.023000   402.004000
TOP 0           2672.000000  2616.359000   402.008000
SQLCLR          1750.000000  1798.098000   402.000000

以下是我用于测试的脚本:

@VladimirBaranov 建议的 SQLCLR proc 源代码:

public static void ExecuteNonQuery(string sql)
{
    using (var connection = new SqlConnection("Context Connection=true"))
    {
        connection.Open();
        var command = new SqlCommand(sql, connection);
        command.ExecuteNonQuery();
    }
}

Xe 跟踪以捕获实际的服务器端时间和资源使用情况:

CREATE EVENT SESSION [test] ON SERVER 
ADD EVENT sqlserver.sql_batch_completed(SET collect_batch_text=(1))
ADD TARGET package0.event_file(SET filename=N'QueryTimes')
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF);
GO

用户表创建和加载:

CREATE TABLE dbo.Foo(
      FooID int NOT NULL CONSTRAINT PK_Foo PRIMARY KEY
    , Bar1 nvarchar(100)
    , Bar2 varchar(100)
    );
WITH 
     t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
    ,t10k AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS num FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c CROSS JOIN t10 AS d)
INSERT INTO dbo.Foo WITH (TABLOCKX)
SELECT num, REPLICATE(N'X', 100), REPLICATE('X', 100)
FROM t10k;
GO

使用丢弃结果查询选项从 SSMS 运行 SQL 脚本,以使用 3 种不同的方法运行 1000 次测试迭代:

SET NOCOUNT ON;
GO

--return and discard results
SELECT v.*,
       o.name
FROM   master..spt_values AS v
       JOIN sys.objects o
         ON o.object_id % NULLIF(v.number, 0) = 0;
GO 1000

--TOP 0
DECLARE @X NVARCHAR(MAX);

SELECT @X = (SELECT TOP 0 v.*,
                          o.name
             FOR XML PATH(''))
FROM   master..spt_values AS v
       JOIN sys.objects o
         ON o.object_id % NULLIF(v.number, 0) = 0;
GO 1000

--SQLCLR ExecuteNonQuery
EXEC dbo.ExecuteNonQuery @sql = N'
SELECT v.*,
       o.name
FROM   master..spt_values AS v
       JOIN sys.objects o
         ON o.object_id % NULLIF(v.number, 0) = 0;
'
GO 1000

--return and discard results
SELECT FooID, Bar1
FROM   dbo.Foo;
GO 1000

--TOP 0
DECLARE @X NVARCHAR(MAX);

SELECT @X = (SELECT TOP 0 FooID, Bar1
             FOR XML PATH(''))
FROM   dbo.Foo;
GO 1000

--SQLCLR ExecuteNonQuery
EXEC dbo.ExecuteNonQuery @sql = N'
SELECT FooID, Bar1
FROM   dbo.Foo
';
GO 1000

--return and discard results
SELECT FooID, Bar1
FROM   dbo.Foo;
GO 1000

--TOP 0
DECLARE @X NVARCHAR(MAX);

SELECT @X = (SELECT TOP 0 FooID, Bar2
             FOR XML PATH(''))
FROM   dbo.Foo;
GO 1000

--SQLCLR ExecuteNonQuery
EXEC dbo.ExecuteNonQuery @sql = N'
SELECT FooID, Bar2
FROM   dbo.Foo
';
GO 1000

【讨论】:

  • 这很有趣,我需要考虑一下。
【解决方案2】:

我会尝试编写一个单独的 CLR 函数,该函数根据需要运行尽可能多的查询来进行测量。它可能有一个参数,其中包含要运行的查询文本或要运行的存储过程的名称。

您对服务器有一个请求。一切都在服务器本地完成。没有网络开销。通过对需要测量的每个查询使用ExecuteNonQuery,您可以在不使用显式临时表的情况下丢弃 .NET CLR 代码中的查询结果。

不要更改您正在测量的查询。优化器很复杂,对查询的更改可能会对性能产生各种影响。

另外,使用SET STATISTICS TIME ON让服务器为您测量时间。获取服务器要说的内容,对其进行解析并以适合您的格式发回。

我认为,SET STATISTICS TIME ON / OFF 的结果最可靠、最准确,并且噪音最少。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-22
    • 2010-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多