并行性是否与数据表写入事务的锁有用

本文关键字:事务 有用 是否 数据表 并行性 | 更新日期: 2023-09-27 18:32:55

并行性是否有助于锁定对象的性能,它应该单线程运行,还是有其他技术?

我注意到在访问数据集并从多个线程添加行时,会抛出异常。 因此,我创建了一个"线程安全"版本,通过在更新行之前锁定表来添加行。 此实现有效,但在许多事务中显得很慢。

public partial class HaMmeRffl
{
    public partial class PlayerStatsDataTable
    {
        public void AddPlayerStatsRow(int PlayerID, int Year, int StatEnum, int Value, DateTime Timestamp)
        {
            lock (TeamMemberData.Dataset.PlayerStats)
            {
                HaMmeRffl.PlayerStatsRow testrow = TeamMemberData.Dataset.PlayerStats.FindByPlayerIDYearStatEnum(PlayerID, Year, StatEnum);
                if (testrow == null)
                {
                    HaMmeRffl.PlayerStatsRow newRow = TeamMemberData.Dataset.PlayerStats.NewPlayerStatsRow();
                    newRow.PlayerID = PlayerID;
                    newRow.Year = Year;
                    newRow.StatEnum = StatEnum;
                    newRow.Value = Value;
                    newRow.Timestamp = Timestamp;
                    TeamMemberData.Dataset.PlayerStats.AddPlayerStatsRow(newRow);
                }
                else
                {
                    testrow.Value = Value;
                    testrow.Timestamp = Timestamp;
                }
            }
        }
    }
}

现在我可以从多个线程安全地调用它,但它真的能给我买任何东西吗?我可以以不同的方式执行此操作以获得更好的性能吗? 例如,有没有办法使用System.Collections.Concurrent命名空间来优化性能或任何其他方法?

此外,我在整个数据集更新后更新基础数据库,这需要很长时间。 这是否被视为 I/O 操作,并且值得在数据集(或一定数量的行(中更新每一行后通过更新它来使用并行处理。

更新

我写了一些代码来测试并发处理与顺序处理,这表明进行并发处理需要大约 30% 的时间,我应该在这里使用顺序处理。我认为这是因为数据库上的锁导致ConcurrentQueue上的开销比并行处理的收益更昂贵。 这个结论是否正确,我是否可以采取任何措施来加快处理速度,或者我是否陷入了"您必须同步任何写入操作"Datatable

这是我的测试代码,在科学上可能不正确。 这是计时器和它们之间的呼叫。

            dbTimer.Restart();
            Queue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRow = InsertToPlayerQ(addUpdatePlayers);
            Queue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRow                     = InsertToPlayerStatQ(addUpdatePlayers);
            UpdatePlayerStatsInDB(addPlayerRow, addPlayerStatRow);
            dbTimer.Stop();
            System.Diagnostics.Debug.Print("Writing to the dataset took {0} seconds single threaded", dbTimer.Elapsed.TotalSeconds);
            dbTimer.Restart();
            ConcurrentQueue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows                    = InsertToPlayerQueue(addUpdatePlayers);
            ConcurrentQueue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows                    = InsertToPlayerStatQueue(addUpdatePlayers);
            UpdatePlayerStatsInDB(addPlayerRows, addPlayerStatRows);
            dbTimer.Stop();
            System.Diagnostics.Debug.Print("Writing to the dataset took {0} seconds concurrently", dbTimer.Elapsed.TotalSeconds);

在这两个示例中,我都以相同的方式添加到Queue和单线程ConcurrentQueue。 唯一的区别是插入到数据表中。 单线程方法插入如下:

    private static void UpdatePlayerStatsInDB(Queue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows, Queue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows)
    {
        try
        {
            HaMmeRffl.PlayersRow.PlayerValue row;
            while (addPlayerRows.Count > 0)
            {
                row = addPlayerRows.Dequeue();
                TeamMemberData.Dataset.Players.AddPlayersRow(
                    row.PlayerID, row.Name, row.PosEnum, row.DepthEnum,
                    row.TeamID, row.RosterTimestamp, row.DepthTimestamp,
                    row.Active, row.NewsUpdate);
            }
        }
        catch (Exception)
        {
            TeamMemberData.Dataset.Players.RejectChanges();
        }
        try
        {
            HaMmeRffl.PlayerStatsRow.PlayerStatValue row;
            while (addPlayerStatRows.Count > 0)
            {
                row = addPlayerStatRows.Dequeue();
                TeamMemberData.Dataset.PlayerStats.AddUpdatePlayerStatsRow(
                    row.PlayerID, row.Year, row.StatEnum, row.Value, row.Timestamp);
            }
        }
        catch (Exception)
        {
            TeamMemberData.Dataset.PlayerStats.RejectChanges();
        }
        TeamMemberData.Dataset.Players.AcceptChanges();
        TeamMemberData.Dataset.PlayerStats.AcceptChanges();
    }

并发添加如下

    private static void UpdatePlayerStatsInDB(ConcurrentQueue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows, ConcurrentQueue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows)
    {
        Action actionPlayer = () =>
        {
            HaMmeRffl.PlayersRow.PlayerValue row;
            while (addPlayerRows.TryDequeue(out row))
            {
                TeamMemberData.Dataset.Players.AddPlayersRow(
                    row.PlayerID, row.Name, row.PosEnum, row.DepthEnum,
                    row.TeamID, row.RosterTimestamp, row.DepthTimestamp,
                    row.Active, row.NewsUpdate);
            }
        };
        Action actionPlayerStat = () =>
        {
            HaMmeRffl.PlayerStatsRow.PlayerStatValue row;
            while (addPlayerStatRows.TryDequeue(out row))
            {
                TeamMemberData.Dataset.PlayerStats.AddUpdatePlayerStatsRow(
                    row.PlayerID, row.Year, row.StatEnum, row.Value, row.Timestamp);
            }
        };
        Action[] actions = new Action[Environment.ProcessorCount * 2];
        for (int i = 0; i < Environment.ProcessorCount; i++)
        {
            actions[i * 2] = actionPlayer;
            actions[i * 2 + 1] = actionPlayerStat;
        }
        try
        {
            // Start ProcessorCount concurrent consuming actions.
            Parallel.Invoke(actions);
        }
        catch (Exception)
        {
            TeamMemberData.Dataset.Players.RejectChanges();
            TeamMemberData.Dataset.PlayerStats.RejectChanges();
        }
        TeamMemberData.Dataset.Players.AcceptChanges();
        TeamMemberData.Dataset.PlayerStats.AcceptChanges();
    }

单线程的时间差为 4.6 秒,并行线程的时间差为 6.1 秒。调用。

并行性是否与数据表写入事务的锁有用

Lock & 事务不利于并行性和性能。

1(尽量避免锁定:不同的线程是否需要更新数据集中的同一行?

2(最小化锁定时间。

对于数据库操作使用可以尝试批量更新未来 ADO.NET:http://msdn.microsoft.com/en-us/library/ms810297.aspx

多线程可以在一定程度上提供帮助,因为一旦数据跨越您的应用程序边界,您将开始等待I/O,在这里您可以进行异步处理,因为您的应用程序无法控制各种参数(资源访问,网络速度等(,这将提供更好的用户体验(如果UI应用程序(。

现在对于你的场景,你可能想使用某种生产者/消费者队列,一旦队列中有一行可用,另一个线程就会开始处理它,但这同样会在一定程度上起作用。