C# 如何根据数据库中的现有条目快速缩减数据表

C# 如何根据数据库中的现有条目快速缩减数据表,c#,sql,sql-server,datatable,sql-server-2012,C#,Sql,Sql Server,Datatable,Sql Server 2012,我目前正在尝试将datatable插入数据库。它工作得又好又快。唯一的问题出现了 如果数据库中已有任何行(重复键) 为了解决这个问题,我修改了我的程序,以便首先检查每个新条目是否已经存在于数据库中。 这很慢(在目前的情况下,我没有太多的条目,但后来有超过20万条条目需要检查,而且需要检查几次)。 因此,我需要使它更快,因为它是现在(如果可能的话) 数据表的结构如下: DataTable transactionTable.Columns.Add("DeviceId", typeof(Int32))

我目前正在尝试将datatable插入数据库。它工作得又好又快。唯一的问题出现了 如果数据库中已有任何行(重复键)

为了解决这个问题,我修改了我的程序,以便首先检查每个新条目是否已经存在于数据库中。 这很慢(在目前的情况下,我没有太多的条目,但后来有超过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)
DataTable insertTable=transactionTable.Copy();
insertTable.Clear();
使用(SqlConnection sqlcon=newsqlconnection(this.GetConnString()))
{
sqlcon.Open();
foreach(transactionTable.AsEnumerable()中的var项)
{
使用(SqlCommand sqlCom=sqlCon.CreateCommand())
{
sqlCom.Parameters.Clear();
sqlCom.CommandText=“从myTable中选择1,其中”
+“DeviceId=@DeviceId和LogDate=@LogDate”
+“和LogType=@LogType”
sqlCom.Parameters.AddWithValue(“@DeviceId”,entry.Field(“DeviceId”));
sqlCom.Parameters.AddWithValue(“@LogDate”,entry.Field(“LogDate”));
sqlCom.Parameters.AddWithValue(“@LogType”,entry.Field(“LogType”));
使用(SqlDataREader myRead=sqlCon.ExecuteReader()
{
myRead.Read();
if(myRead.HasRows==false)
{
insertTable.Rows.Add(entry.ItemArray);
}
}
}
}
}
//然后是我认为超出问题范围的插页
//(我在那里使用insertTable)

现在我的问题是:有没有什么方法可以更快地执行此操作以避免出现密钥冲突问题?

在这种情况下,我将使用一些暂存表。以下是一些步骤:

  • 批量插入到临时表中(使用
    SqlBulkCopy
  • 使用带有左联接的存储过程插入基表以消除现有行
  • 截断暂存表

  • 所以,您需要删除代码中的foreach语句,添加用于插入基表的存储过程,添加用于截断的存储过程。或者您可以将最后两个步骤组合在一起。

    我有一个类似的设置

    我正在使用带有参数和语句的存储过程。另请参见如何在.NET中使用它们的示例

    我将把问题的焦点从简单的批量插入转移到将一批行合并到一个包含现有数据的表中

    目的地表

    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
    ))
    
    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
    ))
    
    创建用户定义的表类型

    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
    ))
    
    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
    ))
    
    测试并测量为
    类型指定
    主键
    是否会加快或减慢整个过程

    带有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行的批


    如果您永远不想用新值更新现有行,请在匹配时删除
    ,然后将
    部分的
    合并
    ,这样会运行得更快。

    您可以在
    忽略重复键设置为ON时删除并重新创建索引。类似如下:

    ALTER TABLE datatable
    ADD CONSTRAINT PK_datatable 
    PRIMARY KEY CLUSTERED (DeviceId,LogDate,LogType,LogText) 
    WITH (IGNORE_DUP_KEY = ON)
    
    此选项的作用是在尝试为索引插入任何重复项时报告具有不同严重性的重复项错误和消息。它将不允许输入重复项,但将继续插入所有不重复的记录,并且仅在发现并忽略重复项时才发出警告消息


    更多信息请访问此链接:。

    查看
    SQL合并
    ()如果您的数据库位于另一个系统/服务器上,则在迭代过程中减少对数据库的调用次数可能有助于提高低速网络连接的性能,即一次性从数据库中获取相关记录并比较断开连接的数据data@harman因为它的日志条目只有一个日期和一个类型,我不能说这个类型在此之后,…在相关数据方面没有什么可依据的。因此,不幸的是,在这个方向上,我没有什么可参考的see@JamesBrierley使用sql合并意味着什么?(我的问题是,我有一个datatable,我将它与一个大容量复制一起放入数据库,如果它运行到重复的键中,该大容量复制将自动失败。因此,不确定sql merge语句在这方面有何帮助?)我想您可以使用类似的解决方案,但我自己对sql merge不太了解。我对这种方法与SqlBulkCopy的性能比较感兴趣。对于较小的行,这可能是应用程序多行插入/更新的标准方式。尤其是在可能有触发器的表上。对于使用C#我是否正确,我认为lkcopy到用户定义的表中,然后使用上面的过程将结果合并到普通表中?@Thomas,否。这里没有批量复制。请参阅MSDN中的示例如何使用TVP调用此类存储过程。答案中有此链接:啊,这样我就可以使用普通的sqlcommand执行该过程并一次性填充整个过程从数据表中。不知道(一直认为sqlbulkcopy是唯一的方法).tnxTVP数据使用与SqlBulkCopy相同的内部插入批量技术传递到SQL Server。不同之处在于,目标是由SQL Server管理的内部临时表,该表公开TVP数据。此外,将TVP作为
    IEnumerable
    传递可以流式传输TVP数据,而无需立即将整个源数据集加载到内存中.+1.我完全忘记了这个选项。使用这个选项,简单的批量插入可能比
    合并
    更快。服务器仍然必须执行与
    合并
    几乎相同的操作,但这些内部操作可能会更优化。值得尝试和测量