При анализе 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;
...
Из-за того, что инкремент версии происходит вне транзакции, некоторые гарантии, предоставляемые оптимистичной блокировкой, не выполняются. Поскольку инкремент не является атомарным относительно транзакции.