繁体   English   中英

多个异步任务导致 SQL 服务器上的重复插入存储过程

[英]Multiple async tasks causing duplicates on SQL Server insert stored procedure

我有一个多线程应用程序,它遍历队列并获取数据并将这些数据发送到存储过程,然后将其插入我的表中。 问题是有时会在完全相同的时间插入此数据,这会导致插入重复的行。 现在这些行确实具有作为 id 的主键,但是所有其他列都是完全相同的数据。

这是我的循环,最多产生 20 个线程。

var task = new Task();

foreach(job in jobList)
{
    task = Task.Run(() => ProcessJobs(job)); 
}

Task.WaitAll(task);

每个线程读取自己单独的队列,然后处理每条消息并将其添加到 HashSet 以确保没有重复

private async Task<string> ProcessJobs(Job job)
{
     var messageData = getMessageFromQueue(message);
     HashSet<UserInfo> list = new HashSet<UserInfo>();

     foreach(var message in messageData)
     {
         list.Add(BuildMessage(message));
     }

     InsertIntoDB(list);
}

public HashSet<UserInfo> BuildMessage(MessageData messageData)
{
     return new UserInfo
                {
                    UserName = messageData.UserName,
                    Address = messageData.Address,
                    AccountType = messageData.Campaign?.AccountType == "G" ? "Type1" :"Type2",
                    AccountNumber = messageData.AccountList !=  null ? messageData.AccountList[0].ToString() : string.Empty.
                }
}

public struct UserInfo
{
    public string UserName { get; set; }
    public string Address { get; set; }
    public string AccountType { get; set; }
    public string AccountNumber { get; set; }
}

每条消息都被处理并作为表值参数发送到数据库以插入语句

public async Task<int> InsertIntoDB(HashSet<UserInfo> list)
{
    // First convert the hashset to a dataTable
    var dataTable = list.ToDatatable();

    // Convert to a TVP
    var params = new DynamicParameters();
    parameters.Add("@TVP_UserInfo", dataTable.AsTableValuedParameter("[dbo].[InsertUserInfo]"));

    using (var conn = new SqlConnection(ConfigurationManager.AppSettings["DatabaseConnection"]))
    {
        result = await conn.ExecuteAsync("InsertStoredProcedure", params, commanyType: CommandType.StoredProcedure);
    }
}

public DataTable ToDataTable<T>(this HashSet<T> iHashSet)
{
    DataTable dataTable = new DataTable();
    PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));

    for (int i = 0; i < props.Count; i++)
    {
        PropertyDescriptor propertyDescriptor = props[i];
        Type type = propertyDescriptor.PropertyType;

        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            type = Nullable.GetUnderlyingType(type);

        dataTable.Columns.Add(propertyDescriptor.Name, type);
    }

    object[] values = new object[props.Count];

    foreach (T iListItem in iHashSet)
    {
        for (int i = 0; i < values.Length; i++)
        {
             values[i] = props[i].GetValue(iListItem);
        }

        dataTable.Rows.Add(values);
    }

    return dataTable;
}

insert 语句读取 TVP 并插入

CREATE PROCEDURE [InsertStoredProcedure]
    (@TVP_UserInfo dbo.TVP_UserInfo READONLY)
AS
BEGIN
    DECLARE @currentDate datetime = CURRENT_TIMESTAMP

    INSERT INTO MyTable (UserName, Address, 
                         AccountType, AccountNumber, AccountDisplay,
                         CreatedDate)
        SELECT
            UserName, Address, 
            AccountType, AccountNumber, 
            CASE 
                WHEN AccountNumber IS NULL
                    THEN '' 
                    ELSE 'Anonymous' 
            END,
            @currentDate 
        FROM
            @TVP_UserInfo
END

这是 UDT 创建

CREATE TYPE [dbo].[TVP_UserInfo] 
    AS TABLE
       (
           UserName,
           Address, 
           AccountType,
           AccountNumber
       )

我偶尔会得到重复,我不知道它们是如何或从哪里来的,因为每条消息都应该是唯一的,因为我使用的是哈希集。

我在想它是导致它的多线程但是,如果我只运行一个任务,有时我仍然会得到重复项。 如果您注意到创建的日期一直到毫秒都是完全相同的。 Id (主键)不同,但剩余的行数据是实际重复的。

结果看起来像这样

ID 用户名 地址 帐号 账户显示 创建日期
1 乔斯地址1 123456 匿名的 2022-08-01 01:45:52:352
1 乔斯地址1 123456 匿名的 2022-08-01 01:45:52:352

UserName 是否允许在您的数据库中有重复项? 如果它不能包含重复项,我建议在该列上添加一个唯一索引(至少在开发中)。 这可能有助于您捕获导致重复的代码。

我可以看到一些事情:首先,您需要等待所有任务,而不仅仅是最后一个任务。

var tasks = new List<Task>

foreach(job in jobList)
{
    tasks.add(Task.Run(() => ProcessJobs(job))); 
}

Task.WaitAll(tasks.ToArray());

其次,我看不到 ProcessJobs 代码块将如何工作。

  • 消息变量超出 scope
  • InsertIntoDB 没有等待
  • 字符串没有返回值。

但是我认为您遇到的问题是代码将有多个线程访问getMessageFromQueue。 那么 is 和它的依赖是可重入和线程安全的吗? 如果所有的工作都是同步的,你可以使用锁 object 来限制它,如果你有其他异步工作最好使用 SemaphoreSlim 而不是锁,但锁会给你这个想法。

锁的例子

private lockobj = new lockobj();

private async Task<string> ProcessJobs(Job job)
{
    lock (lockobj)
     {
        var messageData = getMessageFromQueue(message);
     }
    /// rest of your code .... and return value 
    
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM