回答編集履歴

2

回答を追記

2016/10/13 17:25

投稿

KiyoshiMotoki
KiyoshiMotoki

スコア4791

test CHANGED
@@ -241,3 +241,141 @@
241
241
 
242
242
 
243
243
  "3."において、INSERT が実行された`商品番号`に対する更新系のクエリだけがロックされることが確認できると思います。
244
+
245
+
246
+
247
+ # 以下追記
248
+
249
+
250
+
251
+ 私の回答のコメント欄に追記いただいた内容を鑑みるに、私の当初の回答は実現したいことにそぐわないです。
252
+
253
+
254
+
255
+ 当初の回答は
256
+
257
+ 「1回の試行で、ユーザーが選択した**ただ1つの**商品の当たり外れを判定する」
258
+
259
+ という仕様を想定したものでしたので。
260
+
261
+
262
+
263
+ ---
264
+
265
+ で、どうすれば良いかと言うと、 SELECT ... FOR UPDATE 構文を使用するのが良さそうです。
266
+
267
+
268
+
269
+ ワンクエリで全ての処理を実行できればベターではありますが、要件が複雑なため、できそうにありません。
270
+
271
+ (少なくとも、私にはどのようなSQLを書けば良いか思いつきませんorz)
272
+
273
+
274
+
275
+ 以下のようにSQLを実行すれば、`テーブルB`および`テーブルC`の`現在の当選数`が`規定値`に達していないレコードをロックしつつ、賞品の"当たり外れ"を判定するのに必要なデータを取得できます。
276
+
277
+ ```sql
278
+
279
+ BEGIN;
280
+
281
+
282
+
283
+ SELECT b.`賞品番号`, b.`規定値`, b.`現在の当選数`, c.`勝率`
284
+
285
+ FROM `テーブルB` AS b
286
+
287
+ INNER JOIN `テーブルC` AS c ON b.`賞品番号` = c.`賞品番号`
288
+
289
+ WHERE b.`規定値` > b.`現在の当選数`
290
+
291
+ FOR UPDATE;
292
+
293
+ ```
294
+
295
+
296
+
297
+ あとは
298
+
299
+ 0. (PHPなどの)アプリケーション側で"当たり外れ"の判定を行ない、
300
+
301
+ 0. その結果を各テーブルに INSERT または UPDATE していき、
302
+
303
+ 0. 最後に COMMIT
304
+
305
+
306
+
307
+ してやれば十分です。
308
+
309
+
310
+
311
+ ---
312
+
313
+ "過密アクセス"が懸念されるなら、上述の一連の操作を[ストアドプロシージャー](https://dev.mysql.com/doc/refman/5.6/ja/create-procedure.html)として定義してやれば、多少はパフォーマンスが改善するかも知れません。
314
+
315
+
316
+
317
+ ストアドプロシージャーなら 1回の通信で複数のSQL文を実行できるため、
318
+
319
+ アプリケーション <-> MySQL間の通信の往復を減らせるからです。
320
+
321
+
322
+
323
+ その場合、OUT パラメータで`賞品番号`(ハズレの場合は'0000')を返してやれば、
324
+
325
+ アプリケーション側でどの賞品が当たったのか、あるいは外れたのかを判断できます。
326
+
327
+
328
+
329
+ 別のアプローチとして、
330
+
331
+ 「"当たり外れ"の判定を非同期化してしまう」
332
+
333
+ という手もあります。
334
+
335
+
336
+
337
+ (ご質問の機能がWEBサービスの一部だと仮定して)ユーザーが"くじを引く"というボタンを押したらすぐに結果ページを表示してしまい、
338
+
339
+ 例えば「当たり」または「外れ」という文言の部分だけを、処理が完了するまで"進捗インジケータ"を表示するようにしておくのです。
340
+
341
+
342
+
343
+ たいていのユーザーは開こうとしたページがなかなか表示されないとストレスを感じますが、
344
+
345
+ このようにしておけば、数秒以内のタイムラグであればドキドキしながら(w)待ってくれるはずです。
346
+
347
+
348
+
349
+ ---
350
+
351
+ ただし、**上述の方法で保証できるのは「データの整合性」だけ**です。
352
+
353
+
354
+
355
+ 「デッドロックの防止」については、
356
+
357
+ **`テーブルA, B, C, D`(および、その他 本機能で更新系クエリを実行する全てのテーブル)にアクセスする全てのプロセスが、該当テーブルのロックを取得する順番を揃える**必要があります。
358
+
359
+
360
+
361
+ 具体的には、
362
+
363
+ 上述の方法では`テーブルB, C`のロックを取得してから、その他のテーブルのロックを取得しています。
364
+
365
+ しかし、他のプロセスが「その他のテーブル」のロックを取得してから`テーブルB, C`のロックを取得しようとすると、
366
+
367
+ デッドロックの原因となります。
368
+
369
+
370
+
371
+ デッドロックの対処については、以下のリンクが参考になるかと思います。
372
+
373
+ [https://dev.mysql.com/doc/refman/5.6/ja/innodb-deadlocks.html](https://dev.mysql.com/doc/refman/5.6/ja/innodb-deadlocks.html)
374
+
375
+
376
+
377
+ ついでに、
378
+
379
+ 以下のページを読むと SELECT ... FOR UPDATE 構文の挙動について 理解の助けになるかと思います。
380
+
381
+ [http://nippondanji.blogspot.jp/2013/12/innodbrepeatable-readlocking-read.html](http://nippondanji.blogspot.jp/2013/12/innodbrepeatable-readlocking-read.html)

1

回答を追記

2016/10/13 17:25

投稿

KiyoshiMotoki
KiyoshiMotoki

スコア4791

test CHANGED
@@ -240,4 +240,4 @@
240
240
 
241
241
 
242
242
 
243
- "3."において、更新系のクエリだけがロックされることが確認できると思います。
243
+ "3."において、INSERT が実行された`商品番号`に対する更新系のクエリだけがロックされることが確認できると思います。