Работа с переменными Record – 2. Modify.

С первой частью статьи можно ознакомиться  здесь – «Основные команды. Чтение без блокировок».

Примеры изменения записей

Общие советы по записи в БД

  1. Начинайте транзакцию как можно позже (т.е. сначала считайте нужные данные без блокировок таблиц, и только после этого начинайте запись).
  2. Блокируйте как можно меньше (при внесении изменений в одну запись таблицы размером в 10 млн.записей, зачем блокировать все эти записи, когда можно — всего лишь одну?)
  3. Ваша транзакция должна работать максимально быстро (размещать в ней команды типа SLEEP — это ОЧЕНЬ ПЛОХО)
  4. Транзакция должна быть минимальной (ну, по обстоятельствам: если вам надо изменить 1000 записей таблицы, не надо после каждой записи в базу вызывать COMMIT)
  5. Составляйте транзакции так, чтобы все операции записи шли одним блоком, или вообще не делайте транзакцию.

Важное дополнение

LOCKTABLE (а также FINDSET(TRUE,…)) работает на SQL Server и на родной базе по-разному. На родной базе эта инструкция блокирует всю таблицу. На SQL, она означает, что начиная с этой точки, все прочитанные записи будут заблокированы в режиме xlock, НО записи в других таблицах не блокируются. Возможна также ситуация, когда заблокированы некоторые другие записи, так как отсутсвует конроль над механизмом блокировок SQL Server’а изнутри C/AL (в отличие от родной базы).

Вам необходимо изменить одну запись

  1. НЕПРАВИЛЬНО — этот метод  вызывает два SELECT’а на SQL. Первый SELECT работает с хинтом NOLOCK и используется для работы оператора GET. Второй SELECT отработает уже с хинтом EXCLUSIVE-LOCK и будет использован для работы MODIFY. Далее идет вызов SQL-оператора UPDATE. Второй SELECT нужен для эксклюзивной блокировки записи — проверки, что никто больше не изменил запись от GET’а до MODIFY’я.
    // Вы не вызвали LOCKTABLE (LOCKTABLE на другой таблице не будет блокировать текущую)
    TheTable.GET(...); // или, например, FINDFIRST,FINDLAST
    TheTable."Some Field" := 'Some Value';
    TheTable.MODIFY(FALSE);
  2. ПРАВИЛЬНО — GET запустит SELECT с хинтом EXCLUSIVE LOCK. MODIFY запустит UPDATE.
    TheTable.LOCKTABLE;
    TheTable.GET(...); // или, например,FINDFIRST,FINDLAST
    TheTable."Some Field" := 'Some Value';
    TheTable.MODIFY(FALSE);

Чтение одной записи и изменение (опционально)

  1. ПРАВИЛЬНО — при маленькой вероятности изменения записи — стараемся избежать ненужных блокировок:
    // You don't have a LOCKTABLE on it (a LOCKTABLE on another table will NOT lock current table
    TheTable.GET(...); // или, например, FINDFIRST,FINDLAST
    IF to be changed THEN BEGIN
    TheTable."Some Field" := 'Some Value';
    TheTable.MODIFY(FALSE);
    END;
  2. ПРАВИЛЬНО — если надо изменить запись, то избегаем второго SELECT’а на SQL:
    TheTable.LOCKTABLE;
    TheTable.GET(...); // или, например, FINDFIRST,FINDLAST
    IF to be changed THEN BEGIN
    TheTable."Some Field" := 'Some Value';
    TheTable.MODIFY(FALSE);
    END;

Чтение набора записей и изменение ВСЕХ записей

  1. АБСОЛЮТНО НЕВЕРНО — сначала вам надо использовать FINDSET (см.выше). Вы используете одну и ту же переменную типа Record для изменения поля. Может случиться так, что вы пропустите записи, что вы обработаете записи несколько раз, что вы запустите бесконечный цикл. А таблицу вы так и не заблокировали…
    IF TheTable.FINDFIRST THEN // а также FIND('-')
    REPEAT
    TheTable."Some Field" := 'Some Value';
    TheTable.MODIFY(FALSE);
    UNTIL TheTable.NEXT = 0;
  2. НЕВЕРНО — вы не заблокировали таблицы, потому MODIFY вызовет лишний SELECT
    IF TheTable.FINDSET THEN
    REPEAT
    TheTable2 := TheTable;
    TheTable2."Some Field" := 'Some Value';
    TheTable2.MODIFY(FALSE);
    UNTIL TheTable.NEXT = 0;
  3. ПРАВИЛЬНО — вы блокируете табличку при помощи указания первого параметра TRUE при вызове FINDSET. Второй TRUE нужен только в определенных условиях, но я советую использовать его всегда
    IF TheTable.FINDSET(TRUE,TRUE) THEN
    REPEAT
    TheTable2 := TheTable;
    TheTable2."Some Field" := 'Some Value';
    TheTable2.MODIFY(FALSE);
    UNTIL TheTable.NEXT = 0;

Чтение набора записей и необходимость изменить ЧАСТЬ из них

  1. АБСОЛЮТНО НЕВЕРНО и НЕВЕРНО — см. предыдущий пункт.
  2. НЕВЕРНО — пример был верный для предыдущего пункта, но не здесь, потому он блокирует ВСЕ прочтенные записи. Блокировка записей в памяти снижает производительность по причине дополнительной нагрузки серверу, а также потому, что эта сессия блокирует других пользователей, даже если они хотят изменять другие записи. Кроме того, это является причиной страшных дедлоков.
    IF TheTable.FINDSET(TRUE,TRUE) THEN
    REPEAT
    TheTable2 := TheTable;
    TheTable2."Some Field" := 'Some Value';
    TheTable2.MODIFY(FALSE);
    UNTIL TheTable.NEXT = 0;
  3. ПРАВИЛЬНО — сначала вы читаете записи, а те, что хотите изменить — помещаете во временную таблицу ВООБЩЕ БЕЗ БЛОКИРОВКИ
    // вы не вызвали LOCKTABLE на таблицу "TheTable"!
    IF TheTable.FINDSET THEN
    REPEAT
    // сохранение записи во временной таблице
    IF (Record has to be changed) THEN BEGIN
    tmpTheTable := TheTable;
    tmpTheTable.INSERT(FALSE);
    END;
    UNTIL TheTable.NEXT = 0;// вызываем LOCKTABLE. Это значит, что все записи в таблице,
    // которые будут прочитаны- теперь заблокированы с хинтом xlock
    CLEAR(TheTable);
    TheTable.LOCKTABLE;

    Далее идут 2 способа организации цикла:

    Способ 1:

    Запускаем цикл по временной таблице. Данный код использует версию каждой записи во временной таблице для изменения в самой таблице. Если одна из записей за это время изменилась, произойдет ошибка (Примечание: оператора MOIDFY также может сделать запрос на SELECT с хинтом xlock перед выполнением UPDATE’а. Это необходимо, т.к. NAV’у требуется проверить версию записи — все еще ли она та же самая).

    tmpTheTable.RESET;
    IF tmpTheTable.FINDSET THEN
    REPEAT
    TheTable := tmpTheTable;
    TheTable."Some Field" := 'Some Value';
    TheTable.MODIFY(FALSE);
    UNTIL tmpTheTable.NEXT = 0;

    Способ 2:

    Запускаем цикл по временной таблице. Данный код снова получает записи (c EXCLUSIVE-LOCK!), а затем меняет ее. Если со времени первого чтения запись изменилась, это не вызовет ошибки при выполнении MODIFY, т.к. вы получли самую свежую версию.

    tmpTheTable.RESET;
    IF tmpTheTable.FINDSET THEN
    REPEAT
    TheTable := tmpTheTable;
    TheTable.FIND('='); // или TheTable.GET(primary key). Можно использовать любую из этих команд,
    // но надо удостовериться, что на TheTable не наложены фильтры
    // (об этом можно позаботиться при помощи CLEAR(TheTable).
    TheTable."Some Field" := 'Some Value';
    TheTable.MODIFY(FALSE);
    UNTIL tmpTheTable.NEXT = 0;

Данная статья – вольный перевод заметки «How to work with record-variables (version 2)?»

Продолжение читайте здесь – «FILTERGROUP. Работа с другими фирмами», «Практика».

Исправления и дополнения приветствуются :) Пишите на mailbox@naviart.ru

Автор:

В области Navision - с 2003 года. Профессиональные интересы: NAV, MS SQL, .NET, BPMN, IT-менеджмент. Предметная область: логистика, финансы, склады, 3PL.

Количество статей, опубликованных автором: 86.

Комментарии (2 комментария)

  1. Vikol

    ни один из этих способов не помогает избежать блокировок.

  2. Дмитрий

    Чтобы избежать блокирование в наве необходимо переписать множество моментов.
    Данная статья помогает минимизировать блокировки.
    Спасибо.

Добавить комментарий