SQL Server - 加速大表的计数

mrt*_*mrt 5 sql sql-server performance

我有一张有近3000万条记录的桌子.只有几个专栏.其中一列的'Born'不超过30个,并且在其上定义了索引.我需要能够过滤该列并有效地翻阅结果.

现在我有(例如,如果我正在搜索的年份是'1970' - 它是我存储过程中的参数):

WITH PersonSubset as
(
    SELECT *, ROW_NUMBER() OVER (ORDER BY Born asc) AS Row
    FROM Person WITH (INDEX(IX_Person_Born)) 
    WHERE Born = '1970'
)
SELECT *, (SELECT count(*) FROM PersonSubset) AS TotalPeople
FROM PersonSubset
WHERE Row BETWEEN 0 AND 30
Run Code Online (Sandbox Code Playgroud)

该排序的每个查询(仅Born使用参数)仅返回超过100万个结果.我注意到最大的开销是用于返回总结果的计数.如果我(SELECT count(*) FROM PersonSubset) AS TotalPeople从select子句中删除整个事情会加速很多.

有没有办法加快该查询中的计数.我关心的是返回分页结果和总计数.

Kei*_*ith 7

更新了以下评论中的讨论

这里的问题的原因是非常低的基数中的IX_Person_Born索引.

SQL索引非常适合快速缩小值,但是当您有大量具有相同值的记录时,它们会出现问题.

您可以将其视为电话簿的索引 - 如果您想要找到"史密斯,约翰",您首先会发现有许多以S开头的名称,然后是名为史密斯的页面和页面,然后很多约翰斯.你最终扫描了这本书.

这是复杂的,因为电话簿中的索引是聚集的 - 记录按姓氏排序.如果你想要找到所谓的"约翰",你会做很多的查找.

这里有3000万条记录,但只有30个不同的值,这意味着最好的索引仍然可以返回大约100万条记录 - 在这种规模上它也可能是一个表扫描.这100万个结果中的每一个都不是实际记录 - 它是从索引到表格的查找(电话簿类比中的页码),这使得它更慢.

高基数指数(比如说出生日期),而不是年份会快得多.

这是所有OLTP关系数据库的一般问题:low cardinality + huge datasets = slow queries因为索引树没有多大帮助.

简而言之:使用T-SQL和索引获取计数没有明显更快捷的方法.

你有几个选择:

1.数据聚合

OLAP/Cube汇总或自行完成:

select Born, count(*) 
from Person 
group by Born
Run Code Online (Sandbox Code Playgroud)

专家是多维数据集查找或检查您的缓存非常快.问题是数据会过时,你需要一些方法来解决这个问题.

2.并行查询

分为两个查询:

SELECT count(*) 
FROM Person 
WHERE Born = '1970'

SELECT TOP 30 *
FROM Person 
WHERE Born = '1970'
Run Code Online (Sandbox Code Playgroud)

然后在并行服务器端运行它们,或将其添加到用户界面.

3.无SQL

这个问题是无SQL解决方案相对于传统关系数据库的一大优势.在无SQL系统中,Person表是在许多廉价服务器上联合(或分片)的.当用户搜索每个服务器时同时进行检查.

此时技术变革可能已经结束,但可能值得调查,所以我已将其纳入其中.

我过去曾遇到类似问题的这种大小的数据库,并且(取决于上下文)我已经使用了选项1和2.如果这里的总数是分页,那么我可能选择2和AJAX打电话来计算.


Amy*_*y B 2

DECLARE @TotalPeople int
  --does this query run fast enough?  If not, there is no hope for a combo query.
SET @TotalPeople = (SELECT count(*) FROM Person WHERE Born = '1970')


WITH PersonSubset as
(
    SELECT *, ROW_NUMBER() OVER (ORDER BY Born asc) AS Row
    FROM Person WITH (INDEX(IX_Person_Born)) 
    WHERE Born = '1970'
)
SELECT *, @TotalPeople as TotalPeople
FROM PersonSubset
WHERE Row BETWEEN 0 AND 30
Run Code Online (Sandbox Code Playgroud)

通常不能将慢速查询与快速查询结合起来,最后得到快速查询。


其中一列“Born”的不同值不超过 30 个,并且在其上定义了一个索引。

SQL Server 没有使用索引或统计信息,或者索引和统计信息没有足够的帮助。

这是一个绝望的措施,将迫使 Sql 采取行动(潜在的代价是使写入变得非常昂贵 - 测量这一点,并在视图存在时阻止对 Person 表的架构更改)。

CREATE VIEW dbo.BornCounts WITH SCHEMABINDING
AS
SELECT Born, COUNT_BIG(*) as NumRows
FROM dbo.Person
GROUP BY Born

GO 

CREATE UNIQUE CLUSTERED INDEX BornCountsIndex ON BornCounts(Born)
Run Code Online (Sandbox Code Playgroud)

通过在视图上放置聚集索引,可以使其成为系统维护的副本。该副本的大小远小于 3000 万行,并且包含您正在查找的确切信息。我不必更改查询来使其使用视图,但如果您愿意,您可以在查询中自由使用视图的名称。