Проблема быстродействия при использовании позиции документа вместо Даты в виртуальных таблицах




Принцип обмена данными из 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='\

29 Comments

  1. mrstomak

    Молодец, что раскопал такой дикий промах.

    Получается, что в случае получения остатков по дате, ближе к концу месяца, происходит обход ВСЕХ архивных записей.

    Интересно, как на партнерке прокомментируют.

    Reply
  2. gonzo111

    потестил замером производительности действительно с моментом медленее , добавил вышеуказанный код, спасибо

    Reply
  3. AlX0id

    У меня в половине запросов используется Граница, автор напугал прям 🙂

    Протестил для границы с конструктором без использования момента времени — все норм вроде, такой же запрос, как и в случае даты. Момент времени в моих задачах никогда не находил применения, потому тестить не стал, поверю автору на слово )

    Reply
  4. Brawler

    Часто бывает так, что один документ двигает множество регистров и не всегда один и тот же вид документов двигает их все одновременно.

    Выходит проверять нужно вообще все регистры у которых документ значится регистратором.

    Уж лучше пусть пока страдает производительность, но будет более читабельным код, а 1С ну может лет через 5 исправит эту оплошность, а когда исправит, то не придется удалять костыли из программного кода.

    Конечно в самых жутких по быстродействию документах подход будет оправдан, но во все лепить не стоит эти проверки.

    Reply
  5. Dach

    Эх, вот бы разработчики платформы увеличили размерность даты до миллисекунд. Не надо было бы гадать, сколько документов и в каком порядке лежат «внутри секунды». Ведь SQL то поддерживает это: https://msdn.microsoft.com/ru-ru/library/ms187819.aspx

    Reply
  6. anig99

    Я так понимаю, что проблема возникает только при использовании МоментВремени и в незначительной степени от ГраницаВремени? Причем проверяли только на ГраницаВремени.Исключая ?

    А для ГраницаВремени.Включая какая статистика?

    Просто в заголовке статьи Граница и Момент указаны как равнозначные факторы, а по статье видно, что они влияют в разной степени. Это путает.

    Reply
  7. fishca

    Правильная статья! Плюс и здесь и там получил, спасибо за информацию!

    Reply
  8. Aleksey.Bochkov

    (4) Brawler,

    Да, согласен. Везде это вставлять нет смысла. Только там где проблема стабильно воспроизводится и изменение принесет заметный результат.

    В базе, где эту проблему обнаружил, оказалось достаточно сделать это только для запроса по партиям.

    (5) ИМХО, я бы не добавлял. Это тогда нужно эти миллисекунды в интерфейс для пользователей выносить. А если не выносить, то по какому принципу устанавливать миллисекунды для документов создаваемых задним числом? Слишком сложно..

    (6) Согласен. Надо по-удачнее переформулировать. Проблема возникает при использовании МоментаВремени и Границы, созданной на основании МоментаВремени/Ссылки.

    Граница с датой работает нормально.

    ИсключаяИсключая — не влияет на результат, т.к. там условие в запросе незначительно меняется.

    Reply
  9. Bronislav

    Подтверждаю, тоже столкнулся с этой бедой некоторое время назад. Заметил после установки maxDOP = 2, в тж стали попадать аналогичные запросы к движениям регистра. Забороть получилось только отказавшись от момента времени.

    А речь про какую версию платформы? В тексте не увидел. У меня это вылезло на 8.3.5.

    Reply
  10. alon

    Могу предположить, что если бы дата документа была не 15.04, а 02.04, то выборка из таблицы итогов выполнялась бы уже наоборот — на 01.04. И добивалась бы движениями с 01 по 02. И тогда уже надо проверять наличие документов не ранее текущего, а после него.

    Reply
  11. Aleksey.Bochkov

    (9) Bronislav,

    Проверял на 8.3.5 и 8.3.6.

    (10) alon,

    Я не помню как это было в 8.2, но 8.3 считает остатки с конца месяца для любых дат.

    Reply
  12. vasyak319

    Я бы не правил алгоритмы конфигурации — тут я согласен с (4) «Уж лучше пусть пока страдает производительность, но будет более читабельным код», а вместо этого сделал бы скрипт, который добавил бы в базу индекс, про который речь в зелёной надписи на скриншоте с планом запросов. Всё, что нужно, это запускать этот скрипт после каждой реструктуризации регистра, что вряд ли будет часто, если вообще будет.

    Reply
  13. pumbaE

    (8) им бы еще в базе хранить дату и время в UTC, а не в непонятно каком времени.

    Reply
  14. AlX0id

    (12) vasyak319,

    И после каждой загрузки дтшки, видимо, и после каждого ТИИ? Что гораздо более вероятно..

    Reply
  15. vasyak319

    (14) AlX0id, если вы регулярно загружаете из dtшки рабочую базу, то что-то у вас с этой базой глубоко не так.

    Reply
  16. It-developer

    Прикольно. А не может действовать кеш SQL на запросы 3 и 4?

    Reply
  17. It-developer

    А сам запрос

    Запрос = Новый Запрос(

    «ВЫБРАТЬ ПЕРВЫЕ 1

    | 1 КАК Поле1

    |ИЗ

    | РегистрНакопления.ПартииТоваровНаСкладах КАК ПартииТоваровНаСкладах

    |ГДЕ

    | ПартииТоваровНаСкладах.Период = &Период

    | И НЕ ПартииТоваровНаСкладах.Регистратор = &Регистратор

    | И ПартииТоваровНаСкладах.МоментВремени < &МоментВремени»);

    Запрос.УстановитьПараметр(«Период», Дата);

    Запрос.УстановитьПараметр(«Регистратор», Ссылка);

    Запрос.УстановитьПараметр(«МоментВремени», МоментВремени());

    ДокументЕдинственныйВПериоде = Запрос.Выполнить().Пустой());

    насколько он быстрый? не дает тормозов производительности?

    Reply
  18. aexeel

    (5) Dach, У этого подхода есть и обратная сторона — интерактивно вводить дату/время станет совсем не удобно. Но даже если это сделать, исходя из личного опыта, для большого потока документов этого также будет не достаточно.

    Reply
  19. It-developer

    (12) vasyak319, Читаемость кода улучшается с увеличением количества комментариев.

    Вы, наверно, во франче работаете?

    Надо искать «золотую середину» между читаемостью и производительностью, но все же выводя производительность на первое место! В плане данного примера код достаточно читаем и если это даст даже 5-10% прироста скорости проведения конечно же нужно внедрять!

    Reply
  20. AlX0id

    (15) vasyak319,

    Речь не совсем о моих базах ) А о том, что диапазон случаев, когда придется применять предлагаемый в (12) скрипт — чутка шире, нежели указанный в посте.

    Reply
  21. Aleksey.Bochkov

    (12) на картинке система предлагает индекс для второго (более быстрого) запроса :).

    (16) It-developer,

    Про проведении замеров статистика была обновлена, индексы дефрагментированы, кэш очищен.

    Результаты брал из 2-3 итерации.

    (17) — этот запрос выполняется очень быстро и оптимально.

    Reply
  22. Rustig

    (0) а на какой задаче обнаружилась проблема? почему документы продаж заведены на одно и тоже время с точностью до секунды? почему при проведении остатки не проверяются сразу? какая конфа? сколько пользователей? филиальная структура? тогда почему списание с одного склада? заранее спасибо за подробности

    Reply
  23. Rustig

    (0) опережая ответ на свои вопросы, хотел еще раз уточнить: какие разрезы учета номенклатуры? по характеристикам? по сериям? по складам? по другим измерениям? какие другие используются измерения учета?

    в сутках 86400 секунд — может быть имеет смысл при записи документа проверять последовательность и записывать с датой +1сек от предыдущего документа? поступления товаров при этом записывать на начало дня.

    будет ли такое предложение приемлемым решением?

    Reply
  24. Vainemeinen

    Эта фича с разными отборами остатков по дате и моменту времени тянется еще с 8.0 / 8.1, и разница во времени была большая — на одной и той же базе запрос по дате выполнялся 1-3 сек, по моменту времени мог выполняться более 3 минут (база была большая — более 200 ГБ). В результате приходилось переписывать все запросы остатков в модулях проведения на запросы по дате, а документы с одинаковыми датами и временем искусственно растаскивать во времени, сдвигая их посекундно, чтобы не было нескольких документов с одинаковым временем — благо документов хоть было и много, но все же меньше, чем 86400 в день.

    Reply
  25. Aleksey.Bochkov

    (22) Rustig,

    Мне кажется, что эти вопросы не так важны тут.

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

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

    Фактически использование МоментаВремениГраницы требуется крайне редко (мала вероятность попаданию в одну секунду зависящих друг от друга документов — например, по одному и тому же складу), поэтому нет смысла использовать более «тяжелый» запрос, когда «легкий» даст такой же результат.

    P.S. — в запросе в конце статьи добавил отбор по Складу. т.к. документы в той же секунде по другим складам не влияют на результат.

    Reply
  26. Rustig

    (25) наоборот, важно: я думал услышать, что некая обработка регламентно создает документы продаж и сажает списание на одну секунду по одному складу условно говоря, каждые 20 минут…. и тогда естественно эту проблему можно решить другим способом- разнести документы посекундно.

    а после вашего ответа складывается впечатление, что в базе настолько много менеджеров-продавцов, что в программе волей-неволей создаются каждую секунду по два-три документа реализации. на мой взгляд уникальный случай…

    Reply
  27. Andreynikus

    Спасибо за статью, очень грамотно и интересно написано.

    Reply
  28. antz

    [зануда mode on]

    Усугубить — значит «увеличить, усилить».

    [зануда mode off]

    За статью спасибо.

    Reply
  29. Fragster

    Плюсанул на партнерском, спасибо!

    Reply

Leave a Comment

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