Ответ
Плейсхолдеры (placeholders) — это специальные маркеры в SQL-запросе, которые используются для безопасной подстановки пользовательских данных. Этот механизм является частью Prepared Statements (подготовленных выражений) и является стандартом для работы с базами данных.
Главная цель — предотвращение SQL-инъекций
SQL-инъекция — это атака, при которой злоумышленник внедряет вредоносный SQL-код в запрос, чтобы обойти логику приложения или получить несанкционированный доступ к данным.
Неправильно (уязвимо к инъекциям):
// email может содержать вредоносный код, например: ' OR 1=1; --
email := "' OR 1=1; --"
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
db.QueryRow(query) // Опасно!
Правильно (безопасно):
email := "user@example.com"
// Используем плейсхолдер `?` (синтаксис зависит от драйвера)
query := "SELECT * FROM users WHERE email = ?"
// Драйвер БД сам безопасно подставит значение
db.QueryRow(query, email)
При использовании плейсхолдеров данные передаются на сервер БД отдельно от самого SQL-запроса. Сервер сначала получает и парсит шаблон запроса, а затем применяет к нему данные, обрабатывая их как литералы, а не как часть исполняемого кода. Это полностью исключает возможность SQL-инъекции.
Дополнительные преимущества
- Производительность: СУБД может кешировать план выполнения для подготовленного запроса. При повторном вызове того же запроса с другими параметрами не тратится время на его повторный парсинг и анализ.
- Читаемость и поддержка: Код становится чище, так как логика запроса отделена от данных.
- Корректная обработка типов: Драйвер
database/sqlберет на себя заботу о правильном экранировании спецсимволов и преобразовании типов Go в соответствующие типы SQL.
Важно: Синтаксис плейсхолдеров
Стиль плейсхолдеров зависит от используемой базы данных и ее драйвера:
- PostgreSQL: Позиционные,
$1,$2, и т.д.INSERT INTO users (name, age) VALUES ($1, $2) - MySQL, SQLite: Знак вопроса
?.UPDATE users SET name = ? WHERE id = ? - Oracle: Именованные,
:name,:id.SELECT * FROM users WHERE name = :name
Ответ 18+ 🔞
А, ну вот, очередной горе-разработчик думает, что он умнее всех и будет строки в запросы конкатенировать, как в девяностых! Слушай сюда, дружок-пирожок, сейчас будет короткий, но ёмкий урок по выживанию в дикой природе баз данных.
Плейсхолдеры — это, блядь, не просто модная фича, а твой личный бронежилет от SQL-инъекций. Представь, что ты строишь дом, а вместо цемента используешь говно. Вот примерно так же выглядит конкатенация строк в SQL-запросе.
Вот как НЕ НАДО делать, если ты не хочешь, чтобы тебя уволили к хуям собачьим:
// Допустим, пользователь ввёл вот такую безобидную строчку
email := "' OR 1=1; --"
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
db.QueryRow(query) // Пиздец, Карл! Ты только что отдал базу первому встречному!
Этот запрос превратится в SELECT * FROM users WHERE email = '' OR 1=1; --'. А 1=1 — это всегда правда, понимаешь? И двойной дефис комментарий. В итоге злодей получит ВСЕХ пользователей, а не одного. И это самый примитивный пример, ядрёна вошь!
А вот как делают адекватные люди:
email := "user@example.com"
// Видишь этот вопросик? Это твой новый лучший друг
query := "SELECT * FROM users WHERE email = ?"
// Драйвер БД сам, без твоей криворукой помощи, безопасно всё подставит
db.QueryRow(query, email)
Вот тут магия и происходит. Запрос и данные летят на сервер БД РАЗДЕЛЬНО. Сначала сервер видит шаблон: "эй, найди мне пользователя по email". А потом отдельно получает значение. Он его воспринимает как ДАННЫЕ, а не как КОД. Даже если там будет ' OR 1=1; --, это будет просто странная строка для поиска, а не команда. SQL-инъекция — накрылась медным тазом.
И бонусы, о которых ты, возможно, не думал:
-
Скорость, мать её! База данных — не дура. Она видит один и тот же запрос с плейсхолдерами, составляет для него оптимальный план выполнения один раз и кеширует его. Потом ты подставляешь новые значения — а план уже готов. Не надо каждый раз заново парсить и анализировать запрос. Экономия на спичках, но в масштабах — овердохуища.
-
Читаемость. Глаза не вытекают, когда пытаешься разобрать, где в этой каше из плюсов и кавычек заканчивается запрос и начинаются данные.
-
Типы, ёпта! Не надо гадать, как правильно экранировать строку или преобразовать
time.Timeв SQL-дату. Драйверdatabase/sqlза тебя всё сделает. Твоя работа — не накосячить.
Важный момент, чтобы не облажаться:
Синтаксис этих самых плейсхолдеров разный, зависит от базы. Запомни, как "Отче наш":
- PostgreSQL: Любит доллары.
$1,$2и так далее.INSERT INTO users (name) VALUES ($1) - MySQL, SQLite: Старая добрая классика — знак вопроса
?.UPDATE users SET name = ? WHERE id = ? - Oracle: Пафос и именованные параметры, типа
:name.SELECT * FROM users WHERE name = :name
Перепутаешь — получишь ошибку компиляции вместо запроса. И будешь сидеть, чесать репу и думать: "чё за хуйня?". А это просто ты невнимательный.
Короче, правило простое: увидел в запросе пользовательские данные — сразу ищешь, куда воткнуть плейсхолдер. Это must have, а не опция. Всё, лекция окончена, иди делай нормально.