Работа с переменными Record – 2. Modify.
С первой частью статьи можно ознакомиться здесь – «Основные команды. Чтение без блокировок».
Примеры изменения записей
Общие советы по записи в БД
- Начинайте транзакцию как можно позже (т.е. сначала считайте нужные данные без блокировок таблиц, и только после этого начинайте запись).
- Блокируйте как можно меньше (при внесении изменений в одну запись таблицы размером в 10 млн.записей, зачем блокировать все эти записи, когда можно — всего лишь одну?)
- Ваша транзакция должна работать максимально быстро (размещать в ней команды типа SLEEP — это ОЧЕНЬ ПЛОХО)
- Транзакция должна быть минимальной (ну, по обстоятельствам: если вам надо изменить 1000 записей таблицы, не надо после каждой записи в базу вызывать COMMIT)
- Составляйте транзакции так, чтобы все операции записи шли одним блоком, или вообще не делайте транзакцию.
Важное дополнение
LOCKTABLE (а также FINDSET(TRUE,…)) работает на SQL Server и на родной базе по-разному. На родной базе эта инструкция блокирует всю таблицу. На SQL, она означает, что начиная с этой точки, все прочитанные записи будут заблокированы в режиме xlock, НО записи в других таблицах не блокируются. Возможна также ситуация, когда заблокированы некоторые другие записи, так как отсутсвует конроль над механизмом блокировок SQL Server’а изнутри C/AL (в отличие от родной базы).
Вам необходимо изменить одну запись
- НЕПРАВИЛЬНО — этот метод вызывает два 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); - ПРАВИЛЬНО — GET запустит SELECT с хинтом EXCLUSIVE LOCK. MODIFY запустит UPDATE.
TheTable.LOCKTABLE;
TheTable.GET(...); // или, например,FINDFIRST,FINDLAST
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
Чтение одной записи и изменение (опционально)
- ПРАВИЛЬНО — при маленькой вероятности изменения записи — стараемся избежать ненужных блокировок:
// 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; - ПРАВИЛЬНО — если надо изменить запись, то избегаем второго SELECT’а на SQL:
TheTable.LOCKTABLE;
TheTable.GET(...); // или, например, FINDFIRST,FINDLAST
IF to be changed THEN BEGIN
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
END;
Чтение набора записей и изменение ВСЕХ записей
- АБСОЛЮТНО НЕВЕРНО — сначала вам надо использовать FINDSET (см.выше). Вы используете одну и ту же переменную типа Record для изменения поля. Может случиться так, что вы пропустите записи, что вы обработаете записи несколько раз, что вы запустите бесконечный цикл. А таблицу вы так и не заблокировали…
IF TheTable.FINDFIRST THEN // а также FIND('-')
REPEAT
TheTable."Some Field" := 'Some Value';
TheTable.MODIFY(FALSE);
UNTIL TheTable.NEXT = 0; - НЕВЕРНО — вы не заблокировали таблицы, потому MODIFY вызовет лишний SELECT
IF TheTable.FINDSET THEN
REPEAT
TheTable2 := TheTable;
TheTable2."Some Field" := 'Some Value';
TheTable2.MODIFY(FALSE);
UNTIL TheTable.NEXT = 0; - ПРАВИЛЬНО — вы блокируете табличку при помощи указания первого параметра TRUE при вызове FINDSET. Второй TRUE нужен только в определенных условиях, но я советую использовать его всегда
IF TheTable.FINDSET(TRUE,TRUE) THEN
REPEAT
TheTable2 := TheTable;
TheTable2."Some Field" := 'Some Value';
TheTable2.MODIFY(FALSE);
UNTIL TheTable.NEXT = 0;
Чтение набора записей и необходимость изменить ЧАСТЬ из них
- АБСОЛЮТНО НЕВЕРНО и НЕВЕРНО — см. предыдущий пункт.
- НЕВЕРНО — пример был верный для предыдущего пункта, но не здесь, потому он блокирует ВСЕ прочтенные записи. Блокировка записей в памяти снижает производительность по причине дополнительной нагрузки серверу, а также потому, что эта сессия блокирует других пользователей, даже если они хотят изменять другие записи. Кроме того, это является причиной страшных дедлоков.
IF TheTable.FINDSET(TRUE,TRUE) THEN
REPEAT
TheTable2 := TheTable;
TheTable2."Some Field" := 'Some Value';
TheTable2.MODIFY(FALSE);
UNTIL TheTable.NEXT = 0; - ПРАВИЛЬНО — сначала вы читаете записи, а те, что хотите изменить — помещаете во временную таблицу ВООБЩЕ БЕЗ БЛОКИРОВКИ
// вы не вызвали 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.
ни один из этих способов не помогает избежать блокировок.
Чтобы избежать блокирование в наве необходимо переписать множество моментов.
Данная статья помогает минимизировать блокировки.
Спасибо.