Смешивание Active Record и mini_sql приводит к непредсказуемому поведению

При анализе SQL-логов я обнаружил, что некоторые запросы не соответствуют замыслу кода из-за смешанного использования Active Record и mini_sql.

При попытке обновить некоторые объекты reviewables мы инкрементируем версию внутри транзакции, чтобы предотвратить возможные состояния гонки (как показано в примере кода ниже). Хотя на первый взгляд инкремент кажется частью транзакции, на самом деле это не так.

Здесь Reviewable.transaction — это функция Active Record, тогда как increment_version! использует mini_sql:

Active Record не знает о том, что делает mini_sql. Похоже, что Active Record использует какую-то отложенную функцию, при которой BEGIN выполняется непосредственно перед отправкой первого запроса внутри транзакции. Таким образом, версия инкрементируется сначала, а затем транзакция начинается непосредственно перед отправкой первого запроса от Active Record. Это выглядит примерно так:

UPDATE reviewables SET version = version + 1 WHERE version=version AND id = reviewable.id RETURNING version;
BEGIN;
...

Из-за того, что инкремент версии происходит вне транзакции, некоторые гарантии, предоставляемые оптимистичной блокировкой, не выполняются. Поскольку инкремент не является атомарным относительно транзакции.

4 лайка

Интересно, нам стоит научить мини-SQL информировать AR, это определённо стоит исправить.

3 лайка