Обновить столбец определенной строки и вернуть его идентификатор - PullRequest
0 голосов
/ 29 ноября 2018

Мне нужна идея от вас, чтобы решить мою маленькую проблему здесь.У меня есть таблица, которая состоит из нескольких заданий с идентификатором, TIMESTAMP, STATE и некоторых других значений, определяющих, что это за задание на самом деле.Мне нужно найти идентификатор с наименьшим TIMESTAMP и где STATE = 1 и STATE необходимо установить на 2 в той же самой атомарной операции.

Это был бы способ сделать это, если действительно есть только одинклиент подключился к базе данных: сначала выберите идентификатор с наименьшим значением TIMESTAMP.

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

Мы сохраняем идентификатор в переменной, скажем 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_ID и JOB_ID не может встречаться дважды.MASTER_JOB_ID предназначен только для объединения нескольких «подчиненных заданий» и для отображения их группы состояний.

Клиент - это скрипт Python, использующий пакет cx_Oracle в версии 12.1.0.2.0.

Ответы [ 2 ]

0 голосов
/ 30 ноября 2018

Полагаю, я нашел другое, возможно, нетрадиционное решение для этого.

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 атомарна, она должна работать без проблем.Сначала мне было интересно избегать петли, но когда я разговаривал с парнем в #oracle-database на Freenode, похоже, что при гонке по-другому и без петли будут условия гонки.

Я надеваюне хочу отмечать это решение как решение вопроса.Может быть, у кого-то еще есть идея сделать это с помощью Python и cx_Oracle.

0 голосов
/ 29 ноября 2018

Может быть, я не понял вопроса, но - зачем разделять SELECT и UPDATE?Разве такое заявление не сделало бы работу?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;
/
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...