Как стать автором
Обновить

Комментарии 13

Про `defer tx.Close()` не совсем верно. Метод Next() если вызван до конца и мы вышли из цикла по нему не требует:

```

func (rs *Rows) Next() bool {

var doClose, ok bool

withLock(rs.closemu.RLocker(), func() {

doClose, ok = rs.nextLocked()

})

if doClose {

rs.Close()

}

return ok

}

```

Да, (rs *Rows) Next() действительно освобождает соединение. Но тут слишком много "если". В defer нужно оставлять вызов Close() для надежности. Например, может возникнуть паника до того, как мы дошли до конца используя метод Next(). Поэтому я бы советовал все же в defer оставить освобождение соединения. И ничего страшного если мы вызовем метод Close() лишний раз.

Просто для более полного обзора.

Если производительности "стандартного" пакета к мускулю не достаточно, то можно взять альтернативу: https://github.com/go-mysql-org/go-mysql/. Он не особо совместим с database/sql, но позволяет прокачивать значительно больше запросов через себя за счет минимизации абстракций и аллокаций. Плюс умеет быть не только клиентом, но это уже не по теме поста.

Спасибо за дополнение.
Не против, если добавлю к статье как UPD ?

Не против, что уж тут такого.

Возможно мой вопрос прозвучит по дилетански, но я только учусь писать на этом чудесном языке, поэтому и читаю подобные статьи… А настоящим Гуру такое читать наверное и неинтересно…
Все бы ничего, но понять статью без знания библиотеки не представляется возможным.
Например откуда вдруг взялось:

driversMu.Lock()
defer driversMu.Unlock()


Ведь пока не полезешь в доки и не найдешь:

var driversMu sync.RWMutex
var drivers = make(map[string]Driver)


Ничего не понятно… Дальше читать не стал…

А в чем, собственно, сам вопрос? Буду рад ответить.

К сожалению, если бы я останавливался на каждых деталях реализации и возможностях библиотеки, то одной статьей здесь бы не обошлось. Да и вряд ли бы я вообще смог закончить такую серию статей.

Я старался сжато показать и рассказать именно про настройку пула соединений, отметить основные моменты и сделать этот процесс более осознанным, чтобы лучше понимать как это может влиять на сервис.

Ну мне пытающемуся изучить язык в славном Skillbox после сдачи 29 заданий из 31 то, что изложено в вашем тексте, просто непонятно как читать. Просто я не дорос до такого изложения. Жалко, тема меня заинтересовала… Вот был бы исходный код, по которому Вы прошлись бы выбирая ключевые моменты, может я и приблизился к пониманию. Просто видимо Вы расчитываете на более квалифицированных читателей. Извините, что отвлек своим комментарием.

Превосходная статья ! Вынес очень много полезного, автор большой молодец! Браво !!!

Спасибо за отличную и актуальную статью!

Есть пара моментов, которые очень хочется понять при одновременном использовании 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-клиентами?

Буду благодарен за разъяснения:)

Спасибо за хороший вопрос, ответ на который может даже претендовать на отдельную статью)
Но постараюсь ответить коротко и понятно.
Для начала, определим как происходит подготовка запроса для общего понимания:

  1. Клиент отправляет команду серверу на подготовку запроса с плейсхолдерами;

  2. Сервер обрабатывает запрос и возвращает идентификатор подготовленного выражения;

  3. Клиент выполняет запрос отправляя серверу идентификатор подготовленного выражения и параметры, с которыми должен выполниться запрос.

Для подготовки выражения мы можем воспользоваться методом (*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 а использовать их только в для однотипных действий в рамках одного соединения, как в примере документации.

Спасибо за статью. Очень полезно и понятно все написано. Благодаря вам быстро понял почему фейлятся запросы при нагрузке и как это исправить.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий