抽選/秒殺同時の場合在庫がマイナスになる問題
2142 ワード
質問:抽選項目で、賞品の発行数が賞品プールの数より多い.操作:在庫を減らす前に在庫を照会し、その後update:
同時実行の場合、在庫を負数に減らす可能性があります(両方のプロセスが同時にselectされた場合は>0で、updateが実行されます).どうすればいいですか?
メソッド1:InnoDBは、特定の文による表示ロックをサポートします.
select...ロック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の結果に影響しません(排他ロックをかけたデータ行は他のトランザクションではデータを変更できません.for updateとlock in share modeロックでデータをクエリーすることはできませんが、select...from...で直接データをクエリーできますが、他のトランザクションが変更される前の古いデータを読み取ることができます).updateを実行すると、他のトランザクションがこのレコードをロックしている場合、updateは待機します.他のトランザクションがロックを解除するとupdateが実行されますが、quantityの数が変更された場合、updateの実行は影響行数0を返します.
≪データベースのデフォルトの独立性レベル|Database Default Isolate Level|emdw≫:繰返し可能な読取りレベル:排他ロックを追加したデータ行は、他のトランザクションではデータを変更できないし、for updateとlock in share modeロックではデータをクエリーできませんが、select...from...で直接データをクエリーできますが、他のトランザクションが変更される前の古いデータが読み込まれます.ただしupdateの更新は、最新のデータを読み出して更新する必要があります.上記のように、update文に入力されたquantityは3ですが、データベースの本当のquantityデータは3ではありません.トランザクションをクエリーするときに、再びselectを選択したときに検出された$quantityは3に等しいです.selectの読み取りは「スナップショット読み取り」であり、履歴データが読み込まれます.
参照先:https://blog.csdn.net/springfeng661/article/details/58082301
select quantity from products WHERE id=3;
update products set quantity = ($quantity-1) WHERE id=3;
同時実行の場合、在庫を負数に減らす可能性があります(両方のプロセスが同時にselectされた場合は>0で、updateが実行されます).どうすればいいですか?
メソッド1:InnoDBは、特定の文による表示ロックをサポートします.
select...ロックin share mode#共有ロック
select...for udpate#排他ロック
select quantity from products WHERE id=3 for update;/select quantity from products WHERE id=3 lock in share mode;
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の結果に影響しません(排他ロックをかけたデータ行は他のトランザクションではデータを変更できません.for updateとlock in share modeロックでデータをクエリーすることはできませんが、select...from...で直接データをクエリーできますが、他のトランザクションが変更される前の古いデータを読み取ることができます).updateを実行すると、他のトランザクションがこのレコードをロックしている場合、updateは待機します.他のトランザクションがロックを解除するとupdateが実行されますが、quantityの数が変更された場合、updateの実行は影響行数0を返します.
≪データベースのデフォルトの独立性レベル|Database Default Isolate Level|emdw≫:繰返し可能な読取りレベル:排他ロックを追加したデータ行は、他のトランザクションではデータを変更できないし、for updateとlock in share modeロックではデータをクエリーできませんが、select...from...で直接データをクエリーできますが、他のトランザクションが変更される前の古いデータが読み込まれます.ただしupdateの更新は、最新のデータを読み出して更新する必要があります.上記のように、update文に入力されたquantityは3ですが、データベースの本当のquantityデータは3ではありません.トランザクションをクエリーするときに、再びselectを選択したときに検出された$quantityは3に等しいです.selectの読み取りは「スナップショット読み取り」であり、履歴データが読み込まれます.
参照先:https://blog.csdn.net/springfeng661/article/details/58082301