-1

使用多线程处理从 SQL Server 中的单个表读取的最佳方法,并确保不会使用 c# 在不同线程中读取相同的记录两次

提前谢谢你的帮助

4

4 回答 4

3

您是在尝试从表中并行读取记录以加快检索数据的速度,还是只是担心线程访问相同数据时数据损坏?

像 MsSQL 这样的数据库管理系统可以非常好地处理并发性,因此如果您有多个线程读取同一个表,则在这方面的线程安全不是您必须在代码中关注的事情。

如果您想在没有任何重叠的情况下并行读取数据,您可以运行带有分页的 SQL 命令,并让每个线程获取不同的页面。您可以说 20 个线程同时读取 20 个不同的页面,并且可以保证它们不会读取相同的行。然后你可以连接数据。页面越大,您从创建线程中获得的性能提升就越大。

实现分页的有效方法

于 2012-06-02T00:54:29.227 回答
0

解决方案 1:
我假设您正在尝试从大表中处理或提取数据。如果我被分配了这个任务,我会首先查看 paging 。如果您试图在线程之间分配工作,那就是。所以线程 1 处理页面 0 到 10,线程 2 处理页面 11 到 20,等等……或者您可以使用实际的行号对行进行批处理。所以在你的存储过程中你会这样做;

WITH result_set AS (
  SELECT
    ROW_NUMBER() OVER (ORDER BY <ordering>) AS [row_number],
    x, y, z
  FROM
    table
  WHERE
    <search-clauses>
) SELECT
  *
FROM
  result_set
WHERE
  [row_number] BETWEEN @IN_Thread_Row_Start AND @IN_Thread_Row_End;

另一个更有效的选择是,如果您有一个自然键或一个非常好的代理项,则使用该键进行分页并让线程传递键参数而不是它感兴趣的记录(或页码)。

对此解决方案的直接担忧是:

  1. ROW_NUMBER 次效果
  2. CTE 性能(我相信它们存储在内存中)

因此,如果这是我要解决的问题,我会通过键查看分页。

解决方案 2:
第二种解决方案是在处理行时对其进行标记,虚拟锁定它们,即如果您具有数据写入权限。因此,您的表将有一个名为 Processed 或 Locked 的字段,因为您的线程选择了行,它们被更新为 Locked = 1;

然后您从其他线程中选择仅选择未锁定的行。当您的过程完成并处理所有行时,您可以重置锁。

很难说什么会在一些试验中表现最好...... GL

于 2012-06-02T03:12:18.927 回答
0

假设依赖于 SQL Server,您可能会查看 SQL Server Service Broker 功能来为您提供排队。需要记住的一件事是,目前 SQL Server 服务代理在 SQL Azure 上不可用,因此如果您计划迁移到 Azure 云上,这可能是个问题。

无论如何 - 使用 SQL Server Service Broker,并发访问是在数据库引擎层进行管理的。另一种方法是让一个线程读取数据库,然后以消息作为输入来调度线程。这比尝试使用数据库中的事务来确保消息不会被读取两次要容易一些。

就像我说的那样,SQL Server Service Broker 可能是要走的路。或者适当的外部排队机制。

于 2012-06-02T00:54:36.467 回答
0

这个问题非常古老,但仍然非常相关,我花了很多时间来寻找这个解决方案,所以我想把它发布给其他遇到这个问题的人。这在使用 sql 表作为队列而不是 msmq 时很常见。

解决方案(经过大量调查)很简单,可以通过在 ssms 中打开 2 个选项卡进行测试,每个选项卡运行自己的事务以模拟多个进程/线程访问同一个表。

快速回答是这样的:关键是在您的选择上使用 updlock 和 readpast 提示。

为了说明没有重复的读取工作,请查看这个简单的示例。

--在 ssms 中的选项卡 1

begin tran
SELECT TOP 1 ordno FROM table_queue WITH (updlock, readpast) 

--在 ssms 中的选项卡 2

begin tran
SELECT TOP 1 ordno FROM table_queue WITH (updlock, readpast) 

您会注意到第一个选定的记录被锁定,并且不会被第二个选项卡/进程上的 select 语句复制。

现在在现实世界中,您不会像上面的简单示例那样只在表上执行选择。如果您将表用作队列,您可以将记录更新为“isprocessing=1”或类似的东西。上面的代码只是演示了这允许并发读取而不重复。

因此,在现实世界中(例如,如果您将表用作队列并使用多个服务处理此队列),您很可能会在子查询中执行 select 以更新语句。

像这样的东西。

begin tran
    update table_queue set processing= 1 where myId in
    (
        SELECT TOP 50  myId FROM table_queue WITH (updlock, readpast)
    )

commit tran

您还可以将 yoru update 语句与 output 关键字结合起来,这样您就可以得到一个现在锁定的所有 id 的列表(处理 = 1),以便您可以使用它们。

如果您使用表作为队列处理数据,这将确保您不会在选择语句中重复记录,而无需分页或其他任何东西。

这个解决方案正在一个企业级应用程序中进行测试,当我们被运行在许多不同机器上的许多服务监视时,我们的选择语句中出现了很多重复。

于 2017-03-21T21:01:56.883 回答