Joh*_*ohn 8 index sql-server btree
考虑以下具有多列索引的示例表:
create table BigNumbers (
col1 tinyint not null,
col2 tinyint not null,
col3 tinyint not null,
index IX_BigNumbers clustered (col1, col2, col3)
)
DECLARE @n INT = 100;
DECLARE @x1 INT = 0;
DECLARE @x2 INT = 0;
DECLARE @x3 INT = 0;
SET NOCOUNT ON;
WHILE @x3 <= @n BEGIN
SET @x2 = 0;
WHILE @x2 <= @n BEGIN
SET @x1 = 0;
WHILE @x1 <= @n BEGIN
insert into BigNumbers values (@x1, @x2, @x3);
SET @x1 = @x1 + 1;
END;
SET @x2 = @x2 + 1;
END;
SET @x3 = @x3 + 1;
END;
Run Code Online (Sandbox Code Playgroud)
我现在的目标是从该索引中获取几行,从给定的键开始。
听起来微不足道的事情有点复杂,因为 SQL 中没有简单的方法来表达索引所在的字典顺序:
DECLARE @x1 INT = 60;
DECLARE @x2 INT = 40;
DECLARE @x3 INT = 98;
select top 5 *
from BigNumbers
where
col1 > @x1 or
(col1 = @x1 and
(col2 > @x2 or
(col2 = @x2 and col3 >= @x3)))
order by col1, col2, col3
Run Code Online (Sandbox Code Playgroud)
正确的结果是:
60 40 98
60 40 99
60 40 100
60 41 0
60 41 1
Run Code Online (Sandbox Code Playgroud)
但是,查询计划告诉我这使用索引扫描。
底层索引应该能够查找并返回大于或等于(@x1, @x2, @3)
索引顺序的前几行,但由于 SQL 中无法轻松表达此意图,因此查询规划器似乎无法接受提示,而是进行扫描。
索引提示没有帮助,而且FORCESEEK
给出了一个可怕的计划。
有趣的是,以下两列版本有效:
select top 5 *
from BigNumbers
where
col1 = @x1 and
(col2 > @x2 or
(col2 = @x2 and col3 >= @x3))
order by col1, col2, col3
Run Code Online (Sandbox Code Playgroud)
我不确定为什么会这样,但该计划不仅使用了搜索,而且还正确地报告了仅触及 5 行:
我想知道是否有人知道一种方法,可以通过简单的可靠查找来查询大于或等于给定值元组的索引的几行。
数据库在其更高级别的抽象下掩盖了这一基本功能,这似乎很奇怪。
如果有人有兴趣知道这是什么问题,我正在为 SQL 数据库开发一个通用 UI。您需要此功能的最明显的地方是“加载更多”按钮,您希望在其中继续显示给定起点的索引内容。如果这通常不可能,解决方法是首先查询修复除最后一列之外的所有列,然后执行第二个查询,依此类推。但不得不这样做会有点遗憾。
您所指的是row-comparison,并且您将其用于Keyset Pagination query。在支持它的DBMS 中,您可以简单地执行以下操作
where (col1, col2, col3) >= (@x1, @x2, @x3)
Run Code Online (Sandbox Code Playgroud)
然而 SQL Server 不支持这一点。它支持的是多个范围上的索引查找。因此,单个索引查找变为两个或三个,但编译器可以理解和维护顺序,因此它实际上充当一个范围内的单个查找。
它在许多不同类型的查询上使用它,主要是IN
列表和OR
查询。它还在行比较逻辑上使用它,这在执行键集分页时非常有用。
就您而言,它无法识别这种模式。看来这是因为您使用嵌套布尔逻辑来表达它。它将成功识别以下逻辑,语义上完全相同
where (col1 = @x1 and col2 = @x2 and col3 >= @x3)
or (col1 = @x1 and col2 > @x2)
or (col1 > @x1)
Run Code Online (Sandbox Code Playgroud)
究竟为什么它能识别其中一种而不是另一种,目前尚不清楚。也许有权访问调试器和/或了解优化器规则的人可以详细说明。
在完美的世界中,人们将能够以任何逻辑等效的形式编写查询,并且优化器将在所有情况下生成相同的最佳执行计划。
\n这不是一个实用的命题\xe2\x80\x94优化器的工作时间有限,并且对可能的转换的了解不完整。因此,我们有时需要以特定的书面形式表达我们的要求才能获得最佳结果。
\nSQL Server 确实做出了一些努力来标准化(规范化)所提交语句的逻辑形式,但它们并不详尽。例如,逻辑子句转换为否定范式(NNF),但不是合取(CNF) 或析取范式 (DNF)。向 NNF 的转换始终是简单且紧凑的;对于 CNF 或 DNF 来说并非总是如此。
\n这对你的情况来说是一种耻辱,因为 DNF 形式恰好很容易派生并且与索引匹配逻辑配合得很好,如另一个答案所示所示:
\nDECLARE \n @x1 tinyint = 60,\n @x2 tinyint = 40,\n @x3 tinyint = 98;\n\nSELECT TOP (5) \n BN.col1, \n BN.col2, \n BN.col3 \nFROM dbo.BigNumbers AS BN\nWHERE \n (BN.col1 = @x1 AND BN.col2 = @x2 AND BN.col3 >= @x3)\n OR (BN.col1 = @x1 AND BN.col2 > @x2)\n OR (BN.col1 > @x1)\nORDER BY \n BN.col1, \n BN.col2, \n BN.col3;\n
Run Code Online (Sandbox Code Playgroud)\n这会生成一个TRIVIAL
执行计划,在聚集索引查找中包含三个独立的查找操作,按顺序执行(短路):
正如已经指出的,理想的情况是 SQL Server 实现行构造函数,并根据需要将逻辑扩展为索引匹配的最佳形式。这是长期以来的要求,但令人沮丧的是尚未交付。目前,我们需要以特定的方式编写基于锚点的分页查询。
\n一个效率稍低但仍然不错的解决方案是:
\nDECLARE \n @x1 tinyint = 60,\n @x2 tinyint = 40,\n @x3 tinyint = 98;\n\nSELECT BN.col1, BN.col2, BN.col3 \nFROM dbo.BigNumbers AS BN \nWHERE BN.col1 = @x1 \nAND BN.col2 = @x2 \nAND BN.col3 >= @x3\n\nUNION ALL \n\nSELECT BN.col1, BN.col2, BN.col3 \nFROM dbo.BigNumbers AS BN \nWHERE BN.col1 = @x1 \nAND BN.col2 > @x2\n\nUNION ALL \n\nSELECT BN.col1, BN.col2, BN.col3 \nFROM dbo.BigNumbers AS BN \nWHERE BN.col1 > @x1\n\nORDER BY BN.col1, BN.col2, BN.col3\nOFFSET 0 ROWS \nFETCH FIRST 5 ROWS ONLY;\n
Run Code Online (Sandbox Code Playgroud)\n\n该计划有 3 个单查找,但每个查找必须至少生成一行用于合并串联比较,因此不能完全短路。
\n\n