2パターン示して頂いて有り難うございました。パターンAがよく咀嚼できなかったのですが、最初のインサート時にSTS(勝ち負けステータス?)を入れ籠まないのはなぜでしょうか。テーブルAは負けても結果をインサートするので、IDと当選結果とのペアをどこかに保持しておく必要があるように思えます。
SELECT ... FOR UPDATEは使用していないものの、今現在、2の段階でテーブルBとCを結合した情報をもってくじ引きをし、その結果とユーザ情報をテーブルAとDにINSERT、テーブルBに「現在の当選数」に1足したものをUPDATEしてコミットしています。
トランザクションの使用で整合性は保たれるとは思いますが、懸念点されている点は、ある程度の過密アクセスにも絶えうるシステムにするという目的のためにデッドロックの心配があることです。この辺については現在自分で勉強中ですが、非ロック参照系(SELECT ... FOR UPDATE)さえ使用すれば、その後、複数のINSERTやUPDATEをしても果たして回避できるものでしょうか。結合でSELECT ... FOR UPDATEした場合の挙動も不明です。
INSERT INTO T SELECT ... FROM S WHERE ... は、T に挿入された各行に、ギャップロックなしの排他インデックスレコードロックを設定します。トランザクション分離レベルが READ COMMITTED である場合、または innodb_locks_unsafe_for_binlog が有効になっていて、トランザクション分離レベルが SERIALIZABLE でない場合、InnoDB は一貫性読み取り (ロックなし) として S 上で検索を実行します。それ以外の場合、InnoDB は S から取得した行に共有ネクストキーロックを設定します。
若干 複雑ですが、例えば以下のようなSQL文になります。
※ 商品番号'0001'を挿入する場合
sql
1INSERTINTO`テーブルA`(`商品番号`,`日付`)2SELECT b.`商品番号`, CURDATE()3FROM`テーブルB`AS b
4LEFTOUTERJOIN`テーブルA`AS a ON b.`商品番号`= a.`商品番号`5WHERE b.`商品番号`='0001'6GROUPBY b.`商品番号`, b.`規定値`7HAVING b.`規定値`>COUNT(a.`商品番号`);
sql
1mysql>CREATETABLE`テーブルA`(2-> ID intPRIMARYKEYAUTO_INCREMENT,3->`商品番号`varchar(4)NOTNULL,4->`日付`DATENOTNULL5->);6Query OK,0rows affected (0.02 sec)78mysql>CREATETABLE`テーブルB`(9-> ID intPRIMARYKEYAUTO_INCREMENT,10->`商品番号`varchar(4)NOTNULL,11->`規定値`intNOTNULL12->);13Query OK,0rows affected (0.02 sec)1415mysql>INSERTINTO`テーブルA`(`商品番号`,`日付`)VALUES16->('0001','2016-11-02'),17->('0002','2016-11-02'),18->('0003','2016-11-03'),19->('0002','2016-11-04');20Query OK,4rows affected (0.01 sec)21Records: 4 Duplicates: 0Warnings: 02223mysql>INSERTINTO`テーブルB`(`商品番号`,`規定値`)VALUES24->('0001',2),25->('0002',2),26->('0003',2);27Query OK,3rows affected (0.00 sec)28Records: 3 Duplicates: 0Warnings: 02930mysql>SELECT*FROM`テーブルA`;31+----+--------------+------------+32| ID | 商品番号 | 日付 |33+----+--------------+------------+34|1|0001|2016-11-02|35|2|0002|2016-11-02|36|3|0003|2016-11-03|37|4|0002|2016-11-04|38+----+--------------+------------+394rowsinset(0.00 sec)4041mysql>SELECT*FROM`テーブルB`;42+----+--------------+-----------+43| ID | 商品番号 | 規定値 |44+----+--------------+-----------+45|1|0001|2|46|2|0002|2|47|3|0003|2|48+----+--------------+-----------+493rowsinset(0.00 sec)5051mysql>INSERTINTO`テーブルA`(`商品番号`,`日付`)52->SELECT b.`商品番号`, CURDATE()53->FROM`テーブルB`AS b
54->LEFTOUTERJOIN`テーブルA`AS a ON b.`商品番号`= a.`商品番号`55->WHERE b.`商品番号`='0001'56->GROUPBY b.`商品番号`, b.`規定値`57->HAVING b.`規定値`>COUNT(a.`商品番号`);58Query OK,1row affected (0.00 sec)59Records: 1 Duplicates: 0Warnings: 06061mysql>SELECT*FROM`テーブルA`;62+----+--------------+------------+63| ID | 商品番号 | 日付 |64+----+--------------+------------+65|1|0001|2016-11-02|66|2|0002|2016-11-02|67|3|0003|2016-11-03|68|4|0002|2016-11-04|69|5|0001|2016-10-11|70+----+--------------+------------+715rowsinset(0.00 sec)7273mysql>INSERTINTO`テーブルA`(`商品番号`,`日付`)74->SELECT b.`商品番号`, CURDATE()75->FROM`テーブルB`AS b
76->LEFTOUTERJOIN`テーブルA`AS a ON b.`商品番号`= a.`商品番号`77->WHERE b.`商品番号`='0001'78->GROUPBY b.`商品番号`, b.`規定値`79->HAVING b.`規定値`>COUNT(a.`商品番号`);80Query OK,0rows affected (0.00 sec)81Records: 0 Duplicates: 0Warnings: 08283mysql>SELECT*FROM`テーブルA`;84+----+--------------+------------+85| ID | 商品番号 | 日付 |86+----+--------------+------------+87|1|0001|2016-11-02|88|2|0002|2016-11-02|89|3|0003|2016-11-03|90|4|0002|2016-11-04|91|5|0001|2016-10-11|92+----+--------------+------------+935rowsinset(0.00 sec)
DROP PROCEDURE IF EXISTS ADD_TBL_A;
DELIMITER //
CREATE PROCEDURE ADD_TBL_A(IN a INT)
BEGIN
START TRANSACTION;
SET @now:=NOW();
INSERT INTO tbl_a SET no=a,dt=@now,hit=1;
SET @a:=(SELECT max FROM tbl_b WHERE no=a);
SET @b:=(SELECT COUNT(*) FROM tbl_a WHERE no=a);
IF @a<@b THEN
ROLLBACK;
ELSE
UPDATE tbl_b SET hit=@b,dt=@now WHERE no=a;
COMMIT;
END IF;
END
//
DELIMITER ;