Bor*_*rka 117 sql t-sql sql-server performance sql-server-2008
我在使用1000个INSERT语句之间运行性能比较:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('6f3f7257-a3d8-4a78-b2e1-c9b767cfe1c1', 'First 0', 'Last 0', 0)
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('32023304-2e55-4768-8e52-1ba589b82c8b', 'First 1', 'Last 1', 1)
...
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('f34d95a7-90b1-4558-be10-6ceacd53e4c4', 'First 999', 'Last 999', 999)
Run Code Online (Sandbox Code Playgroud)
..versus使用具有1000个值的单个INSERT语句:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES
('db72b358-e9b5-4101-8d11-7d7ea3a0ae7d', 'First 0', 'Last 0', 0),
('6a4874ab-b6a3-4aa4-8ed4-a167ab21dd3d', 'First 1', 'Last 1', 1),
...
('9d7f2a58-7e57-4ed4-ba54-5e9e335fb56c', 'First 999', 'Last 999', 999)
Run Code Online (Sandbox Code Playgroud)
令我惊讶的是,结果与我的想法相反:
测试直接在MSSQL Management Studio中执行,SQL Server Profiler用于测量(我使用SqlClient从C#代码运行它得到了类似的结果,考虑到所有DAL层往返,这更令人惊讶)
这可以合理或以某种方式解释?怎么来的,在10次(!)一个所谓更快的方法将导致糟糕的表现?
谢谢.
编辑:附加两者的执行计划:
Mar*_*ith 122
增加: SQL Server 2012在此领域显示了一些改进的性能,但似乎没有解决下面提到的具体问题.这显然应该在 SQL Server 2012 之后的下一个主要版本中修复!
您的计划显示单个插入使用参数化过程(可能是自动参数化),因此这些的解析/编译时间应该是最小的.
我想我会更多地研究这个,但是设置一个循环(脚本)并尝试调整VALUES
子句的数量并记录编译时间.
然后,我将编译时间除以行数,以获得每个子句的平均编译时间.结果如下
直到250个VALUES
条款出现,编译时间/条款数量略有上升趋势,但没有太多戏剧性.
但后来突然发生了变化.
该部分数据如下所示.
+------+----------------+-------------+---------------+---------------+
| Rows | CachedPlanSize | CompileTime | CompileMemory | Duration/Rows |
+------+----------------+-------------+---------------+---------------+
| 245 | 528 | 41 | 2400 | 0.167346939 |
| 246 | 528 | 40 | 2416 | 0.162601626 |
| 247 | 528 | 38 | 2416 | 0.153846154 |
| 248 | 528 | 39 | 2432 | 0.157258065 |
| 249 | 528 | 39 | 2432 | 0.156626506 |
| 250 | 528 | 40 | 2448 | 0.16 |
| 251 | 400 | 273 | 3488 | 1.087649402 |
| 252 | 400 | 274 | 3496 | 1.087301587 |
| 253 | 400 | 282 | 3520 | 1.114624506 |
| 254 | 408 | 279 | 3544 | 1.098425197 |
| 255 | 408 | 290 | 3552 | 1.137254902 |
+------+----------------+-------------+---------------+---------------+
Run Code Online (Sandbox Code Playgroud)
线性增长的缓存计划大小突然下降,但CompileTime增加了7倍,CompileMemory上升.这是自动参数化(具有1,000个参数)的计划与非参数化计划之间的截止点.此后,似乎线性效率降低(在给定时间内处理的值子句的数量).
不知道为什么会这样.据推测,在为特定文字值编制计划时,它必须执行一些不能线性扩展的活动(例如排序).
当我尝试完全由重复行组成的查询时,它似乎不会影响缓存查询计划的大小,也不会影响常量表的输出顺序(并且当您插入堆时排序时)即使它确实如此,也毫无意义).
此外,如果将聚簇索引添加到表中,则计划仍显示显式排序步骤,因此它似乎在编译时不进行排序以避免在运行时进行排序.
我试图在调试器中查看这个,但我的SQL Server 2008版本的公共符号似乎不可用,所以我不得不看一下UNION ALL
SQL Server 2005 中的等效结构.
典型的堆栈跟踪如下
sqlservr.exe!FastDBCSToUnicode() + 0xac bytes
sqlservr.exe!nls_sqlhilo() + 0x35 bytes
sqlservr.exe!CXVariant::CmpCompareStr() + 0x2b bytes
sqlservr.exe!CXVariantPerformCompare<167,167>::Compare() + 0x18 bytes
sqlservr.exe!CXVariant::CmpCompare() + 0x11f67d bytes
sqlservr.exe!CConstraintItvl::PcnstrItvlUnion() + 0xe2 bytes
sqlservr.exe!CConstraintProp::PcnstrUnion() + 0x35e bytes
sqlservr.exe!CLogOp_BaseSetOp::PcnstrDerive() + 0x11a bytes
sqlservr.exe!CLogOpArg::PcnstrDeriveHandler() + 0x18f bytes
sqlservr.exe!CLogOpArg::DeriveGroupProperties() + 0xa9 bytes
sqlservr.exe!COpArg::DeriveNormalizedGroupProperties() + 0x40 bytes
sqlservr.exe!COptExpr::DeriveGroupProperties() + 0x18a bytes
sqlservr.exe!COptExpr::DeriveGroupProperties() + 0x146 bytes
sqlservr.exe!COptExpr::DeriveGroupProperties() + 0x146 bytes
sqlservr.exe!COptExpr::DeriveGroupProperties() + 0x146 bytes
sqlservr.exe!CQuery::PqoBuild() + 0x3cb bytes
sqlservr.exe!CStmtQuery::InitQuery() + 0x167 bytes
sqlservr.exe!CStmtDML::InitNormal() + 0xf0 bytes
sqlservr.exe!CStmtDML::Init() + 0x1b bytes
sqlservr.exe!CCompPlan::FCompileStep() + 0x176 bytes
sqlservr.exe!CSQLSource::FCompile() + 0x741 bytes
sqlservr.exe!CSQLSource::FCompWrapper() + 0x922be bytes
sqlservr.exe!CSQLSource::Transform() + 0x120431 bytes
sqlservr.exe!CSQLSource::Compile() + 0x2ff bytes
Run Code Online (Sandbox Code Playgroud)
因此,关闭堆栈跟踪中的名称似乎花费了大量时间来比较字符串.
这篇知识库文章指出,这DeriveNormalizedGroupProperties
与以前称为查询处理的规范化阶段有关
此阶段现在称为绑定或algebrizing,它从前一个解析阶段获取表达式解析树输出,并输出一个代数表达式树(查询处理器树)以继续优化(在这种情况下是简单的计划优化)[ref].
我尝试了另外一个实验(脚本),重新运行原始测试,但看了三个不同的情况.
可以清楚地看到,字符串越长越糟糕,相反,重复越多,事情就越好.如前所述,重复项不会影响缓存的计划大小,因此我假设在构造代数表达式树本身时必须存在重复标识的过程.
编辑
@Lieven在这里展示了利用这些信息的地方
SELECT *
FROM (VALUES ('Lieven1', 1),
('Lieven2', 2),
('Lieven3', 3))Test (name, ID)
ORDER BY name, 1/ (ID - ID)
Run Code Online (Sandbox Code Playgroud)
因为在编译时它可以确定Name
列没有重复项,它1/ (ID - ID)
在运行时跳过次表达式排序(计划中的排序只有一ORDER BY
列)并且不会引发除零错误.如果将重复项添加到表中,则排序运算符按列显示两个顺序,并引发预期的错误.
das*_*ght 23
这并不太令人惊讶:微小插入的执行计划计算一次,然后重复使用1000次.解析和准备计划很快,因为它只有四个值.另一方面,1000行计划需要处理4000个值(如果参数化了C#测试,则需要4000个参数).通过消除到SQL Server的999往返,这可以轻松地节省您节省的时间,特别是如果您的网络不是太慢.
该问题可能与编译查询所花费的时间有关.
如果你想加速插入,你真正需要做的是将它们包装在一个事务中:
BEGIN TRAN;
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('6f3f7257-a3d8-4a78-b2e1-c9b767cfe1c1', 'First 0', 'Last 0', 0);
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('32023304-2e55-4768-8e52-1ba589b82c8b', 'First 1', 'Last 1', 1);
...
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('f34d95a7-90b1-4558-be10-6ceacd53e4c4', 'First 999', 'Last 999', 999);
COMMIT TRAN;
Run Code Online (Sandbox Code Playgroud)
从C#开始,您还可以考虑使用表值参数.通过用分号分隔单个批次中发出多个命令是另一种方法,也会有所帮助.