Mysql: Лучшая практика

Созданный на 3 июн. 2016  ·  40Комментарии  ·  Источник: go-sql-driver/mysql

С этим драйвером рекомендуется каждый HTTP-запрос открывать базу данных, используя Open("mysql","...") и закрывать ее, используя Close() в конце каждого цикла запроса.

ИЛИ

просто открыть его один раз и никогда не закрывать независимо от цикла запроса?

Самый полезный комментарий

@pjebs Я звоню им сразу после открытия соединения.
Честно говоря, кажется, что если вы не установите эти параметры ( SetMaxOpenConns ), он просто установит столько соединений, сколько захочет. Вы пытались установить SetConnMaxLifetime на 5 или 10 минут, чтобы увидеть, закроет ли он тогда соединения?

Попробуйте вызвать их, как только ваше соединение будет открыто:

db.DB().SetConnMaxLifetime(time.Minute*5);
db.DB().SetMaxIdleConns(0);
db.DB().SetMaxOpenConns(5);

Посмотрите, превышает ли он когда-либо 5 подключений и не простаивает ли какое-либо из подключений в течение последних 5 минут. Дай мне знать. Я бы подумал, что он завис бы, если бы в пуле не было доступных свободных соединений, но я не читал код, поэтому в этом случае он может просто потерпеть неудачу.

Все 40 Комментарий

В комментариях к коду сказано:

БД закрывают редко, так как дескриптор БД должен быть долгоживущим и использоваться совместно многими горутинами.

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

Также см. http://go-database-sql.org/accessing.html (часть после нумерованного списка)

@benguild как вы настроили SetConnMaxLifetime , SetMaxIdleConns и SetMaxOpenConns

Любая идея, где мы можем получить принципы передовой практики по их настройке для различных условий нагрузки?

Более года я использовал этот драйвер, используя Open() в начале запроса и Close() в конце жизненного цикла запроса. Это сработало отлично.

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

Я не устанавливал SetConnMaxLifetime , SetMaxIdleConns и SetMaxOpenConns . Они оставлены по умолчанию.

Эта простая техника не работает для меня. Количество активных подключений к CloudSQL продолжает расти, пока не достигнет предела подключений Google App Engine. После этого я не могу подключиться к базе данных.

Соединения не закрываются.

Пример Go для CloudSQL вызывает Open() только один раз в начале: https://github.com/GoogleCloudPlatform/golang-samples/blob/master/getting-started/bookshelf/db_mysql.go

Интересно, эта проблема специфична для CloudSQL и не связана с самим драйвером?

Может быть, у @broady есть понимание?

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

Я написал этот пример кода, понимая, что соединение будет долговечным. На самом деле я никогда не использовал SQL из Go в рабочем приложении.

Это отключение из-за тайм-аута (поэтому нам нужен периодический пинг поддержки) или из-за того, что сокет сломан?

Кто должен этим заниматься? Водитель или пользователь?

На самом деле то, что я сказал, не имеет большого смысла. Пакет sql должен обрабатывать плохие соединения в своем пуле. Ограничили ли мы эту проблему этим драйвером или это ошибка в базе данных/sql?

@pjebs Я звоню им сразу после открытия соединения.
Честно говоря, кажется, что если вы не установите эти параметры ( SetMaxOpenConns ), он просто установит столько соединений, сколько захочет. Вы пытались установить SetConnMaxLifetime на 5 или 10 минут, чтобы увидеть, закроет ли он тогда соединения?

Попробуйте вызвать их, как только ваше соединение будет открыто:

db.DB().SetConnMaxLifetime(time.Minute*5);
db.DB().SetMaxIdleConns(0);
db.DB().SetMaxOpenConns(5);

Посмотрите, превышает ли он когда-либо 5 подключений и не простаивает ли какое-либо из подключений в течение последних 5 минут. Дай мне знать. Я бы подумал, что он завис бы, если бы в пуле не было доступных свободных соединений, но я не читал код, поэтому в этом случае он может просто потерпеть неудачу.

Интересно, проблема в коде приложения? Если обработчики не закрывают наборы результатов, драйверу придется открывать все больше и больше соединений, потому что существующие все еще используются. Когда соединение было открыто и закрыто для каждого запроса, соединения, удерживаемые открытыми из-за выполняемых запросов, разрываются, поэтому они никогда не накапливаются.

Это объясняет, почему возникла проблема с переключением на единое глобальное постоянное соединение.

Кроме того, @pjebs , проверьте, чтобы убедиться (очевидно), что вы не создаете дополнительный пул базы данных совершенно случайно где-то в своем коде. Если где-то выделяется новый пул, это может постепенно вызвать проблему. Если вы раньше вызывали defer db.close() , то что-то подобное могло остаться незамеченным, но мне кажется, что, возможно, есть экземпляр, в котором создается/открывается совершенно новый пул на основе вашего предыдущего кода, находящегося в сортировке одноразового использования. стиля.

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

@dgryski Да, я тоже об этом подумал (см. Выше). Во время переключения с одноразового использования на сохраненный пул может быть случай, когда где-то в коде создается другой пул, который никогда не освобождается или что-то в этом роде.

Еще одна проблема, которая связана:

С моим предыдущим методом для целей отладки я использую это (чтобы избежать ошибки ограничения внешнего ключа):

db.Exec("SET foreign_key_checks = 0;")
_, err := db.Exec("INSERT IGNORE INTO " + c.Q_VALENTINES_PIVOT_CUSTOMER_EVENT + " (customer_id, event_id, gender, allocated_group, session_1, session_2, public_name, profile_photo, registered_time) VALUES " + values + ";")
    if err != nil {
        db.Exec("SET foreign_key_checks = 1;")
        return err
    }
    db.Exec("SET foreign_key_checks = 1;")

это использовать, чтобы ВСЕГДА работать.

Теперь, когда я использую «пул соединений», это никогда не работает.
https://github.com/go-sql-driver/mysql/issues/208 : это объясняет, почему. В нем говорится, что нет гарантии, что я буду использовать одно и то же соединение, и, поскольку SET установлен только для сеанса, он не обязательно применяется для основного оператора.

Я это понимаю. Но почему это сработало в моем предыдущем методе. Хотя я всегда создавал новое соединение и закрывал его в конце цикла запроса, создаваемое мной «соединение» по-прежнему будет использовать пул соединений?

@pjebs пул соединений _может_ открывать несколько соединений. Если вам нужно только одно соединение за раз, вы останетесь с одним. В вашем старом случае у вас никогда не было двух одновременных запросов к базе данных в одном и том же пуле, поэтому когда-либо было установлено только одно соединение. Таким образом, все Execs происходили на одном и том же соединении. Как только вы перешли на правильные пулы, это больше не так.

Это было бы проще отлаживать, если бы мы могли видеть больше вашего кода.

@pjebs вам нужно сделать это как транзакцию базы данных, чтобы убедиться, что она попадает в одно и то же соединение: http://jinzhu.me/gorm/advanced.html#transactions

Хотя мне неясно, почему он будет делать то, о чем вы говорите, использование разных соединений для каждого запроса оператора не является совершенно неуместным. Это своего рода идея «пула» ... учитывая, что он не обязательно знает, что делает запрос, и может быть много запросов, работающих одновременно. Было бы глупо, если бы одно соединение было зарезервировано для одного запроса на всю его жизнь, но да. Используйте транзакцию.

Было бы совершенно неуместно менять соединения внутри транзакции. То, что вы делаете, в любом случае должно быть внутри транзакции, поскольку изменение параметров конфигурации может повлиять на другие запросы из других частей вашего кода или другие запросы. То, как это написано в настоящее время... если ваш код паникует, он никогда не будет изменен обратно на этом конкретном соединении! В этой заметке также убедитесь, что вы откладываете откат транзакции, если она никогда не завершится (например, при сбое/панике) ... потому что в противном случае это может повесить это соединение с базой данных, или другой процесс может принять это соединение с базой данных и запустить внесение изменений в эту транзакцию, которые никогда не будут зафиксированы, и т. д. — https://github.com/jinzhu/gorm/issues/1056

Я заметил в коде @broady : https://github.com/GoogleCloudPlatform/golang-samples/blob/master/getting-started/bookshelf/db_mysql.go#L82
после того, как он открывает соединение, он немедленно вызывает Ping() , чтобы проверить, работает ли соединение.
Если это не так, он просто уходит. Есть ли что-нибудь еще, что мы можем сделать? Должны ли мы попытаться открыть снова?

В библиотеке https://godoc.org/github.com/garyburd/redigo/redis#Pool также реализован хороший пул.
У него есть хорошая функция под названием TestOnBorrow , где в примере чуть ниже они также выполняют Ping() .

Есть ли что-то подобное в этом драйвере?

@pjebs какова актуальная текущая проблема?

Просто спрашиваю о лучших практиках, когда внедрять Ping() и что делать, если Ping терпит неудачу.

О, я вижу. Насколько я могу судить, вам не нужно...? Но если в пуле все еще есть мертвые соединения... это кажется необходимым.

Вам обязательно нужно пропинговаться. Они делают это в redigo (ссылка выше) и в коде @broady .

db.Exec("SELECT 1+1") можно использовать как PING.

Нам "обязательно" нужно пинговать? Это не имеет смысла... что произойдет, если соединение недействительно? Прервать или повторить попытку?

Почему запуск запроса не вызывает автоматически эквивалент .Ping ?

db.Exec("SELECT 1+1") можно использовать как PING.

Хорошо, тогда почему нам обязательно нужно .Ping ?

Ping проверяет правильность подключения и аутентификации. Я думаю, что лучше ошибиться при создании пула, а не по первому запросу.

Я полагаю, вы могли бы повторить попытку, но с каким условием отказа вы работаете? Плохое сетевое соединение?

@benguild db.Ping() не использует эту библиотеку.
Как насчет того, чтобы сходить с ума или пойти на форум?

Да, иногда одно из возвращенных соединений пула не работает. В настоящее время я вызываю ping, и если ping терпит неудачу, я явно close , а затем открываю и повторяю запрос (не уверен, что это то, что вы должны делать с возможностями этой библиотеки pooling , учитывая, что документация по существу говорит НИКОГДА ЗАКРЫТЬ.

@broady , хорошо, это имеет больше смысла ... Я не думал, что это сохранится в [пуле] или освободит мертвые соединения из пула к вызывающему абоненту. @pjebs К вашему сведению, я знаю, что мы оба используем GORM , и на самом деле это происходит в конце gorm.Open() :

if err == nil {
    err = db.DB().Ping() // Send a ping to make sure the database connection is alive.
}

Итак, учитывая, что эта конкретная библиотека пытается .Ping , но сама по себе в противном случае не повторяет попытку в случае сбоя (она просто возвращает ошибку) .... Может быть что-то подобное необходимо для разумного уровня совместимость с этим драйвером?

for databaseConnectionAttemptLoop := 0; databaseConnectionAttemptLoop<4; databaseConnectionAttemptLoop++ {
    err=nil; // NOTE: If there is an error, the loop will continue and replace the value for "err" with each attempt. This will continue until the maximum number of attempts have been made... at which point the loop simply exits and the error is returned at the end of the function.

    if db, err = gorm.Open(/* database connection string... NOTE: This function attempts `.Ping` on its own. */))); err == nil && db != nil {
        /* configure connection */

    }
    ////

    if (err==nil) {
        break; // Here, if there is no error, it simply breaks out and does not retry again.

    }

}

В настоящее время не уверен, что ожидается, если плохие соединения могут быть преднамеренно возвращены.

В настоящее время, если Ping терпит неудачу (обычно из-за плохого соединения), я просто делаю бесконечный цикл при попытке снова подключиться с небольшим time.sleep() между каждой попыткой. Я знаю, что Google App Engine уничтожит запрос после истечения 60-секундного сторожевого таймера запроса, поэтому нет опасности реального бесконечного цикла.

Хм, я не знаю, сделал бы я это…
time.sleep() звучит хорошо, но я бы попробовал максимум 5 раз или около того. Если ящик законно не работает или перегружен, вы можете сломать свой веб-сервер или получить огромный счет за использование, если будет много зависающих запросов в течение 60 секунд.

Это то, что я сейчас делаю для тестирования. В производстве этого не будет. Я установлю ограничение на количество попыток.
К счастью, я никогда не видел, чтобы облачный sql не работал дольше минуты.

Книга SRE от Google предлагает максимум 3 попытки с рандомизированным экспоненциальным отставанием с глобальным бюджетом повторных попыток для каждого процесса в 10%. Если вы начинаете сбоить более 10% ваших звонков, прекратите повторные попытки.

Ух ты. Спасибо @dgryski . Где я могу найти книгу SRE?

@dgryski Что вы подразумеваете под «глобальным бюджетом повторных попыток для каждого процесса»?

@ Бенгильд

db.DB().SetConnMaxLifetime(time.Minute*5);
db.DB().SetMaxIdleConns(0);
db.DB().SetMaxOpenConns(5);

используйте этот код фрагмента, с несколькими goruntime, я получил ошибку

(dial tcp 127.0.0.1:3306: connect: can't assign requested address)

см. https://github.com/jinzhu/gorm/issues/1898

Попробуйте вызвать их, как только мое соединение будет открыто:

db.DB().SetConnMaxLifetime(time.Minute*5);
db.DB().SetMaxIdleConns(5);
db.DB().SetMaxOpenConns(5);

реши.

Более года я использовал этот драйвер, используя Open() в начале запроса и Close() в конце жизненного цикла запроса. Это сработало отлично.

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

Я не устанавливал SetConnMaxLifetime , SetMaxIdleConns и SetMaxOpenConns . Они оставлены по умолчанию.

Эта простая техника не работает для меня. Количество активных подключений к CloudSQL продолжает расти, пока не достигнет предела подключений Google App Engine. После этого я не могу подключиться к базе данных.

Соединения не закрываются.

У меня такая же проблема с подключением к proxysql. Я установил следующую конфигурацию для производственного узла с высокой нагрузкой:

max_idle: 1
max_open: 100
max_life_time: 5s
write_timeout: 5s
read_timeout: 3s
connection_timeout: 10s

Но когда я проверяю количество соединений для порта db, оно показывает более 21 тыс. соединений в состоянии TIME_WAIT .

Была ли эта страница полезной?
0 / 5 - 0 рейтинги