Pull to refresh

Comments 196

Сначала прочитал в эпиграфе «немасштабируемые» как «масштабируемые», и очень долго был в ступоре.
Стоит еще заметить, что подготовленные выражения незаменимы при работе с блобами
Особенно учитывая наличие mysql_stmt_send_long_data().
Так много сказано про достоинства и почти ни слова про недостатки. Я мог бы многое рассказать про связываемые переменные в Oracle, но так как статья про MySQL (непонятно зачем в конце статьи дана ссылка на Кайта, ведь это совершенно разные СУБД), то поделюсь своими небольшими знаниями по теме топика, а именно по недостаткам, которые следует учитывать:
  • При использовании связываемых переменных (СП) не всегда работает кеш запросов, а именно: до версии 5.1.17 он вообще не использовался для запросов со СП, а после — используется при определённых условиях (How the Query Cache Operates).
  • Если запросы однократные, то СП только ухудшат произвотельность
  • Подготавливаться (prepare) могут только определённые запросы: кажется только DML + create table, а подставляться могут только значения, но никак не названия колонок, таблиц и проч. Поэтому если говорить про SQL-инъекции — СП не серебряная пуля, если у вас, например, динамически формируется название таблицы или колонки (например, заказы за 2012 год хранятся в таблице orders_2012, пользователь на сайте вводит год, и вы формируете название таблицы в виде «orders».$year)
  • Раньше были проблемы со связыванием списков, сейчас не в курсе. Т.е. у вас не получится связать "… WHERE X IN (?)" с массивом.
  • Ну и последний момент: в логах сохраняется запрос без подставленных значений, поэтому администрировать будет сложнее.
Ещё, например, в PostgreSQL у запроса со СП план может получиться хуже, чем без СП (т.к. план составляется один раз без знания конкретных значений СП). Так что, если делается, к примеру, тяжелый поисковый запрос, то СП лучше не пользоваться. Интересно, насколько это актуально для других СУБД?
Для oracle тоже актуально.
Поэтому в DWH и OLAP часто не рекомендуют не использовать СП, в отличие от OLTP систем, где литералы положат систему парсингом.
А в каких СУБД названия колонок и таблиц можно подставить в СП? По такой логике ни один оптимизатор не сможет построить адекватный план запроса и использовать статистики таблиц и индексов.
СП то для того и нужны, чтобы в некоторых случаях сократить время на создания плана запроса (естественно надо понимать, когда это нужно делать, а когда нет). Сами ведь знаете что при СП оракловая база будет использовать один план запроса.
Ответил ниже. В том числе и про:
при СП оракловая база будет использовать один план запроса

Это справедливо лишь для Oracle версии <= 10.2.0.1. Сейчас с этим, к счастью, дело обстаят получше.
Под одним планом я и подрузамевал, что один для всех значений параметров (adaptive cursor sharing)
Возможно я вас неправильно понимаю, но при adaptive cursor sharing как раз таки для одного и того же запроса (один sql_id) Оракл может сгенерировать несколько различных планов запросов в зависимости от значений параметров и статистики их распределения, в этом-то и прелесть ACS — запрос один, в планов может быть несколько.
Я думаю, что мы говорим об одном и том же.
Мне, к моему счастью, больше не приходится писать на PL/SQL, поэтому я могу путать терминологию.
Я мог бы многое рассказать про связываемые переменные в Oracle…

Расскажите. Чем больше полезной информации по теме, тем лучше, не так ли?

Статью я старался писать вообще про prepared statements, не зацикливаясь именно на MySQL. Связываемые переменные есть в любой вменяемой СУБД (даже в SQLite есть) и основные принципы их работы похожи. Ссылку же на Тома Кайта я дал потому, что у него дано очень хорошее объяснение, чем плохо неиспользование связанных переменных в Oracle (гуглить фразу, приведенную в эпиграфе).

С остальными замечаниями согласен. Хотя с моей (сугубо моей личной) точки зрения, если у вас названия таблиц/колонок зависит от ввода пользователя — это сигнал о, скажем так, несколько странном проектировании базы данных.
Насчет IN люди есть небольшие рассуждения — там приведены некоторые методы решения.
Да, prepared statement-ы есть почти везде, но принцип работы везде разный.

Про постргрес уже написали выше, в Оракле когда-то было похожее поведение: план запроса вычислялся один раз, и для его генерации использовались первые параметры. Т.е. имея запрос: select col from tab where x = ?, и выполняя его впервые для x = 1, план строился именно для x = 1, и учитывал статистику распределения именно для x = 1. К примеру в таблице имеется только одна строка с x = 1 и миллион с x = 2. Для запроса с x = 1 оптимизатор выбрал доступ по индексу. Следующий запрос с x = 2 будет использовать доступ по индексу, несмотря на то, что значительно быстрее было бы сделать full table scan.

С появлением adaptive cursor sharing всё изменилось. Запрос с x = 2 опять будет тормозить, но только первый раз. После того как Оракл увидит, что актуальная статистика выполнения запроса сильно отличается от предполагаемой он создаст новую версию плана (старая при этом остаётся), и уже следующий раз запрос с x = 2 сделает full table scan, a x = 1 всё также будет делать поиск по индексу.

В общем есть ещё много чего, о чём хотелось бы рассказать про bind variables в Оракле, но комментарий будет слишком длинный.

Насчёт подстановок имён таблиц. Я бы не сказал что это странное поведение. К сожалению low-end базы данных не имеют номальных возможностей, например, партицирования таблиц, поэтому иногда приходится извращатся. Конечно это оправдано, если это вынужденная мера. Но часто это делают без особой необходимости, в большей степени это относится к PHP и MySQL, где средний процент «извращенцев», уж извините, больше чем где-либо (из моего опыта). Я бы сказал что это связанно с недостаточной квалификацией соответсвующих разработчиков, но боюсь меня закидают помидорами :)
Честно говоря, по моему опыту администрирования ничего хорошего про этот ACS сказать не могу.
В теории все красиво, а на практике регулярно получаешь кривые планы и практически лежащую БД.
В идеале, если у нас OLTP система, то запросов с x=2 из примера не должно быть в принципе, а для использования индекса достаточно было было и старого доброго rule-based плана :)
А если у нас DWH, то там ресурсы на парсинг несоизмеримо меньше, чем на выполнения и эффектвнее иметь актуальную статистику и использовать литералы, отказавшись от СП.
Допустим, у меня есть таблица. Пользователь может сортировать её по нескольким колонкам — скажем, названию товара, цене, наличию. Что в этом странного?
В этом странного ничего нет. Обычный order by name, price, count. Не вижу здесь динамического формирования названий таблиц или колонок в них.
Странное — это когда названия колонок и таблиц в базе данных зависят от ввода пользователя (например, сделано «ручное» партиционирование по годам):

$sql = 'select * from price_'.mysql_escape_string($_GET['year']).' order by name';

Кстати, мне кажется, в данном случае mysql_real_escape_string внезапно не защищает от SQL-инъекции.
Ведь переданная в _GET['year'] строка ;drop table user;... явно сделает то, что хотел хакер. И простой mysql_real_escape_string никак не спасет, нужна более серьезная проверка.
Ключевое слово — пользователь. Пользователь выбирает колонки для сортировки. Может выбрать один порядок, а может — другой. Так понятнее?
Вы бывали когда-нибудь в интернет-магазине? Там можно сортировать список товаров — можно отсортировать по имени, а можно — по цене. Теперь динамическое формирование видно? Вопросов не вызывает?

Вам совершенно правильно кажется.
Я рад, что вас «внезапно» это озарило :)
Теперь попробуйте окончательно понять и сформулировать — что это за функция и для чего она нужна на самом деле.
После этого было бы неплохо исправить вашу фразу «проблема с функцией mysql_real_escape_string в том, что ей вообще пользуются», добавив к ней важное уточнение.
Вы что-то путаете. Повторяю: странным подходом я называл случай, когда имена таблиц формируются в зависимости от ввода пользователя, как я привел в примере. Порядок сортировки никак на имена таблиц не влияет, влияет только на порядок имен в order by clause. Да, возможно, в этом случае запрос надо формировать динамически. Не вижу, как это относится к теме статьи.
Да как это — не относится?
Если имя поля подставляется в запрос динамически, на основании пользовательского ввода, это ещё как относится к теме статьи! И к утверждению, что «А вот PDO (и MySQLi) как раз панацея, поскольку SQL injection при их грамотном использовании невозможны».
Еще как возможны, оказывается. И безо всяких «странных подходов», а в самых что ни на есть обычных ситуациях.
В вашем примере (сортировка полей) названия полей не формируются динамически из пользовательского ввода (необходимости в этом нет). Динамически формируется только их порядок в order by clause. Вот приблизительный код формирования данного запроса:

$sql = 'select * from price';
$cond = Array();
for($i=0;$i<2;$i++)
{
     if($_GET['order_'.$i]=='name')
          $cond []= 'name';
     if($_GET['order_'.$i]=='price')
          $cond []= 'price';
     if($_GET['order_'.$i]=='count')
          $cond []= 'count';
}
if(count($cond)>0)
   $sql .= ' order by '.implode(',',$cond);

Укажите, как именно здесь возможна SQL инъекция.

Вариант, что программист передает в _GET/_POST собственно названия колонок и напрямую вставляет в запрос названия из пришедших данных без всяких проверок, не предлагать — это уж совсем неквалифицированный программист.
Да-да, именно этим заканчивают все ламеры, взявшиеся учить других в вопросе, по которому они прочли полторы статьи и сразу почувствовали себя гуру :)
«Ну я же пишу для квалифицированных программистов! Поэтому всё, о чем я не знал и во что меня ткнули носом в комментах — это само собой разумеется и всякий квалифицированный программист и так это знает» :)

Вопрос — зачем было тогда вообще статью писать, если все и так всё знают — им в голову не приходит :)
Речь именно о случае
for($i=0;$i<2;$i++)
{
     $cond []= $_GET['order_'.$i;
}

и добавляй mysql_real_escape_string() в присваивании или не добавляй разницы нет в плане защиты от SQL-инъекций. Использовать pdo, mysqli или mysql тоже разницы нет.
UFO just landed and posted this here
Если ваши запросы собираются динамически, то вы сами должны заботиться об их безопасности. А вот о безопасности статических запросов прекрасно заботятся СП.
О безопасности статических запросов заботиться не нужно — на то они и статические :)
Другое дело, если динамически в запрос подставляются не только данные.
Но вот если технология защиты требует разработчика «самого заботиться о безопасности», то грош ей цена.

А собирать условия в запросе на самом деле не так уж и сложно:

if ($_GET['num']) {
$sql .= «num = ?»;
$params[] = $_GET['num'];
}
Все прекрасно поняли о какой статике идет речь — если в запрос подставляются только переменные. А вот если в запрос подставляются так же имена таблиц, функции и т.п. то тут уж придется подумать.

Что касается технологии — она не требует от разработчика «самого заботиться о безопасности». Забота о безопасности возникает когда разработчик начинает придумывать велосипеды с динамическим склеиванием запросов.
if ($_GET['num']) {
$sql .= «num = ?»;
$params[] = $_GET['num'];
}

А если в запросе больше одного условия? :)
… то таких проверок пишется больше одной.
Дело в том, что «динамическое склеивание запросов» — не «велосипед», а насущная необходимость. В частности, для случаев «сложных форм поиска», о которых говорил автор оригинального комментария.

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

Да, и если в запрос подставляются данные, то это уже не статический запрос. Давайте будем точны в формулировках.
Уверяю вас, всегда есть варианты как решить эту задачу без склеивания запросов.
Во-первых, склеивание не есть что-то ужасное, как вам, по всей видимости, кажется.
Любое веб-приложении по сути всегда работает с текстом, динамически формируя («склеивая») его из имеющихся фрагментов. Почему вы считаете это нормальным для случаев XML или HTML, но называете «велосипедом» для случая SQL — для меня загадка.

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

public function getFormContent($language, $dict_id, $from, $to, $filter){
        $this->connect();
        self::$connection_oracle->setRequestParam('p_lang', $language);
        self::$connection_oracle->setRequestParam('p_dict_id', $dict_id);
        self::$connection_oracle->setRequestParam('p_from', $from);
        self::$connection_oracle->setRequestParam('p_to', $to);
        $queryParams = '';
        foreach($filter as $key=>$value){
            self::$connection_oracle->setRequestParam($key, $value);
            $queryParams=$queryParams.',:'.$key;
        }
        $query = "SELECT * FROM TABLE(". self::PKG_DICT_VIEW .".get_dict_data(:p_lang, :p_dict_id, :p_from, :p_to".$queryParams."))";
        return self::$connection_oracle->requestArrayHash($query);
    }


Но это одно дело, а склеивать в запрос данные пришедшие от пользавателя без использование СП, это совсем другое дело.
Что касается «сложных форм поиска», не вижу причины передавать в запрос при помощи СП ВСЕ параметры пришедшие из формы поиска.
Ничто не мешает подготавливать запросы для всех вариантом и складывать их не в одну переменную, а в массив в зависимости от того, какой финальный запрос получился.
UFO just landed and posted this here
Я же не предлагаю их заранее подготавливать.
Правильно, что написали про подготовленные выражения
Автор не совсем понимает тему, на которую взялся писать.

«Достоинства»
(1) и (2) В типичном веб-приложении данные достоинства могут проявиться очень редко. Один и тот же запрос выполненный несколько раз в пределах одного и того же соединения — это, чаще всего, показатель кривого кода.
(3) Многие апологеты подготовленных выражений забывают, что кроме данных в запросах могут быть и другие динамические элементы.

«Ответы на комментарии»:
— Проблем у функциии mysql_real_escape_string практически нет. Проблема есть с пониманием, для чего она служит. Многие люди, действительно, полагают, что эта функция имеет какое-то отношение к инъекциям. Такая проблему существует, да. Но рекомендовать не использовать mysql_real_escape_string — это все равно что не рекомендовать пользоваться вилкой только потому, что некие люди зачем-то пытаются есть ей суп.

— PDO (и MySQLi) не панацея, о чем я писал выше.

Автор путает понятия «подготовленного выражения» и «плейсхолдера».

Ничего нет о недостатках подготовленных выражений.

А заблуждений, связанных с инъекциями, в народе действительно много.
Я подробно разбирал их на ПХПКонфе для желающих.
Я с удовольствием поясню.
Prepared statement — это SQL запрос, который был предварительно prepared. То есть, это запрос, над которым совершили совершенно конкретную операцию — prepare() — отправив его на сервер БД с плейсхолдерами вместо данных.

Понятие плейсходера же гораздо шире. Это подстановка в общем смысле. %s в printf — это тоже плейсхолдер. И совсем не обязательно плейсхолдер в SQL должен быть реализован с помощью подготовленных выражений. Его можно обработать и на клиенте, руками. Причём клиентский плейсхолдер получается гораздо более гибким и полезным. В итоге запросы вполне могут быть безопасными и без использования подготовленных выражений. Причем даже более безопасными.
UFO just landed and posted this here
Да, в целом вы верно поняли (если я сам правильно понял). Из-за этого для однократных запросов prepared statements могут быть медленнее — все-таки запросов к БД выполняется два.
Но лично мне кажется, что в подавляющем большинстве случаев собственно запросы повторяются очень часто, меняются только данные в них. Остальные весьма специфичные варианты можно отследить здесь в комментариях.
Мне что табличку Sarcasm, как Леонард, все время держать?
Prepared Statement включает в себя placeholder, поэтому их можно считать чем-то схожим.
Prepared Statement может не включать в себя ни одного плейсхолдера. Это просто одна из реализаций. Могут быть другие, не на плейсхолдерах
Спасибо, капитан. Я даже не догадывался!
«Достоинства»
(1) и (2) В типичном веб-приложении данные достоинства могут проявиться очень редко. Один и тот же запрос выполненный несколько раз в пределах одного и того же соединения — это, чаще всего, показатель кривого кода.

Ну вообще уважающий себя SQL-сервер кэширует план выполнения параметризованного запроса существенно долше, чем время одного соединения.
Ну так он прекрасно кэширует план и для обычных, неподготовленных запросов :)
А вот нифига. Запросы «select * from qqq where aaa = 'ppp'» и «select * from qqq where aaa = 'rrrr'» — это с точки зрения кэша два разных запроса (хотя, например, в MS SQL и для таких случаев срабатывает оптимизатор, но не всегда), а вот запросы «select * from qqq where aaa = @p1», p1 = 'ppp' и «select * from qqq where aaa = @p1», p1 = 'rrrr' — это один запрос.
С точки зрения кэша запросов — разные.
С точки зрения кэша плана запросов — одинаковые.

Не будем менять топик на ходу, хорошо?
С точки зрения кэша плана запросов — одинаковые.

Нифига. Что такое, по вашему, кэш плана запросов? Это кэш, где значением является план, а ключом — нормализованный текст запроса. Так вот, нет никакой гарантии, что запросы с явными значениями будут нормализованы до одного текста (и, как следствие, второй из них создаст cache hit).
Не понял. С какой это стати нет гарантии? А в чем препятствия-то? Для запроса «с явными значениями» парсер не сможет отделить значение от ключевых слов, что ли? :)
С какой это стати нет гарантии?

Ну вот производители серверов не дают такой гарантии.

А в чем препятствия-то?

В парсере.

Для запроса «с явными значениями» парсер не сможет отделить значение от ключевых слов, что ли?

Видимо, не всегда. Особено это понятно для запросов, где один и тот же параметр используется больше одного раза.
То есть, в выражении «WHERE id=42» парсер не всегда может разобрать, где ключевое слово, где идентификатор, а где значение?
Вероятно да, раз производители БД в один голос утверждают, что параметризованные планы кэшируются лучше (а MS SQL отдельно хвалится тем, что умеет в некоторых случаях</> разбирать такие выражения до параметризованного плана).
Простое упражнение:

WHERE id=42 AND parentId=42

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

Парсер, очевидно, может разобрать, где ключевое слово, а где значение. Но парсер не знает, будет ли это значение меняться, и как следствие, не понимает, можно ли его использовать для построения плана запроса. Считать все значения в запросе параметризуемыми — слишком дорого, получится запрос, который не учитывает статистику.
Отвечу, как я это представляю. Я не изучал исходные коды MySQL или PostgreSQL, но думаю, что обработка запроса выполняется приблизительно так:
1. Сначала полный текст запроса ищется в кэше. Если он есть — сразу грузится скомпилированный код запроса, план запроса и так далее. Иначе — шаг 2.
2. Выполняется синтаксический разбор запроса, в котором уже возможно отделение запроса и данных, которые в него переданы. Здесь тоже возможно обращение к кэшу уже с результатами синтаксического разбора, и план запроса тоже может грузиться из него.

Так вот, синтаксический парсинг (шаг 2) — сама по себе «тяжелая» операция (в том же Oracle, по уверениям Тома Кайта, синтаксический разбор иногда занимает больше времени, чем собствено выполнение запроса).
Очевидно, что в случае использования prepared statements ситуация (2) будет выполняться гораздо реже, чем при обычных запросах (склеенных с данными в виде одной текстовой строки).

Кстати, и имел я в виду не кэш плана запросов, а синтаксический кэш, в котором хранятся результаты «компиляции» (синтаксического разбора) запроса. План запроса появляется чуть-чуть позже.
В этом треде действительно присутствую некоторый закос в сторону обсуждения идеальной СУБД в вакууме :)
Для J2EE-серверов исполнение одного и того же запроса в пределах одного соединения — это скорее правило. Открытые соединения кэшируются в пуле соединений сервера и обслуживают несколько потоков, а повторение одинаковых запросов (или запросов, которые отличаются только значениями) в разных потоках — это вполне нормальное явление.
1. Хабр не только для веб программистов.
2. На PHP пишут не только веб приложения.
Это очень важное замечание. Без него мой комментарий полностью теряет смысл.
Большое спасибо за уточнение.
Из этих двух топиков скалыдвается ощущение, что некоторая часть разработчиков ведут себя как страусы: если ORM/библиотека/язык/что-то еще скрыли от меня работу с БД, то значит не нужно интересоваться основами ее работы.
Никто за разработчика его работу делать не будет — ни ORM, ни язык, ни библиотека. Нужно знать как ваша БД работает с запросами, исполняет их и хотя бы простейшие способы оптимизации (что бы не отстрелить себе ногу).
Напишу, пожалуй, о недостатках подготовленных выражений.

Как это часто бывает, страстными сторонниками того или иного явления или технологии являются неофиты. И это объяснимо — люди, которые давно работают, уже не испытывают того пиетета и желания срочно поделиться с окружающими.

В этом нет большой проблемы кроме той, что знания у неофита по большей части теоретические, из рекламных проспектов. В реальной же жизни не всё так красочно.

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

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

— такие конструкции, как IN(...). WHERE IN(:stroka_s_sapyatymi) почему-то не работает. Слабые духом сдаются и подставляют вместо плейсхолдера переменную. Сильные духом пишут специальный код для динамического составления оператора IN… И вместо сокращения кода мы получили его разрастание в несколько раз… Совсем не так все красиво оказалось, как в рекламе?

— идентификаторы. Опять разочарование. `:field_name` почему-то не работает. причем никакой код тут уже не помогает, увы.

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

Пользуйтесь профайлером, который показывает запрос с параметрами в виде, который просто копируется в консоль.

такие конструкции, как IN(...). WHERE IN(:stroka_s_sapyatymi) почему-то не работает.

И какую долю такой код составляет от общего? Опыт показывает, что далеко не превосходящую.

идентификаторы

Какие идентификаторы?

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

Как уже говорилось выше — нет. Кэш плана запросов длиннее, чем одно соединение к БД.

а вот очевидная задержка — из-за того что для выполнения каждого запроса скрипт обращается к БД по два раза — объективно присутствует.

Ну так пользуйтесь такими клиентами, которые не требуют отдельного обращения к БД для создания параметризованных запросов.
Можно пример «профайлера», который берет на себя функции парсинга SQL и подстановки данных на место плейсхолдера?

Что такое идентификатор — в принципе, неважно (хотя и вполне понятно из приведенного рядом примера). Нам важен сам факт того, что подготовленные выражения для идентификаторов в принципе невозможны.

По поводу клиента, который требует отдельного обращения к БД — это не ко мне, а к автору топика. Это он ратует за их использование.
Можно пример «профайлера», который берет на себя функции парсинга SQL и подстановки данных на место плейсхолдера?

Неверная формулировка. Пример профайлера, который дает готовый к исполнению запрос с параметрами — SQL Server Profiler.

Что такое идентификатор — в принципе, неважно (хотя и вполне понятно из приведенного рядом примера). Нам важен сам факт того, что подготовленные выражения для идентификаторов в принципе невозможны.

… потому что план запроса будет разным, очевидно. Но это не повод не использовать параметры во всех остальных местах, где они работают.

По поводу клиента, который требует отдельного обращения к БД — это не ко мне, а к автору топика. Это он ратует за их использование.

Насколько я понял, автор топика ратует за использование prepared statement и параметров, а не конкретного клиента.
SQL Server Profiler никак не поможет мне в моей разработке, когда я составляю динамический запрос средствами серверного ЯП, и передаю для него данные средствами серверного ЯП.

Всё верно. Понятие «клиента» ввели в дискуссию вы сами.
Автор же статьи рассказывает нам о механизме работы родных подготовленных выражений, при которых подстановка данных производится на сервере. при использовании данной технологии обращений к серверу будет два. Вне зависимости от клиента. Это требование технологии.
Если вы не хотите ей пользоваться — предъявляйте претензии автору статьи, а не мне.
SQL Server Profiler никак не поможет мне в моей разработке, когда я составляю динамический запрос средствами серверного ЯП, и передаю для него данные средствами серверного ЯП.

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

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

Автор статьи, как уже неоднократно сказано, путает подготовленные выражения и параметризованные. Двойной запрос нужен только для первых, а ощутимый профит получается как раз от вторых.
Что бы он ни путал, но статья его — о первых.
Иначе ему придется признать использование mysql_real_escape_string() в php, которую он сам же и предал анафеме :)

Насчет профайлера — да, на стороне сервера это можно сделать. Именно в моей формулировке, «парсинг SQL и подстановка данных на место плейсхолдеров».
Что бы он ни путал, но статья его — о первых.
Иначе ему придется признать использование mysql_real_escape_string() в php, которую он сам же и предал анафеме :)

Вообще-то, второе никак не вытекает из первого.

Насчет профайлера — да, на стороне сервера это можно сделать. Именно в моей формулировке, «парсинг SQL и подстановка данных на место плейсхолдеров».

Отчетливо видно, что вы понятия не имеете, как SQL Server работает с параметризованными выражениями.
Как это не вытекает?
Очень интересно узнать, как можно обойтись без mysql_real_escape_string() или её аналогов, если мы парсим плейсхолдеры на клиенте.

Судя по всему, вы забыли, о чем шла речь :)
Напомню — о выводе запроса, в пригодном для использовании в консоли виде ;)
Очень интересно узнать, как можно обойтись без mysql_real_escape_string() или её аналогов, если мы парсим плейсхолдеры на клиенте.

А кто сказал, что они парсятся на клиенте? Вы правда не знаете о RDBMS, которые поддерживают использование параметризованных запросов на уровне сервера?

Судя по всему, вы забыли, о чем шла речь :)
Напомню — о выводе запроса, в пригодном для использовании в консоли виде ;)

Легко и непринужденно.

exec sp_executesql N'SELECT TOP (2) [Extent1].[IdAgency] AS [IdAgency], [Extent1].[IdOrg] AS [IdOrg] FROM [Refer].[Agency_q] AS [Extent1] WHERE [Extent1].[IdOrg] = @p__linq__0',N'@p__linq__0 uniqueidentifier',@p__linq__0='6FE130E8-BF13-494B-BABC-D9E85AAA9B56'

Копипаст из окна профайлера. Копипастим в соседнее окно с консолью и радостно выполняем.
Окей, спасибо за политинформацию.
Итак, у нас есть профайлеры, которые добавляют к запросу определение переменных.

И, насколько я понял, есть — гипотетически — СУБД, которые позволяют прислать параметризованный запрос и его данные в одном пакете. MySQL, использовавшаяся автором для примеров, впрочем, к ним не относится.
Угу, очень гипотетически.

MS SQL. Начиная как минимум с 2000, более ранних я просто живыми не видел.
И да, вы не поняли, это не «добавили к запросу определение переменных», это и есть запрос, ушедший на сервер. Дословный.
Ну, если это действительно так, то принцип работы MS в корне отличается от используемого в MySQL/Oracle и описанного выше.
Что ж, небесполезная вышла дискуссия.
Да при чем здесь параметры. Речь не о параметрах, а о способе их реализации. В консоли Mysql тоже можно руками написать два запроса: один с плейсхолдерами, а второй с присвоением значения переменной, со всем положенным искейпингом.

Но бинарный протокол в Мускуле (используемый клиентскими библиотеками) не посылает на сервер два запроса и ему не надо искейпить данные (что, по вашим словам делает МС). Запрос уходит только один, а данные передаются в отдельном бинарном пакете, примерно в таком же виде, в котором mysql возвращает данные.
Это, простите, личные проблемы бинарного протокола MySQL.
Ну, проблемы-то как раз у МС-а в данном случае (если он действительно работает так, как у вас описано)

Как правильно отмечено у автора, МС-у в этом случае приходится "«шерстить» мегабайтную строку, чтобы найти места, где все-таки поставить обратный слэш", или чем там МС искейпит строки.

Не бог весть, какая нагрузка, но — неаккуратненько ;-)
Как правильно отмечено у автора, МС-у в этом случае приходится "«шерстить» мегабайтную строку, чтобы найти места, где все-таки поставить обратный слэш", или чем там МС искейпит строки.

Эмм, а зачем их эскейпить? Вы что, думаете, что сервер внутри себя запрос на уровне строк обрабатывает? Вам термин «синтаксическое дерево» ничего не говорит?
Я таки думаю что перед тем, как «внутри себя запрос обрабатывать», сервер должен его сначала распарсить. Чтобы понять, где у него имя переменной, а где — собственно данные.

А вот этот «дословный запрос», ушедший, по вашим словам, на сервер:
@p__linq__0='6FE130E8-BF13-494B-BABC-D9E85AAA9B56'
как-то на дерево не очень похож

Если вы посмотрите на то, что ушло на сервер, то там отдельно текст команды, а отдельно параметры. Соответственно, тест запроса парсится до дерева (это всегда происходит), в узлах, где параметры, остаются параметры, а потом уже при выполнении туда подставляются значения.
Я понимаю что это «отдельно параметры». И что эти параметры — на самом деле дерево. Но ничего не могу с собой поделать: на вид это вылитый SQL запрос, присваивающий переменной строковое значение. Даже не знаю, верить ли теперь своим глазам.

Вы запрос целиком прочитать не пробовали? Сразу бы увидели, что «на вид» это вылитый вызов (системной) хранимой процедуры с именованными параметрами.
Ну хорошо, пусть это будет теперь не «синтаксическое дерево», а «вылитый вызов (системной) хранимой процедуры с именованными параметрами.» :)

Но параметры в эту (системную) хранимую процедуру передаются в строковом виде.

Вы знаете, у меня сложилось ощущение, что у вас претензии к своему собственному комментарию. А высказываете вы их почему-то мне :)
У меня вот сложилось ощущение, что вы никак не можете увидеть вещи, которые выходят за рамки ожидаемого вами.

Я тут специально тестик сделал (только на MSSQL, оракла под рукой нет).

Так вот, «обычный» запрос (вида SELECT * FROM Agency WHERE IdAgency = 8) фиксируется профайлером как событие SqlBatch с текстом

SELECT * FROM Agency WHERE IdAgency = 8

Что, в принципе, логично.

Что с ним происходит потом? Он нормализуется, парсится до синтаксического дерева, и потом ищется в кэше планов запросов. Дальше, понятное дело, он там либо есть, и тогда план используется, либо нет, и тогда план создается и ложится в кэш.

А как выглядит параметризованный запрос (команда: SELECT * FROM Agency WHERE AgencyName LIKE :name AND IdAgency = :Id, параметры name="%Москва%", Id=8). Это уже совсем другое событие (RPC, вызов процедуры), и в профайлере оно отрисовывается вот так:

exec sp_executesql N'SELECT * FROM Agency WHERE AgencyName LIKE :name AND IdAgency = :Id',N':name nvarchar(8),:Id int',:name=N'%Москва%',:Id=8

(обратите, кстати, внимание, что параметры строго типизованы, никакой передачи в строковом виде нет, кроме того, что строкой и является)

Что происходит дальше? На первом этапе никакого парсинга и/или построения плана не происходит, потому что из пакета очевидно, что это вызов хранимой процедуры. Процедура определяется по имени, потом (с сохранением типов!) биндятся параметры, и все это запускается. Что происходит внутри процедуры — да то же самое, что выше: запрос нормализуется, парсится до синтаксического дерева, ищется в кэше… и только после построения плана (мы сейчас не рассматриваем оптимизатор, который может смотреть на параметры) в дело вообще вступают параметры, которые «скармливаются» уже готовому плану выполнения.

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

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

Судя по всему, МС все-таки не выпендривается, а общается с сервером по бинарному протоколу как все, а ваш «запрос, ушедший на сервер. Дословный.» — всего лишь представление, сформированное профайлером для удобства. В формате SQL. С обязательным для этого формата искейпингом.
Ну, утверждать, что нет никакого искейпинга, можно только если в данных будут подлежащие искейпингу символы.

Его нет в нижележащем слое (и его не делает сервер, потому что он не нужен). Как вы же верно пишете дальше, в представлении он будет (потому что иначе не будет работать копирование для выполнения).

Поэтому приходится подправлять информацию.

Понятия подправлять не пробовали?

Судя по всему, МС все-таки не выпендривается, а общается с сервером по бинарному протоколу как все, а ваш «запрос, ушедший на сервер. Дословный.» — всего лишь представление, сформированное профайлером для удобства.

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

«А я, заметим, не утверждал» — вы не поленились написать отдельный комментарий на эту тему, чтобы попенять мне незнанием. И ещё один, чтобы попенять Mysql-ю на его проблемы.
— «это и есть запрос, ушедший на сервер. Дословный.»
— «А я… не утверждал, что запрос передается… именно в таком виде.»
в каком-то из этих утверждений вы соврали.
Счастливо оставаться.
Вы, видимо, не вполне понимаете неприменимость слова «дословный» применительно к компьютерным протоколам.

Ну и да, счастливо оставаться.
Ну я понял, mssql использует бинарный протокол и при этом еще и _параллельно_ парсит и конвертит запросы с плейсхолдерами в одну строку. Двойная работа.

А еще мне понравилось ваше высказывание, что раз IN(..) редко используется, значит и проблемы нет. Вы понимаете, что если хотя бы раз оно используется, то уже надо что-то делать чтоб заменить плейсхолдеры?
Ну я понял, mssql использует бинарный протокол и при этом еще и _параллельно_ парсит и конвертит запросы с плейсхолдерами в одну строку. Двойная работа.

Простите, а где он это делает?

А еще мне понравилось ваше высказывание, что раз IN(..) редко используется, значит и проблемы нет.

Не «проблемы нет», а «не повод не использовать параметры во всех остальных местах».

Вы понимаете, что если хотя бы раз оно используется, то уже надо что-то делать чтоб заменить плейсхолдеры?

На самом деле, совершенно не обязательно. Например, можно собрать динамический запрос, где в качестве значений внутри IN будут параметры.
Не стоит усилий, ей-богу :)
Он будет крутиться как уж на сковородке, но никогда не признает, что написал ерунду :)
А когда припрёшь к стенке — «вы не понимаете неприменимости слова(!)», хотя сам же его и применил :)
— при попытке вывести результирующий SQL мы видим все тот же запрос с плейсхолдерами, и выполнить его для отладки в консоли без плясок с бубном несколько затруднительно.

Не знаю, как в PHP, а в Perl'овых модулях p5-DBI для этой задачи прекрасно подходит одна-единственная команда вида $dbh->trace;
Это значит, что p5-DBI не использует встроенный механизм prepared statements, о котором говорит автор статьи, а лишь эмулирует его.
Нет. Это означает лишь то, что для целей отладки perl (точнее DBI) сохраняет исходный текст подготовленного выражения, а при выполнении выводит его в STDERR с подставленными значениями плейсхолдеров.
Проверить легко: $dbh->prepare с синтаксически неверным запросом. Ошибку в данном случае вернет именно сервер БД.
А подскажите, с какими параметрами? Или вы просто про возможность увидеть, какие фактические значения плэйсхолдеров были использованы в конкретном запросе?

К примеру, оракловый драйвер в trace отдельно показывает селект с плэйсхолдерами и фактические их значения.
Сильные духом пишут специальный код для динамического составления оператора IN… И вместо сокращения кода мы получили его разрастание в несколько раз… Совсем не так все красиво оказалось, как в рекламе?

Тут двояко получается — код действительно вырастает, поскольку надо динамически формировать запрос в зависимости от числа переменных в IN. Но выгода от использования кэша запросов остается (хоть и менее выраженная — все-таки WHERE… IN (?) и WHERE… IN(?,?,?) — это все-таки разные выражения). Что оптимальнее — должен решать программист, все-таки это его работа.

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


Выше уже про это писали — разве кэш запросов создается только в рамках одного соединения? Все-таки он «живет» все-таки дольше, чем одно соединение. А в случае, когда у вас высоконагруженный веб-проект, в котором, например, несколько тысяч пользователей одновременно автоизуются (запрос типа select user from users where name='name', passwordMd5='password'), то этот кэш начинает играть уже значительную роль.
> select user from users where name='name', passwordMd5='password'
вот уж как выбирать не стоит…
особенно в «высоконагруженном проекте» иначе будет «в основном лежащий проект»
Прошу пояснений, как же стоит выбирать?
уж явно не выборкой из таблицы по двум строкам n-ого размера…

Один из вариантов:
в key-value store должно быть соответствие логина и id юзера. далее берём юзера из кэша и сравниваем пароли. если юзера нет в кэше (например в MC) — получаем его пароль (или всю строку из таблицы), а затем сравниваем пароль. А если такой пользователь есть в кэше, то опять же у нас есть вся инфа для проведения его аутентификации.
при чем здесь размер строк? да ещё и их количество?
Выборка производится по индексу, причем индекс строится только по одной «строке» — юзернейму. Её длина никакой проблемы не составляет — индекс прекрасно строится по строкам переменной длины.
При этом он и так уже лежит в кэше (базы данных).

Не надо наводить тень на плетень и городить хайлоад на пустом месте.
Не думаю, что Вам стоит рассказывать мне как работают индексы)

Попробуйте сравнить время выборки по числовому PK и по строке с индексом на большой базе под нагрузкой.
Т.е. мы должны изменить авторизацию логин пароль на число/число чтобы индексы хорошо работали? :)
Нет.
Строку в таблице с юзерами стоит искать не по строковому индексу, а по числовому (id пользователя)
ну так авторизуется он же по логину/паролю, дальше получает сессию и выборка идет по нет. Если говорить об оптимизации, то сессию можно сделать из двух частей: id юзера и самой сессии, выборку делать по id и проверять сессию. Но сам запрос выше совершенно часто встречается
> Но сам запрос выше совершенно часто встречается
И?.. Можно привести огромное количество вещей которых делать не стоит, но которые «очень часто встречаются». Встречаются они из-за низкой квалификации людей, которые это имплементят. И беда в том, что они не желают расти =)

Как мне кажется, я пояснил один из вариантов решения задачи в связке с SQL БД. Я уж не говорю о том, что, возможно (это зависит от проекта над которым вы работаете), далее полученную инфу стоит закэшировать, а не плодить запросы к БД для получения модельки пользователя. В приведённом выше запросе, кэшировать будет нечего и вы снова полезете в БД.

А откуда взять числовой id пользователя? :-/ Предлагаете два запроса для аутентификации делать? Первым получать id по строковому логину (опять же используя уникальный строковый индекс), а вторым проверять пароль для этого id (используя числовой PK и простое строковое сравнение)? Это точно быстрее чем получить id, используя строковый индекс для логина и простое сравнение для пароля? Или вообще проверку пароля вынести из базы в приложение, чтобы приложение получало id и пароль (опять же по строковому индексу) и само решало верный пароль или нет, а не доверяло сравнение базе, решая что логин+пароль неверны, если вернут пустой результат? Разгрузить немного СУБД ценой большей нагрузки на приложение и теоретически меньшей безопасностью (искомый хэш так будет попадать в приложение, а не находиться только внутри СУБД — три варианта (из базы, из приложения, из канала между базой и приложением) утечки вместо одного)?
> А откуда взять числовой id пользователя?
habrahabr.ru/post/148446/#comment_5012835

Да, пароли стоит сравнивать программно после извлечения записи. Вполне вероятно, что профиль пользователя (если он извлекался из БД) полезно будет закэшировать. Иначе потребуется второй запрос теперь уже на извлечение профиля, а не на сравнение пароля…

Какие утечки? Это же бэкенд…
Ну, я обычно делаю нечто вроде SELECT SQL_NO_CACHE u.id, u.login, u.name., u.email,… FROM users u WHERE u.login = $login AND u.password_hash = $password_hash и записываю id и профиль в сессии приложения, по сути кэширую в том же memcache. Ошибка аутентификации — результат 0 записей, успешная — 1, содержащая профиль. При ошибке не имеет значение неправильно введён логин или пароль, об ид пользователя, а тем более о профиле, тоже речи нет. SQL_NO_CACHE опытным путём добавлена, т. к. хитов по запросов аутентификации практически нет.

> А откуда взять числовой id пользователя?

Так писали же выше — из key-value storage. Выигрыш тут не только за счет увеличения скорости выборки по id юзера, по сравнению с выборкой по логину, но и за счет того, что юзер уже может быть в мемкеше (и ключом в MC выступает все-таки id, а не логин) — на highload'е мемкеш может быть ОЧЕНЬ большим. Да и схема эта в общем-то типовая.

> Разгрузить немного СУБД ценой большей нагрузки на приложение и теоретически меньшей безопасностью (искомый хэш так будет попадать в приложение, а не находиться только внутри СУБД — три варианта (из базы, из приложения, из канала между базой и приложением) утечки вместо одного)?

Опять же, все зависит от… Но для безопасности, если аккаунты настолько ценны, то авторизацию вообще стоит делать отдельно от самого проекта, и не хранить данные в общей БД.
не стоит умножать сущности, высасывая из пальца жизнеспособную схему реализации для высосанной же из пальца проблемы :)

А то в итоге получается как в анекдоте — «И не выиграл, а проиграл, и не машину, а сто рублей».

Из трехходовой в комбинации
1. «key-value store должно быть соответствие логина и id юзера.
2. „далее берём юзера из кэша и сравниваем пароли.“
3. „получаем его пароль (или всю строку из таблицы)“
вызванной к жизни для того, чтобы избежать ужасной „выборки из таблицы по двум строкам n-ого размера“

общими усилиями получилось „Дублируем юзеринфу в мемкеше потому что запрос по индексу недостаточно быстрый“.

при этом вопросы
1. какой вообще смысл дублировать юзеринфу в мемкеше?
2. стоит ли сутки держать в нем инфу, ожидая, пока юзер залогинится
3. Так ли уж страшен запрос по индексу

Никто себе не задал.

сферические девелоперы в вакууме такие сферические…
> общими усилиями получилось „Дублируем юзеринфу в мемкеше потому что запрос по индексу недостаточно быстрый“

Не перевирайте. Мемкеш не потому, что запрос по индексу быстрый, а потому, что SQL сам по себе недостаточно быстрый. Кстати, даю подсказку: на highload'е последовательные запросы одного и того же пользователя могут быть обработаны совершенно разными серверами, так-что хранить данные в сессия (или как в PHP это называется?) — не вариант. Каждый раз получать юзера из SQL БД — тоже.
Опечатка: не потому, что запрос по индексу *недостаточно* быстрый
Начнем с того, что обсуждаемая высосанная из пальца проблема никакого отношения к сессиям не имеет.
Сессии у нас ещё нету. Информация, которую вы с безумным изобретателем, которого вдруг кинулись защищать, хотите кэшировать, не нужна «каждый раз». А только один раз на сессию, при старте.
(речь, напомню, шла об авторизации)
И для такой частоты запросов скорости SQL хватает за глаза.

сдается мне, вы просто не поняли те безумные идеи, которые вдруг зачем-то ударились защищать :)

> сдается мне, вы просто не поняли те безумные идеи, которые вдруг зачем-то ударились защищать :)

Нет. Просто я, как и kapitansky, работаю на highload'е (средняя нагрузка — 1.5 млрд. хитов в сутки). И там подобные, как вы выразились «идеи» — естественная и проверенная временем практика.
Беда в том, что вы никак не определитесь, какие именно идеи :)
И не можете ответить на простой вопрос — зачем держать в кэше инфу, которая понадобится раз в сутки.
> Беда в том, что вы никак не определитесь, какие именно идеи :)

Ок. Перечисляю «идеи»:

1) Хранить пользователя в мемкеше (или аналогичном in-memory storage)
2) В SQL БД за пользователем обращаться *только* при отсутствии данных о нем в мемкеше

Думаю тут понятно без комментариев.
А вот необходимость хранить связь login -> id в key-value storage следует из:

1) Получить id из key-value storage быстрее, чем из SQL, даже по индексу
2) В случае присутствия пользователя в мемкеше мы обойдемся вообще без SQL-запросов

> И не можете ответить на простой вопрос — зачем держать в кэше инфу, которая понадобится раз в сутки.

Все вышеперечисленное актуально для highload проектов. Для «сайта Васи Пупкина» все это, понятное дело, не нужно. А в целом — простая арифметика — экономя 10мс на каждом хите мы экономим дофига времени при миллиардах хитов в сутки. И ничего не экономим при 100 хитах в сутки — там на простой больше времени тратится. :-)
10 мс — это миллисекунд, я понимаю? 1 десятая секунды?
> 10 мс — это миллисекунд, я понимаю? 1 десятая секунды?

Сотая вообще-то :-)
Но тут речь не о величине — может 10мс, может 1мс, может всего 100мкс экономим. Важно, что на таких нагрузках ни одна оптимизация не оказывается лишней.
Вопрос именно о величине.
Во-первых, это важно чтобы понять — человек со знанием дела говорит, или просто языком от балды чешет.
Во-вторых, мне интересно, сколько времени у вас выполняется запрос по ключу, если вы экономите на нем 0,01 секунды.
Я думаю, вас не затруднит прямо сейчас посмотреть и написать сюда?
> Я думаю, вас не затруднит прямо сейчас посмотреть и написать сюда?

Затруднит. Вы явно не представляете highload проекты.
Проводить бенчмарки и эксперименты на рабочей БД и рабочей нагрузке никто не даст. Есть только общая статистика, по которой было уменьшение среднего времени выполнения. Возвращать все назад, что бы посмотреть а сколько же было до этого было время выполнения — никто не будет. А смотреть время исполнения на пустой или девелоперской БД — бесполезно, цифры будут совсем другие.
понятно.
Ну, раз по делу сказать нечего, и раз у вас запросы выполняются сотые доли секунды, то лечите кого-нибудь другого, теоретики :)
Девелоперская БД, просто навскидку. Большая таблица:

Выбор по логину (разные логины, точно не в кеше БД):

> select * from user where username='...';

1 row in set (0.04 sec)
1 row in set (0.01 sec)
1 row in set (0.02 sec)

Выбор по id (разные id, точно не в кеше):

> select * from user where id=...;

1 row in set (0.00 sec)
1 row in set (0.00 sec)
1 row in set (0.00 sec)

И?
Ох.
Детский сад просто.
Кто ж вас к хайлоаду-то пустил?
Вы прирост «в 10мс» тоже таким же образом измеряли, как здесь?
С таким, примерно, сбором данных:
размер таблицы: «большая»
кэш отключён: «мамом клянус!»
ключи: «наверное праймари есть»
профайлинг: «база под запросом написала»

Вы хоть понимаете, что этот ваш пост ни о чём? Что он абсолютно ничего окружающим не говорит и не доказывает?

Хотя бы вот так можно было сделать?

show index in users;
+-------------------+------------+----------+
| Table | Non_unique | Key_name |
+-------------------+------------+----------+
| users | 0 | PRIMARY |
| users | 0 | email |

select count(*) from users;
+----------+
| count(*) |
+----------+
| 747092 |

SET SESSION query_cache_type = OFF;

SHOW VARIABLES LIKE 'query_cache_type';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| query_cache_type | OFF |
+------------------+-------+

mysql> SET profiling = 1;

mysql> select 1 from users where email='foo@bar.com';
Empty set (0.00 sec)

mysql> select 1 from users where email='foo@bar.com';
Empty set (0.00 sec)

mysql> select 1 from users where email='foo@bar.com';
Empty set (0.00 sec)

show profiles;
+----------+------------+-----------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+-----------------------------------------------+
| 1 | 0.00027950 | select 1 from users where email='foo@bar.com' |
| 2 | 0.00024750 | select 1 from users where email='foo@bar.com' |
| 3 | 0.00017850 | select 1 from users where email='foo@bar.com' |
> Ох. Детский сад просто. Кто ж вас к хайлоаду-то пустил?

Гуляйте дальше. В таком ключе дискуссию с вами я продолжать не буду.
Конечно, это зеркало виновато :)
Я оболгал про ваш технически безупречный пост, снабженный все необходимой информацией

хехе
Простите за любопытство, у вас на это ушёл целый час?)
Под какой нагрузкой база?
давайте сразу договоримся, что у меня всё плохо :)
но у меня есть хотя бы это.
у вас же нету вообще ничего :) дырка от бублика.
когда дошло до дела, от вас был получен только «профайлинг» которого постеснялся бы даже столь презираемый вами «вася пупкин» с «базой в 10к записей» :)

Давайте я вам с ним подкину на вентилятор материал для обсуждения. Примерно в том виде, как ее просил FanatPHP

kapitansky, вам будет достаточным хайлоадом БД на 140Гб
пара таблиц в которой содержат 100kk+ записей?

Хотя очень странно вы хайлоад меряете объемом таблицы :(
Ну, в довесок на всякий случай сообщу, что сервер обрабатывает примерно 2.5-3млн запросов в минуту.
Основные таблицы в InnoDB

А вот вам выборка по varchar и по id

pastebin.com/i9T3a5MF

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

mysql> show profiles;
+----------+--------------+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Query_ID | Duration | Query |
+----------+--------------+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 1 | 150.97058275 | select count(*) from smalltable |
| 2 | 0.00673750 | select varchar255 from smalltable where id = 178245354 |
| 3 | 0.00032600 | select id from smalltable where varchar255='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' |
| 4 | 0.00024225 | select varchar255 from smalltable where id = 178245354 |
| 5 | 0.12738300 | select varchar255 from smalltable where id = 78245354 |
| 6 | 0.01478350 | select varchar255 from smalltable where id = 128245354 |
| 7 | 0.00030250 | select id from smalltable where varchar255='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' |
| 8 | 0.01827275 | select id from smalltable where varchar255='wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww' |
| 9 | 0.00021725 | select id from smalltable where varchar255='qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq' |
| 10 | 0.02816250 | select varchar255 from smalltable where id between 123423456 and 241874111 limit 10 |
| 11 | 0.00021200 | select varchar255 from smalltable where varchar255 ='zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' |
| 12 | 0.00029850 | select id from smalltable where varchar255 ='rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr' |
| 13 | 0.00018800 | select varchar255 from smalltable where id = 123423456 |
| 14 | 0.00029400 | select id from smalltable where varchar255 ='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' |
| 15 | 0.00017075 | select varchar255 from smalltable where id = 123423465 |
+----------+--------------+------------------------------------------------------------------------------------------------------------------------------------------------------------+
9 rows in set (0.00 sec)
Хм. Выбираются одни и те же записи, сначала по одному ключу, затем по другому. Интересно посмотреть на результаты выборки в обратной последовательности — сначала по строке, потом числу. Дабы исключить влияние факторов вроде кеша БД, дискового кеша и т.п.
Да легко.

| 16 | 0.00034450 | select id from smalltable where varchar255 = '1111111111111111111111111111111111111111111111111111111111111111111111111111' |
| 17 | 0.10699225 | select varchar255 from smalltable where id = 236522972 |
| 18 | 0.00434975 | select varchar255 from smalltable where id = 189472354 |
| 19 | 0.01045600 | select id from smalltable where varchar255 ='222222222222222222222222222222222' |
| 20 | 0.00035775 | select varchar255 from smalltable where id=110421174 |
| 21 | 0.00410175 | select id from smalltable where varchar255 = '333333333333333333333333333333333333333333333333333333' |
не смешите меня) кто же так тестирует? :-D

нет, ну я, конечно, понимаю, что в Донецкой области с техническим образованием всё печально, но хоть какие-нибудь лабораторные вы сдавали? как вы там замеряли результат?
Поскольку вы хайлоад меряете размером БД, то для вас и этого хватит за глаза. (для справки, и база с табличкой на 1000 записей может оказаться хайлоадом)

Факт того, что вы не знаете элементарщины элементов профилирования запросов к БД говорит еще более красноречиво о качестве вашего технического образования и наличном опыте.

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

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

>Поскольку вы хайлоад меряете размером БД
Вы утверждаете, что от кол-ва записей в таблице ничего не зависит? От него зачастую будет зависеть и время выборки и временные и количественные затраты на обслуживание индексов.

Когда я спрашиваю под какой нагрузкой находится таблица я подразумеваю как минимум следующие характеристики(мне казалось, что это нечто очевидное, что не нужно пояснять в каждом посте):
1) количество операций выборки и измения/добавления/удаления в минуту
2) среднее количество выбираемых строк и модифицируемых строк в минуту
3) среднее время выполнения запроса по типам операций
4) количество медленных запросов в минуту

>А проводить для вас тестирование по методикам TPC на мой взгляд слишком большая честь.
А вас никто и не просил делать это по методикам TPC, главное не делать «говнотестов» которые вообще ни о чём не говорят (почему они ни о чём не говорят, думаю, пояснять не стоит).

> вы не знаете элементарщины элементов профилирования запросов
О_о откуда у вас такая информация? :-D
Я-то как раз осилил. Но вы именно бредите, рассказывая всем о том, что кеш основанный на key-value хранилище будет быстрее чем обращение к БД — да, будет. Но вопрос ведь обсуждается совсем не этот.

И я утверждал лишь то, что вы профан, не более того.

Хайлоад — это нагрузка в первую очередь.
И образоваться может он и на таблице в 1000 записей и на таблице в 1млрд записей. Но вам-то это не понятно, ибо знаний нет, есть лишь распальцовка.

И про нагрузку на БД, я вам весьма прозрачно указал в выборках выше. Это реальные выборки, на реальном рабочем сервере, который обрабатывает около 2.5-3млн запросов в минуту к разным таблицам.

Вы что-то из этого прочли? нет? Тогда что вы тут пытаетесь рассказать?
>Хайлоад — это нагрузка в первую очередь. И образоваться может он и на таблице в 1000 записей и на таблице в 1млрд записей. Но вам-то это не понятно, ибо знаний нет, есть лишь распальцовка.

именно по-этому я и спрашивал про объём таблицы и нагрузку, а «распальцовкой» тут занимаетесь вы с господином FanatPHP. И повторюсь — то, что вы привели выше — говнотест — такие тесты составляют школьники, которые вообще не понимают что они измеряют, поэтому держите свои выебоны при себе.

Если у меня будет свободное время, то приведу здесь результаты нормального тестирования.
Специально для доцента Аваса, я указал и объем таблиц и объем БД и даже число запросов обрабатываемых БД. Без разбивки по таблицам, но делать разбивку мне лень. Поскольку это реальная рабочая система, а не стенд.

Но читать кто-то не умеет. Именно поэтому этот школьник задает вопросы на которые ему ответили раньше, и пытается тут рассказать сказки.

P.S. И не будет тестирования от вас. Ибо не осилите.
> Специально для доцента Аваса, я указал и объем таблиц и объем БД и даже число запросов обрабатываемых БД.

Вы, что реально не понимаете почему эти тесты — ни о чём не говорят? О_о Видимо намёк про снятие измерений на лабораторных работах был вам не понятен, намекну яснее — ваша методика снятия замеров — полнейшее фуфло.

> Без разбивки по таблицам, но делать разбивку мне лень. Поскольку это реальная рабочая система, а не стенд.

Что за фигню вы тут лечите? Вы утверждаете, что в системе в которой MySQL обрабатывает 50к запросов в секунду вы знаете только общую сумму запросов к БД и не знаете ни какие запросы там исполняются, ни сколько строк модифаится и селектится, ни количество медленных запросов?
Еще раз повторюсь, она (методика? жесть, кто же это в ранг методики возвел?) не претендует на уровень TPC.
Вы действительно настолько глупы, что не знаете как проводить замеры в первом приближении?
Так погуглите прикладную статистику и теорию измерений.

Ваша категоричность сыграла с вами весьма злую шутку.
Поскольку ваша сферическая теория от практики несколько отличается вот и приходится вам надувать щеки и пытаться доказать «неправильность» выданных выше цифр.

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

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

хочешь верить свои измерения с половиной замера, которые ты произвёл — верь) бей себя лопатой в грудь и вопи, что все настолько глупы и не видят очевидных вещей :-D

Ты бы хоть сам вдумался бы то, что ты утверждаешь) Ты уверяешь нас в том, что с индексом по строке переменной длинны в 255 символов искать быстрее чем по целочисленному PK (uint 4 байта) =) так глупо)))
Ну конечно доказывать ничего не надо.
Ибо даже представления не имеем, что и как доказывать. И почему такие странные результаты в замерах выше.

Так вот для тупых объясняю.
Шанс попасть на пик нагрузки в ряде последовательных измерений — есть, но уже для 4х случайных запросов он невелик.
Выборка по строке в 255 символов теоретически может быть быстрее по одной простой причине. Индексы лежат полностью в памяти. А данные необходимо читать.

В первом случае я читал те самые 4 байта. Во втором случае 255 байт.
Этого оказалось достаточно, чтобы отклонения под нагрузкой не давали возможность однозначно сказать, что выборка по целочисленному PK быстрее.

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

Поэтому вам, — мальчик, — я рекомендую заняться более плотно сферическими конями. Пока же незачет…
вы сами себе противоречите) вы хотите сказать, что PK не является индексом? и поэтому его приходится читать с диска, в то время как индекс по строке (по велению каких-то неведомых сил :-D ) будет в оперативке? вы меня простите, но это ПОЛНЕЙШИЙ БРЕД)

кроме того индексы далеко не обязательно лежать в оперативке. но когда вы начинаете работать с таблицей, то вся инфа по ней по возможности туда подгружается (в случае если её там ещё небыло)

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

Индексы УЖЕ лежат в оперативке. Ведь вы обсуждали конкретную систему.
Данные же лежат на диске.
Выборка — это не только найти позицию в индексе, но и прочитать конкретные данные.

Печально. Очень печально сейчас у таких как вы с образованием.
Походу вы сами запутались в своих же постах:

> Индексы лежат полностью в памяти. А данные необходимо читать.
> В первом случае я читал те самые 4 байта. Во втором случае 255 байт.

Так что же быстрее прочитать 4 байта или 255? :-D я уж молчу о том, что далеко не всегда там будет 255 байт — 255 * кол-во символов необходимых для кодирования одной буквы :-D

> Выборка — это не только найти позицию в индексе, но и прочитать конкретные данные.

Не справедливо ли считать, что после определения адреса строки при помощи индекса, для доступа к данным нам потребуется одинаковое количество времени (вне зависимости от того по строковому индексу мы искали или же по числовому)? Это же очевидно :-)

> Индексы УЖЕ лежат в оперативке.
СУБД по возможности (!) пытается кэшировать индексы в оперативке, но они далеко не всегда будут находиться в оперативной памяти.

> Печально. Очень печально сейчас у таких как вы с образованием.
Повторяетесь. Видимо я задел больную тему об образовании. Простите, не хотел вас обидеть.
:D господи, ну насколько можно быть тупым? :)
Показываю на пальцах:

select INT from table where VARCHAR;

select VARCHAR from table where INT;

Потребуется одинаковое время на получение с диска INT и VARCHAR?
У вас еще вопросы про время и что и где справедливо считать?
Удачной учебы бездарность :)
Уж про тупость бы помолчал уж тем более если ты не в состоянии прочитать, осмыслить и понять.

Я > Не справедливо ли считать, что после определения адреса строки при помощи индекса, для доступа к данным нам потребуется одинаковое количество времени (вне зависимости от того по строковому индексу мы искали или же по числовому)? Это же очевидно :-)

Вы > select INT from table where VARCHAR;
Вы > select VARCHAR from table where INT;
Вы > Потребуется одинаковое время на получение с диска INT и VARCHAR?

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

Надеюсь твой высокоодарённый мозг с сотой попытки сможет это понять.
Сколько угодно можно считать ее одинаковой.
Но на практике она разная. И выше тому был пример.

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

Кроме того в нормальной системе табличка уже будет находиться в оперативке, а не на диске.
FanatPHP бесполезно что-либо объяснять)
Вот сессию (включая профиль юзера) как раз и храним в мемкэше на отдельном хосте (или даже хостах в теории, но на практке не приходилось), чтобы не усложнять логику балансера.
Профиль юзера должен быть отделен от сессии, если используется мультилогин.
Мы боремся с мультилогинами, если я правильно понял, что это :)
но и за счет того, что юзер уже может быть в мемкеше

С таким же успехом он может быть и в кэше СУБД. Не спорю, схема имеет право на существование, но, имхо, на очень большом highload, где важно даже время на синтаксический разбор SQL-запроса и т. п. или в случае если NoSQL обеспечивает большую скорость поиска id (и профиля?) по строковому логину чем по уникальному индексу в SQL РСУБД, в общем когда можно и нужно идти на усложнение архитектуры ради сотни микросекунд. У меня, увы, таких проектов не было и многие проблемы с производительностью решались более тонким управлением обычного MySQL или кэшированием отрендеренного HTML, чем изменениями архитектуры подсистемы хранения данных.
И как вы планируете сделать это на этапе авторизации?
Ведь вы именно оттуда выдернули этот запрос.
Во-первых, разницы нету.
Во-вторых, ваш велосипед, изобретенный на коленке, УЖЕ реализован в базе данных.
Ваше key-value store — это как раз индекс. Где ключом является имя пользователя, а значением — смещение в файле БД. Все читается очень быстро.
В-третьих, вы хотя бы перестали нести пургу про «выборку из таблицы по двум строкам n-ого размера», что не может не радовать. Ещё немного, и вы осилите то, как работают индексы, и придете к выводу, что изобретать трехэтажные конструкции для авторизации не нужно.
во-первых: разница есть и она существенна.
во-вторых: индекс это не key-value store
в-третьих: индексы я давно осилил, чего и вам советую
ну и речь, разумеется, не о базе с 10к записей…

в комменте, на который, я отвечал было слово «высоконагруженный» (на англ. highload)
В общем, утверждаешь, что два запроса (сначала по логину получить id, потом по id — пользователя будет быстрее, чем сразу по логину — пользователя)
Так и запишем :)
Так и будет если из key-value storage'a доставать. У тебя сократится время выборки из sql-БД, а именно она и будет узким местом. Как следствие, ты сможешь обслужить больше запросов за единицу времени.
Кто сказал, что выборка по ключу будет узким местом?
Какие-то тесты проводились, профайлинг?
Можно, вообще, ближе к жизни? привести пример конкретного приложения, которое работает по схеме «сначала залезть в один карман, потом в другой» вместо того, чтобы сразу смотреть в нужном?

К чему здесь приплетена очередь — тоже непонятно. Как будто наличие дополнительного запроса как-то помешает клиентам встать в очередь на втором.
Задача сократить время выборки из БД, чего мы и достигаем. Если вам это интересно, то проведите тесты и убедитесь лично. У меня нет времени на то, чтобы разжёвывать очевидное любому выпускнику технического вуза.
Вот и очередной ламер слился :)
Начал «выборкой из таблицы по двум строкам n-ого размера» а закончил классическим «мне некогда» по им же самим высосанной из пальца проблеме :)
Во-первых, давай не будем переходить на личности
Во-вторых, я уже длительное время работаю в highload проекте c MAU > 30'000'000. Поверь мне на слово, я что-то понимаю о высоких нагрузках =)
В-третьих, о ламерах — советую подтянуть свои знания о подготовленных выражениях, их обработке сервером и индексах(деревьях и тп). А то так жизнь пройдёт в слепой уверенности, что ты всё знаешь)
Я с удовольствием подтяну свои знания, если мне приведут осмысленный аргумент или практический пример.
Но вот если мне будут писать очевидный бред про «выборку из таблицы по двум строкам n-ого размера», а в качестве практических примеров будут только надувание щёк и бряцание медальками — то здесь я лишь укреплюсь в своем невысоком мнении о среднем обывателе хабра.
Слышал байку про учителя, который сказал: «Каждый раз когда когда вы создаёте строковый PK — вы убиваете котёнка»? ;-D
Ещё раз объясняю на пальцах — разное время поиска по числам и строкам. SQL-БД будет узким местом в высоконагруженной системе и нагрузку на неё нужно стремиться сокращать. Сократил время обработки запроса — сократил нагрузку.
Сократил кол-во запросов к БД — сократил нагрузку. И тд и тп.
Чувак.
Твоя схема не предполагает сокращения запросов.
Не считаешь же ты, в самом деле, сокращением количества запросов поиск в мемкеше данных пользователя, который еще не авторизовался.

В этом твоя проблема. Ты путаешься в своих собственных показаниях.
А все потому, что высасываешь их из пальца по ходу дела.
Не позорился бы.
1) Схема предполагает более быструю выборку из БД.
2) Если удалось выбрать данные из кэша, то я вообще сэкономил на запросе к БД.
3) То, что пользователь не залогинен далеко не показатель того, что его нет в кэше. Вот вообще не факт)
для осмысленного использования кэша вероятность хита должна быть выше, чем «не факт, что нету»
ой, ну давайте не будем начинать тему про осмысленное использование кэша.

да, и для пользователя, который является частым гостем на сайте эта вероятность будет достаточно высокой, а если он не заходил неделями — разумеется нет. далее можно сравнить процент тех и других)
Грубо говоря поиск в key-value in-memory хранилище, где key — это логин пользователя, а value — сериализованные данные профиля (включая id), сильно быстрее (учитывая десериализацию) чем поиск в SQL таблице по уникальному индексу по тому же логину, учитывая примерно ту же вероятность, что будет хит кэша СУБД, что и хит мемкэша?
перечитал несколько раз и ничего не понял)

из того, что понял:
«учитывая примерно ту же вероятность, что будет хит кэша СУБД, что и хит мемкэша» — это не корректно.
это абсолютно разные кэши. Они устроены по-разному и срок жизни инфы в них, в связи с тем, что они решают различные задачи, тоже различен (в БД срок жизни кэша очень маленький в сравнении с тем же мемкешем).
когда я говорил о key-value хранилище я не имел ввиду следующую структуру:
{
email1: 1,
email2: 2,
},
где email — логин, 1 и 2 — это идентификаторы пользователей.
Идея заключается в том, чтобы крайне быстро получить id пользователя по его логину (сторока), а уже далее работать с числовым идентификатором пользователя.
опечатка:
когда я говорил о key-value хранилище я не имел ввиду следующую структуру:
А почему бы, кстати, не хранить в кеше сериализованный профиль, вместо одного id? Тогда, если пользователь есть в кеше, экономим на запросе к бд. Из-за растущих объемов кеша?
не надо путать кэш и персистентное хранилище.
И не стоит забывать о том, что если посыпятся хотя бы несколько долгих обработок запроса, то твои клиенты станут в очередь на обработку и всё может очень печально закончиться.
Вот ещё интересная статья про prepared statements в mysql с тестами и бенчмарками. Статья за 2008 год, но, думаю, всё ещё актуальна.
Статья — полная ерунда.
Любой тест производительности веб-приложения, который использует не siege, а «мильён пустых циклов» можно смело выкидывать в помойку. Поскольку в реальной жизни получится не 10%, а 0,1%.

причем в контексте подготовленных выражений это особенно актуально
В среднем веб-приложении не бывает 100500 запросов на один вызов.
А для 100500 вызовов будет выполнено 100500 prepare и 100500 execute, и статистика будет уже СОВСЕМ другая.
А где вы в статье увидели тесты производительности веб-приложения? Понятно, что сказать что-то о производительности конкретного веб-приложения исходя из этих тестов нельзя, но как бы и статья не об этом вовсе.
Во-первых, я видел цифры. Ради этих цифр статья и писалась. И если эти цифры заведомо на порядки ниже, чем в реальной жизни, то статья — фуфло.

Во-вторых, как я уже писал, в конкретном случае prepared statements это вообще не работает, поскольку prepare работает в пределах одного запуска скрипта. И для 5000 запусков картина будет совсем другая, нежели для 5000 циклов в пределах одного скрипта. Go figure.
Будьте добры, покажите и мне эти цифры производительности веб-приложения, которые вы там нашли. Про конкретные случаи с вами и не спорит никто, напротив.
Ну, скажем, выбор технологий — mysql и PHP/PDO — достаточно красноречив.
Но если так уж хочется отстоять возможность написать на них демона, то да — для этих нескольких случаев статья будет смысл :)
Да, удиветельно, что в статье под названием «PDO_MYSQLND: Prepared Statements, again» расказывается про mysql и PHP/PDO. Признайтесь, что статью вы не читали, иначе бы не повторяли бы то, что и так в статье есть.

I would appreciate if everybody recommending prepared statements as a best practice teaches the pros and cons of the technology in the context of PHP applications: neither does a portable standard definition for server-side prepared statements exist nor did PDO manage to provide a convincing solution for client-side (emulated) prepared statements. Consider that when talking about the best practice “prepared statements”…


Улавливаете мысль? Автор сам скептически относится к prepared statements и говорит, что раз уж кто-то преподносит их как «best practice», то будет не лишним также рассказать и о минусах данного подхода.
Все-таки, в комментах выше правильно пишут, не надо путать prepared statement и parametrized statement.

Первое — это когда текст команды явно обработали на сервере, создав план исполнения. Это своего рода «компиляция» команды.

Второе — это когда в тексте команды те или иные данные не зашиты явно, а передаются отдельно при выполнении команды.

Несмотря на то, что обычно в prepared statement используют параметры (потому что это эффективнее), эти две вещи никак не связаны. Можно «подготовить» выражение без параметров и можно создать параметризованное выражение, не делая prepare.
Очень слабенько… К сожалению, автор сам не удостоился во всём разобраться перед тем как постить статью.

Ещё одно доказательство того, что автор должен быть «на голову выше» всех в том вопросе, который он взялся освещать.
> Каким боком сюда приплели ORM – непонятно. А вот PDO (и MySQLi) как раз панацея, поскольку SQL injection при их грамотном использовании невозможны, как уже и было описано.

А с моим комментарием-то что не так? %) ORM приплели потому, что есть те, кто думает раз они используют PDO или ORM, то SQL-инъекции им не страшны. Собственно, ваш комментарий и подтверждает это «панацея, поскольку SQL injection при их грамотном использовании невозможны». Т.е. не совсем панацея, даже работая с подобными «абстракциями» нужно помнить об этой уязвимости.
Согласен, я высказался резковато и, может, не совсем корректно. Prepared statements — хороший метод защиты от тех типов инъекций, которые были описаны в упомянутой в первом параграфе статье (инъекции в передаваемых данных, от которых в качестве защиты предлагается mysql_escape_string). В ORM может быть встроенный механизм, основанный на prepared statements или их эмуляции.
В том случае, если программист динамически формирует SQL-запрос и подставляет в него непроверенные пришедшие от пользователя данные в некоторых случаях, где подготовленные выражения неприменимы (например, для динамической генерации названий таблицы — $sql = 'select * from reports_'.$_GET['year'];) — бесполезны и mysql_escape_string, и prepared statements, и прочее. Тут надо самого программиста бить, и больно :).

Программист должен 1)знать, использует ли его ORM защиту от инъекций и какую и как ей правильно пользоваться; 2)грамотно формировать SQL-запросы и/или пользоваться ORM, PDO и прочими; 3)уметь думать своей головой. Сойдемся на этом, ок?
правило «думать своей головой» неформализуемо, увы :)
Sign up to leave a comment.

Articles