我有一个数据库表,以每秒/秒/设备约4条记录的速率收集记录。这张桌子非常快。当设备完成其任务时,另一个进程将遍历所有记录,执行一些操作,将它们组合成5分钟的块,压缩它们并存储它们以供以后使用。然后它删除该表中该设备的所有记录。
目前,有几个设备有近100万条记录。我可以循环浏览它们就可以执行处理了,但是当我尝试删除它时,我会超时。有没有办法更快地删除这些记录?也许暂时关闭对象跟踪?使用一些锁定提示?设计是否更好,只需在每个设备开始其任务时为每个设备创建一个单独的表,然后在数据处理完成后将其丢弃?超时设置为10分钟。如果可能的话,我真的希望在10分钟内完成该过程。
CREATE TABLE [dbo].[case_waveform_data] (
[case_id] INT NOT NULL,
[channel_index] INT NOT NULL,
[seconds_between_points] REAL NOT NULL,
[last_time_stamp] DATETIME NOT NULL,
[value_array] VARBINARY (8000) NULL,
[first_time_stamp] DATETIME NULL
);
CREATE CLUSTERED INDEX [ClusteredIndex-caseis-channelindex] ON [dbo]. [case_waveform_data]
(
[case_id] ASC,
[channel_index] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [NonClusteredIndex-all-fields] ON [dbo].[case_waveform_data]
(
[case_id] ASC,
[channel_index] ASC,
[last_time_stamp] ASC
)
INCLUDE ( [seconds_between_points],
[value_array]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON PRIMARY]
SQL Server 2008+标准是数据库平台
更新2014年3月31日:
我开始走上似乎有问题的道路。这真的那么糟糕吗? 我正在创建一个存储过程,它接受一个包含我要追加的数据的table-value参数,以及一个包含设备唯一表名的varchar参数。这个存储过程将检查表是否存在,如果不存在,则使用特定结构创建它。然后它将从TVP插入数据。我看到的问题是我必须在SP中使用动态sql,因为似乎没有办法将表名作为变量传递给CREATE或INSERT。另外,我读到的关于如何做到这一点的每篇文章都说不是......
不幸的是,如果我有一个表以4 /秒/设备的频率获取所有插入,那么即使在case_id和channel_index上使用聚簇索引,只需对表执行特定case_id的计数需要17分钟。所以试图删除它们需要大约25-30分钟。这也导致发生锁定,因此插入件开始花费的时间越来越长,这导致服务落后。即使没有发生删除也会发生这种情况。 所描述的存储过程旨在将插入从4 /秒/设备减少到1 /秒/设备,并且可以在完成时丢弃表而不是单独删除每个记录。想法?
更新2 3/31/2014
我没有像你想象的那样使用游标或任何循环。这是我用来遍历记录的代码。然而,这以可接受的速度运行:
using (SqlConnection liveconn = new SqlConnection(LiveORDataManager.ConnectionString))
{
using (SqlCommand command = liveconn.CreateCommand())
{
command.CommandText = channelQueryString;
command.Parameters.AddWithValue("channelIndex", channel);
command.CommandTimeout = 600;
liveconn.Open();
SqlDataReader reader = command.ExecuteReader();
// Call Read before accessing data.
while (reader.Read())
{
var item = new
{
//case_id = reader.GetInt32(0),
channel_index = reader.GetInt32(0),
last_time_stamp = reader.GetDateTime(1),
seconds_between_points = reader.GetFloat(2),
value_array = (byte[])reader.GetSqlBinary(3)
};
// Perform processing on item
}
}
}
我用来删除的SQL很简单:
DELETE FROM case_waveform_data where case_id = @CaseId
此行需要25多分钟才能删除100万行
示例数据(value_array被截断):
case_id channel_index seconds_between_points last_time_stamp value_array first_time_stamp
7823 0 0.002 2014-03-31 15:00:40.660 0x1F8B0800000000000400636060 NULL
7823 0 0.002 2014-03-31 15:00:41.673 0x1F8B08000000000004006360646060F80F04201A04F8418C3F4082DBFBA2F29E5 NULL
7823 0 0.002 2014-03-31 15:00:42.690 0x1F8B08000000000004006360646060F80F04201A04F8418C3F4082DBFB NULL
答案 0 :(得分:0)
当从表中删除大量数据时,在SQL服务器下面标记要删除的数据,并且作为后台作业,sql server实际上将从页面中删除它们,因为它有一些空闲时间。也不像Truncate;删除是日志启用。
如果您有Enterprise Edition,正如其他开发人员建议使用分区是可行的方法,但您有标准版。
选项:1“更长,更乏味,主观100%表现”
假设您保留单一表格方法。您可以添加新列“IsProcessed”以指示已处理的记录。当您插入新数据时,它将具有默认值0,因此使用此数据的其他进程现在也将使用此列过滤其查询。处理完毕后,您需要对表进行额外更新,将这些行标记为IsProcessed = 1。现在,您可以创建SQL Server JOB以删除Isprocessed = 1的前N行,并在理想时隙上尽可能频繁地安排该作业。 “TOP N”因为您必须通过尝试和错误找出适合您环境的最佳数字。它可能是100,1000,10,000。根据我的经验,如果数量较小,效果最佳。增加工作执行的频率。让我们说“从表中删除前1000名”需要2分钟。如果没有使用此表,您有4个小时的清洁窗口,您可以安排此工作每5分钟运行一次。 3分钟就是缓冲区。因此,在4小时内每次执行12个exec /小时和1000行,您将从表中删除48k行。然后在周末你有更大的窗口,你将不得不赶上剩余的行。 你可以在这种方法中看到许多来回的广告细节,但不确定这将是否会持续很长时间以满足您的需求。突然,输入的数据量增加了一倍,你的所有计算都会崩溃。这种方法的另一个缺点是消费者对数据的查询现在必须依赖于IsProcessed Column值。在您的特定情况下,使用者总是读取设备的所有数据,因此索引表对您没有帮助,否则会损害插入过程的性能。 我亲身体验了这个解决方案,并在我们的客户环境中持续了2年。
选项:2“快速,高效,对我有意义,可为你工作”
为设备创建一个表,如上所述,如果不存在,则使用存储过程动态创建表。这是我最近的经验,我们有元数据驱动的ETL,并且所有ETL Target对象和API都是在运行时根据用户配置创建的。是的,它是动态SQL,但如果明智地使用,一旦性能测试,它也不错。如果出现问题,这种方法的缺点是在初始阶段进行调试。但在你的情况下,你知道表结构,它是固定的,你不处理表结构的每日变化。这就是为什么我认为这更适合你的情况。另一件事是你现在必须确保正确配置TempDB,因为使用TVP和临时表大大增加了tempDB的使用率,因此你初始化和增加分配给tempdb的空间,tempDB所在的磁盘是两个要看的主要内容。正如我在Option-1中所说的,因为消费者对数据的处理总是使用ALL DATA我认为你不需要任何额外的索引。事实上,我也会测试没有任何索引的性能。它就像处理所有分段数据一样。
查看此approch的示例代码。如果您对此方法感到满意并且对此方法或其他任何方面有疑问,请告知我们。
准备架构对象
IF OBJECT_ID('pr_DeviceSpecificInsert','P') IS NOT NULL
DROP PROCEDURE pr_DeviceSpecificInsert
GO
IF EXISTS (
SELECT TOP 1 *
FROM sys.table_types
WHERE name = N'udt_DeviceSpecificData'
)
DROP TYPE dbo.udt_DeviceSpecificData
GO
CREATE TYPE dbo.udt_DeviceSpecificData
AS TABLE
(
testDeviceData sysname NULL
)
GO
CREATE PROCEDURE pr_DeviceSpecificInsert
(
@DeviceData dbo.udt_DeviceSpecificData READONLY
,@DeviceName NVARCHAR(200)
)
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
BEGIN TRAN
DECLARE @SQL NVARCHAR(MAX)=N''
,@ParaDef NVARCHAR(1000)=N''
,@TableName NVARCHAR(200)=ISNULL(@DeviceName,N'')
--get the UDT data into temp table
--because we can not use UDT/Table Variable in dynamic SQL
SELECT * INTO #Temp_DeviceData FROM @DeviceData
--Drop and Recreate the Table for Device.
BEGIN
SET @SQL ='
if object_id('''+@TableName+''',''u'') IS NOT NULL
drop table dbo.'+@TableName+'
CREATE TABLE dbo.'+@TableName+'
(
RowID INT IDENTITY NOT NULL
,testDeviceData sysname NULL
)
'
PRINT @SQL
EXECUTE sp_executesql @SQL
END
--Insert the UDT data in to actual table
SET @SQL ='
Insert INTO '+@TableName+N' (testDeviceData)
Select testDeviceData From #Temp_DeviceData
'
PRINT @SQL
EXECUTE sp_executesql @SQL
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
SELECT ERROR_MESSAGE()
END CATCH
SET NOCOUNT OFF
END
执行示例代码
DECLARE @DeviceData dbo.udt_DeviceSpecificData
INSERT @DeviceData (testDeviceData)
SELECT 'abc'
UNION ALL SELECT 'xyz'
EXECUTE dbo.pr_DeviceSpecificInsert
@DeviceData = @DeviceData, -- udt_DeviceSpecificData
@DeviceName = N'tbl2' -- nvarchar(200)