MySQL
メルカリ

デッドロックおじさんの言ってる事がなんかおかしい

http://tech.mercari.com/entry/2017/12/18/deadlock
こちらを読ませていただいたのですが、色々腑に落ちないので…

事例1)出品者と購入者

メルカリはC2Cのフリマアプリですので、
お客さまは出品も購入もできます。 ここで、あるAPIの中に、

「(1)出品者の情報を更新し、(2)購入者の情報を更新する」

という処理が書かれていたとしたらどうでしょうか? 
もちろん、処理は1トランザクションの中で行われます。
以下のコードに何か問題はあるでしょうか?(制限時間5秒)

この質問だけではわからないですよね ? 後の説明を読めばわかるのですが、問題はこの操作の対象が、出品者購入者と呼んでいるものはユーザに対する個別の識別子(例えばユーザID)を元に同一のテーブル上に存在する事なはずです。

要するに例えば

CREATE DATABASE TEST;
USE TEST
CREATE TABLE mercari_test (id int primary key, seller_data text, buyer_data text);
INSERT INTO mercari_test (id) VALUES (1);
INSERT INTO mercari_test (id) VALUES (2);

という mercari_test というテーブルを作って

START TRANSACTION;
UPDATE mercari_test SET seller_data = 'hoge' WHERE id = 1;
UPDATE mercari_test SET buyer_data = 'moge' WHERE id = 2;
COMMIT;

という操作と

START TRANSACTION;
UPDATE mercari_test SET seller_data = 'hoge' WHERE id = 2;
UPDATE mercari_test SET buyer_data = 'moge' WHERE id = 1;
COMMIT;

という操作を同時に行うとデッドロックするよねと言っているのかなと思います。

そりゃそうだよね、だから ? と思ってしまっているのですが、まずそもそもこの update を二つに分けている意味がわからない(後述)のですが、その前に何かしらの避けられない理由があって分けていると仮定しても TRANSACTION 先頭で共有ロックかければ良いのでは無いかなと。

例えば前述の mercari_test の話で言えば

SELECT * FROM mercari_test WHERE id=1 OR id=2  LOCK IN SHARE MODE;

とか入れておけば良いだけなのではないの ? と思えてならないです。これであればそのトランザクションで更新されるレコードが同時にロックされるので処理順序がどうのと言った状況は起きないし、後からきた処理の前半だけが処理されロックされるということもありません。
もちろん

UPDATE
    (SELECT  1 AS buyer_id,  2 AS seller_id) AS t1
    INNER JOIN mercari_test AS buyer  ON t1.buyer_id  = buyer.id
    INNER JOIN mercari_test AS seller ON t1.seller_id = seller.id
SET
    buyer.buyer_data   = 'buyer',
    seller.seller_data = 'seller'
;

なんて書き方で一回でできるんじゃ無いの ? とも思えたりもします。

事例2)正規化に伴う複雑化

提示されている構造は transaction に対して transaction_address が 1:多 になっているものです。ただしここで問題となるのは

住所情報は引っ越しや入力ミスなどを想定して、取引の最中に変更することができます。この時、実装的には関連しそうなレコードを全部更新するようにしていました。

という理由の元に

UPDATE ... WHERE transaction_id = xxx AND type = 'src';
UPDATE ... WHERE transaction_id = xxx AND type = 'dst';
UPDATE ... WHERE transaction_id = xxx AND type = 'cvs';

という操作を行なっていることに思えます。とりあえずギャップロックするかどうかに関係なく

UPDATE ... WHERE transaction_id = xxx AND type IN ('src', 'dst', 'cvs');

で一回で終わるじゃんとも思えるのだけれど、その前後でなんか別の事をやっているかもしれずその辺の事情は見えません。でも、少なくとも提示されている情報だけであれば "なんで分けているの ?" という疑問しかわかないのです。

ところでこれ、正規化関係ありますか ?

ごめんなさい、天下のメルカリ様のエンジニアの書かれる文章にケチをつけるわけでは無いですが、どうもなんかおかしい気がしてならなくてちょっと駄文を綴ってみました。