同時在庫がマイナスに減少する問題を解決する--update 2016.04.24
3221 ワード
シーン:
1つの商品に在庫があり、注文時にまず在庫をチェックし、もし>0であれば、在庫-1を注文し、<=0であれば注文できません.トランザクションには2つのsql文が含まれています.
同時実行の場合、在庫を負数に減らす可能性があります(両方のプロセスが同時にselectされた場合は>0で、updateが実行されます).どうすればいいですか?
方法1:
InnoDBは、特定の文による表示ロックをサポートします.
select...lock in share mode
select...for udpate
しかしfor updateの実行は他の影響を及ぼします
1.select文が遅くなる
2.インデックスオーバーライドスキャンなど、いくつかの最適化が正常に使用できない
3.サーバのロック競合の原因となりやすい
方法2:
udpate文を前に書いて、まず数量-1を書いて、それからselectが在庫を出してもし>-1がcommitならば、さもなくばrollback.
上のトランザクションではupdateが先に実行されるため、id=3のローにローロックが付加され、commit/rollbackのみが解放されます(トランザクションではロックは徐々に取得されますが、いずれもcommitの場合に解放されます).同時問題をうまく解決した.
方法3:
update文は更新と同時に条件を付けます
これにより、select文はロックされていませんが、mysqlのトランザクション分離レベルは繰り返し読み取り可能であるため、他のトランザクションの変更はselectの結果に影響しません.updateを実行すると、他のトランザクションがこのレコードをロックしている場合、updateは待機し、他のトランザクションがロックを解放するまでupdateは実行されますが、quantityの数が変更されている場合、updateの実行は影響行数0を返します.
理由:
シーンは、例えば、先にselectから出た$quantity=3、それからupdateを実行する条件はid=3 and quantity=3、更新を実行して影響関数を返すのは0ですが、再びselectを実行するとid=3の記録quantityは確かに3ですね.どうしてこの記録があるのに更新できないのですか.
これは、「mysqlの事物分離レベルとMVCCのおかげで、最初のselectで$quantity=3になり、その後、他のトランザクションがこのupdateより先に実行され、updateの条件に適切な記録が見つからなかった.繰り返し可能な読み取りレベルでは、updateの読み取りは「現在の読み取り」であり、最新のデータが読み取られるためである.また、再度selectで調べた$quantityは3に等しい.selectの読み取りは「スナップショット読み取り」であり、履歴データが読み込まれるため、これも繰り返し読み取りレベルの特性である.
詳細なInnodbのトランザクション・アイソレーション・レベルは、MVCCとギャップ・ロックについて説明されている美団評価技術チームの記事を参照してください.http://tech.meituan.com/innodb-lock.html
1つの商品に在庫があり、注文時にまず在庫をチェックし、もし>0であれば、在庫-1を注文し、<=0であれば注文できません.トランザクションには2つのsql文が含まれています.
select quantity from products WHERE id=3;
update products set quantity = ($quantity-1) WHERE id=3;
同時実行の場合、在庫を負数に減らす可能性があります(両方のプロセスが同時にselectされた場合は>0で、updateが実行されます).どうすればいいですか?
方法1:
InnoDBは、特定の文による表示ロックをサポートします.
select...lock in share mode
select...for udpate
select quantity from products WHERE id=3 for update;
update products set quantity = ($quantity-1) WHERE id=3;
しかしfor updateの実行は他の影響を及ぼします
1.select文が遅くなる
2.インデックスオーバーライドスキャンなど、いくつかの最適化が正常に使用できない
3.サーバのロック競合の原因となりやすい
方法2:
udpate文を前に書いて、まず数量-1を書いて、それからselectが在庫を出してもし>-1がcommitならば、さもなくばrollback.
update products set quantity = quantity-1 WHERE id=3;
select quantity from products WHERE id=3 for update;
上のトランザクションではupdateが先に実行されるため、id=3のローにローロックが付加され、commit/rollbackのみが解放されます(トランザクションではロックは徐々に取得されますが、いずれもcommitの場合に解放されます).同時問題をうまく解決した.
方法3:
update文は更新と同時に条件を付けます
$quantity = select quantity from products WHERE id=3;
update products set quantity = ($quantity-1) WHERE id=3 and queantity = $quantity;
これにより、select文はロックされていませんが、mysqlのトランザクション分離レベルは繰り返し読み取り可能であるため、他のトランザクションの変更はselectの結果に影響しません.updateを実行すると、他のトランザクションがこのレコードをロックしている場合、updateは待機し、他のトランザクションがロックを解放するまでupdateは実行されますが、quantityの数が変更されている場合、updateの実行は影響行数0を返します.
理由:
シーンは、例えば、先にselectから出た$quantity=3、それからupdateを実行する条件はid=3 and quantity=3、更新を実行して影響関数を返すのは0ですが、再びselectを実行するとid=3の記録quantityは確かに3ですね.どうしてこの記録があるのに更新できないのですか.
これは、「mysqlの事物分離レベルとMVCCのおかげで、最初のselectで$quantity=3になり、その後、他のトランザクションがこのupdateより先に実行され、updateの条件に適切な記録が見つからなかった.繰り返し可能な読み取りレベルでは、updateの読み取りは「現在の読み取り」であり、最新のデータが読み取られるためである.また、再度selectで調べた$quantityは3に等しい.selectの読み取りは「スナップショット読み取り」であり、履歴データが読み込まれるため、これも繰り返し読み取りレベルの特性である.
詳細なInnodbのトランザクション・アイソレーション・レベルは、MVCCとギャップ・ロックについて説明されている美団評価技術チームの記事を参照してください.http://tech.meituan.com/innodb-lock.html