Зачем использовать плейсхолдеры (prepared statements) в SQL-запросах?

Ответ

Плейсхолдеры (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-инъекции.

Дополнительные преимущества

  1. Производительность: СУБД может кешировать план выполнения для подготовленного запроса. При повторном вызове того же запроса с другими параметрами не тратится время на его повторный парсинг и анализ.
  2. Читаемость и поддержка: Код становится чище, так как логика запроса отделена от данных.
  3. Корректная обработка типов: Драйвер 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-инъекция — накрылась медным тазом.

И бонусы, о которых ты, возможно, не думал:

  1. Скорость, мать её! База данных — не дура. Она видит один и тот же запрос с плейсхолдерами, составляет для него оптимальный план выполнения один раз и кеширует его. Потом ты подставляешь новые значения — а план уже готов. Не надо каждый раз заново парсить и анализировать запрос. Экономия на спичках, но в масштабах — овердохуища.

  2. Читаемость. Глаза не вытекают, когда пытаешься разобрать, где в этой каше из плюсов и кавычек заканчивается запрос и начинаются данные.

  3. Типы, ёпта! Не надо гадать, как правильно экранировать строку или преобразовать 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, а не опция. Всё, лекция окончена, иди делай нормально.