Перейти к содержимому
Назад к пути
Теория 3 мин чтения

5. Использование database/sql с параметрами

Передача параметров в Go: Практика правильного кода

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

Научимся использовать плейсхолдеры и разберемся, почему синтаксис SQL-параметров отличается в зависимости от выбранной вами СУБД.


1. Плейсхолдеры (Placeholder)

Это специальные символы в тексте SQL-запроса, которые говорят Go-пакету database/sql — "Сюда на этапе выполнения будут подставлены данные".

СУБД Плейсхолдеры Пример запроса в Go
PostgreSQL $1, $2, $3 WHERE id = $1 AND name = $2
MySQL ? WHERE id = ? AND name = ?
SQLite ? WHERE id = ? AND name = ?

Важный нюанс:

В Go-пакете database/sql вы передаете данные в том же порядке, в котором идут плейсхолдеры.

// Для PostgreSQL
db.Query("SELECT * FROM users WHERE id = $1 AND name = $2", id, name)

// Для MySQL
db.Query("SELECT * FROM users WHERE id = ? AND name = ?", id, name)

2. Различия между Query и Exec

В Go-пакете database/sql есть два основных способа отправки запросов. Оба поддерживают параметры.

  • db.Query: Используется для команд, возвращающих данные (SELECT). Вы получаете объект *sql.Rows, по которому нужно пройтись в цикле.
  • db.Exec: Используется для команд, изменяющих состояние (INSERT, UPDATE, DELETE). Возвращает sql.Result, где можно узнать ID вставленной строки или количество затронутых строк.

И Query, и Exec принимают параметры одним и тем же способом — через ?-плейсхолдер и список аргументов. INSERT-запрос с пользовательскими данными выглядит так:

// ✅ INSERT с параметризацией — даже значение HTTP-заголовка безопасно
query := "INSERT INTO tracking (tracking_id, user_agent) VALUES (?, ?)"
_, err := db.Exec(query, headerValue, "Go-Client")

Тот же приём применим к UPDATE и DELETE — никаких отличий от SELECT в плане безопасности.


3. Шаблон с подстановкой LIKE

Подстановка значений в LIKE-условие новичков часто сбивает: знаки % нельзя оставить внутри SQL-шаблона, иначе ввод пользователя снова вклеится в команду. Правильный приём — добавить % в значение аргумента, а не в саму строку запроса:

// ❌ УЯЗВИМО: % внутри SQL, ввод склеивается со строкой запроса
query := fmt.Sprintf("SELECT id, name FROM products WHERE name LIKE '%%%s%%'", search)
rows, _ := db.Query(query)

// ✅ БЕЗОПАСНО: % добавлены к значению аргумента, шаблон фиксированный
query := "SELECT id, name FROM products WHERE name LIKE ?"
rows, _ := db.Query(query, "%"+search+"%")

Драйвер по-прежнему обрабатывает "%abc%" как обычное строковое значение — даже если search содержит ' UNION SELECT ..., оно интерпретируется как часть подстроки для LIKE-сравнения. Тот же паттерн годится для cookie, заголовков, имени пользователя из БД (Second-Order) — везде, где нужна wildcard-подстрока.


4. Кэширование Prepared Statement в структуре

При частом вызове одного и того же запроса (логин, поиск, applied_coupon) подготовку шаблона можно сделать один раз — в конструкторе хендлера — и переиспользовать *sql.Stmt. План запроса кэшируется в БД, повторные вызовы становятся дешевле.

type Handler struct {
    db        *sql.DB
    loginStmt *sql.Stmt // кэш одного prepared statement
}

func NewHandler(db *sql.DB) (*Handler, error) {
    stmt, err := db.Prepare("SELECT id FROM users WHERE username=? AND password=?")
    if err != nil {
        return nil, fmt.Errorf("prepare login: %w", err)
    }
    return &Handler{db: db, loginStmt: stmt}, nil
}

// В обработчике:
var id int
err := h.loginStmt.QueryRow(username, password).Scan(&id)

stmt.QueryRow(args...) точно так же разделяет шаблон и данные — но шаблон уже скомпилирован один раз при старте приложения. Альтернатива — локальный db.Prepare + defer stmt.Close() внутри обработчика; для одноразовых запросов проще ограничиться обычным db.QueryRow(query, args...).


5. Таблица типичных ошибок при работе с параметрами

Ошибочное действие Почему это плохо? Исправление
fmt.Sprintf внутри Query Инъекция всё еще возможна! Query("... WHERE id = ?", id)
Использование не того плейсхолдера База вернет синтаксическую ошибку Используйте $1 для Postgres
Перепутан порядок аргументов Будут подставлены не те данные Сверяйте порядок $1, $2...
% внутри SQL-строки в LIKE Ввод снова склеивается со строкой db.Query("... LIKE ?", "%"+v+"%")

6. Почему параметры в Go — это безопасность?

Параметризация на 100% блокирует возможность внедрения SQL-кода. Хакер может подставить любую строку, но Go-драйвер передает её базе как "литеральное значение". База данных никогда не будет воспринимать данные из параметров как SQL-команду.


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

Но почему именно этот метод так эффективен? Пора заглянуть глубже и изучить механику защиты на уровне протокола.

Продолжить чтение

Что бы прочитать модуль полностью, зарегистрируйтесь/войдите на платформу

Когда закончишь — отметь раздел, чтобы продолжить.

🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru