[T-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='\

41 Comments

  1. DimDiemon

    Каков смысл написания этой статьи?

    Ни один вопрос не раскрыт…

    Reply
  2. ipoloskov
    Вывод: не важно как написан запрос — важно как он выполнится. Но это особая, черная магия…

    В этом и смысл правильного написания запросов — чтобы он Всегда выполнялся оптимально. Вне зависимости от фазы луны и настроения сервера. И никакой магии. В первом примере отца русской демократии спасет, наверное, это:

            ВЫБРАТЬ
    Док.Номенклатура.Комплект КАК Комплект,      —< соединение с «Номенклатура»
    Док.Склад КАК Склад,
    Док.ПроцентБонуса КАК ПроцентБонуса,
    ЧекККМ.Рецепт КАК Рецепт,
    Док.СуммаАвтоматическойСкидки КАК СуммаАвтоматическойСкидки,
    Док.ИнтернетЗаказ КАК ИнтернетЗаказ
    ИЗ Документ.ЧекККМ КАК ЧекККМ
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЧекККМ.Товары КАК Док
    ПО ЧекККМ.Ссылка = Док.Ссылка
    ГДЕ
    ЧекККМ.Дата МЕЖДУ &НачДата И &КонДата
    И ЧекККМ.КассаККМ = &КассаККМ
    И ЧекККМ.Проведен
    

    Показать

    Reply
  3. МихаилМ

    (2) на данном этапе развития субд запрос ВСЕГДА не может выполняться оптимально.

    тк отимизаторы запросов несовершенны в условиях изменяющейся нагрузки на субд.

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

    но и это на этапе разработки не всегда возможно обеспечить.

    опять же любимая фирма 1с не забывает радовать неудачными решениями в платформе.

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

    и все увеличивающиеся вычислительные мощности не всегда.

    Поэтому задачи проектирования и эксплуатации ис — два различных ремесла

    Reply
  4. nytlenc
    Reply
  5. nytlenc

    В заключение к вышесказанному хочется добавить в части разыменования ссылочных полей — ты забыл упомянуть например RLS в случае использования которого на такой запрос нагромоздится еще куча различных соединений и он начнет тормозить еще больше. Так же следует помнить про составные типы в основном из-за которых и даются подобные рекомендации по оптимизации. Например использовать конструкцию ВЫРАЗИТЬ()

    Reply
  6. herfis

    Увидел только одно соображение, почему «разименование это плохо» для простых случаев (без составных типов и прочих понятных вариаций) — мол на файловом движке может быть выбран неоптимальный план выполнения. А может и не будет. А может починят во очередном релизе.

    И что, ради этого отказываться от средства, специально призванного упрощать описание простых деклараций?

    Не вижу смысла. Проще писать как проще и разруливать узкие места при их появлении. А не заниматься преждевременной оптимизацией.

    Reply
  7. nytlenc

    (6)

    Не вижу смысла. Проще писать как проще и разруливать узкие места при их появлении. А не заниматься преждевременной оптимизацией.

    А может проще не говонокодить создавать эти узкие места изначально оформляя оптимальный код?

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

    Reply
  8. bulpi

    По запросу 1.

    «Кто-то скажет, что этот запрос писал не очень грамотный программист. »

    Возможно, это я не очень грамотный программист. Но я не вижу в этом запросе ничего крамольного. И вариант , предложенный в (2) не будет быстрее (ИМХО).

    По запросу 2.

    Капитан Очевидность с нами! Тут даже обсуждать нечего.

    Reply
  9. herfis

    (7) Т.е. любое использование разыменование в запросах 1С вы автоматически определяете в «говнокод»? А какое же тогда название вы приберегли для разработчиков платформы, давшим в руки разработчикам лишний и вредный (по-вашему) инструмент?

    Лично я не сталкивался в продакшене (клиент-сервере) с ситуациями, где использование неявных соединений в простых случаях прямо вело к появлению узких мест. Использование разименования в простых случаях — наоборот, повышает читабельность кода.

    Reply
  10. TODD22

    Да вроде как разыменование будет преобразовано в левое соединение. И что его руками написать, что разыменованием сделать по идее в простых случаях должно дать один и тот же результат. Или нет?

    Reply
  11. herfis

    (7) Для тех, кто пишет статьи на ИТС, вы возможно также найдете пару теплых слов. Цитата:

    «В 1С:Предприятии 8 допускается обращение к свойствам объектов через одну или несколько точек, например, «Номенклатура.Поставщик.Страна». Это позволяет значительно упростить написание запросов. Рекомендуется всегда пользоваться разыменованием полей там, где это возможно, чтобы не усложнять запросы лишними конструкциями.»

    Reply
  12. kuzyara

    (7) Вот любишь потеоризировать о правильности и оптимальность запроса!

    Нет взять в руки профайлер да проверить исполнение:

    Fields:(

    T1._Fld3356RRef

    )

    _DOCUMENT152 (T2) RANGE SCAN USING INDEX (_DOCUMEN152_BYDOCDATE_TR) (1 fields)

    NESTED LOOP

    _DOCUMENT152_VT3354 (T1) RANGE SCAN USING INDEX (_DOCUMEN152_VT3354_INTKEYIND) (1 fields)

    WHERE

    (T1._Document152_IDRRef = T2._IDRRef)

    GROUPING

    Statistics: RecordsScanned = 262972, ParseTime = 0, ExecuteTime = 849, BuffersMemory = 188372, ResultRecords = 7529, RecordSize = 18

    Fields:(

    T1._Fld3356RRef

    )

    _DOCUMENT152 (T2) RANGE SCAN USING INDEX (_DOCUMEN152_BYDOCDATE_TR) (1 fields)

    NESTED LOOP

    _DOCUMENT152_VT3354 (T1) RANGE SCAN USING INDEX (_DOCUMEN152_VT3354_INTKEYIND) (1 fields)

    WHERE

    (T1._Document152_IDRRef = T2._IDRRef)

    WITHOUT DUPLICATES

    Statistics: RecordsScanned = 262972, ParseTime = 0, ExecuteTime = 928, BuffersMemory = 188372, ResultRecords = 7529, RecordSize = 18

    https://yadi.sk/i/jvgtfkx03TsMCV

    Одинаковы они! Яж говорю, это ОСОБАЯ магия!

    Reply
  13. sergathome

    (11) Столкнулся тут. В бух 3. Производился поиск объекта по его допсвойству. Запрос выглядел примерно так:

    ВЫБРАТЬ
    ДополнительныеСведения.Объект.Ссылка КАК ОбъектСсылка
    ИЗ
    РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
    ГДЕ
    ДополнительныеСведения.Свойство = &Свойство
    И ДополнительныеСведения.Значение = &Значение

    В половине случаев исполняется нормально — сначала отбирает записи регистра, потом соединяет с объектами, в половине — угадай как ;))

    Не смешно, на самом деле…

    Reply
  14. herfis

    (13) Это фигня какая-то, а не запрос.

    1) ты разыменовываешь поле составного типа, а это совершенно особый случай. Спор шел о простых случаях.

    2) непонятно, зачем ты это делаешь — ведь «Объект» это уже ссылка. А ты на ровном месте провоцируешь соединение со всеми таблицами составного типа.

    3) Что значит, угадай как? Сиквел-запрос адекватный, но выбирается неоптимальный план выполнения? Тогда причем здесь разыменование?

    Reply
  15. nytlenc

    (9) Блин да причем тут разыменование полей?

    Цитирую Вас дословно

    Проще писать как проще и разруливать узкие места при их появлении.

    Это и называется говнокодить.

    Reply
  16. sergathome

    (14) Просто пример, как простая швейная машинка Зингера легко превращается в аццкую. Ошибся в запросе — по памяти писал, там, на самом деле, отбор по ПометкаУдаления был, а не ссылка, конечно, выбиралась. Но самое смешное, что даже вот в таком виде:

    ВЫБРАТЬ
    ДополнительныеСведения.Объект КАК ОбъектСсылка
    ПОМЕСТИТЬ Тбл
    ИЗ
    РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
    ГДЕ
    ДополнительныеСведения.Свойство = &Свойство
    И ДополнительныеСведения.Значение = &Значение
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    Тбл.ОбъектСсылка КАК ОбъектСсылка
    ИЗ
    Тбл КАК Тбл
    ГДЕ
    НЕ Тбл.ОбъектСсылка.ПометкаУдаления = ИСТИНА

    Показать

    оно исполняется через раз — оптимизатор упорно пытается, таки, во втором подзапросе, сначала выбирать все объекты и потом только проверять.

    Я всё это к тому, что рекомендациями 1С весьма часто можно смело подтираться.

    Reply
  17. nytlenc

    Коля )))) Ты так и не понял )))

    Возьми базу с 10 миллионами строк и выполни )))) Потом покажешь результаты и замеры.

    Reply
  18. herfis

    (15)

    Блин да причем тут разыменование полей?

    Как это причем? Мы что, о мухах общались? Вы выдернули фразу из контекста, экстраполировали ее как вам захотелось и на основании этих эротических фантазий обвиняете меня в говнокодерстве. Перечитайте мой коммент, последнюю фразу из которого вы выдернули.

    Reply
  19. ipoloskov

    (16)

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

    А как еще он должен делать?

    Reply
  20. sergathome

    (19) Как учит нас партия и правительство производитель платформы, при использовании временной таблицы оптимизатор (якобы) рассчитывает её статистику и по итогам делает выводы. В представленном мною варианте количество записей в Тбл несопоставимо вообще с количеством записей по объектам и тем не менее…

    Reply
  21. ipoloskov

    (20) а, кажется понял — оптимизатор выбрал все объекты из базы с ОбъектСсылка.ПометкаУдаления = ЛОЖЬ, и потом соединил их с Тбл?

    Reply
  22. sergathome

    (21) +1

    Но даже не это самое несмешное. Самое — это то, что в половине случаев он делает правильно…

    Reply
  23. ipoloskov

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

    Reply
  24. sergathome

    (23) так оно и есть. в данном случае причина скорее всего в этом. Если бы платформщики позаботились о том, как задать для смешанного типа тип по-умолчанию… Но об этом можно только мечтать. (с) Зато рекомендаций дурацких на ИТСе — масса 🙁

    Reply
  25. herfis

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

    Во всяком случае, я ее понял именно так. Или я вообще не понял, о чем публикация.

    Reply
  26. herfis

    (25)

    Зато рекомендаций дурацких на ИТСе — масса 🙁

    По особенностям разыменования составных полей на ИТС есть специальная статья. Так что не надо ля-ля.

    Reply
  27. sergathome

    Статья — это прекрасно. В одной пишем одно, в другой — другое… Документации внятной нет, как и не было.

    Reply
  28. kuzyara

    (17) У меня запросы из (4) выполняются одинаково.

    Если не трудно, покажи свой вывод субд.

    Reply
  29. kuzyara

    (17)

    Теперь заключительный вопрос, что отработает быстрее

    Монопенисуально, блджад! http://www.itprotoday.com/microsoft-sql-server/distinct-vs-group

    Q: Should I use DISTINCT or GROUP BY to eliminate duplicates in a result set?

    A: A DISTINCT and GROUP BY usually generate the same query plan, so performance should be the same across both query constructs. For example, say you want to generate a unique list of product IDs from the Order Details table in Northwind. The two following queries both give you a correct answer:

    SEL ECT DISTINCT od.productid

    FROM [order details] OD

    SEL ECT od.productid

    FR OM [order details] OD

    GROUP BY od.productid

    Which one is more efficient? Checking execution plans is a simple way to determine the relative efficiency of different queries that generate the same result set. Enable Show Execution Plan in Query Analyzer by pressing Ctrl+K or by selecting Show Execution Plan fr om the Query menu. Then, execute the above queries. Figure 1 shows that the execution plans of both queries are the same. In most cases, DISTINCT and GROUP BY generate the same plans, and their performance is usually identical.

    So, how do you decide which SQL command to use? GROUP BY is required if you’re aggregating data, but in many cases, DISTINCT is simpler to write and read if you aren’t aggregating data. Pick whichever syntax you prefer for your situation.

    Саша, всё, не буду больше спорить про производительность, в данном случае она одинакова.

    Но синтаксически DISTINCT здесь, естественно, правильнейлогичней.

    Reply
  30. KAV2

    Все таки оптимизацию первого запроса полагаю делать надо в случае возникновения проблем с производительностью, когда запрос реально тормозит. Заранее устранять разименовывание в этом конкретном запросе я бы не стал, не целесообразно, потому что как правило все же запрос будет выполняться оптимально, да и по хорошему надо денормализировать — записывать данные в регистр, если же регистра нет, то это либо ошибка проектирования либо просто задача не особо важная и 5 сек. вполне можно подождать.

    Reply
  31. spezc

    посмотрите на типовые. «разыменование» там чуть меньше чем везде.

    Reply
  32. nytlenc

    (29)


    Саша, всё, не буду больше спорить про производительность, в данном случае она одинакова.

    Но синтаксически DISTINCT здесь, естественно, правильнейлогичней.

    ну наконец то… )))

    Reply
  33. kuzyara

    (32) Наверное, я слишком буквально понял задачу оптимизации… А ответ на поверхности! )))

    Reply
  34. awk

    1. Оптимизировать можно только работающее решение — оптимизация неработающего решения бесполезна.

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

    3. Оптимизировать надо когда это требуется, а не «пожизни». Нет смысла оптимизировать код который работает 0.1 секунды. Если вы его оптимизируете и он будет работать 0.001 секунду, то разницу 0.099 секунды не заметит НИКТО. И ваше стократное улучшение в и затраченное время пойдет «псу под хвост».

    Reply
  35. AlexeyDmuhin

    (34) В ларьке может и не заметно, а вот на сервере, где несколько сотен пользователей, очень даже заметно. Ресурс не резиновый ни разу

    Reply
  36. awk

    (35) «А за козла придется ответить перед всем стадом» 🙂

    Тест кейс:

    Дано: ларек 700 человек. заполнение документа по ЗП 24 минуты.

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

    Результат: Кадровик перестал успевать попить чаю.

    Вывод: Я, дебил, не посмотрел частоту формирования документа.

    Ты то же ресурс, и то же не резиновый. Ограничение рабочего дня, для умных — 8 часов, для дебилов (я себя из них не выделяю) 24 часа в сутках — физическое ограничение.

    Reply
  37. AlexeyDmuhin

    (36) отличный результат! После сотого заполнения вы отобьете временные затраты :), по стоимости часов сами считайте 🙂

    Reply
  38. awk

    (37)

    1. 24 * 100 / 60 = 40 часов

    2. 5 * 8 = 40 часов

    Вот только у меня ЗП раза в три выше чем у оператора.следовательно:

    24 * Х / 60 = 120

    Х = 120 *60 / 24 = 300 раз. 300 месяцев = 25 лет.

    Дебил в тест-кейсе я. Даже если он по 10 раз запустит в месяц это все равно 2.5 года. А зарплатные документы запускают раза 2-3. 1С новые конфигурации раз в 3-5 лет выпускает. Вот оно и «псу под хвост»

    Reply
  39. herfis

    (36) Речь не про оптимизацию эксплуатируемой системы, а про первоначальное написание качественного кода (простого, читабельного, но при этом обеспечивающего хорошую масштабируемость). Здесь невозможно говорить «в общем», можно обсуждать только конкретные ситуации.

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

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

    Reply
  40. AlexeyDmuhin

    (38) Дело в том, что гораздо чаще все сводится не только к одному себе любимому, высокооплачиваемому спецу. Не забывайте, что в системе с Вашим запросом могут работать большое количество людей и все эти секунды, минуты и т.д. выливаются в увеличенное время ожидания. И запросов таких может быть много и все вместе это будет давать колоссальную нагрузку из-за не оптимальных выборок данных. Попробуй потом разобраться — действительно нужен новый сервер или руки программиста все могут исправить. Пример с заполнением документа ЗП действительно не показатель.

    Reply
  41. awk

    (39) https://checkist.livejournal.com/160130.html

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

    Reply

Leave a Comment

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