在America's Cup游艇中,我们生成大型数据集,其中在每个时间戳(例如100Hz)我们需要存储100-1000个通道的传感器数据(例如速度,负载,压力)。我们将其存储在MS SQL Server中,并且需要能够检索数据的通道子集以进行分析,并执行查询,例如测试中特定传感器的最大压力,或整个赛季。
要存储的通道集对于数千个时间戳保持不变,但是随着新传感器的添加,重命名等等,日常会发生变化......并且取决于测试,比赛或模拟,频道数量可能差异很大。
构建SQL表的教科书方式可能是:
选项1
ChannelNames
+-----------+-------------+
| ChannelID | ChannelName |
+-----------+-------------+
| 50 | Pressure |
| 51 | Speed |
| ... | ... |
+-----------+-------------+
Sessions
+-----------+---------------+-------+----------+
| SessionID | Location | Boat | Helmsman |
+-----------+---------------+-------+----------+
| 789 | San Francisco | BoatA | SailorA |
| 790 | San Francisco | BoatB | SailorB |
| ... | ... | ... | |
+-----------+---------------+-------+----------+
SessionTimestamps
+-------------+-------------+------------------------+
| SessionID | TimestampID | DateTime |
+-------------+-------------+------------------------+
| 789 | 12345 | 2013/08/17 10:30:00:00 |
| 789 | 12346 | 2013/08/17 10:30:00:01 |
| ... | ... | ... |
+-------------+-------------+------------------------+
ChannelData
+-------------+-----------+-----------+
| TimestampID | ChannelID | DataValue |
+-------------+-----------+-----------+
| 12345 | 50 | 1015.23 |
| 12345 | 51 | 12.23 |
| ... | ... | ... |
+-------------+-----------+-----------+
这种结构整洁但效率低下。每个DataValue需要三个存储字段,每个时间戳我们需要插入100-1000行。
如果我们总是拥有相同的频道,那么每个时间戳和结构使用一行更为明智:
选项2
+-----------+------------------------+----------+-------+----------+--------+-----+
| SessionID | DateTime | Pressure | Speed | LoadPt | LoadSb | ... |
+-----------+------------------------+----------+-------+----------+--------+-----+
| 789 | 2013/08/17 10:30:00:00 | 1015.23 | 12.23 | 101.12 | 98.23 | ... |
| 789 | 2013/08/17 10:30:00:01 | 1012.51 | 12.44 | 100.33 | 96.82 | ... |
| ... | ... | ... | | | | |
+-----------+------------------------+----------+-------+----------+--------+-----+
然而,频道每天都在变化,并且在几个月内,列数会增长和增长,大多数单元格都会变空。我们可以为每个新的Session创建一个新表,但是使用表名作为变量感觉不对,并且最终会导致数万个表 - 而且,在一个季节中查询变得非常困难,数据存储在多个表中。
另一种选择是:
选项3
+-----------+------------------------+----------+----------+----------+----------+-----+
| SessionID | DateTime | Channel1 | Channel2 | Channel3 | Channel4 | ... |
+-----------+------------------------+----------+----------+----------+----------+-----+
| 789 | 2013/08/17 10:30:00:00 | 1015.23 | 12.23 | 101.12 | 98.23 | ... |
| 789 | 2013/08/17 10:30:00:01 | 1012.51 | 12.44 | 100.33 | 96.82 | ... |
| ... | ... | ... | | | | |
+-----------+------------------------+----------+----------+----------+----------+-----+
从Channel列ID到通道名称的查找 - 但是这需要EXEC或eval来执行预构造的查询以获得我们想要的通道 - 因为SQL不是设计为将列名称作为变量。从好的方面来说,我们可以在频道改变时重新使用列,但仍然会有很多空单元格,因为表格必须与我们遇到的最大数量的频道一样宽。使用SPARSE表可能对此有所帮助,但我对上面的EXEC / eval问题感到不舒服。
这个问题的正确解决方案是什么,可以实现存储,插入和查询的效率?
答案 0 :(得分:2)
我会选择选项1。
首先是数据完整性,优化(如果需要) - 第二。
其他选项最终会有很多NULL
值,而其他问题则源于未正常化。管理数据和进行有效查询将很困难。
此外,桌子可以有一个limit on the number of columns - 1024,所以如果你有1000个传感器/通道,你已经危险地接近极限了。即使你使表wide table(允许30,000列),表中行的大小仍有限制 - 每行8,060字节。并且有一定的performance considerations。
在这种情况下,我不会使用宽表,即使我确定每行的数据永远不会超过8060字节,并且不断增加的通道数永远不会超过30,000。
我没有看到在选项1中插入100 - 1000行而在其他选项中插入1行时出现问题。要做到这一点INSERT
有效地做出1000个单独的INSERT
声明,请批量处理。在我系统的不同位置,我使用以下两种方法:
1)构建一个长INSERT
语句
INSERT INTO ChannelData (TimestampID, ChannelID, DataValue) VALUES
(12345, 50, 1015.23),
(12345, 51, 12.23),
...
(), (), (), (), ........... ();
包含1000行并在一个事务中以普通INSERT
执行,而不是1000个事务(检查syntax details)。
2)拥有一个接受table-valued parameter的存储过程。调用此类过程将1000行作为表传递。
CREATE TYPE [dbo].[ChannelDataTableType] AS TABLE(
[TimestampID] [int] NOT NULL,
[ChannelID] [int] NOT NULL,
[DataValue] [float] NOT NULL
)
GO
CREATE PROCEDURE [dbo].[InsertChannelData]
-- Add the parameters for the stored procedure here
@ParamRows dbo.ChannelDataTableType READONLY
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
INSERT INTO [dbo].[ChannelData]
([TimestampID],
[ChannelID],
[DataValue])
SELECT
TT.[TimestampID]
,TT.[ChannelID]
,TT.[DataValue]
FROM
@ParamRows AS TT
;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH;
END
GO
如果可能,在插入之前累积来自多个时间戳的数据以使批量更大。您应该尝试使用您的系统并找到批次的最佳大小。我使用存储过程批量约10K行。
如果你的数据来自传感器每秒100次,那么我首先会将传入的原始数据转储到一些非常简单的CSV文件中,并且有一个并行的后台进程,可以将它以块的形式插入到数据库中。换句话说,为传入数据设置一些缓冲区,这样如果服务器无法处理传入的卷,您就不会丢失数据。
根据您的评论,当您说某些频道可能更有趣并且多次查询时,而其他频道则不那么有趣,这里有一个我会考虑的优化。除了为所有频道设置一个表ChannelData
之外,还有另一个表InterestingChannelData
。 ChannelData
将拥有整套数据,以防万一。 InterestingChannelData
只有最有趣的频道的子集。它应该小得多,查询它应该花费更少的时间。无论如何,这是在正确规范化结构之上构建的优化(非规范化/数据复制)。
答案 1 :(得分:1)
你的流程是这样的:
如果这些是单独的活动,那么您可能需要考虑使用不同的“插入”和“选择”模式。您可以创建一个快速插入船上的模式,然后批量上传此数据到分析优化模式。这需要转换步骤(例如,您将通用列名称映射到有用的列名称)
这与数据仓库和数据集市一致。在这种设计中,您可以批量加载和优化模式以进行报告。您当前的每日上传是否有很多窗口?