Как стать автором
Обновить

Комментарии 24

Очень интересная статья, напоминает фильм Криминальное чтиво. Особенно связностью эпизодов и ощущением после просмотра - а о чем это, собственно?

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

Насколько я знаю, PDO::MYSQL_ATTR_INIT_COMMAND не нужно использовать для SET NAMES. Возможно, причина и есть, но приведенная аргументация не выглядит убедительной.

Мы знаем, что для согласования кодировки в соединении и кодировки в DSN строке можно использовать SET NAMES.

Здесь написана какая-то ерунда. Согласовывать кодировку в DSN ни с чем не нужно.При соединении надо сообщить базе данных кодировку, в которой мы отправляем данные в базу, и кодировку, в которой она должна возвращать данные. Это можно сделать ИЛИ с помощью SET NAMES, ИЛИ в DSN. Причем второй способ является предпочтительным.

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

При этом, раз уж мы выключили, то упарываться по типизованной привязке не нужно, все будет работать и так. Случаи, когда реально нужно указать тип, можно пересчитать по пальцам за все время вашей карьеры. Собственно, во всем этом разделе присутствует легкий налет шизофрении: вроде бы, нам автор "рекомендует" эмуляцию выключать, но по тексту ниже становится ясно, что сам он её наоборот - включает.

В целом, если честно, то я бы убрал часть про mysqli, поскольку на этом месте вообще перестаешь понимать, о чем эта статья, и про GBK, поскольку она не имеет отношения к написанию ORM (что, как я под конец понял, и является темой статьи). Ну и подача, соответственно: Начали за здравие(PDO::quote()) - кончили за упокой (почему-то mysql_real_escape_string). Нужно или заранее знать, или по коду ниже догадаться, что речь про функцию С API. Но, главное - эта полумифическая уязвимость работает только если неправильно указать кодировку. А если указать ее правильно, то даже и с ужасной GBK все будет отлично работать без уязвимостей.

Про 0x-истеричную систему тоже не очень понятно. Во-первых - а чем, собственно, не устроил биндинг? Ну то есть я склонен доверять автору и допускаю, что скорее всего причина была. Ну вот это тогда и есть самое интересное - почему почему именно такое решение выбрано для работы с JSON, а не нормальный биндинг (или тот же квотинг, на худой конец). И почему только MSSQL? Старушка MySQL точно так же поддерживает хекс-енкодинг. Но опять же, я не вижу ни одного преимущества по сравнению о стандартным квотингом.

Но в целом, повторюсь, статья очень захватывающая, побольше бы таких.

Как уже упомянул в начале статьи - это моё частноем мнения и я легко могу ошибаться.

По поводу MYSQL_ATTR_INIT_COMMAND - взял не с потолка, видел примеры с set names в коде. Про согласование кодировки - тоже встречал баг-репорты, но может я и не правильно трактовал их причину.

Про ATTR_EMULATE_PREPARES - опять же, это моё частное мнение, что так можно себе навредить.

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

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

"У Рабиновича было свое мнение, но он его не разделял"
Как-то нелогично выходит, мнение у вас одно, а в примерах — совсем другое :)


Ну и чисто по логике, где тут "навредить"?
Само по себе выполнение мультизапросов — это нелепые страхи. В свое время, кстати, ircmaxell решил вдруг резко наехать на Сашу, добавив здоровенный WARNING к его ответу как раз про мультизапросы и вообще предлагал этот ответ удалить. Вы определитесь, с кем вы — с Феррарой или с Макаровым ;)


Ну и там же очень простая логика: борьба с мультизапросами — это не борьба с уязвимостью! Если у тебя инъекция, то и без мультизапросов УЖЕ все плохо. Бороться надо с инъекциями, а не с мультизапросами.


И, кстати, по поводу SET NAMES такая же шняга. Если смотреть на все "примеры в коде", то получится один испанский стыд. Просто потому что плохих примеров в РНР — пруд пруди. Слава богу, Камиль начал заниматься документацией по PDO, и повычистил оттуда самые дикие примеры, типа die($e->getMessage()) или вот этих SET NAMES.


В коде они встречаются разве что для обратной совместимости с ископаемой версией 5.3.6. Но же вы сами ниже говорите, что зажали версию на 8. А в целом, MYSQL_ATTR_INIT_COMMAND с SET NAMES — это чистый карго-культ, из палаты мер и весов.

Перечитал статью и не увидел, где написано что мы используем MYSQL_ATTR_INIT_COMMAND в разработке. Просто написал, что так можно. Используйте или нет, не настаиваю.

Также и с примерами - в статье примеры "возможных" применений.

Подготовка параметров запросов на клиенте мне просто не нравится. К сожалению иногда без этого не обойтись - как к примеру в случаях с использованием одного биндинга в запросе несколько раз. Ну и с мультизапросами. Но нравиться это мне не начинает.

Ну и ладно, это, в общем мелочи.
Я тут посмотрел краем глаза в код. Я правильно понимаю, что вы делаете типизованный биндинг на основании типа данных в таблице? Это прямо хорошее решение. Должно быть безопасно, в отличие от того, как сделано в Елоквенте, который вынюхивает тип биндинга из типа переданного значения.

Классная статья! Спасибо! Видно, что проделана большая работа.

Мы переехали на mysqlnd — изменился режим работы с буферизированными запросами. Сделали большой запрос — и это при получении может вызвать переполнение памяти. Выключите и получайте большие объёмы данных из соединения постепенно.

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

Обрабатывать данные на месте, если уж строится инфраструктурный слой приложения, некруто. Поэтому их как-то надо отдавать на уровень бизнес-логики в обработчики. И тут на помощь могут прийти правильно приготовленные генераторы. "Правильно приготовленные" я тут понимаю так, что нигде в приложении при работе с небуферизованными запросами в БД не надо пытаться складывать весь пакет данных - его надо сразу обрабатывать, получать консолидированный результат и забывать.

Долго вчитывался в ваш комментарий, пока не понял, что со всем согласен :)
В общем да, не лишнее замечание. Так-то обычно люди приходят к небуферизованым запросам из практики, то есть уже набив шишек с получением массива и выучив, что надо фетчить по одной. А потом уже узнают, что надо еще и чтобы РНР не собирал все в буфер. Но если давать эту инфу заранее, то лучше лишний раз напомнить, и про фетч по одному, и про генератор, чтобы абстрагировать получение данных от их обработки.


Я кстати забыл отметить, что в процитированном абзаце немного неверная формулировка. Я и сам так раньше писал, и меня сам Никита поправил: само по себе переполнение памяти может выйти в любом случае, просто при использовании mysqlnd память, занятая под буфер, считается как память РНР процесса (и попадает под memory_limit), а в libmysql она была как бы "бесплатной" с точки зрения РНР (но системную память, разумеется, все равно занимала).

Тут еще вопрос подачи информации же :)

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

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

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

Да. Генераторы, все-таки, относятся больше к архитектуре приложения, чем к экономии памяти. В статье достаточно упомянуть получение строк в цикле, а уж использовать его по месту или переносить в другое место — это уже вопрос архитектуры. Хотя… если вспомнить, что статья изначально про ORM, то, в этом контексте как раз и стоило бы упомянуть. Вроде бы, Eloquent как раз использует генераторы, в коллекциях. Причем, если я не путаю, как раз "дурные". То есть сама коллекция — это генератор, но вот результат в нее передается в виде массива. Или это я с чьим-то самописом путаю.

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

Если говорить про Laravel в целом, то там вроде в LazyCollections еще генераторы используются. И если верить бенчмаркам, то кажется, что даже эффективненько.

Мы для DataProvider как раз используем FETCH_CURSOR + генераторы. По остальному добавить нечего )

Соврал ( cursor + обычный fetch в dataReader

Спасибо за такой полезный материал! Как раз создаю ORM с PDO драйверами.

Статья объемная и может быть что-то пропустил. Хотел спросить как вы решаете проблему поддержки разных версий PDO. Сигнатуры методов для версий разных версий PDO различаются. Например метод PDO::query

PDO v.7( PHP 5.6 на сколько помню ): public function query( $statement, $mode = PDO::ATTR_DEFAULT_FETCH_MODE, $arg3 = null, array $ctorargs = [] )

PDO v.8( PHP 7+ ): public function query($statement, $mode = PDO::ATTR_DEFAULT_FETCH_MODE, ...$fetch_mode_args)

Делаете ли вы поддержку для старых версий или отказались от неё?

Для 3 версии, срезали старое и ограничили минимально поддерживаемые версии PHP до 8.0, также поступили и с DBMS.

В вашем случае могу посоветовать только чекать версии и подкладывать костыли както вот так: version_compare(PHP_VERSION, '5.6', '<')

Большое спасибо за ответ!

Наверно надо отказаться от поддержки старого. Работая над новым проектом как-то по-инерции думаю о PHP 5.6 =(

Вы что-то путаете.
"Сигнатура", если ее можно так назвать, там действительно адовая. Но она вроде не менялась.
Мне тут скорее интересно узнать юзкейс, в котором PDO::query используется в каком-то варианте, отличном от public function query( $statement). Вот реально интересно.

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

fetch mode можно указать например непосредственно в query(), а не выставлять его отдельно.

Извините за занудство, но мне очень хочется разобраться.
А не могли бы вы привести пример кода, который работает в 8 и не работает в 5.6?
Вот есть очень удобный онлайн шелл, который как раз поддерживает и PDO, и разные версии РНР.

Хорошая статья, есть много полезной информации. Единственное пара моментов зацепила.

PDO::ATTR_ORACLE_NULLS

Как мы знаем в Oracle пустая строка = NULL, потому этот флаг добавлен для поддержания "единообразия поведения" между разными бд.

Хранение JSON в BLOB полях. Типичная задача для ... RBAC

Во-первых, задачу можно сделать разными путями, но канонично было бы разложить данные в нормализованные таблички. Во-вторых, JSON это символьный формат и потому хранить его надо в CLOB. Выбор BLOB кажется неуместным. В-третьих, большинство БД поддерживают JSON тип данных нативно.

Цитата из статьи "Лучше использовать специализированные данные, но мы попытаемся сохранить JSON в непредназначенном для этого BLOB-поле.".

Удивительным образом не раскрыта тема типизации результата произвольного запроса (SELECT ARRAY[1,2,3]). Вроде бы и есть getColumnMeta, но помнится с ней грабли были везде. В Постгресе, помнится, из-за сложных типов и массивов, а у Сфинкса / Мантикоры, вроде, были причуды из-за MVA.

Я до сих пор не могу нормально использовать ORM. Это намного сложнее, чем составлять SQL запросы. Возможно потому, что я знакомился подробно с той же PostgreSQL и мне проще составить оптимальный запрос в SQL варианте, чем искать как это сделать через ORM. Понятно, что в ORM базовые вещи делаются проще без составления всяких там запросов. Но если хочется получить из БД реальные данные чем то сложнее, чем JOIN, то их получение через ORM - это изучение тонн документации ORM, чтобы понять как это сделать так, чтобы оно работало и работало эффективно. Это лишний геморрой, потому что можно просто взять и составить SQL запрос.

Всё верно, лично я так и делаю — CRUD операции через ORM, а всякая аналитика — чистым SQL.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий