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

Posted

技术标签:

【中文标题】在 sybase 中,如何锁定正在执行的存储过程并更改存储过程返回的表?【英文标题】:In sybase, how would I lock a stored procedure that is executing and alter the table that the stored procedure returns? 【发布时间】:2019-06-28 11:23:16 【问题描述】:

我有一张如下表:

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

另外,我有一个存储过程,它返回一个表,其中前 100 条记录的状态为“na”。存储过程可以由环境中的多个节点调用,我不希望它们获取重复数据。所以,我想在存储过程执行时锁定存储过程,并将从存储过程获取的记录的状态设置为“进行中”并返回该表然后释放锁,这样不同的节点就不会获取相同的数据。我将如何做到这一点?

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

【问题讨论】:

已经有两个很好的答案,但请确保您的错误处理和事务管理正确。否则,您最终可能会锁定应用程序(在声明和释放锁定之间发生错误)。更强大的解决方案是使用消息队列。 哪个 Sybase RDBMS(ASE?IQ?SQLAnywhere?Advantage?)和版本? 【参考方案1】:

假设 Sybase ASE ...

您可能需要考虑的更大问题是,您是希望单个进程在获取前 100 行时锁定整个,还是希望其他进程仍然访问表?

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

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

1 - 如果可能,确保表使用 datarows 锁定(默认通常是 allpages);这会将锁的粒度降低到行级别(与 allpages 的页面级别相反);如果您想允许多个进程同时查找/更新表中的行,则该表需要是 datarows

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

3 - 当需要获取 100 行的集合时,您会想要...在事务中...update 具有 status 值的 100 行对于您的会话来说是唯一的,选择关联id,然后再次将status 更新为“进行中”

操作要点如下:

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

潜在问题:

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

status 列上有索引,考虑到所有这些updates 将强制进行大量索引更新,这可能会导致一些昂贵的延迟更新强>

如果使用 datarows 并且您的锁升级太低,则更新可能会查看整个表,这将导致另一个(并发)进程 readpast 表锁并找不到要处理的行

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

如果您在 status 上有一个索引,并且多个并发进程锁定了表中的不同行,则可能会发生死锁(可能在987654337@ 列),这反过来又需要对您的客户端/应用程序进行编码以预期和解决死锁

想一想:

如果表相对较小,因此表扫描的成本并不高,您可以删除status 列上的任何索引,这应该会降低延迟更新的性能开销>(与更新索引有关)

如果您可以使用特定于会话的 status 值(例如,'In Progress - @mysession'),那么您可以消除第二条 update 语句(如果您遇到 延迟索引status列的更新)

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

假设您可以使用特定于会话的 status 值,请考虑使用允许您跟踪和修复孤立行的东西(例如,行 status 仍然是“进行中 - @mysession”,因为调用进程死了,再也没有回来(重新)设置状态)

1234563在第一次更新时放入@variable,允许您在第一次更新中设置status = 'In Progress',还允许您消除select 和第二个update

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

的分钟数

如果 datarowsreadpast、锁升级设置和/或死锁可能性太大,并且您可以忍受表上的简短表级锁,您可以让进程获取执行updateselect 语句之前的独占表级锁;排他锁需要在用户定义的事务中获得,以便在您的工作期间“持有”锁;一个简单的例子:

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

【讨论】:

【参考方案2】:

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

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

然后,更新行:

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

然后,您可以返回或处理给定连接“正在进行”的 100 个 ID。

【讨论】:

【参考方案3】:
    创建另一个表 proc_lock,它有一行 当控制进入存储过程时,启动一个事务并对 proc_lock 中的行进行更新选择(参见this 链接)。如果这对 Sybase 不起作用,那么您可以尝试 this answer 中的技术来锁定行。 在程序退出之前,请确保提交事务。

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

【讨论】:

以上是关于在 sybase 中,如何锁定正在执行的存储过程并更改存储过程返回的表?的主要内容,如果未能解决你的问题,请参考以下文章

存储过程中的“select *”如何执行?

将sybase存储过程作为链接服务器过程sql server 2008执行

sybase 如何像SQL SERVER一样建立作业,让他能定时运行存储过程

Sybase·调用存储过程并返回结果

Java存储过程无限运行

如何使用 JDBC 调用带有命名参数的 Sybase 存储过程