Вы могли заметить, что пакет database/sql по сути предоставляет стандартный интерфейс между нашим приложением на Go и миром баз данных SQL.

Содержание статьи

Пакет database/sql в Go

Пока вы используете пакет database/sql, ваш код будет работать с любым типом SQL базы данных — будь то MySQL, PostgreSQL, SQLite или что-то еще. Это означает, что ваше приложение не зависит от определённой базы данных, которую вы используете в настоящее время. Следовательно, вы можете менять базу данных в будущем, не переписывая весь код (за исключением специфических особенностей драйвера и SQL запросов).

Премиум 👑 канал по Golang

Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎

Подписаться на канал

Уроки, статьи и Видео

Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.

Go в ВК ЧАТ в Telegram

Важно отметить, что, хотя пакет database/sql обычно хорошо справляется предоставляя стандартный интерфейс для работы с SQL базами данных, существуют некоторые особенности в работе различных драйверов и баз данных. Всегда нужно ознакомиться с документацией к новому драйверу, чтобы понять его особенности перед началом его использования.

NULL значения в SQL и Golang

Одним из недостатков Go является тот факт, что данный язык не очень хорошо справляется с управлением NULL значениями в записях базы данных.

Предположим, что столбец title в таблице snippets содержит значением NULL в определенном ряду. Если выполнить запрос к данному ряду, то метод rows.Scan() вернет ошибку, потому что он не может конвертировать NULL в строку.

Грубо говоря, исправить это можно, изменив сканируемое поле из string на тип sql.NullString. В качестве примера можете ознакомиться с этим gist.

Как правило, проще всего вообще избегать значений NULL. Поставьте ограничения NOT NULL для всех столбцов базы данных, как мы это сделали в этом курсе.

Работа с SQL транзакциями в Go

Важно понимать, что вызовы Exec(), Query() и QueryRow() могут использовать любое соединение из пула sql.DB. Даже если в вашем коде есть два вызова Exec() рядом друг с другом, нет гарантии, что они будут использовать одно и то же соединение с базой данных.

Иногда это неприемлемо. Например, если заблокировать таблицу с помощью MySQL команды LOCK TABLES, нужно вызвать UNLOCK TABLES точно для того же соединения, чтобы избежать блокировки всего процесса работы с данной таблицей из других соединений.

Для гарантии использования одного и того же соединения можно заключить в транзакцию несколько SQL-операторов.

Вот основной шаблон метода в нашей тестовой модели:

Транзакции очень полезны, если нужно выполнять несколько SQL запросов как одно единое действие. Даже если возникнет ошибка, но пока вы используете метод tx.Rollback(), транзакция гарантирует, что:

  • Все запросы выполняются успешно… или
  • Никакие запросы не выполняются, и база данных остается неизменной.

Управление SQL соединениями в Go

Пул подключений sql.DB состоит из соединений, которые либо простаивают, либо используются. По умолчанию нет ограничения для максимального количества открытых подключений (простаивающих + используемых) одновременно, но максимальное количество неактивных подключений в пуле по умолчанию равно 2.

Эти значения можно изменить с помощью методов SetMaxOpenConns() и SetMaxIdleConns(). Например:

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

Например, ограничение по умолчанию для MySQL равно 151. Таким образом, если настроить SetMaxOpenConns() полностью неограниченным или установить значение больше 151 подключений, это может привести к ошибке "too many connections" при высокой нагрузке. Чтобы эта ошибка не возникла, нужно установить максимальное значение открытых подключений ниже 151.

Однако, так появляется другая проблема. Когда достигается предел SetMaxOpenConns(), любые новые задачи к базы данных, которые приложению необходимо выполнить, будут ждать, пока соединение не станет свободным и продолжить работу.

Для некоторых приложений такое поведение может быть нормальным, но веб-приложению лучше сразу записать ошибку в журнал как "too many connections" и отправлять пользователю ответ 500 Internal Server Error вместо того, чтобы их HTTP запрос завис и ожидал освобождения нового подключения к базе данных.

По этой причине мы не использовали методы SetMaxOpenConns() и SetMaxIdleConns() в нашем приложении все настройки по умолчанию. Такие моменты лучше настраивать в конфигурационном файле самого MySQL.