Comments 13
Да, (rs *Rows) Next()
действительно освобождает соединение. Но тут слишком много "если". В defer
нужно оставлять вызов Close()
для надежности. Например, может возникнуть паника до того, как мы дошли до конца используя метод Next()
. Поэтому я бы советовал все же в defer оставить освобождение соединения. И ничего страшного если мы вызовем метод Close()
лишний раз.
Просто для более полного обзора.
Если производительности "стандартного" пакета к мускулю не достаточно, то можно взять альтернативу: https://github.com/go-mysql-org/go-mysql/. Он не особо совместим с database/sql, но позволяет прокачивать значительно больше запросов через себя за счет минимизации абстракций и аллокаций. Плюс умеет быть не только клиентом, но это уже не по теме поста.
Все бы ничего, но понять статью без знания библиотеки не представляется возможным.
Например откуда вдруг взялось:
driversMu.Lock()
defer driversMu.Unlock()
Ведь пока не полезешь в доки и не найдешь:
var driversMu sync.RWMutex
var drivers = make(map[string]Driver)
Ничего не понятно… Дальше читать не стал…
А в чем, собственно, сам вопрос? Буду рад ответить.
К сожалению, если бы я останавливался на каждых деталях реализации и возможностях библиотеки, то одной статьей здесь бы не обошлось. Да и вряд ли бы я вообще смог закончить такую серию статей.
Я старался сжато показать и рассказать именно про настройку пула соединений, отметить основные моменты и сделать этот процесс более осознанным, чтобы лучше понимать как это может влиять на сервис.
Превосходная статья ! Вынес очень много полезного, автор большой молодец! Браво !!!
Спасибо за отличную и актуальную статью!
Есть пара моментов, которые очень хочется понять при одновременном использовании interpolateParams=true
, DB.Prepare(query)
и DB.SetMaxOpenConns(n)
для http-сервера:
Первый. Если глобально складывать часто используемые подготовленные операторы в карту
stmt := DB.Prepare(query)
stmtMap[query] = stmt
а потом их брать оттуда для выполнения запросов к базе от разных http-клиентов
results, err := stmtMap[query].Query(userId)
то при в включенном interpolateParams=true
это вообще имеет смысл? Что происходит в данном случае, stmt
хранится в памяти GO и на сервер MySQL не передается? Экономия только на подготовке запроса в GO? Или все равно передается, но уже подготавливается новый оператор с включенным параметром userId
? Тогда получается двойная работа...
Второй. Подготовленные операторы stmt := DB.Prepare(query)
вроде как привязываются к конкретному соединению, после отправки на сервер MySQL. Получается один stmt
будет дублироваться в каждое соединение из пула DB.SetMaxOpenConns(n)
при запросе с его использованием? При желании сохранять stmt в карте (памяти GO) вызов stmt.Close()
после каждого клиента не предполагается... Или вызов stmt.Close()
освобождает только соединение от stmt
и сохранит его в пригодном виде в stmtMap
для повторного использования новыми http-клиентами?
Буду благодарен за разъяснения:)
Спасибо за хороший вопрос, ответ на который может даже претендовать на отдельную статью)
Но постараюсь ответить коротко и понятно.
Для начала, определим как происходит подготовка запроса для общего понимания:
Клиент отправляет команду серверу на подготовку запроса с плейсхолдерами;
Сервер обрабатывает запрос и возвращает идентификатор подготовленного выражения;
Клиент выполняет запрос отправляя серверу идентификатор подготовленного выражения и параметры, с которыми должен выполниться запрос.
Для подготовки выражения мы можем воспользоваться методом (*DB).Prepare()
, который после выполнения подготовки вернет нам ссылку на структуру *Stmt
.
Метод (*DB).Prepare()
никак не учитывает настройку interpolateParams
, он просто отправляет команду mysql серверу.
Чтобы выполнить запрос с подготовленным выражением нужно воспользоваться методом (*Stmt) Query()
, замечу, что это другой метод, отличный от метода (*DB) Query()
Отвечая на первый вопрос, при выполнении (*Stmt) Query()
настройка interpolateParams
не учитывается, мы явно просим библиотеку выполнить подготовку на сервере и потом отправляем вторую команду на выполнение запроса для подготовленного выражения.
По второму вопросу, да, подготовленное выражение завязывается на соединение, и тут есть нюанс. Если при выполнении (*Stmt) Query()
соединение, с которым выполнялась подготовка, недоступно (занято или вовсе закрылось), то будет создано (взято из пула) новое соединение и снова выполнится команда подготовки выражения, и только потом уже сам запрос. Это может привести к лишним подготовкам одних и тех же запросов. Метод (*Stmt) Close()
при этом закрывает выражение и выполнение запросов после этого приведет к ошибке sql: statement is closed
.
Надеюсь я смог чем-то помочь.
Часть вашего ответа стоит добавить в статью) или действительно написать подробно новую на эту тему)
Из описания type Stmt в документации показалось, что GO самостоятельно "готовит" операторы без участия БД. А также то, что параметр interpolateParams
является частью метода DB.Open()
, воспринималось как обязательное ко всем действиям с БД.
Спасибо, вы сэкономили много времени!
Вобщем, не стоит складировать *Stmt
а использовать их только в для однотипных действий в рамках одного соединения, как в примере документации.
Спасибо за статью. Очень полезно и понятно все написано. Благодаря вам быстро понял почему фейлятся запросы при нагрузке и как это исправить.
Go и MySQL: настраиваем пул соединений