使用 COUNT(DISTINCT ...) 为查询索引和管理表
Indexing and managing tables for queries with COUNT(DISTINCT ...)
我有几个大型(~10 亿行,~100GB)存档 tables,其中包含用于分析目的的客户行为日志(还没有仓库,正在进行中)。
每个每天从每日日志中填充一次 table 并包含当年的数据。
除每日插入外,这些 table 从未被修改,仅选自
每日日志按 servertime
列的顺序排列,可以用作主键,但除了每日插入外,它不用于任何查询,
- 所有查询都受限于
datetime
列的范围。这两列大约 90% 的时间是相似的,其余的时间甚至可以相隔几天,
servertime
注释发送日志包的时间(因此它们通常在平面文件中升序排列),datetime
是实际日志生成时间,可以在缓存中保留多天。
许多请求要求查询计算不同时间范围(从每小时到每月、3 个月、上一季度、当年)的不同值的查询,这就是我们将当年全部放在一个地方的原因,但是行数开始变得荒谬。
SELECT
CAST(datetime as date),
element,
COUNT(DISTINCT client_id),
COUNT(DISTINCT session_id),
COUNT(*)
FROM dbo.pageviews
WHERE DATETIME >= ''
AND DATETIME < ''
GROUP BY CAST(DATETIME as date), element
其他时候,我们必须提取给定时间段内给定 client_id
或 session_id
的所有日志。
我们有一些遗留索引(包含多个列的非聚集索引,索引的大小是 table 的数倍)。
在等待仓库的过程中,我想稍微改善一下情况,所以我开始进行一些基本的更改。
问题一:
我在 datetime
列上添加了聚集索引(以帮助查询)并在 servetime
上添加了非聚集索引(以帮助每日插入)。这是正确的还是应该相反?
问题二:
如果我们将 tables 分成,比如说,每月的块,并针对一个 VIEW 执行查询,所有这些都作为 UNION ALL
和 datetime
作为聚簇索引,会不会如果我们经常在跨越多个 tables?
的时间段内计算 COUNT(distinct X)
,这会有所帮助
任何其他可以在短期内帮助管理此问题的修复程序?
测试用例:
我在 3 个月的时间内使用各种索引测试了上述查询,得到了以下结果:
- 根本没有索引 - ~38 分钟
datetime
上的聚簇索引 - ~34 分钟
datetime
的非聚集索引,所有相关列上的 include
- 约 34 分钟
编辑:附加信息:
最近33%的时间servertime
大于datetime
,datetime
也受时区变化的影响,夏令时下降到18% (格林威治标准时间+2/格林威治标准时间+1)
使用 datetime
上传的日志数量很少,但并非微不足道,比当前时间戳早得多,有时甚至几个月。从我们的报告角度来看,这是 acceptable,但如果聚集索引不断添加新行,它可能会对 datetime
产生重大影响。
您为 "datetime" 列创建聚集索引是正确的。如果不定期搜索 "servetime" 列,则非聚集索引没有太大帮助。
您在这里需要的是 分区 您的 table 并且可能会显着提高性能。它在逻辑上拆分您的 tables 数据,因此您无需更改任何现有查询,同时受益于拆分数据。
分区是一个复杂的概念。您可以找到有用的信息 here.
您的问题很复杂;它可能太宽泛了。但它有一个简单的解决方案,在 datetime
列上进行分区,因为这是用于查询表的列。我只想指出一些与此相关的高级问题。
但是,更简单的分区列是 servertime
——这将允许您只交换分区的进出。但是,这是以增加查询难度为代价的。如果您知道 datetime
总是在一个分区中,例如其值的三天内,您就可以完成这项工作。
一些数据库限制了您可以插入的 "open" 分区的数量。我不认为SQL服务器有这个限制。
但是,您会遇到另一个问题。 结果可能会随着时间的推移而改变。 因此,如果您要计算 2019-10-31 的任何数量。然后,您可以在 2019-11-01 和 2019-11-02 获得不同的值,依此类推,因为数据不断涌入。
如果您将数据用于需要静态的内容(例如财务报告),这可能是个大问题。您可能希望在查询中包含任意限制。像这样:
select *
from . . .
where partition_date = '2019-10-31' and
abs(datediff(day, servertime, datetime)) < 7;
请注意,我添加了一个伪列 partition_date
只是为了清楚什么用于分区。您可以为此直接使用 datetime
。
也就是一周内有数据进来。您没有指定 servertime
是否大于 datetime
。请注意,即使您认为这不是由于计算机上的时间漂移和可能的时区问题,这也是可能的。
我有几个大型(~10 亿行,~100GB)存档 tables,其中包含用于分析目的的客户行为日志(还没有仓库,正在进行中)。
每个每天从每日日志中填充一次 table 并包含当年的数据。
除每日插入外,这些 table 从未被修改,仅选自
每日日志按
servertime
列的顺序排列,可以用作主键,但除了每日插入外,它不用于任何查询,- 所有查询都受限于
datetime
列的范围。这两列大约 90% 的时间是相似的,其余的时间甚至可以相隔几天, servertime
注释发送日志包的时间(因此它们通常在平面文件中升序排列),datetime
是实际日志生成时间,可以在缓存中保留多天。
许多请求要求查询计算不同时间范围(从每小时到每月、3 个月、上一季度、当年)的不同值的查询,这就是我们将当年全部放在一个地方的原因,但是行数开始变得荒谬。
SELECT
CAST(datetime as date),
element,
COUNT(DISTINCT client_id),
COUNT(DISTINCT session_id),
COUNT(*)
FROM dbo.pageviews
WHERE DATETIME >= ''
AND DATETIME < ''
GROUP BY CAST(DATETIME as date), element
其他时候,我们必须提取给定时间段内给定 client_id
或 session_id
的所有日志。
我们有一些遗留索引(包含多个列的非聚集索引,索引的大小是 table 的数倍)。
在等待仓库的过程中,我想稍微改善一下情况,所以我开始进行一些基本的更改。
问题一:
我在 datetime
列上添加了聚集索引(以帮助查询)并在 servetime
上添加了非聚集索引(以帮助每日插入)。这是正确的还是应该相反?
问题二:
如果我们将 tables 分成,比如说,每月的块,并针对一个 VIEW 执行查询,所有这些都作为 UNION ALL
和 datetime
作为聚簇索引,会不会如果我们经常在跨越多个 tables?
COUNT(distinct X)
,这会有所帮助
任何其他可以在短期内帮助管理此问题的修复程序?
测试用例: 我在 3 个月的时间内使用各种索引测试了上述查询,得到了以下结果:
- 根本没有索引 - ~38 分钟
datetime
上的聚簇索引 - ~34 分钟datetime
的非聚集索引,所有相关列上的include
- 约 34 分钟
编辑:附加信息:
最近33%的时间
servertime
大于datetime
,datetime
也受时区变化的影响,夏令时下降到18% (格林威治标准时间+2/格林威治标准时间+1)使用
datetime
上传的日志数量很少,但并非微不足道,比当前时间戳早得多,有时甚至几个月。从我们的报告角度来看,这是 acceptable,但如果聚集索引不断添加新行,它可能会对datetime
产生重大影响。
您为 "datetime" 列创建聚集索引是正确的。如果不定期搜索 "servetime" 列,则非聚集索引没有太大帮助。
您在这里需要的是 分区 您的 table 并且可能会显着提高性能。它在逻辑上拆分您的 tables 数据,因此您无需更改任何现有查询,同时受益于拆分数据。
分区是一个复杂的概念。您可以找到有用的信息 here.
您的问题很复杂;它可能太宽泛了。但它有一个简单的解决方案,在 datetime
列上进行分区,因为这是用于查询表的列。我只想指出一些与此相关的高级问题。
但是,更简单的分区列是 servertime
——这将允许您只交换分区的进出。但是,这是以增加查询难度为代价的。如果您知道 datetime
总是在一个分区中,例如其值的三天内,您就可以完成这项工作。
一些数据库限制了您可以插入的 "open" 分区的数量。我不认为SQL服务器有这个限制。
但是,您会遇到另一个问题。 结果可能会随着时间的推移而改变。 因此,如果您要计算 2019-10-31 的任何数量。然后,您可以在 2019-11-01 和 2019-11-02 获得不同的值,依此类推,因为数据不断涌入。
如果您将数据用于需要静态的内容(例如财务报告),这可能是个大问题。您可能希望在查询中包含任意限制。像这样:
select *
from . . .
where partition_date = '2019-10-31' and
abs(datediff(day, servertime, datetime)) < 7;
请注意,我添加了一个伪列 partition_date
只是为了清楚什么用于分区。您可以为此直接使用 datetime
。
也就是一周内有数据进来。您没有指定 servertime
是否大于 datetime
。请注意,即使您认为这不是由于计算机上的时间漂移和可能的时区问题,这也是可能的。