Mysql: Странное поведение при отмене запроса с использованием контекста

Созданный на 2 окт. 2018  ·  19Комментарии  ·  Источник: go-sql-driver/mysql

Сервер: 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)
    }
}

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

Мы могли бы использовать SHOW FULL PROCESSLIST и KILL для отправки KILL на правильный запрос.

К сожалению, это не может быть легко реализовано на уровне драйвера. Для этого потребуется второе соединение, на котором драйвер выполняет эти команды. Это может быть либо общее «соединение управления», либо соединения, открытые по запросу.

Прежде всего, это сделало бы драйвер намного более сложным. Сейчас вся обработка сеансов и подключений осуществляется пакетом database/sql , а не драйвером, который просто предоставляет функции для открытия нового подключения и т.д.

Но гораздо более проблематичным является то, что у нас фактически нет возможности найти нужный сервер (если только он не один). Драйвер в основном развертывается с прокси-серверами балансировки нагрузки mysql. В этом случае, скорее всего, SHOW FULL PROCESSLIST выполняется не на том сервере, и мы не можем найти запрос.

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

Мы сталкиваемся со странным поведением.
Когда мы запускаем 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

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