如何根据数据库中的现有条目快速减少数据表
How to fast reduce a datatable according to existing entries in a DB
我目前正在尝试将数据表批量插入到数据库中。它工作得很好而且很快。唯一的问题出现
如果数据库中已有任何行(重复键)。
为了解决这个问题,我修改了我的程序,以便我首先检查每个新条目是否已存在于数据库中。
这是.......慢(在目前的情况下,我没有很多条目,但后来我需要检查超过 20 万个条目,并且检查了几次)。
因此我需要让它像现在这样更快(如果可能的话)。
数据表的结构如下:
DataTable transactionTable.Columns.Add("DeviceId", typeof(Int32));
transactionTable.Columns.Add("LogDate", typeof(DateTime));
transactionTable.Columns.Add("LogType", typeof(Int32));
transactionTable.Columns.Add("LogText", typeof(String));
transactionTable.PrimaryKey = new DataColumn[3] {
transactionTable.Columns[0],
transactionTable.Columns[1],
transactionTable.Columns[2]
};
到目前为止我有以下内容:
DataTable insertTable = transactionTable.Copy();
insertTable.Clear();
using (SqlConnection sqlcon = new SqlConnection(this.GetConnString()))
{
sqlcon.Open();
foreach (var entry in transactionTable.AsEnumerable())
{
using (SqlCommand sqlCom = sqlCon.CreateCommand())
{
sqlCom.Parameters.Clear();
sqlCom.CommandText = "SELECT 1 FROM myTable WHERE"
+ " DeviceId = @DeviceId AND LogDate = @LogDate"
+ " AND LogType = @LogType"
sqlCom.Parameters.AddWithValue("@DeviceId", entry.Field<Int32>("DeviceId"));
sqlCom.Parameters.AddWithValue("@LogDate", entry.Field<DateTime>("LogDate"));
sqlCom.Parameters.AddWithValue("@LogType", entry.Field<Int32>("LogType"));
using (SqlDataREader myRead = sqlCon.ExecuteReader()
{
myRead.Read();
if (myRead.HasRows == false)
{
insertTable.Rows.Add(entry.ItemArray);
}
}
}
}
}
// And afterwards the bulkinsert which I think is out of scope for the question itself
// (I use the insertTable there)
现在我的问题是:有没有什么方法可以更快地完成此操作以避免出现密钥违规问题?
在这种情况下,我会使用一些分期 table。以下是一些步骤:
- 批量插入分段 table(使用
SqlBulkCopy
)
- 使用带左连接的存储过程插入到基 table 中以消除现有行
- 截断分期table
因此您需要删除代码中的 foreach 语句,添加用于插入到基 table 的存储过程,添加用于截断的存储过程。或者您可以将最后 2 个步骤合二为一。
我有一个类似的设置。
我正在使用带有 Table-Valued parameter and MERGE
statement. See also Table-Valued Parameters 的存储过程,例如如何在 .NET 中使用它们。
我会将问题的重点从简单的批量插入转移到将一批行合并到 table 与现有数据中。
目的地table
CREATE TABLE [dbo].[MyTable](
[DeviceId] [int] NOT NULL,
[LogDate] [datetime] NOT NULL,
[LogType] [int] NOT NULL,
[LogText] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED
(
[DeviceId] ASC,
[LogDate] ASC,
[LogType] ASC
))
创建用户自定义table类型
CREATE TYPE [dbo].[MyTableType] AS TABLE(
[DeviceId] [int] NOT NULL,
[LogDate] [datetime] NOT NULL,
[LogType] [int] NOT NULL,
[LogText] [nvarchar](50) NOT NULL,
PRIMARY KEY CLUSTERED
(
[DeviceId] ASC,
[LogDate] ASC,
[LogType] ASC
))
测试并衡量为 TYPE
指定 PRIMARY KEY
是否会使整个过程更快或更慢。
带 TVP 的存储过程
CREATE PROCEDURE [dbo].[MergeMyTable]
@ParamRows dbo.MyTableType READONLY
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
MERGE INTO dbo.MyTable as Dest
USING
(
SELECT
TT.[DeviceId],
TT.[LogDate],
TT.[LogType],
TT.[LogText]
FROM
@ParamRows AS TT
) AS Src
ON
(Dest.[DeviceId] = Src.[DeviceId]) AND
(Dest.[LogDate] = Src.[LogDate]) AND
(Dest.[LogType] = Src.[LogType])
WHEN MATCHED THEN
UPDATE SET
Dest.[LogText] = Src.[LogText]
WHEN NOT MATCHED BY TARGET THEN
INSERT
([DeviceId]
,[LogDate]
,[LogType]
,[LogText])
VALUES
(Src.[DeviceId],
Src.[LogDate],
Src.[LogType],
Src.[LogText]);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH;
END
调用此存储过程将一批要合并的行传递给它。测试和测量性能如何随批次大小而变化。尝试使用 1K、10K、100K 行进行批处理。
如果您不想用新值更新现有行,请删除 MERGE
的 WHEN MATCHED THEN
部分,这样会更快。
您可以在 IGNORE_DUP_KEY
设置为 ON 的情况下删除并重新创建索引。像这样:
ALTER TABLE datatable
ADD CONSTRAINT PK_datatable
PRIMARY KEY CLUSTERED (DeviceId,LogDate,LogType,LogText)
WITH (IGNORE_DUP_KEY = ON)
此选项的作用是在尝试对索引进行任何重复插入时报告具有不同严重性和消息的重复键错误。它不允许输入重复项,但会继续插入所有不重复的记录,并且仅在发现并忽略重复项时才给出警告消息。
更多信息在此 link:Creating Unique Indexes。
我目前正在尝试将数据表批量插入到数据库中。它工作得很好而且很快。唯一的问题出现 如果数据库中已有任何行(重复键)。
为了解决这个问题,我修改了我的程序,以便我首先检查每个新条目是否已存在于数据库中。 这是.......慢(在目前的情况下,我没有很多条目,但后来我需要检查超过 20 万个条目,并且检查了几次)。 因此我需要让它像现在这样更快(如果可能的话)。
数据表的结构如下:
DataTable transactionTable.Columns.Add("DeviceId", typeof(Int32));
transactionTable.Columns.Add("LogDate", typeof(DateTime));
transactionTable.Columns.Add("LogType", typeof(Int32));
transactionTable.Columns.Add("LogText", typeof(String));
transactionTable.PrimaryKey = new DataColumn[3] {
transactionTable.Columns[0],
transactionTable.Columns[1],
transactionTable.Columns[2]
};
到目前为止我有以下内容:
DataTable insertTable = transactionTable.Copy();
insertTable.Clear();
using (SqlConnection sqlcon = new SqlConnection(this.GetConnString()))
{
sqlcon.Open();
foreach (var entry in transactionTable.AsEnumerable())
{
using (SqlCommand sqlCom = sqlCon.CreateCommand())
{
sqlCom.Parameters.Clear();
sqlCom.CommandText = "SELECT 1 FROM myTable WHERE"
+ " DeviceId = @DeviceId AND LogDate = @LogDate"
+ " AND LogType = @LogType"
sqlCom.Parameters.AddWithValue("@DeviceId", entry.Field<Int32>("DeviceId"));
sqlCom.Parameters.AddWithValue("@LogDate", entry.Field<DateTime>("LogDate"));
sqlCom.Parameters.AddWithValue("@LogType", entry.Field<Int32>("LogType"));
using (SqlDataREader myRead = sqlCon.ExecuteReader()
{
myRead.Read();
if (myRead.HasRows == false)
{
insertTable.Rows.Add(entry.ItemArray);
}
}
}
}
}
// And afterwards the bulkinsert which I think is out of scope for the question itself
// (I use the insertTable there)
现在我的问题是:有没有什么方法可以更快地完成此操作以避免出现密钥违规问题?
在这种情况下,我会使用一些分期 table。以下是一些步骤:
- 批量插入分段 table(使用
SqlBulkCopy
) - 使用带左连接的存储过程插入到基 table 中以消除现有行
- 截断分期table
因此您需要删除代码中的 foreach 语句,添加用于插入到基 table 的存储过程,添加用于截断的存储过程。或者您可以将最后 2 个步骤合二为一。
我有一个类似的设置。
我正在使用带有 Table-Valued parameter and MERGE
statement. See also Table-Valued Parameters 的存储过程,例如如何在 .NET 中使用它们。
我会将问题的重点从简单的批量插入转移到将一批行合并到 table 与现有数据中。
目的地table
CREATE TABLE [dbo].[MyTable](
[DeviceId] [int] NOT NULL,
[LogDate] [datetime] NOT NULL,
[LogType] [int] NOT NULL,
[LogText] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED
(
[DeviceId] ASC,
[LogDate] ASC,
[LogType] ASC
))
创建用户自定义table类型
CREATE TYPE [dbo].[MyTableType] AS TABLE(
[DeviceId] [int] NOT NULL,
[LogDate] [datetime] NOT NULL,
[LogType] [int] NOT NULL,
[LogText] [nvarchar](50) NOT NULL,
PRIMARY KEY CLUSTERED
(
[DeviceId] ASC,
[LogDate] ASC,
[LogType] ASC
))
测试并衡量为 TYPE
指定 PRIMARY KEY
是否会使整个过程更快或更慢。
带 TVP 的存储过程
CREATE PROCEDURE [dbo].[MergeMyTable]
@ParamRows dbo.MyTableType READONLY
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN TRANSACTION;
BEGIN TRY
MERGE INTO dbo.MyTable as Dest
USING
(
SELECT
TT.[DeviceId],
TT.[LogDate],
TT.[LogType],
TT.[LogText]
FROM
@ParamRows AS TT
) AS Src
ON
(Dest.[DeviceId] = Src.[DeviceId]) AND
(Dest.[LogDate] = Src.[LogDate]) AND
(Dest.[LogType] = Src.[LogType])
WHEN MATCHED THEN
UPDATE SET
Dest.[LogText] = Src.[LogText]
WHEN NOT MATCHED BY TARGET THEN
INSERT
([DeviceId]
,[LogDate]
,[LogType]
,[LogText])
VALUES
(Src.[DeviceId],
Src.[LogDate],
Src.[LogType],
Src.[LogText]);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH;
END
调用此存储过程将一批要合并的行传递给它。测试和测量性能如何随批次大小而变化。尝试使用 1K、10K、100K 行进行批处理。
如果您不想用新值更新现有行,请删除 MERGE
的 WHEN MATCHED THEN
部分,这样会更快。
您可以在 IGNORE_DUP_KEY
设置为 ON 的情况下删除并重新创建索引。像这样:
ALTER TABLE datatable
ADD CONSTRAINT PK_datatable
PRIMARY KEY CLUSTERED (DeviceId,LogDate,LogType,LogText)
WITH (IGNORE_DUP_KEY = ON)
此选项的作用是在尝试对索引进行任何重复插入时报告具有不同严重性和消息的重复键错误。它不允许输入重复项,但会继续插入所有不重复的记录,并且仅在发现并忽略重复项时才给出警告消息。
更多信息在此 link:Creating Unique Indexes。