12 sql-server query
使用 MS SQL 2008,我从 250 万条记录中选择了一个平均字段。每条记录代表一秒。MyField 是这些 1 秒记录的每小时平均值。当然服务器CPU命中100%,选择时间太长。我可能需要保存这些平均值,以便 SQL 不必在每个请求中选择所有这些记录。可以做什么?
SELECT DISTINCT
CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour,
MIN([timestamp]) as TimeStamp,
AVG(MyField) As AvgField
FROM MyData
WHERE TimeStamp > '4/10/2011'
GROUP BY CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR)
ORDER BY TimeStamp
Run Code Online (Sandbox Code Playgroud)
查询的一部分是长时间使 CPU 最大化的部分是 GROUP BY 子句中的函数以及在这种情况下分组总是需要无索引排序的事实。虽然时间戳字段上的索引将有助于初始过滤器,但必须在过滤器匹配的每一行上执行此操作。加快速度是使用更有效的路线来完成 Alex 建议的相同工作将有所帮助,但您仍然存在巨大的低效率,因为您使用查询规划器的任何功能组合都无法想出任何索引都会有所帮助的东西,因此它必须首先运行每一行来运行函数来计算分组值,然后才能对数据进行排序并计算结果分组的聚合。
因此,解决方案是以某种方式通过它可以使用索引的东西使进程分组,或者以其他方式消除一次考虑所有匹配行的需要。
您可以为包含四舍五入到小时的时间的每一行维护一个额外的列,并索引该列以用于此类查询。这是对您的数据进行非规范化,因此可能会感觉“脏”,但它会起作用,并且比缓存所有聚合以供将来使用(并在更改基本数据时更新该缓存)更干净。额外的列应该由触发器维护或者是一个持久化的计算列,而不是由其他地方的逻辑维护,因为这将保证所有当前和未来可能插入数据或更新时间戳列或现有行的位置导致新的数据一致柱子。您仍然可以获取 MIN(时间戳)。以这种方式查询的结果仍然是遍历所有行(显然这是无法避免的)但它可以执行索引顺序,在到达索引中的下一个值时为每个分组输出一行,而不必在执行分组/聚合之前记住整个行集以进行未索引的排序操作。它也将使用更少的内存,因为它不需要记住来自先前分组值的任何行来处理它现在正在查看的行或其余的行。
该方法不需要在内存中查找整个结果集,并对组操作进行无索引排序,并从大查询中删除组值的计算(将该作业移到产生数据),并且应该允许此类查询以可接受的方式运行,而无需维护汇总结果的单独存储。
一种方法不非规范化您的数据,但仍然需要额外的结构,是使用“时间表”,在这种情况下,您可能会考虑的所有时间每小时包含一行。此表不会占用 DB 中的大量空间或可观的大小 - 要涵盖 100 年的时间跨度,包含一行两个日期(小时的开始和结束,例如 '2011-01-01@ 00:00:00.0000','2011-01-01@00:00:59.9997',“9997”是 DATETIME 字段不会向上舍入到下一秒的最小毫秒数),它们都是聚集主键将占用约 14Mbyte 的空间(每行 8+8 个字节 * 24 小时/天 * 365.25 天/年 * 100,加上聚集索引树结构的开销,但开销不会很大) .
SELECT CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour
, MIN([timestamp]) as TimeStamp
, AVG(MyField) As AvgField
FROM TimeRangeByHours tt
INNER JOIN MyData md ON md.TimeStamp BETWEEN tt.StartTime AND tt.EndTime
WHERE tt.StartTime > '4/10/2011'
GROUP BY tt.StartTime
ORDER BY tt.StartTime
Run Code Online (Sandbox Code Playgroud)
这意味着查询计划器可以安排使用 MyData.TimeStamp 上的索引。查询计划器应该足够聪明,可以计算出它可以与 MyData.TimeStamp 上的索引同步地沿着驯服表走下去,再次为每个分组输出一行,并在遇到下一个分组值时丢弃每个集合或行。不要将所有中间行存储在 RAM 中的某处,然后对它们执行未索引的排序。当然,此方法要求您创建时间表并确保它向后和向前跨越足够远,但您可以使用时间表对不同查询中的许多日期字段进行查询,而“额外列”选项需要您需要通过这种方式过滤/分组的每个日期字段的额外计算列,以及表格的小尺寸(除非您需要它跨越 10,
与您当前的情况和计算列解决方案相比,时间表方法有一个额外的区别(这可能非常有利):它可以返回没有数据期间的行,只需更改上面示例查询中的 INNER JOIN成为左外的。
有些人建议不要有物理时间表,而是总是从表返回函数中返回它。这意味着时间表的内容永远不会存储在(或需要从)磁盘上读取,如果函数写得很好,你永远不必担心时间表需要在时间上来回跨越多长时间,但我怀疑为每个查询生成某些行的内存表的 CPU 成本是否值得为创建(和维护,如果其时间跨度需要扩展到超出初始版本的限制)物理时间表的麻烦进行小幅节省。
旁注:您的原始查询也不需要 DISTINCT 子句。分组将确保这些查询在所考虑的每个时间段只返回一行,因此 DISTINCT 只会使 CPU 旋转多一点(除非查询计划器注意到不同的将是空操作,在这种情况下它会忽略它并且不使用额外的 CPU 时间)。
小智 -3
我会考虑放弃使用关系数据库模型实现此类计算的想法。特别是如果您有许多数据点,并且每秒都会收集其值。
如果您有钱,您可以考虑购买专用的过程数据历史记录,例如:
这些产品可以存储大量极其密集的时间序列数据(以专有格式),同时允许快速处理数据提取查询。查询可以指定许多数据点(也称为标签)、较长的时间间隔(月/年),并且还可以进行各种汇总数据计算(包括平均值)。
..一般来说:我DISTINCT
在编写 SQL 时总是尽量避免使用关键字。这绝不是一个好主意。在您的情况下,您应该能够通过添加到您的子句来删除DISTINCT
并获得相同的结果。MIN([timestamp])
GROUP BY
归档时间: |
|
查看次数: |
7778 次 |
最近记录: |