另外: 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 |
+------+----------------+-------------+---------------+---------------+
线性增长的缓存计划大小突然下降,但是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
因此,取消堆栈跟踪中的名称似乎会花费大量时间比较字符串。
该知识库文章表明DeriveNormalizedGroupProperties
与查询处理的规范化阶段相关联
此阶段现在称为绑定或代数化,它采用前一个解析阶段输出的表达式解析树,并输出经过代数化的表达式树(查询处理器树)以进行优化(在这种情况下为简单计划优化)[ref]。
我尝试了另一个实验(Script),该实验重新运行了原始测试,但查看了三种不同的情况。
- 长度为10个字符的名字和姓氏字符串,无重复。
- 长度为50个字符的名字和姓氏字符串,无重复。
- 长度为10个字符的名字和姓氏字符串,所有重复项。
可以清楚地看到,弦越长,效果越差;相反,重复越多,效果越好。如前所述,重复项不会影响缓存的计划大小,因此我认为构造代数表达式树本身时必须进行重复标识的过程。
编辑
@Lieven在此处显示了利用此信息的一个地方
SELECT *
FROM (VALUES ('Lieven1', 1),
('Lieven2', 2),
('Lieven3', 3))Test (name, ID)
ORDER BY name, 1/ (ID - ID)
因为在编译时它可以确定该Name
列没有重复项,所以它会1/ (ID - ID)
在运行时跳过辅助表达式的排序(计划中的排序只有一ORDER BY
列),并且不会出现除以零的错误。如果将重复项添加到表中,则排序运算符将按列显示两个顺序,并且会引发预期的错误。