在 sybase 中,如何锁定正在执行的存储过程并更改存储过程 returns 的 table?

In sybase, how would I lock a stored procedure that is executing and alter the table that the stored procedure returns?

我有一个table如下:

id    status
--    ------
1     pass
1     fail
1     pass
1     na
1     na

另外,我有一个存储过程 return 是一个 table,前 100 条记录的状态为 'na'。存储过程可以由环境中的多个节点调用,我不希望它们获取重复数据。所以,我想在存储过程执行的时候加锁,将从存储过程中获取的记录的状态设置为'In Progress'和return即table,然后释放锁,所以不同的节点不会获取相同的数据。我将如何做到这一点?

类似的问题在ms中已经有解决方案sql但是在sybase中使用时显示错误

我不是 100% 确定如何在 Sybase 中执行此操作。但是,思路如下。

首先,向 table 添加一个新列,表示用于更改数据的会话或连接。您将使用此列提供隔离。

然后,更新行:

update top (100) t
    set status = 'in progress',
        session = @session
    where status = 'na'
    order by ?;  -- however you define the "top" records

然后,您可以 return 或处理给定连接的 "in progress" 的 100 个 ID。

  1. 创建另一个 table、proc_lock,它有一行
  2. 当控制进入存储过程时,启动事务并对 proc_lock 中的行执行 select 更新(参见 this link). If that doesn't work for Sybase, then you could try the technique from this answer 锁定行。
  3. 在过程退出之前,确保提交事务。

这将确保一次只有一个用户可以执行该过程。当第二个用户尝试执行 proc 时,它将阻塞直到第一个用户对 proc_lock 行的锁定被释放(例如,当提交事务时)

假设 Sybase ASE ...

您可能要考虑的更大问题是您是否希望单个进程在抓取前 100 行时锁定整个 table ,或者如果您希望其他进程仍然访问 table?

另一个问题是您是否希望多个进程同时从 table 中拉取 100 行而不相互阻塞?

我假设您 a) 不想锁定整个 table 并且 b) 您可能希望允许多个进程同时从 table 中提取行。

1 - 如果可能,确保 table 使用 datarows 锁定(默认通常是 allpages);这会将锁的粒度降低到行级别(与 allpages 的页级别相反); table 需要是 datarows 如果你想允许多个进程同时 find/update 行在 table

2 - 确保 table 上的锁定升级设置足够高,以确保单个进程的 100 行更新不会锁定 table(sp_setpglockpromote for allpages, sp_setrowlockpromote for datarows);这里的关键是确保您的 update 不会升级为 table-level 锁!

3 - 当需要获取 100 行的集合时,您会希望...在事务中...update 具有唯一值 status 的 100 行到您的会话,select 关联的 id,然后再次将 status 更新为 'In Progress'

操作的要点如下所示:

declare @mysession varchar(10)

select  @mysession = convert(varchar(10),@@spid)  -- replace @@spid with anything that
                                                  -- uniquely identifies your session
set rowcount 100  -- limit the update to 100 rows

begin tran get_my_rows

    -- start with an update so that get exclusive access to the desired rows;
    -- update the first 100 rows you find with your @@spid

    update mytable
    set    status = @mysession   -- need to distinguish your locked rows from
                                 -- other processes; if we used 'In Progress'
                                 -- we wouldn't be able to distinguish between
                                 -- rows update earlier in the day or updated
                                 -- by other/concurrent processes

    from   mytable readpast      -- 'readpast' allows your query to skip over
                                 -- locks held by other processes but it only
                                 -- works for datarows tables
    where  status = 'na'

    -- select your reserved id's and send back to the client/calling process

    select  id
    from    mytable
    where   status = @mysession

    -- update your rows with a status of 'In Progress'

    update mytable
    set    status = 'In Progress'
    where  status = @mysession

commit            -- close out txn and release our locks

set rowcount 0    -- set back to default of 'unlimited' rows

潜在问题:

  • 如果您的 table 很大并且您在 status 上没有索引,那么您的查询可能需要比 运行 所需的更长的时间;通过确保锁升级足够高并且您正在使用 datarows 锁定(因此 readpast 有效)您应该看到其他进程的最小阻塞,无论需要多长时间找到所需的行

  • status 列上有一个索引,考虑到所有这些 update 都会强制执行大量索引更新,这可能会导致一些昂贵的 延迟更新

  • 如果使用 datarows 并且您的锁升级太低,那么更新可能会查看整个 table,这会导致另一个(并发)处理 readpast table 锁,发现没有行要处理

  • 如果使用 allpages,您将无法使用 readpast,因此并发进程将阻塞您的锁(即,它们不会无法阅读您的锁)

  • 如果您在 status 上有一个索引,并且多个并发进程锁定 table 中的不同行,则可能会发生死锁(很可能在 status 列的索引的索引树中),这反过来又需要对 client/application 进行编码以预期并解决死锁

思考:

  • 如果 table 相对较小,因此 table 扫描的成本不是很大,您可以删除 status 列上的任何索引,这应该减少 延迟更新 的性能开销(与更新索引相关)

  • 如果您可以使用会话特定的 status 值(例如,'In Progress - @mysession'),那么您可以消除第二个 update 语句(可能会派上用场如果您在索引 status 列上进行 延迟 更新)

  • 如果您在 table 中还有其他列可用于唯一标识会话的行(例如,last_updated_by_spid = @@spid,last_updated_date = @mydate - @mydate 最初设置为 getdate()) 然后你的第一个 update 可以设置状态 = 'In Progress',select 将使用 @@spid和 @mydate 用于 where 子句,不需要第二个 update [注意:实际上,这与 Gordon 在他的 session 专栏中试图解决的问题相同。]

  • 假设您可以使用特定于会话的 status 值,请考虑使用允许您跟踪和修复孤立行(例如,行 status 保留'In Progress - @mysession'因为调用进程死了,再也没有回来(重新)设置状态)

  • 如果您可以将 id 列表作为单个串接的 id 值传递回调用程序,您可以使用我在此 [=55] 中概述的方法=] 在第一次更新期间将 id 附加到 @variable 中,允许您在第一次更新中设置 status = 'In Progress' 并允许您消除 select 和第二个 update

  • 您如何判断哪些行已被孤立?您可能希望能够使用发布 update 时的 getdate() 更新(小)日期时间列;然后,如果您通常希望 status 在 5 分钟内更新,您可以有一个监视过程来查找孤立的ows 其中 status = 'In Progress' 距离上次 update

  • 已经超过 10 分钟了

如果 数据行 readpast、锁升级设置 and/or 死锁可能性太大,您可以忍受短暂 table-level table 上的锁,您可以让进程在执行 updateselect 语句之前获得独占 table 级别的锁;排他锁需要在 user-defined 事务中获得,以便 'hold' 在您的工作期间获得锁;一个简单的例子:

begin tran get_my_rows

    -- request an exclusive table lock; wait until it's granted

    lock table mytable in exclusive mode

    update ...

    select ...

    update ...

commit