Columnstore 索引 - offset-fetch 查询性能低下
Columnstore index - slow performance on offset-fetch query
我们在 Azure 数据库(高级层)上有大约 3500 万行的事实 table,这个 table 启用了集群列存储索引以提高查询性能。
我们使用类似下面的代码对 Fact table 进行了分页(以在 Elastic Search 上建立索引):
SELECT *
FROM [SPENDBY].[FactInvoiceDetail]
ORder by id
offset 1000000 rows fetch next 1000 rows only
但是这个查询执行的很慢,甚至超过了10分钟,还没有完成。如果我们改为使用 TOP
,效果非常好,大约需要 30 秒:
SELECT TOP 1000 *
FROM [SPENDBY].[FactInvoiceDetail]
WHERE ID > 1000000
ORDER BY Id
offset-fetch 查询的估计执行计划:
我不确定我是否理解 offset-fetch
查询在集群列存储索引上是否执行得非常差。
这个table在外键上也有很多none-cluster B-tree索引,在Fact table的Id
上有一个唯一索引,顺序是提高性能
偏移获取查询的执行计划:
这条语句:
SELECT TOP 1000 *
FROM [SPENDBY].[FactInvoiceDetail]
WHERE ID > 1000000
ORDER BY Id
完全与 ID > 1000000 就绪的(集群?)ID 字段索引(是主键?)一起工作
另一个语句排序并搜索将满足偏移量 1000000 行的 ID 值
对于优化器,偏移量 1000000 行不等于 WHERE ID > 1000000,除非 ID 值之间没有间隙。
这里的主要问题是 OFFSET 大值..
offset 1000000 rows fetch next 1000 rows only
OFFSET 和 Fetch 效果很好,当 OFFSET 值较小时,请参见下面的示例了解更多详情
SELECT orderid, orderdate, custid, filler
FROM dbo.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET 50 ROWS FETCH NEXT 10 ROWS ONLY;
我按列排序,因为关键列和 select 中的列都包含在内。这导致下面的计划..
这里要观察的关键点是 SQLServer 最终读取 Offset+fetch (50+10) 行,然后最终过滤 10 行
因此,使用大 Offset,即使有合适的索引,您也将以 1000000+1000 行读取结束,这是非常巨大的
如果你可以要求,sql服务器在扫描后立即过滤掉 1000 行,这可以帮助你查询..这可能是(未针对你的测试schema) 通过像下面这样重写你的查询来实现
WITH CLKeys AS
(
SELECT ID
FROM yourtable
ORDER BY ID desc
OFFSET 500000 ROWS FETCH FIRST 10 ROWS ONLY
)
SELECT K.*, O.rest of columns
FROM CLKeys AS K
CROSS APPLY (SELECT columns needed other than id
FROM yourtable AS A
WHERE A.id= K.id) AS O
ORDER BY Id desc;
参考文献:
http://sqlmag.com/t-sql/offsetfetch-part-1#comment-25061
这里有一些问题。
1) Ordering BTree index is not a covering index for the paging query.
2) The rows must be reconstructed from the CCI.
3) The offset is large.
分页查询需要排序列上的 BTree 索引来计算应返回哪些行,如果该 BTree 索引不包括所有请求的列,则需要对每一行进行行查找。这是查询计划中的 "Nested Loops" 运算符。
但是行存储在 CCI 中,这意味着每一列都在一个单独的数据结构中,读取一行需要为每一列、每一行一个逻辑 IO。这就是此查询特别昂贵的原因。以及为什么 CCI 不是分页查询的最佳选择。排序列上的聚集索引,或包含其余请求列的排序列上的非聚集索引会好得多。
这里的一个次要且较小的问题是大偏移量。 SQL 必须跳过偏移行,并计算它们。所以这将读取 BTree 叶级页面的前 N 页以跳过行。
我们在 Azure 数据库(高级层)上有大约 3500 万行的事实 table,这个 table 启用了集群列存储索引以提高查询性能。
我们使用类似下面的代码对 Fact table 进行了分页(以在 Elastic Search 上建立索引):
SELECT *
FROM [SPENDBY].[FactInvoiceDetail]
ORder by id
offset 1000000 rows fetch next 1000 rows only
但是这个查询执行的很慢,甚至超过了10分钟,还没有完成。如果我们改为使用 TOP
,效果非常好,大约需要 30 秒:
SELECT TOP 1000 *
FROM [SPENDBY].[FactInvoiceDetail]
WHERE ID > 1000000
ORDER BY Id
offset-fetch 查询的估计执行计划:
我不确定我是否理解 offset-fetch
查询在集群列存储索引上是否执行得非常差。
这个table在外键上也有很多none-cluster B-tree索引,在Fact table的Id
上有一个唯一索引,顺序是提高性能
偏移获取查询的执行计划:
这条语句:
SELECT TOP 1000 *
FROM [SPENDBY].[FactInvoiceDetail]
WHERE ID > 1000000
ORDER BY Id
完全与 ID > 1000000 就绪的(集群?)ID 字段索引(是主键?)一起工作
另一个语句排序并搜索将满足偏移量 1000000 行的 ID 值
对于优化器,偏移量 1000000 行不等于 WHERE ID > 1000000,除非 ID 值之间没有间隙。
这里的主要问题是 OFFSET 大值..
offset 1000000 rows fetch next 1000 rows only
OFFSET 和 Fetch 效果很好,当 OFFSET 值较小时,请参见下面的示例了解更多详情
SELECT orderid, orderdate, custid, filler
FROM dbo.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET 50 ROWS FETCH NEXT 10 ROWS ONLY;
我按列排序,因为关键列和 select 中的列都包含在内。这导致下面的计划..
这里要观察的关键点是 SQLServer 最终读取 Offset+fetch (50+10) 行,然后最终过滤 10 行
因此,使用大 Offset,即使有合适的索引,您也将以 1000000+1000 行读取结束,这是非常巨大的
如果你可以要求,sql服务器在扫描后立即过滤掉 1000 行,这可以帮助你查询..这可能是(未针对你的测试schema) 通过像下面这样重写你的查询来实现
WITH CLKeys AS
(
SELECT ID
FROM yourtable
ORDER BY ID desc
OFFSET 500000 ROWS FETCH FIRST 10 ROWS ONLY
)
SELECT K.*, O.rest of columns
FROM CLKeys AS K
CROSS APPLY (SELECT columns needed other than id
FROM yourtable AS A
WHERE A.id= K.id) AS O
ORDER BY Id desc;
参考文献:
http://sqlmag.com/t-sql/offsetfetch-part-1#comment-25061
这里有一些问题。
1) Ordering BTree index is not a covering index for the paging query.
2) The rows must be reconstructed from the CCI.
3) The offset is large.
分页查询需要排序列上的 BTree 索引来计算应返回哪些行,如果该 BTree 索引不包括所有请求的列,则需要对每一行进行行查找。这是查询计划中的 "Nested Loops" 运算符。
但是行存储在 CCI 中,这意味着每一列都在一个单独的数据结构中,读取一行需要为每一列、每一行一个逻辑 IO。这就是此查询特别昂贵的原因。以及为什么 CCI 不是分页查询的最佳选择。排序列上的聚集索引,或包含其余请求列的排序列上的非聚集索引会好得多。
这里的一个次要且较小的问题是大偏移量。 SQL 必须跳过偏移行,并计算它们。所以这将读取 BTree 叶级页面的前 N 页以跳过行。