<?php // Полная загрузка сервисных книжек, создан 2025-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='\
Молодец, что раскопал такой дикий промах.
Получается, что в случае получения остатков по дате, ближе к концу месяца, происходит обход ВСЕХ архивных записей.
Интересно, как на партнерке прокомментируют.
потестил замером производительности действительно с моментом медленее , добавил вышеуказанный код, спасибо
У меня в половине запросов используется Граница, автор напугал прям 🙂
Протестил для границы с конструктором без использования момента времени — все норм вроде, такой же запрос, как и в случае даты. Момент времени в моих задачах никогда не находил применения, потому тестить не стал, поверю автору на слово )
Часто бывает так, что один документ двигает множество регистров и не всегда один и тот же вид документов двигает их все одновременно.
Выходит проверять нужно вообще все регистры у которых документ значится регистратором.
Уж лучше пусть пока страдает производительность, но будет более читабельным код, а 1С ну может лет через 5 исправит эту оплошность, а когда исправит, то не придется удалять костыли из программного кода.
Конечно в самых жутких по быстродействию документах подход будет оправдан, но во все лепить не стоит эти проверки.
Эх, вот бы разработчики платформы увеличили размерность даты до миллисекунд. Не надо было бы гадать, сколько документов и в каком порядке лежат «внутри секунды». Ведь SQL то поддерживает это:https://msdn.microsoft.com/ru-ru/library/ms187819.aspx
Я так понимаю, что проблема возникает только при использовании МоментВремени и в незначительной степени от ГраницаВремени? Причем проверяли только на ГраницаВремени.Исключая ?
А для ГраницаВремени.Включая какая статистика?
Просто в заголовке статьи Граница и Момент указаны как равнозначные факторы, а по статье видно, что они влияют в разной степени. Это путает.
Правильная статья! Плюс и здесь и там получил, спасибо за информацию!
(4) Brawler,
Да, согласен. Везде это вставлять нет смысла. Только там где проблема стабильно воспроизводится и изменение принесет заметный результат.
В базе, где эту проблему обнаружил, оказалось достаточно сделать это только для запроса по партиям.
(5) ИМХО, я бы не добавлял. Это тогда нужно эти миллисекунды в интерфейс для пользователей выносить. А если не выносить, то по какому принципу устанавливать миллисекунды для документов создаваемых задним числом? Слишком сложно..
(6) Согласен. Надо по-удачнее переформулировать. Проблема возникает при использовании МоментаВремени и Границы, созданной на основании МоментаВремени/Ссылки.
Граница с датой работает нормально.
ИсключаяИсключая — не влияет на результат, т.к. там условие в запросе незначительно меняется.
Подтверждаю, тоже столкнулся с этой бедой некоторое время назад. Заметил после установки maxDOP = 2, в тж стали попадать аналогичные запросы к движениям регистра. Забороть получилось только отказавшись от момента времени.
А речь про какую версию платформы? В тексте не увидел. У меня это вылезло на 8.3.5.
Могу предположить, что если бы дата документа была не 15.04, а 02.04, то выборка из таблицы итогов выполнялась бы уже наоборот — на 01.04. И добивалась бы движениями с 01 по 02. И тогда уже надо проверять наличие документов не ранее текущего, а после него.
(9) Bronislav,
Проверял на 8.3.5 и 8.3.6.
(10) alon,
Я не помню как это было в 8.2, но 8.3 считает остатки с конца месяца для любых дат.
Я бы не правил алгоритмы конфигурации — тут я согласен с (4) «Уж лучше пусть пока страдает производительность, но будет более читабельным код», а вместо этого сделал бы скрипт, который добавил бы в базу индекс, про который речь в зелёной надписи на скриншоте с планом запросов. Всё, что нужно, это запускать этот скрипт после каждой реструктуризации регистра, что вряд ли будет часто, если вообще будет.
(8) им бы еще в базе хранить дату и время в UTC, а не в непонятно каком времени.
(12) vasyak319,
И после каждой загрузки дтшки, видимо, и после каждого ТИИ? Что гораздо более вероятно..
(14) AlX0id, если вы регулярно загружаете из dtшки рабочую базу, то что-то у вас с этой базой глубоко не так.
Прикольно. А не может действовать кеш SQL на запросы 3 и 4?
А сам запрос
Запрос = Новый Запрос(
«ВЫБРАТЬ ПЕРВЫЕ 1
| 1 КАК Поле1
|ИЗ
| РегистрНакопления.ПартииТоваровНаСкладах КАК ПартииТоваровНаСкладах
|ГДЕ
| ПартииТоваровНаСкладах.Период = &Период
| И НЕ ПартииТоваровНаСкладах.Регистратор = &Регистратор
| И ПартииТоваровНаСкладах.МоментВремени < &МоментВремени»);
Запрос.УстановитьПараметр(«Период», Дата);
Запрос.УстановитьПараметр(«Регистратор», Ссылка);
Запрос.УстановитьПараметр(«МоментВремени», МоментВремени());
ДокументЕдинственныйВПериоде = Запрос.Выполнить().Пустой());
насколько он быстрый? не дает тормозов производительности?
(5) Dach, У этого подхода есть и обратная сторона — интерактивно вводить дату/время станет совсем не удобно. Но даже если это сделать, исходя из личного опыта, для большого потока документов этого также будет не достаточно.
(12) vasyak319, Читаемость кода улучшается с увеличением количества комментариев.
Вы, наверно, во франче работаете?
Надо искать «золотую середину» между читаемостью и производительностью, но все же выводя производительность на первое место! В плане данного примера код достаточно читаем и если это даст даже 5-10% прироста скорости проведения конечно же нужно внедрять!
(15) vasyak319,
Речь не совсем о моих базах ) А о том, что диапазон случаев, когда придется применять предлагаемый в (12) скрипт — чутка шире, нежели указанный в посте.
(12) на картинке система предлагает индекс для второго (более быстрого) запроса :).
(16) It-developer,
Про проведении замеров статистика была обновлена, индексы дефрагментированы, кэш очищен.
Результаты брал из 2-3 итерации.
(17) — этот запрос выполняется очень быстро и оптимально.
(0) а на какой задаче обнаружилась проблема? почему документы продаж заведены на одно и тоже время с точностью до секунды? почему при проведении остатки не проверяются сразу? какая конфа? сколько пользователей? филиальная структура? тогда почему списание с одного склада? заранее спасибо за подробности
(0) опережая ответ на свои вопросы, хотел еще раз уточнить: какие разрезы учета номенклатуры? по характеристикам? по сериям? по складам? по другим измерениям? какие другие используются измерения учета?
в сутках 86400 секунд — может быть имеет смысл при записи документа проверять последовательность и записывать с датой +1сек от предыдущего документа? поступления товаров при этом записывать на начало дня.
будет ли такое предложение приемлемым решением?
Эта фича с разными отборами остатков по дате и моменту времени тянется еще с 8.0 / 8.1, и разница во времени была большая — на одной и той же базе запрос по дате выполнялся 1-3 сек, по моменту времени мог выполняться более 3 минут (база была большая — более 200 ГБ). В результате приходилось переписывать все запросы остатков в модулях проведения на запросы по дате, а документы с одинаковыми датами и временем искусственно растаскивать во времени, сдвигая их посекундно, чтобы не было нескольких документов с одинаковым временем — благо документов хоть было и много, но все же меньше, чем 86400 в день.
(22) Rustig,
Мне кажется, что эти вопросы не так важны тут.
Конкретно в этой конфигурации проблема проявилась на партиях. Решить ее можно было бы разными путями, вплоть до отказа от оперативного партионного учета с расчетом результата в конце месяца, но я другую мысль пытался донести.
При достаточном стечении обстоятельств проблема может проявиться на любых регистрах. Я не проверял, но скорее всего, с регистрами бухгалтерии аналогичная ситуация.
Фактически использование МоментаВремениГраницы требуется крайне редко (мала вероятность попаданию в одну секунду зависящих друг от друга документов — например, по одному и тому же складу), поэтому нет смысла использовать более «тяжелый» запрос, когда «легкий» даст такой же результат.
P.S. — в запросе в конце статьи добавил отбор по Складу. т.к. документы в той же секунде по другим складам не влияют на результат.
(25) наоборот, важно: я думал услышать, что некая обработка регламентно создает документы продаж и сажает списание на одну секунду по одному складу условно говоря, каждые 20 минут…. и тогда естественно эту проблему можно решить другим способом- разнести документы посекундно.
а после вашего ответа складывается впечатление, что в базе настолько много менеджеров-продавцов, что в программе волей-неволей создаются каждую секунду по два-три документа реализации. на мой взгляд уникальный случай…
Спасибо за статью, очень грамотно и интересно написано.
[зануда mode on]
Усугубить — значит «увеличить, усилить».
[зануда mode off]
За статью спасибо.
Плюсанул на партнерском, спасибо!