使用更长的CommandText来降低SqlCommand的性能

Jer*_*nes 6 c# sql-server

SqlCommand的CommandText的长度是否有所不同?我也不是在谈论成千上万的角色.这就是我所拥有的:

SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = sql;

for (int i=0; i<1000; ++i)
{
    string name = i.ToString() + "Bob" + i.ToString();
    string email = i.ToString() + "Jim" + i.ToString();
    // etc...

    cmd.Parameters.Clear();
    cmd.Parameters.Add(new SqlParameter("@name", name));
    cmd.Parameters.Add(new SqlParameter("@country", country));

    DateTime cmdStart = DateTime.Now;
    cmd.ExecuteNonQuery();
    DateTime cmdEnd = DateTime.Now;
    TimeSpan len = cmdEnd - cmdStart;
}
Run Code Online (Sandbox Code Playgroud)

如果我使用以下sql,第一次迭代需要0.5秒.第二个需要1.1秒.第三个需要3.3秒.等等,直到它只是暂停超时.

string sql =
    "INSERT INTO Test " +
    "           ([name] " +
    "           ,[email] " +
    "           ,[country] " +
    "           ,[comment] " +
    "           ,[date] " +
    "           ,[key_v0] " +
    "           ,[key_v1] " +
    "           ,[expires_v1] " +
    "           ,[expires_v2] " +
    "           ) " +
    "     VALUES " +
    "           (@name " +
    "           ,@email " +
    "           ,@country " +
    "           ,' ' " +
    "           ,@date " +
    "           ,@key_v0 " +
    "           ,@key_v1 " +
    "           ,@expires_v1 " +
    "           ,@expires_v2 " +
    "           )";
Run Code Online (Sandbox Code Playgroud)

但是,如果我使用以下sql,整个循环将在一秒钟内执行.

string sql =
    "INSERT INTO Test " +
    "([name] " +
    ",[email] " +
    ",[country] " +
    ",[comment] " +
    ",[date] " +
    ",[key_v0] " +
    ",[key_v1] " +
    ",[expires_v1] " +
    ",[expires_v2] " +
    ") " +
    "VALUES " +
    "(@name " +
    ",@email " +
    ",@country " +
    ",' ' " +
    ",@date " +
    ",@key_v0 " +
    ",@key_v1 " +
    ",@expires_v1 " +
    ",@expires_v2 " +
    ")";
Run Code Online (Sandbox Code Playgroud)

唯一的区别是空白.删除空格会使总字符数从428增加到203.除了引用4k和8k限制外,我无法找到任何引用CommandText长度的内容.我离它不远了.

我已经运行了两个版本,并且所有调用的持续时间都低于10毫秒.从SQL引擎内部完成命令直到ExecuteNonQuery返回时,似乎发生了延迟.

我知道有其他方法可以做到这一点.我不是在问更好的方法.我在问减速的来源.

更新:作为测试,我在查询的末尾添加了空格.一旦我超过400个字符总数,它就会放慢速度.有趣的是,在414个字符中,前99个插入速度很快.在415个字符处,前9个插入是快速的.因为我根据迭代次数改变了一些字符串,所以这有点意义.例如,第10次插入比第9次插入略长,第100次插入比第99次插入略长.

虽然我有点理解更长的插入需要更长时间,但我无法理解快速和慢速之间的明显区分以及差异的绝对大小.我也不明白为什么花费的时间会增加.

更新2 :(回应Peter Oehlert回答的附加信息):整个数据库都很干净.没有其他表,并且每次运行都会删除并重新创建测试表.没有索引,触发器或外键.有一个'id'列是主键.

这是从专门用于解决此问题的控制台应用程序中提取的代码.它只包含重现此行为的必要代码.

(Aditional profiler信息):运行SQL事件探查器时,有一个名为TextData的列显示命令和数据.一个例子是:

exec sp_executesql N'INSERT INTO Test ([name] ,[email] ,[country] ,[comment] ,[date] ,[key_v0] ,[key_v1] ,[expires_v1] ,[expires_v2] ) VALUES (@name ,@email ,@country ,'' '' ,@date ,@key_v0 ,@key_v1 ,@expires_v1 ,@expires_v2 )                                                                                                                                                                                                                          ',N'@name nvarchar(4),@country nvarchar(2),@email nvarchar(3),@date datetime,@key_v0 nvarchar(4000),@key_v1 nvarchar(4000),@expires_v1 datetime,@expires_v2 datetime',@name=N'9Bob',@country=N'us',@email=N'Jim',@date='2009-08-28 15:35:36.5770000',@key_v0=N'',@key_v1=N'',@expires_v1='2009-08-28 15:35:36.5770000',@expires_v2='2009-08-28 15:35:36.5770000'
Run Code Online (Sandbox Code Playgroud)

该行长796个字符并且运行速度很快.将名称从"9Bob"更改为"10Bob"会导致插入缓慢.796和797都不是一个重要的数字.删除exec sp_executesql部分意味着长度为777和778.它们似乎也不重要.

我很难过.

更新:发布跟踪:http://www.jere.us/WierdInserts.trc

Pet*_*ert 2

好吧,我没有直接的答案,但这是我解决这个问题的方法。

  1. 确定是谁造成了问题。第一步是运行 SQL 探查器并查看数据库是否有问题或者代码中存在问题。

SQL

如果是数据库,那么我会看一些事情:每个人都在喋喋不休地讨论字符串连接问题,虽然这是真的,但很可能会占用你不到 5 毫秒的时间。我也认为这些空间是问题的根源。同样,它会产生很小的差异,但不会解释您所描述的退化。您正在寻找具有这种级数(0.5、1.1、3.3)的东西。

我会特别查看您在此表上定义的索引、该表上有哪些约束/触发器以及存在多少外键关系。此外,我会拉出执行缓慢的查询并在查询管理器(sql 企业管理器)中运行它们。

我可能调查的最后一件事是是否存在错误的缓存计划,从而引入了某些数据相关功能的问题。仅当您有一些有趣的触发器使用您的数据或某些类型的索引更新时,这才有效。您可以通过在调用插入语句之间调用 DBCC FREEPROCCACHE 来查看这一点,看看它是否有区别。此命令将清除计划缓存,强制 sql 为查询重新生成新的执行计划。

客户

如果是客户端,那么您需要确定代码中导致问题的原因。如果您有一个性能跟踪工具(例如 Visual Studio 性能分析器)可以检测您的代码,我会使用它,因为它会非常快速地告诉您到底是什么占用了这么多时间。

如果您没有该选项,那么首先将代码提取到一个新的控制台应用程序中,并尽可能减少意外情况,看看是否可以重现该行为。您正在寻找可能导致您所看到的进展的原因。