Сервер: MySQL 5.6
Сохраненная процедура:
DELIMITER //
CREATE PROCEDURE slowInsert(IN t int)
BEGIN
SELECT SLEEP(t);
INSERT INTO `table_x` (message) VALUES (UUID());
END //
DELIMITER ;
Код перехода:
package main
import (
"context"
"database/sql"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "<url>")
if err != nil {
panic(err)
}
db.SetConnMaxLifetime(9 * time.Second)
db.SetMaxIdleConns(12)
db.SetMaxOpenConns(12)
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Duration(3)*time.Second)
defer cancel()
_, err = db.ExecContext(ctx, "call slowInsert( 10 )") // context will cancel before insert occurs
if err != nil {
panic(err)
}
}
Мы сталкиваемся со странным поведением.
Когда мы запускаем slowInsert( 10 )
на клиенте mysql (например, сиквел про), соблюдается 10-секундное ожидание перед вставкой. У нас нет оснований полагать, что сохраненная процедура неверна или недействительна.
Когда мы запускаем приведенный выше код Go, контекст отменяется через 3 секунды — все в порядке.
... Но сразу происходит вставка. (Он не ждет 10 секунд).
Теперь, если мы изменим хранимую процедуру так, чтобы она была:
DELIMITER //
CREATE PROCEDURE slowInsert(IN t int)
BEGIN
SELECT SLEEP(t/2); // <---- The change
SELECT SLEEP(t/2); // <---- The change
INSERT INTO `table_x` (message) VALUES (UUID());
END //
DELIMITER ;
Код Go работает отлично и отменяет запрос (т.е. вставки не происходит).
В обоих случаях наш клиент mysql (sequel pro) учитывает период ожидания, поэтому обе сохраненные процедуры эквивалентны.
Но в нашем коде Go второй хранимый процесс работает, а первый нет, что наводит нас на мысль, что в драйвере есть какая-то странная ошибка.
@ алессио-палумбо
Это не ошибка. Мы пока не поддерживаем отмену запроса.
А SLEEP()
— это очень специальный запрос. Он проверяет соединение живо или нет.
В общем случае MySQL не проверяет соединение до завершения запроса.
Файл Readme: https://github.com/go-sql-driver/mysql#contextcontext-support
Go 1.8 added database/sql support for context.Context. This driver supports query timeouts and cancellation via contexts. See context support in the database/sql package for more details.
Хорошо, README вводит в заблуждение. Он просто отменяет «ожидающий» результат запроса, а не выполнение запроса.
Протокол MySQL не обеспечивает безопасный способ отмены выполнения запроса.
Некоторые CLI поддерживают отмену, но в некоторых средах это небезопасно/не стабильно.
Благодарность
Что вы должны сделать, это использовать транзакцию. Если вы не фиксируете, он откатывается.
Таким образом, тайм-аут, пока транзакция безопасна.
@andizzle @alexclifford @edwardhutchison
Почему первый storeProc вставляется после тайм-аута, а второй storeProc не вставляется. Я бы предположил, что, основываясь на вашем утверждении, он либо вставляется, либо нет. Если драйвер не поддерживает отмену выполнения запроса, почему он отменяет выполнение?
Это поведение MySQL. Я мало что знаю. Но SLEEP() имеет очень странное поведение, когда соединение закрыто.
@julienschmidt, что вы думаете об этой идее: https://github.com/src-d/gitbase-web/issues/241#issuecomment -427740194
Мы могли бы использовать
SHOW FULL PROCESSLIST
иKILL
для отправкиKILL
на правильный запрос.
К сожалению, это не может быть легко реализовано на уровне драйвера. Для этого потребуется второе соединение, на котором драйвер выполняет эти команды. Это может быть либо общее «соединение управления», либо соединения, открытые по запросу.
Прежде всего, это сделало бы драйвер намного более сложным. Сейчас вся обработка сеансов и подключений осуществляется пакетом database/sql
, а не драйвером, который просто предоставляет функции для открытия нового подключения и т.д.
Но гораздо более проблематичным является то, что у нас фактически нет возможности найти нужный сервер (если только он не один). Драйвер в основном развертывается с прокси-серверами балансировки нагрузки mysql. В этом случае, скорее всего, SHOW FULL PROCESSLIST
выполняется не на том сервере, и мы не можем найти запрос.
Сможете ли вы сделать предложение Go 2, чтобы изменить все, что когда-либо требуется для database/sql
чтобы сделать запрос действительно отменяемым?
Я искренне думаю, что проблема здесь не в database/sql
или в этом драйвере, а в самом простом MySQL, который не предоставляет простого способа отменить выполняемый запрос .
Хорошим началом было бы, если бы ответ на запросы, отправленные на сервер , содержал бы идентификатор процесса.
В случае настройки одного сервера мы могли бы затем отменить запросы аналогично Postgres : открыть новое соединение и немедленно отправить команду KILL
.
С некоторыми прокси дело обстоит сложнее. Сейчас такие прокси полностью прозрачны для водителя. Что потребуется, так это какой-то механизм, гарантирующий, что соединение будет открыто для того же бэкэнда, что и другое существующее соединение.
На самом деле у нас уже есть открытый PR для аналогичного подхода (для случая установки одного сервера): https://github.com/go-sql-driver/mysql/pull/791.
Я собираюсь отправить запрос функции с MySQL. Если я попрошу, чтобы запросы содержали идентификатор процесса, создаст ли это обратно несовместимое изменение, от которого они категорически откажутся?
Проблема не только в отмене. Есть и другие проблемы.
На земле не существует протокола «надежная отмена на удалении».
Его следует использовать только для экономии некоторых процессоров.
В любом случае, в чем суть этого вопроса?
Странного поведения не избежать путем реализации отмены.
Вы не можете знать, что запрос «отменен по контексту» выполняется или отменяется на сервере MySQL. Это вопрос времени.
Только вы можете использовать транзакцию; все запросы будут отброшены.
Если нам действительно нужна проблема для «Реализовать запрос на отмену», создайте новую проблему и закройте ее.
Вот мое решение: https://medium.com/@rocketlaunchr.cloud/canceling -mysql-in-go-827ed8f83b30
Самый полезный комментарий
К сожалению, это не может быть легко реализовано на уровне драйвера. Для этого потребуется второе соединение, на котором драйвер выполняет эти команды. Это может быть либо общее «соединение управления», либо соединения, открытые по запросу.
Прежде всего, это сделало бы драйвер намного более сложным. Сейчас вся обработка сеансов и подключений осуществляется пакетом
database/sql
, а не драйвером, который просто предоставляет функции для открытия нового подключения и т.д.Но гораздо более проблематичным является то, что у нас фактически нет возможности найти нужный сервер (если только он не один). Драйвер в основном развертывается с прокси-серверами балансировки нагрузки mysql. В этом случае, скорее всего,
SHOW FULL PROCESSLIST
выполняется не на том сервере, и мы не можем найти запрос.