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




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

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

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

<?php // Полная загрузка сервисных книжек, создан 2026-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 не будет опубликован. Обязательные поля помечены *