
Представьте, что вы создаёте мощное поисковое приложение. Пользователи вводят ключевые слова, а ваш бэкенд должен отправить запрос к Manticore Search и найти подходящие результаты. Распространённый (и соблазнительный!) подход — напрямую подставлять пользовательский ввод в SQL-запросы. Например, вы можете фильтровать по числовому полю, такому как категория или идентификатор записи. Если пользователь передаёт обычное значение, например 5, запрос будет SELECT * FROM products WHERE id=5. А что, если он передаст 1 OR 1=1? Запрос станет SELECT * FROM products WHERE id=1 OR 1=1 — условие всегда истинно, поэтому вместо одной строки вернутся все. Это SQL-инъекция.
К счастью, существует более безопасный и эффективный способ: prepared statements. По сути, prepared statements отделяют ваш SQL‑код от передаваемых данных. Вместо того чтобы каждый раз собирать всю строку запроса, вы один раз задаёте структуру запроса с маркерами параметров, а затем отдельно передаёте поисковые термины. Подробнее о концепции можно узнать на Wikipedia .
Manticore Search поддерживает prepared statements через стандартный протокол MySQL, и это даёт вам удобный инструмент для создания безопасных поисковых приложений. Используя prepared statements, вы не только заметно снижаете риск SQL-инъекций, но и делаете код понятнее.
prepared statements — это не просто полезная функция; иногда без них вообще не обойтись. Например, библиотека Rust sqlx работает с MySQL-эндпоинтом только через prepared statements. Кроме того, некоторые OLE DB-коннекторы, позволяющие MS SQL работать с MySQL-сервером, тоже используют prepared statements внутри.
Зачем использовать prepared statements?
Безопасность прежде всего (SQL-инъекции): SQL-инъекция — это уязвимость веб-безопасности, которая позволяет злоумышленникам вмешиваться в запросы приложения к базе данных. Она возникает, когда пользовательский ввод некорректно включается в SQL-запрос и в результате может выполниться вредоносный код. Например, рассмотрим простой поисковый запрос, построенный конкатенацией поискового термина пользователя прямо в SQL:
// Vulnerable code example (DO NOT USE!) $productId = $_GET['search']; $query = "SELECT * FROM products WHERE id= " . $productId;
Если $productId содержал, например, 0 OR 1=1, запрос превратится в SELECT * FROM products WHERE id= 0 OR 1=1, эффективно обходя условие WHERE и возвращая все товары.
prepared statements предотвращают это, потому что пользовательский ввод рассматривается строго как данные, а не как часть SQL-команды. Драйвер базы данных сам обрабатывает экранирование и кавычки, нейтрализуя потенциально опасные символы. Ниже — тот же запрос, но уже с prepared statement:
// Secure code example using a prepared statement $productId = $_GET['search']; $stmt = $mysqli->prepare("SELECT * FROM products WHERE id= ?"); $stmt->bind_param("i", $productId); $stmt->execute();
В этом случае, даже если $productId содержит вредоносный код, он будет рассматриваться как буквальное значение, а не как исполняемый SQL.
Как они работают
prepared statements работают по простому трёхшаговому процессу:
Подготовка: Сначала вы отправляете в Manticore Search SQL-оператор с маркерами параметров, например
?или?VEC?. Manticore разбирает этот оператор, строит план запроса и возвращает уникальный идентификатор prepared statement.Привязка: Затем вы отправляете фактические данные — значения для маркеров параметров — в Manticore отдельно. Именно здесь и обеспечивается безопасность: данные трактуются только как данные, а не как SQL-код.
Выполнение: Наконец, вы даёте Manticore команду выполнить prepared statement, используя сохранённый план запроса и привязанные параметры.
Проще всего думать об этом как о шаблоне. Один раз задаёте структуру, а потом подставляете в неё разные значения каждый раз, когда хотите выполнить запрос.
Маркеры параметров: ? и ?VEC?
Manticore Search использует специальные маркеры параметров, чтобы обозначать параметры внутри prepared statements:
?означает один параметр — это может быть целое число, число с плавающей точкой или строка. При использовании этого маркера параметра Manticore автоматически выполняет экранирование и расставляет кавычки для строковых значений, защищая от SQL-инъекций и обеспечивая корректное форматирование данных.?VEC?предназначен для списков числовых значений. Он ожидает строку с числами, разделёнными запятыми и необязательными пробелами (например,1, 2.3, 4, 1e-10, INF). Важно, что к значениям внутри?VEC?не применяется ни экранирование, ни кавычки. Корректный ввод состоит только из чисел, запятых и пробелов; любые другие символы, скорее всего, вызовут ошибку. Это делает маркер параметра удобным для прямой вставки числовых векторов — как векторов с плавающей точкой, так и целочисленных MVA (многозначных атрибутов).
Пример: prepared statements в PHP
Посмотрим, как prepared statements работают на практике в PHP. Разберём и простую вставку со строковыми значениями, и более сложную вставку, включающую вектор с плавающей точкой и маркер параметра ?VEC?.
Сначала простая вставка:
<?php // Assuming you have a valid MySQLi connection established ($mysqli) $stmt = $mysqli->prepare("INSERT INTO products (name, description) VALUES (?, ?)"); $productName = "Awesome Widget"; $productDescription = "A truly amazing widget for all your needs."; $stmt->bind_param("ss", $productName, $productDescription); // "ss" indicates two strings $stmt->execute(); echo "Product added successfully!"; ?>
Этот код подготавливает запрос INSERT, привязывает строковые значения для названия и описания товара, а затем выполняет запрос. SQL, который в итоге выполнит Manticore, будет выглядеть так:
INSERT INTO products (name, description) VALUES ('Awesome Widget', 'A truly amazing widget for all your needs.');
Теперь разберём пример с вектором с плавающей точкой. Что такое ?VEC?? Это маркер параметра, который используется только в prepared statements, для вектора — списка чисел, например эмбеддингов или похожих данных. В Manticore SQL векторный литерал всегда записывается в скобках: (0.1, 0.2, 0.3). Поэтому, когда вы используете prepared statement и передаёте параметр-вектор, скобки пишутся прямо в SQL-строке, а ?VEC? ставится на место самих чисел. Вы привязываете только числа, разделённые запятыми, например "0.1,0.2,0.3"; скобки ( и ) не привязываются — они остаются в запросе. Без prepared statements полный литерал (0.1, 0.2, 0.3) пришлось бы собирать вручную.
В PHP mysqli значения ?VEC? обычно привязываются как строки, поэтому в этом примере естественный выбор — iss. Если вы хотите передавать более объёмную векторную нагрузку, параметр также можно привязать как b и отправить содержимое через send_long_data().
<?php // Assuming you have a valid MySQLi connection established ($mysqli) $stmt = $mysqli->prepare("INSERT INTO items (item_id, coords, features) VALUES (?, (?VEC?),(?VEC?))"); $itemId = 123; $coordVector = "20.245,54.354,30.000"; // that is vector of floats $featureSet = "1,4,20,456,112,3"; // that is set of integer values (MVA) $stmt->bind_param("iss", $itemId, $coordVector, $featureSet); // "i" for integer (itemId), "s" for string $stmt->execute(); echo "Item with feature vector added successfully!"; $itemId = 124; $coordVector = "18.500,42.000,31.125"; // Another float vector $featureSet = "0,6,34,665,22,3445,221,564,2232,5644,43"; // Example with more feature values // For larger payloads you can bind the second ?VEC? as a blob and stream it. $featurePlaceholder = ""; $stmt->bind_param("isb", $itemId, $coordVector, $featurePlaceholder); // "b" is for blob data // bind_param() must be called before send_long_data(). $stmt->send_long_data(2, $featureSet); // zero-based index: 2 means the third bound parameter $stmt->execute(); echo "Item with feature vector added successfully!"; ?>
Обратите внимание: скобки — это часть SQL-строки в вызове prepare(). Мы привязываем только значения внутри скобок, используя маркер параметра ?VEC?. SQL, который выполнит Manticore, будет таким:
INSERT INTO items (item_id, coords, features) VALUES (123, (20.245,54.354,30.000), (1,4,20,456,112,3)); INSERT INTO items (item_id, coords, features) VALUES (124, (18.500,42.000,31.125), (0,6,34,665,22,3445,221,564,2232,5644,43));
Использование ?VEC? в prepared statement даёт те же преимущества, что и маркер параметра ?: значения вектора отправляются как данные, а не как часть SQL-текста, поэтому их нельзя интерпретировать как SQL и они не могут вызвать инъекцию. Кроме того, вам не нужно вручную собирать или экранировать векторный литерал в приложении — Manticore получает привязанные числа и сам корректно форматирует вектор, сохраняя запрос безопасным, а данные — согласованными.
Важные соображения и ограничения
Хотя prepared statements в Manticore — мощный инструмент, у них есть несколько ограничений, о которых стоит помнить.
Мульти-запросы: В рамках одного prepared statement разрешён только один SQL-оператор. Попытки использовать мульти-запросы, например SELECT ...; SHOW META, завершатся ошибкой. Если вам нужно выполнить несколько операторов, подготовьте отдельный запрос для каждого и выполняйте их последовательно в рамках одной сессии.
Числовые типы: Некоторые драйверы баз данных (например, mysql2 для Node.js) могут по умолчанию отправлять числовые параметры как DOUBLE. Это может привести к неожиданному поведению, если вам нужна строгая обработка целых чисел (например, чтобы отрицательные ID отклонялись). В таких случаях можно отправлять целые числа как строки или использовать специфичные для драйвера целочисленные типы (например, BigInt), чтобы обеспечить корректную обработку данных.
Пользователи Rust sqlx: Если вы используете crate sqlx в Rust, имейте в виду: при чтении строк результата нужно использовать индексы столбцов, а не их имена. Хотя имена столбцов присутствуют в наборе результатов, sqlx не использует их для сопоставления. Например, используйте row.try_get(0)? вместо row.try_get("id")?.
Заключение
prepared statements дают важное сочетание безопасности, читаемости и потенциального прироста производительности при работе с Manticore Search. Отделяя SQL-логику от данных, вы заметно снижаете риск SQL-инъекций, упрощаете поддержку кода и потенциально ускоряете выполнение запросов. Мы настоятельно рекомендуем использовать prepared statements в ваших приложениях на базе Manticore Search.
Если хотите разобраться глубже, обязательно загляните в эти материалы:
Документация Manticore Search по prepared statements: https://manual.manticoresearch.com/Connecting_to_the_server/MySQL_protocol#Prepared-statements
Wikipedia — prepared statements: https://en.wikipedia.org/wiki/Prepared_statement
Это руководство даёт хорошую основу для эффективного использования prepared statements в проектах на Manticore Search и помогает создавать более безопасные, эффективные и поддерживаемые приложения.
Комментарии (7)

Akina
09.04.2026 05:24Manticore Search поддерживает prepared statements через стандартный протокол MySQL, и это даёт вам удобный инструмент для создания безопасных поисковых приложений.
"Prepared statements через стандартный протокол MySQL", на который вы ссылаетесь, предполагает, что на сервер MySQL передаются запросы:
PREPARE stmt FROM {строка с текстом запроса с плейсхолдерами, или определённая пользователем переменная с текстом запроса}; EXECUTE stmt USING ( {набор параметров № 1} ); EXECUTE stmt USING ( {набор параметров № 2} ); ... DROP PREPARE stmt;Однако всё, написанное далее, говорит о том, что Manticore Search ИГНОРИРУЕТ стандартные prepared statements в MySQL и выполняет все операции САМОСТОЯТЕЛЬНО. Экранирует переданные значения, конкатенирует их в текст запроса на место плейсхолдеров, и затем передаёт полученный текст запроса на MySQL. Самого обычного запроса. То есть это вовсе даже не серверный prepared statement, а его эмуляция на стороне клиента.
Но проблема в том, что Manticore Search - не MySQL. И он выполняет разнообразные экранирования по своему разумению, а не так, как это понимает MySQL. Нет, я понимаю, что разработчики не лохи, исследовали исходники MySQL вдоль и поперёк, сто раз всё проверили. Но все мы люди, все человеки, а потому потенция ошибки остаётся. И я, к примеру, не знаю, как будет обработана специально созданная текстовая строка в какой-нибудь экзотической или вообще несуществующей кодировке. Вы готовы гарантировать, что невозможно создание такой строки, которая после обработки кодом Manticore и конкатенации в текст запроса создаст-таки инъекцию? Я лично - нет.
PS. К слову, стандартные подготовленные запросы в MySQL поддерживают исключительно скалярные параметры предопределённых типов, соответственно никакие списки не допускаются, ибо такого типа данных в MySQL просто нет.
PPS. Стандартный prepared statement в MySQL прекрасно защитит от инъекции, даже если передаваемые в EXECUTE значения параметров будут злонамеренно модифицированы где-то в середине при прохождении трафика от клиента. Тогда как реализация на клиенте от такого не защищает.

klirichek
09.04.2026 05:24MySQL поддерживает два типа prepared statements. Один - это через протокол. Буквально, в бинарном протоколе есть подмножество команд, которые реализуют perepare, execute и опционально long_data. Через консольный mysql клиент "пощупать" их невозможно, он эти команды не шлёт. Но вот некоторые другие клиенты, как оказалось, буквально ВСЮ работу делают через prepared statements. Даже какой-нибудь "show status" они шлют как prepare + execute + delete prepared. Собственно, именно эта часть протокола и реализована.
А то, о чём вы говорите - это другой тип, "синтаксический сахар" поверх обычного текстового протокола. Этот тип не поддерживается, и его не реализовывали.

Akina
09.04.2026 05:24Повторю ещё раз:
стандартные подготовленные запросы в MySQL поддерживают исключительно скалярные параметры предопределённых типов, соответственно никакие списки не допускаются, ибо такого типа данных в MySQL просто нет. ,
В статье же рассказывается о вставке списка в текст. Через подготовленные запросы MySQL этого не сделать - хоть в текстовой форме, хоть через протокол, о котором вы говорите. То есть список вставляется в текст запроса до отправки на сервер БД - другого варианта осуществить такую вставку я не вижу. Нет, если говорить строго, то существует ненулевая вероятность, что в протоколе такая возможность есть, а из текстовой формы нет - но вы не представляете, как такое мне лично сомнительно..
А что самое неприятное - то, что автор статьи в очень неуверенной форме говорит: "Корректный ввод состоит только из чисел, запятых и пробелов; любые другие символы, скорее всего, вызовут ошибку". Я это понимаю однозначно - может и не вызвать. А дальше можно накрутить и до инъекции.
К слову, у MySQL есть такая штука как General Log. Если его включить, то мы увидим все поступившие на MySQl запросы, причём строго в той форме, в какой они поступили. Если бы автор статьи озаботился получением этих данных, вопросов, непониманий и разночтений бы возникало куда как меньше.
Вот автор пишет:
Этот код подготавливает запрос
INSERT, привязывает строковые значения для названия и описания товара, а затем выполняет запрос. SQL, который в итоге выполнит Manticore, будет выглядеть такВот откуда он этот текст запроса взял? придумал? прикинул, как оно должно быть? вычитал из лога? Вот если бы в логе по показанному коду мы увидели показанный запрос, можно бы было смело утверждать, что prepared statement в Manticore есть эмуляция и фикция. А сейчас есть только вульгарное "а хрен знает"...

klirichek
09.04.2026 05:24Синтаксис в разных БД разный. У Mysql строго вопросики, у Pgsql иной. В мантикоре из-за наличия списков сделали ещё ?VEC?. Протоколу всё равно, он просто передаёт символы, не углубляясь в детали.
Список не вставляется в текст запроса. Он отправляется либо в пэйлоад команды execute, либо заранее, через команду long_blob. В этом можно убедиться, например, рассматривая фактический обмен данными с помощью tcpdump/wireshark.
У MySQL свои собственные подготовленные запросы. Но мантикора - не MySQL. Никто не мешает использовать протокол MySQL, но слать через него собственные вариации, что, собственно, и сделано.
Списки валидируются - там, действительно, допускаются ТОЛЬКО числа (включая "не-числа" inf, nan), запятые и пробелы.
Про General Log - это частная особенность MySQL. В мантикоре для этого есть query.log, и можно при желании увеличить verbosity общего лога. Но надо понимать, бинарный протокол всё же разнообразнее, чем содержимое, прилетающее с пэйлоадом команды MYSQL_COM_QUERY(3) - т.е. текстовые запросы.
Prepared statements - это про MYSQL_COM_STMT_PREPARE, MYSQL_COM_STMT_EXECUTE, MYSQL_COM_STMT_SEND_LONG_DATA, MYSQL_COM_STMT_CLOSE и MYSQL_COM_STMT_RESET. Речь именно об этих командах протокола
sadiquemannan
Great breakdown of prepared statements in Manticore Search. I especially like how you showed the difference between raw SQL and the parameterized version. I’m curious: how does the performance of prepared statements in Manticore compare to plain queries under heavy read/write load, and are there any best practices for batching parameterized queries?
ManticoreSearch Автор
We didn't benchmark it, but it's an interesting question. We'd appreciate it if you could do that and share your findings. What exactly do you mean by "batching"?
sadiquemannan
Thanks for the clarification, that makes sense. By “batching” I meant sending many parameter sets for the same prepared statement in one go (for example, inserting/updating a large number of rows in a single round‑trip instead of one by one) to see how much overhead the prepare/bind/execute cycle adds compared to plain SQL. I’ll try to set up a small benchmark with Manticore Load or a custom script to compare single‑row vs multi‑row/batched inserts and SELECTs using prepared statements vs normal queries, and will share the results once I have something meaningful.