更新特定行的列并返回其ID

时间:2018-11-29 15:42:54

标签: oracle locking

我需要您提供一个想法来解决我的小问题。我有一个表,其中包含一些作业,这些作业具有ID,TIMESTAMP,STATE和一些其他值,这些值定义作业的实际含义。我需要找到具有最低TIMESTAMP的ID,其中STATE = 1,并且必须在同一原子操作中将STATE设置为2。

如果实际上只有一个客户端连接到数据库,这将是这样做的方法: 首先选择时间戳最少的ID。

SELECT * FROM SW_ASYNC_JOBS WHERE STATE = 1 ORDER BY TIMESTAMP FETCH FIRST 1 ROW ONLY

我们将ID存储到一个变量中,例如JOB_ID,然后将其STATE设置为2:

UPDATE SW_ASYNC_JOBS SET STATE = 2 WHERE JOB_ID = :JOB_ID

现在,客户端拥有了所需的所有数据,将状态设置为“进行中”并开始处理工作。

当然,实际上在这两个操作之间会有另一个客户端执行相同的操作,并且肯定会出现竞争状况。两位客户可能会从事致命的同一工作。

我在网上搜索时发现SELECT FOR UPDATE和WHERE CURRENT OF语句,但是似乎无法同时检索作业的所有列。我从这样的事情开始:

DECLARE
    CURSOR FRESH_JOB IS
        SELECT * FROM SW_ASYNC_JOBS WHERE STATE = 1 ORDER BY TIMESTAMP FETCH FIRST 1 ROW ONLY
    FOR UPDATE;
BEGIN
    FOR JOB IN FRESH_JOB LOOP
        UPDATE SW_ASYNC_JOBS SET STATE = 2 WHERE CURRENT OF FRESH_JOB;
    END LOOP;
END;

但是由于某种原因,我得到了一个错误ORA-02014: cannot select FOR UPDATE from view,因为表SW_ASYNC_JOBS是一个简单的表,主键超过两列。

解决该问题的最佳方法是什么?我应该锁定整个表以获取最旧的作业并更改其状态吗?

为完整起见,这是我正在谈论的表格:

CREATE TABLE SW_ASYNC_JOBS (
    "MASTER_JOB_ID" NUMBER(22, 0) NOT NULL,
    "JOB_ID" NUMBER(22, 0) NOT NULL,
    "USER_ID" VARCHAR2(64) NOT NULL,
    "UID" VARCHAR2(64) NOT NULL,
    "VIEW" VARCHAR2(64) NOT NULL,
    "JSON" VARCHAR2(2000) NOT NULL,
    "TIMESTAMP" TIMESTAMP NOT NULL,
    "STATE" NUMBER(2, 0) NOT NULL,
    CONSTRAINT "SW_ASYNC_PRIMARY" PRIMARY KEY ("MASTER_JOB_ID", "JOB_ID")
)

还有其他客户端从一个序列中检索新的序列号,以向该表中添加新行。首先,检索MASTER_JOB_ID,然后为每个“从属作业”使用另一个新的序列号。因此,原则上MASTER_JOB_IDJOB_ID中的数字不能出现两次。 MASTER_JOB_ID仅用于组合多个“从属作业”并明智地显示其状态组。

客户端是一个Python脚本,它使用版本12.1.0.2.0中的cx_Oracle包。

2 个答案:

答案 0 :(得分:0)

也许我不明白这个问题,但是-为什么将SELECTUPDATE分开?这样的声明不会完成这项工作吗? RETURNING子句用于返回JOB_ID。

declare
  retval sw_async_jobs.job_id%type;
begin
  update sw_async_jobs s set
    s.state = 2
    where s.job_id = (select s1.job_id
                      from sw_async_jobs s1
                      where s1.state = 1
                      order by s1.timestamp fetch first 1 row only
                     )
  returning job_id into retval;

  dbms_output.put_line('retval = ' || retval);
end;
/

答案 1 :(得分:0)

我想我为此找到了另一个也许非常规的解决方案。

def getNextJobId(self) :
    """ Return Id of the oldest job which is in 'ready' state
    """

    # Lock the table
    cursor = self._execute("LOCK TABLE %s IN EXCLUSIVE MODE" % self._jobTableName)

    try :
        jobId = None

        while jobId is None :

            # Find potential job Id in 'ready' state
            cursor = self._execute("SELECT JOB_ID FROM %s WHERE STATE = :READY_STATE ORDER BY TIMESTAMP, JOB_ID FETCH FIRST 1 ROW ONLY" % self._jobTableName,
                                   {'READY_STATE' : JobStates.ready.value})

            rows = list(cursor)

            # Is there is no such job, quit
            if len(rows) == 0 :
                self._rollback()
                break

            # If there is a potential job, save its Id
            elif len(rows) >= 1 :
                # 'rows' has this form: [(int,)]
                potentialJobId = rows[0][0]

            # Now we change its state. In the WHERE clause we also check if the job still is in 'ready' state.
            statement = "UPDATE %s SET STATE = :WORKING_STATE WHERE JOB_ID = :JOB_ID AND STATE = :READY_STATE" % self._jobTableName

            cursor = self._execute(statement, {'READY_STATE' : JobStates.ready.value,
                                               'WORKING_STATE' : JobStates.working.value,
                                               'JOB_ID' : potentialJobId})

            # If there was exactly one row changed, we now have the job
            if cursor.rowcount == 1 :
                jobId = potentialJobId
                self._commit()

            # If nothing was changed through the UPDATE, another client got the same potential job and changed the state already. Try again.

        return jobId

    finally :
        self._rollback()

由于表已锁定并且UPDATE是原子的,因此现在应该可以正常工作了。起初,我有兴趣避免循环,但是当我在Freenode上与#oracle-database中的一个人聊天时,看起来当以其他方式尝试并且没有循环时,就会出现竞争情况。

我不想将此解决方案标记为该问题的解决方案。也许其他人有一个更好的主意,可以使用Python和cx_Oracle。