Альтернативный контроль помеченных и быстрое удаление средствами SQL




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

47 Comments

  1. tormozit

    Зачем делать запрос к таблице ДвиженияССубконто, если ты все равно делаешь запрос к основной таблице регистра бухглатерии? Оптимальнее делать запрос либо только к ДвиженияССубконто либо к двум таблицам: к основной таблице и к таблице Субконто.

    Reply
  2. barelpro

    (1) tormozit,

    согласен, но я умышленно разнес на два запроса.

    Объясняю:

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

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

    PS. Невнимательно прочитал вопрос. Да, запрос к Субконто более оптимальный, чем к ДвиженияССубконто, исправлю, спасибо!

    Reply
  3. yukon
    И не забывайте, что при массированном удалении объектов база под SQL не уменьшается, а увеличивается за счет разрастания журнала транзакций (*.ldf). За этим надо следить и периодически сжимать, например командой DBCC SHRINKFILE

    Вот ведь все хорошо пишите. Но это вот зачем писать-то? На сколько увеличат объем журнала транзакций именно ваши «ручные» транзакции? Да ни насколько.

    Периодически сжимать журнал транзакций на рабочей базе нельзя, ну только если вы техномазохист и/или у вас НЖМД в «сервере» аж целых 40Гб.

    Reply
  4. barelpro

    (3) yukon,

    вы не поверите, после удаления 50млн записей регистров методом TSQL DELETE база выросла в полтора раза (в моем случае до 150Gb) как раз за счет разрастания журнала транзакций. DELETE пишет себя в журнал транзакций, даже если recovery model = simple.

    Reply
  5. yukon

    (4)

    вы не поверите, после удаления 50млн записей регистров методом TSQL DELETE база выросла в полтора раза

    Охотно верю. И как часто вы удаляете 50 млн записей? Неужели ежедневно.

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

    в моем случае до 150Gb

    Если у вас на сервере один жесткий на 320Gb, то да это ПРОБЛЕМА, согласен.

    Reply
  6. barelpro

    (5) yukon,

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

    ps. По своему опыту знаю, что урезание файла журнала транзакций можно выполнять при работающих пользователях. Эта операция быстрая и безболезненная. А вот урезание файла с данными (mdf) может быть долгим, т.к. процесс похож на дефрагментацию диска. Файл данных разрастается, например, после реструктуризации базы — попробую объяснить почему: все таблицы пересоздаются и записываются в конец файла mdf, данные из старых таблиц переносятся в новые, старые удаляются, и появляются дырки в начале файла mdf. Как-то так…

    Reply
  7. yukon
    По своему опыту знаю, что урезание файла журнала транзакций можно выполнять при работающих пользователях.

    Можно, но крайне не нужно.

    Эта операция быстрая и безболезненная.

    Это не так. Операция далеко не безболезненная.

    все таблицы пересоздаются и записываются в конец файла mdf

    Это весьма упрощенное и, как следствие, в большинстве случаев, неверное представление. На увеличение размера mdf файла(ов) влияют с десяток факторов. А уж на размещение данных внутри mdf-файла(ов) как бы не сотни.

    Внешняя простота работы SQL-сервера, не означает, что внутри все примитивно устроено. Тот факт, что SQL сервер до определенных пределов умеет сам за собой ухаживать, не означает, что можно выполнять любые манипуляции — пока не падает все типа нормально.

    В частности, размер ldf файла в модели восстановление simple SQL сервер выбирает именно такой, чтобы обслуживать базу данных при существующей интенсивности работы. Вместо помощи серверу и увеличения размера ldf файла, хотя бы до размера «чуть больше чем надо», вы предлагаете на постоянной(!) основе вставлять серверу палки в колеса, это же «быстро и безболезненно».

    Reply
  8. barelpro

    (7) yukon,

    Юрий, я прямо чувствую, что вы обладаете тайными знаниями, и очень искусно их прячете от общества 🙂

    Дайте пруфлинки, объясните развернуто, чем таким чревато урезание журнала транзакций при подключенных к базе пользователях? Все вам скажут спасибо, если знания будут полезны. Тут атмосфера вполне доверительная.

    Reply
  9. yukon
    Reply
  10. barelpro

    (9) yukon,

    вот, уже лучше, осталось только чтобы ссылки открывались 🙂

    Reply
  11. yukon

    (10)

    По клику не открываются — какой-то набор букв вываливает 🙁 а copy-paste работает. Мистика 🙂

    Reply
  12. barelpro

    (11) yukon,

    прочитал, но так и не нашел ответа на свой вопрос: если у нас объемная 1С-база, с ежедневным полным бэкапом и включенным recovery mode = simple, и мы провели массированное удаление с помощью команды T-SQL DELETE, и получили распухший журнал транзакций, соизмеримый с размером файла данных, и видим, что свободное файловое пространство на исходе, и спинным мозгом чувствуем, что в любой момент наступит коллапс всей базы, что же все-таки КОНКРЕТНО нам мешает в этом случае сделать усечение файла журнала транзакций?

    Reply
  13. yukon

    (12)

    прочитал

    0_0 за час? Там только чтения страниц 30 А4. Минимум на весь день, а с практикой так и на все 3-4 дня.

    и видим, что свободное файловое пространство на исходе

    Это как так? 150 гигов для современных дисков это не очень внушительный объем. Даже для серверных версий дисков. Заложите в бюджет расширение дискового пространства.

    Само по себе такое событие «свободное файловое пространство на исходе, и спинным мозгом чувствуем, что в любой момент наступит коллапс всей базы» уже ЧП. Говорит о недостаточном уровне планирования выполняемых работ. Вы ведь предварительно на тестовом стенде прогоняли столь массивную операцию? И что, на тестовом стенде журнал транзакций не вырос до столь внушительных размеров?

    все-таки КОНКРЕТНО нам мешает в этом случае сделать усечение файла журнала транзакций

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

    В нормальном режиме эксплуатации SQL сервер самостоятельно производит усечение журнала по мере необходимости.

    Reply
  14. iov

    (9)(10)(11)

    архив zip Сохранил в одном архиве для истории.

    Reply
  15. barelpro

    (13) yukon,

    теперь понял, спасибо за совет! А вы не хотите по этому поводу написать статью на ИС с конкретными рекомендациями для 1С-внедренцев — как правильно настраивать журнал транзакций и почему, чтобы уберечь от возможных негативных последствий?

    PS. Для меня, как для внешнего подрядчика обычно недоступны все IT-ресурсы заказчика. Чаще всего они выделяются по мере необходимости — нарезаются виртуальные машины с определенными параметрами. Поэтому на тестовых базах такое часто случается — недостаток дискового пространства. Эту ситуацию я и описал выше.

    Reply
  16. yukon

    (14)

    Спасибо.

    (15)

    как правильно настраивать журнал транзакций

    Ну вы и задачки ставите. «Правильно» — это к SQL DBA, а «на пальцах» могу попробовать.

    Reply
  17. Pavl0

    Маленький баг есть. Получить его сложно. Если в конфигурации количество регистров > 255 то на базе MS SQL упадет при заполнении объектов поиска. У меня такая ситуация на УПП+Appius на документе КорректировкаЗаписейРегистров.

    Просто повесил проверку на количество таблиц.

    Reply
  18. Wrols

    Почему-то в моем случае сразу не получилось запросто использовать…

    В базе все документы за сворачиваемый период уже помечены на удаление.

    Пытался сделать удаление одного вида документа.

    При заполнении таблица «Документы без движений» — пустая, т.к. при ее заполнении анализируются документы не помеченные на удаление.

    В процедуре «АнализОднойИзТаблиц» в строке модуля 739 под комментом «еще раз проверим отсутствие движений» выполняется запрос на наличие движений у объекта с условием «НЕ ТекДокумент.ПометкаУдаления».

    При этом результат запроса всегда пустой и удаление не происходит.

    К слову, в выражении «ТекстЗапроса_БезДвижений» тоже содержится условие на «НЕ ТекДокумент.ПометкаУдаления».

    Reply
  19. timm00

    Это все потому что данный запрос относится только к таблице документов без движений. А автор как-то забыл поставить условие. После правки данного блока вроде все нормально.

    Reply
  20. berator37

    Можно на почту Альтернативный контроль помеченных и быстрое удаление средствами SQL berator37@mail.ru . Заранее спасибо

    Reply
  21. Збянтэжаны Саўка

    (19) timm00, (18) Wrols,

    насколько я понял то там ошибка не в запросе, а в условии после него:

    вместо условия

    Если Запрос.Выполнить().Выбрать().Количество() = 0 Тогда

    нужно

    Если Запрос.Выполнить().Выбрать().Количество() > 0 Тогда

    т.е. если есть движения, то нельзя удалять

    //еще раз проверим отсутствие движений
    Если Строка.ТипМД = «Документ» Тогда
    
    Запрос.Текст =
    «ВЫБРАТЬ ПЕРВЫЕ 1 1
    |»
    + НайденныеСтроки[0].ТекстЗапроса_БезДвижений
    + »
    |И ТекДокумент.Ссылка = &Параметр
    |И НЕ ТекДокумент.ПометкаУдаления
    |»;
    
    //Если Запрос.Выполнить().Выбрать().Количество() = 0 Тогда
    Если Запрос.Выполнить().Выбрать().Количество() > 0 Тогда
    Строка.МожноУдалять = Ложь;
    Прервать;
    КонецЕсли;
    
    КонецЕсли;
    

    Показать

    Reply
  22. Збянтэжаны Саўка

    глюк какой-то?, не могу изменить адресат поста, ошибся и ответил 20-му

    (18) Wrols, (19) timm00,

    мой предыдущий пост предназначался вам

    Reply
  23. zekrus

    Добрый день!

    Пытаюсь в УТ 11.0 почистить справочник «Контрагенты».

    {Форма.Форма.Форма(539)}: Поле объекта не обнаружено (ОбщийРеквизит)

    МдРез = Метаданные[ИмяПоля];

    Reply
  24. b-dm

    А можно ли как то удалить документы за определенный период ?

    Reply
  25. Aleksey81

    barelpro, вы очень душевно поработали. Восхищает, что обработка (субъективно) раз в 50 быстрее удаляет данные и при этом позволяет другим пользователям полноценно работать с базой (в том числе запись и проведение документов. Спасибо.

    Однако есть и просьба устранить две странные ошибки.

    1) Заменить процедуру ЗначениеНеЗаполнено на сам знаете что….

    2) Возможно я не прав, но…. у похоже в коде процедуры АнализОднойИзТаблиц

    Понадобилось заменить

    Строка.МожноУдалять = Ложь;
    //Алексей
    //Возврат; неправильная команда
    Продолжить;
    

    Потому как ваша обработка останавливает любую активность, как только находит строку, которую удалять нельзя.

    Поправьте меня, если я не прав.

    Reply
  26. pit201201

    (23) zekrus,

    В своей версии обработки я добавил

    СпЗамен.Вставить(«ОбщийРеквизит», «ОбщиеРеквизиты»);

    последней строчкой. Вроде взлетело.

    Reply
  27. pit201201

    Хорошая обработка! Даже не смотря на некоторые, выше отмеченные ошибки.

    Есть предложение добавить опцию «отложенное удаление», при котором скрипт не выполняется, а сохраняется в текстовый файл.

    Reply
  28. pit201201

    Очень не хватает функции «Разорвать циклическую ссылку» .

    А то есть два документа СчетФактураВыданный (реквизит ДокументОснование) и ОказаниеУслуг(реквизит СчетФактура) оба помечены на удаление и ссылаются друг на друга.

    Reply
  29. pit201201

    Чтобы удалить документы, ссылающиеся друг на друга добавил строки

    Если (Найти(ИмяТаблицыВЗапросе,»Документ.»)>0) Тогда
    НоваяСтрока.ТекстЗапроса = НоваяСтрока.ТекстЗапроса + » И НЕ Т.Ссылка.ПометкаУдаления»;
    КонецЕсли;
    

    в процедуру СоздатьЗапросВСхемеАнализа

    Очень хочется услышать мнение автора обработки на этот счет.

    Reply
  30. Tciban

    (29) pit201201, А где это добавить, поточнее можно?

    Reply
  31. b-dm

    У меня не заработало.

    Reply
  32. zekrus

    Странно у меня работает.

    Reply
  33. avasl

    (21) правильное исправление такое

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

    Запрос.Текст = СтрЗаменить(Запрос.Текст, «НЕ ТекДокумент.ПометкаУдаления», «ИСТИНА»)

    Кусок тогда будет выглядеть так

        //еще раз проверим отсутствие движений
    Если Строка.ТипМД = «Документ» Тогда
    
    Запрос.Текст = «ВЫБРАТЬ ПЕРВЫЕ 1 1
    |»
    + НайденныеСтроки[0].ТекстЗапроса_БезДвижений
    + »
    |И ТекДокумент.Ссылка = &Параметр
    |И НЕ ТекДокумент.ПометкаУдаления»;
    
    //s
    Запрос.Текст = СтрЗаменить(Запрос.Текст, «НЕ ТекДокумент.ПометкаУдаления», «ИСТИНА»);
    
    Если Запрос.Выполнить().Выбрать().Количество() = 0 Тогда
    
    Строка.МожноУдалять = Ложь;
    Продолжить; //s //Прервать;
    
    КонецЕсли;
    
    КонецЕсли;
    

    Показать

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

    Запрос.Выполнить().Выбрать().Количество() = 0

    и у документ справедливо не будет удален.

    Автору бы не мешало поправить обработку

    Reply
  34. user839171

    На файловой базе не заработала

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

    Жаль бессмысленно потраченные 5 $m .

    Reply
  35. Харьковец

    Подтверждаю, что для файловой базы ЭТА обработка не работает! Занимаюсь отладкой и исправлением ошибок уже второй час и пока результата нет. Действительно жаль 5$m!!!!

    Reply
  36. bsturtle

    (25) Алексей, спасибо. я поправил как вы сказали и все заработало. а то у меня тоже останавливалась сразу обработка.

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

    Reply
  37. bsturtle

    РАБОТАЕТ! тем у кого не сработало — см. комментарий 25

    Reply
  38. bsturtle

    прогонять приходится 2-3 раза. потом уже остатки добиваем штатным удалением помеченных.

    базу упп 1.3 на 150 тысяч помеченных объектов, на обычном пк corei5 с 8 гб оперативки зачистила за сутки. автору спасибо

    Reply
  39. doctorov_s

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

    Reply
  40. doctorov_s

    Жаль потраченные 5 стартмани

    Reply
  41. b-dm

    (41) — там уже выше комментировали, что она действительно работает очень выборочно…но вы же всегда можете заминусовать 🙂

    Reply
  42. Deryni

    Блок, начинающийся со строки 739

    Если Строка.ТипМД = «Документ» Тогда
    
    Запрос.Текст = «ВЫБРАТЬ ПЕРВЫЕ 1 1
    |»
    + НайденныеСтроки[0].ТекстЗапроса_БезДвижений
    + »
    |И ТекДокумент.Ссылка = &Параметр
    |И НЕ ТекДокумент.ПометкаУдаления»;
    
    Если Запрос.Выполнить().Выбрать().Количество() = 0 Тогда
    
    Строка.МожноУдалять = Ложь;
    Прервать;
    
    КонецЕсли;
    
    КонецЕсли;
    

    Показать

    Я так понимаю, что тут проверяется, есть ли у документа движения. Во-первых при этом вылетает на первом же документе, у которого нет движений. Поэтому вот это:

    Если Запрос.Выполнить().Выбрать().Количество() = 0 Тогда
    

    меняем на это:

    Если Запрос.Выполнить().Выбрать().Количество() <> 0 Тогда
    

    а Прервать; на Продолжить;

    Reply
  43. acanta

    А что понимается под выбрать()?

    Создается и сразу же уничтожается в памяти объект из которого берется только количество()? Флуктуация какая то..

    А потом что с запросом, снова выбрать или выгрузить?

    Reply
  44. Deryni

    (44) тут как раз всё понятно, проверяем пустая ли выборка.

    Выполнить() создаёт объект РезультатЗапроса.

    Выбрать() создаёт объект ВыборкаИзРезультатаЗапроса для этого объекта

    Количество() возвращает количество значений в выборке.

    В принципе можно заменить на

    НЕ Запрос.Выполнить().Пустой()

    А дальше выгружать нечего. Это запрос просто проверить, есть ли ссылки на этот объект в движениях.

    Reply
  45. GC_Rogneda

    Буду пробовать на базе 200 ГБ )

    Reply
  46. altshift

    На управляемых формах не планируете делать?

    Reply
  47. buy_sale

    Хотелось бы, чтобы автор учел комментарии и исправил свою обработку, иначе непонятно можно ей пользоваться или нет.

    Reply

Leave a Comment

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