"На пальцах" — чем отличается "repeatable read" от "read commited" и "read commited snapshot"?




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2024-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

23 Comments

  1. Сурикат

    А кто-нибудь исследовал чем отличается инструкция (взятая с сайта Гилева):

    USE [master]

    GO

    ALTER DATABASE [Моя База] SET SINGLE_USER WITH ROLLBACK IMMEDIATE

    GO

    ALTER DATABASE [Моя База]

    SET ALLOW_SNAPSHOT_ISOLATION ON

    ALTER DATABASE [Моя База]

    SET READ_COMMITTED_SNAPSHOT ON

    ALTER DATABASE [Моя База] SET MULTI_USER WITH ROLLBACK IMMEDIATE

    GO

    От того, что есть по умолчанию в 1С в режиме совмести 8.3.6 и выше?

    Reply
  2. max_st

    Пробовал, когда нужно было принудительно включить RCSI, но конфигурация была совместима только с 8.2. Работает.

    Reply
  3. CSiER

    Несколько вопросов по статье:

    Собственно, мораль read commited, в нашем случае: чтение данных в транзакции НЕ блокирует их автоматически от записи. Для принудительной блокировки нужно использовать объект БлокировкаДанных.

    — разве без БлокировкаДанных это будет не READ UNCOMMITTED?

    Для начала конфигурация под MS SQL у нас находится в режиме автоматических блокировок, что соответствует уровню изоляции repeatable read

    и ниже

    Это и есть иллюстрация «грязного чтения» (dirty read)

    — то есть при repeatable read получили dirty read?

    • Не увидел ссылку на обработку.

    Таким образом, хотя таблица 3.6.2 на стр.41 книги Е.В.Филиппова «Настольная книга 1С:Эксперта по технологическим вопросам» и сообщает нам, что PostgreSQL в режиме управляемых блокировок использует уровень изоляции read commited — этот уровень изоляции в PostgreSQL соответсвует по поведению уровню read commited snapshot в MS SQL.

    — книга хорошая, написано верно, ведь Postgre чистый версионник, а ms sql — блокировочник.

    Reply
  4. Makushimo

    (3) CSiER,

    Поддержу вопрос коллеги.

    Разве грязное чтение не исчезает на уровне изоляции READ COMMITED и выше?

    Reply
  5. starik-2005

    В принципе то же самое рассказывают на курсах подготовки к эксперту/профессионалу по тех.вопросам. Единственное, что после курсов вопросы из комментов не возникают. ))

    Reply
  6. headMade

    (3) CSiER,

    Надо разделять менеджер блокировок MSQL и менеджер блокировок 1С.

    Когда у вас включен автоматический режим блокировок, то работает только менеджер блокировок MSQL, при этом используется уровень изоляции транзакций repeatable read и serializable.

    Причем это не сам MSQL решает какой уровень изоляции использовать при получении данных, а это сервер 1С при передаче запроса на выполнение MSQL «говорит» какой уровень изоляции для какой таблицы необходимо установить. Т.е. тут за блокировку полностью отвечает MSQL.

    Когда вы включаете управляемый режим блокировок, то начинает работать менеджер блокировок 1С. И соответственно сервер 1С решает что для MSQL будет достаточным уровень изоляции транзакций read commited т.к. за блокировку данных будет отвечать сам сервер 1С. В этом случае MSQL фактически ничего не блокирует.

    А «БлокировкаДанных » — это фактически команда серверу 1С, что необходимо наложить блокировку на определенные данные.

    Т.е. не взирая на то используем мы или нет «БлокировкаДанных «, на уровне MSQL всегда будет использоваться read commited.

    Reply
  7. headMade

    (3) CSiER,

    то есть при repeatable read получили dirty read?

    В статье же написано про 2 сеанса.

    В первом сеансе у нас устанавливается блокировка с уровнем изоляции repeatable read.

    А вот ВО ВТОРОМ сеансе у нас будет видно dirty read.

    Т.к. в приведенном примере стоит уровень совместимости с 8.2, то read commited snaphot не используется. Соответственно когда пользователь во втором сеансе открывает форму списка, то фактически сервер 1С передает запрос MSQL с хинтом «with no lock». Что фактически означает что будут прочитаны все данные в т.ч. данные не зафиксированных транзакций. Поэтому именно ВО ВТОРОМ сеансе будет наблюдаться грязное чтение, невзирая на то что в первом сеансе на этих записях стоит блокировка с уровнем узоляции repeatable read.

    Reply
  8. starik-2005

    (6) headMade, а вот г-н Филиппов на курсах, помнится, говорит о том, что блокировки MS SQL все-таки устанавливает. В режиме 8.2 — реад комиттед, в режиме 8.3 — реад комиттед снапшот. В профайлере при этом нет инструкции WITH (NOLOCK), но даже наличие WITH (NOLOCK) не гарантирует их отсутствие.

    Reply
  9. Sergey.Noskov

    (1) Сурикат, ничем не отличается, просто 8.3 это делает сама

    Reply
  10. herfis

    (8) Вы путаете блокировки с уровнями изоляции транзакций. И блокировки у MSSQL бывают разные, есть много вспомогательных. С практической точки зрения важно то, что в режиме изоляции «реад комиттед снапшот» читающие транзакции не «держат» пишущие транзакции и наоборот. И при этом прочитанные данные всегда согласованы, как при repeatable read.

    Reply
  11. herfis

    Ну и да — включение режима «реад комиттед снапшот» по сути меняет поведение MSSQL с «блокировочника» на «версионник». MSSQL начинает фигачить версии изменяемых строк в tempdb и при чтении брать данные подходящих версий оттуда, а не из родных таблиц.

    В отличие от старых уровней изоляции — это механизм совершенно другого качества. Поэтому его еще надо явно активировать в свойствах конкретной БД.

    Reply
  12. CSiER

    (7) headMade, спасибо за ответ, вроде все понятно, но, чувствую, только повторение шагов автора с профайлером окончательно расставят все точки на «и» в моей голове )

    Reply
  13. headMade

    (8) starik-2005,

    помнится, говорит о том, что блокировки MS SQL все-таки устанавливает.

    Конечно же блокировки всегда устанавливаются. Важное значение имеет с каким уровнем изоляции они устанавливаются и когда они снимаются.

    В некоторых случаях блокировка держится до конца транзакции (при repeatable read), а в некоторых снимается сразу после выполнения запроса (read commited).

    но даже наличие WITH (NOLOCK) не гарантирует их отсутствие.

    — естественно можно еще рассматривать блокировки стабильности схемы или блокировки намерений, но они на параллельность работы пользователей в 1С никак не влияют.

    Reply
  14. starik-2005

    (13) лично я вот конкретно на это отвечал:

    Когда вы включаете управляемый режим блокировок, то начинает работать менеджер блокировок 1С. И соответственно сервер 1С решает что для MSQL будет достаточным уровень изоляции транзакций read commited т.к. за блокировку данных будет отвечать сам сервер 1С. В этом случае MSQL фактически ничего не блокирует.

    А есть такая еще штука, как эскалация блокировок. Может внезапно оказаться, что блокируется вся таблица в SQL, при том 1С вроде как заблокировала только часть данных (до 100к).

    Reply
  15. herfis

    (14)

    А есть такая еще штука, как эскалация блокировок. Может внезапно оказаться, что блокируется вся таблица в SQL, при том 1С вроде как заблокировала только часть данных (до 100к).

    Ну, эти страшилки остались в прошлом, слава богу. Такое было не редкость в режиме автоматических блокировок, где на MSSQL при проведении использовался serializable — самый строгий режим изоляции, беспощадный по блокировкам. В postgresql вообще были табличные блокировки.

    А сейчас на версионниках с управляемыми блокировками одинэсник в самом деле фактически самолично управляет блокировками. Я не представляю, что нужно сделать, чтобы спровоцировать экскалацию блокировок СУБД в этой ситуации. Перезаписывать значительную часть большой таблицы в одной транзакции, разве что. Так тогда так и так разница невелика. И то помешает эта эскалация только другим транзакциям на запись.

    Reply
  16. Сурикат

    (9) Sergey.Noskov,

    1С ставит только SET READ_COMMITTED_SNAPSHOT ON

    Хотя на MSDN написано (https://msdn.microsoft.com/ru-ru/library/tcbchxcb(v=vs.110).aspx):

    Перед использованием в транзакциях изоляция моментального снимка должна быть включена путем установки параметра базы данных ALLOW_SNAPSHOT_ISOLATION в значение ON. Это приводит к активизации механизма сохранения версий строк во временной базе данных (tempdb).

    Собственно вопрос был:

    Как работает READ_COMMITTED_SNAPSHOT без ALLOW_SNAPSHOT_ISOLATION?

    Reply
  17. headMade

    (16) Сурикат,

    ссылка нерабочая

    Reply
  18. TMV

    (17) headMade, там последние 2 символа зацепило. Вот верная

    Reply
  19. qwinter

    (15) herfis,

    Ну, эти страшилки остались в прошлом, слава богу.

    обмен 500 новых контрагентов с 10 дополнительными значениями. Сущий пустяк правда?)

    Reply
  20. Yashazz

    А, ещё один пересказ общедоступных источников…

    Reply
  21. Makushimo

    (20) Yashazz,

    Ну не вредничайте.

    Статья полезная, а общедоступные источники еще нужно найти и вычитать там именно то что нужно.

    Статья хорошая

    Reply
  22. herfis

    (19)

    обмен 500 новых контрагентов с 10 дополнительными значениями. Сущий пустяк правда?)

    Скорее белиберда какая-то.

    Reply
  23. ture

    (19)мильёнами грузим ежедневно. Так что ЭТО пустяк.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *