Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

今更ながらDBのカウントアップの話

88 views

Published on

ARCANA Meetup #44
で使用したスライドです。

Published in: Internet
  • Be the first to comment

  • Be the first to like this

今更ながらDBのカウントアップの話

  1. 1. 今更ながらDBのカウントアップ の話 2018/11/22 - ARCANA Meetup #44 カツミ ユキヒロ
  2. 2. ⾃⼰紹介 所属:スタジオ・アルカナ 名前:勝⾒ 幸弘 年齢:32歳 サーバサイドメイン
  3. 3. 注意事項 • ソースは動かして確認はしているが、バージョ ンが古かったり雑なコードが含まれます。マ ネしないで下さい。
  4. 4. 例えば動画のイイネを カウントする処理
  5. 5. こんなイメージ ユーザ1 ユーザ2動画1
  6. 6. こんなイメージ ユーザ1 ユーザ2動画1 イイネ
  7. 7. こんなイメージ ユーザ1 ユーザ2動画1 イイネ イイネ数=1
  8. 8. こんなイメージ ユーザ1 ユーザ2動画1 イイネ イイネ イイネ数=1
  9. 9. こんなイメージ ユーザ1 ユーザ2動画1 イイネ イイネ イイネ数=2
  10. 10. 前提条件 • 1ユーザにつきイイネは1回のみ。
  11. 11. テーブル作成 (laravelのmigrate) public function up() { Schema::create('movies', function (Blueprint $table) { $table->increments('id'); $table->integer('vote')->default(0); }); Schema::create('users_votes', function (Blueprint $table) { $table->unsignedInteger('movie_id'); $table->unsignedInteger('user_id'); }); } public function down() { Schema::dropIfExists('movies'); Schema::dropIfExists('users_votes'); } users_votesテーブルにデータがなければ初回イイネ
  12. 12. カウント処理 (laravelのroutes/web.php) use Illuminate¥DatabaseEloquentModel; class UsersVotes extends Model { class Movie extends Model { protected $guarded = []; public $timestamps = false; } class UsersVotes extends Model { protected $guarded = []; public $timestamps = false; } Route::get('/v1/movie/{id}/{user_id}', function ($id, $user_id) { DB::beginTransaction(); $users_vote = UsersVotes::firstOrCreate(['movie_id' => $id, 'user_id' => $user_id]); if ($users_vote->wasRecentlyCreated) { Movie::firstOrCreate(['id' => $id])->increment('vote'); DB::commit(); return '初回いいね'; } DB::commit(); return '既にいいね'; }); users_votesテーブルにインサートできた時だけカウントアップ
  13. 13. /v1/movie/1/1 と /v1/movie/1/2 にアクセスする
  14. 14. 実⾏されたsql [06:55:50] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 1) limit 1 [06:55:50] insert into `users_votes` (`movie_id`, `user_id`) values (1, 1) [06:55:50] select * from `movies` where (`id` = 1) limit 1 [06:55:50] insert into `movies` (`id`) values (1) [06:55:50] update `movies` set `vote` = `vote` + 1 where `id` = 1 [06:56:31] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 2) limit 1 [06:56:31] insert into `users_votes` (`movie_id`, `user_id`) values (1, 2) [06:56:31] select * from `movies` where (`id` = 1) limit 1 [06:56:31] update `movies` set `vote` = `vote` + 1 where `id` = 1 ユーザ1のアクセス ユーザ2のアクセス
  15. 15. 実⾏されたsql ユーザ1のアクセス ユーザ2のアクセス 両⽅とも初回アクセスのためusers_votesにはinsertされる [06:55:50] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 1) limit 1 [06:55:50] insert into `users_votes` (`movie_id`, `user_id`) values (1, 1) [06:55:50] select * from `movies` where (`id` = 1) limit 1 [06:55:50] insert into `movies` (`id`) values (1) [06:55:50] update `movies` set `vote` = `vote` + 1 where `id` = 1 [06:56:31] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 2) limit 1 [06:56:31] insert into `users_votes` (`movie_id`, `user_id`) values (1, 2) [06:56:31] select * from `movies` where (`id` = 1) limit 1 [06:56:31] update `movies` set `vote` = `vote` + 1 where `id` = 1
  16. 16. 実⾏されたsql ユーザ1のアクセス ユーザ2のアクセス 動画への初回アクセスであるユーザ1の場合のみmoviesにinsertされる [06:55:50] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 1) limit 1 [06:55:50] insert into `users_votes` (`movie_id`, `user_id`) values (1, 1) [06:55:50] select * from `movies` where (`id` = 1) limit 1 [06:55:50] insert into `movies` (`id`) values (1) [06:55:50] update `movies` set `vote` = `vote` + 1 where `id` = 1 [06:56:31] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 2) limit 1 [06:56:31] insert into `users_votes` (`movie_id`, `user_id`) values (1, 2) [06:56:31] select * from `movies` where (`id` = 1) limit 1 [06:56:31] update `movies` set `vote` = `vote` + 1 where `id` = 1
  17. 17. 実⾏されたsql ユーザ1のアクセス ユーザ2のアクセス 動画1のイイネカウントアップが流れる [06:55:50] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 1) limit 1 [06:55:50] insert into `users_votes` (`movie_id`, `user_id`) values (1, 1) [06:55:50] select * from `movies` where (`id` = 1) limit 1 [06:55:50] insert into `movies` (`id`) values (1) [06:55:50] update `movies` set `vote` = `vote` + 1 where `id` = 1 [06:56:31] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 2) limit 1 [06:56:31] insert into `users_votes` (`movie_id`, `user_id`) values (1, 2) [06:56:31] select * from `movies` where (`id` = 1) limit 1 [06:56:31] update `movies` set `vote` = `vote` + 1 where `id` = 1
  18. 18. 実は問題があります
  19. 19. ユーザ1とユーザ2が同時に 動画1にアクセスした場合
  20. 20. ユーザ1とユーザ2が同時に動画 1にアクセスした場合 [09:25:30] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 2) limit 1 [09:25:30] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 1) limit 1 [09:25:30] insert into `users_votes` (`movie_id`, `user_id`) values (1, 2) [09:25:30] insert into `users_votes` (`movie_id`, `user_id`) values (1, 1) [09:25:30] select * from `movies` where (`id` = 1) limit 1 [09:25:30] select * from `movies` where (`id` = 1) limit 1 [09:25:30] insert into `movies` (`id`) values (1) [09:25:30] update `movies` set `vote` = `vote` + 1 where `id` = 1 [09:25:30] SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 'PRIMARY' (SQL: insert into `movies` (`id`) values (1)) moviesがinsertされる初回のみエラーが発⽣する
  21. 21. 何らかの理由により ユーザ1が動画1に 同時に2回アクセスした場合
  22. 22. 何らかの理由によりユーザが動画1 に同時に2回アクセスした場合のsql [1回⽬] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 1) limit 1 [2回⽬] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 1) limit 1 [1回⽬] insert into `users_votes` (`movie_id`, `user_id`) values (1, 1) [2回⽬] insert into `users_votes` (`movie_id`, `user_id`) values (1, 1) [1回⽬] select * from `movies` where (`id` = 1) limit 1 [2回⽬] select * from `movies` where (`id` = 1) limit 1 [1回⽬] insert into `movies` (`id`) values (1) [2回⽬] insert into `movies` (`id`) values (1) [1回⽬] update `movies` set `vote` = `vote` + 1 where `id` = 1 [1回⽬] update `movies` set `vote` = `vote` + 1 where `id` = 1
  23. 23. [1回⽬] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 1) limit 1 [2回⽬] select * from `users_votes` where (`movie_id` = 1 and `user_id` = 1) limit 1 [1回⽬] insert into `users_votes` (`movie_id`, `user_id`) values (1, 1) [2回⽬] insert into `users_votes` (`movie_id`, `user_id`) values (1, 1) [1回⽬] select * from `movies` where (`id` = 1) limit 1 [2回⽬] select * from `movies` where (`id` = 1) limit 1 [1回⽬] insert into `movies` (`id`) values (1) [2回⽬] insert into `movies` (`id`) values (1) [1回⽬] update `movies` set `vote` = `vote` + 1 where `id` = 1 [2回⽬] update `movies` set `vote` = `vote` + 1 where `id` = 1 insertが2回実⾏されてしまう 何らかの理由によりユーザが動画1 に同時に2回アクセスした場合のsql
  24. 24. サーバサイドだけで対応する にはどうするか? の1例。
  25. 25. テーブル作成 (laravelのmigrate) public function up() { Schema::create('v2_movies', function (Blueprint $table) { $table->increments('id'); $table->integer('vote')->default(0); }); Schema::create('v2_users_votes', function (Blueprint $table) { $table->unsignedInteger('movie_id'); $table->unsignedInteger('user_id'); $table->tinyInteger('is_update')->default(0); $table->unique(['movie_id', 'user_id']); }); } public function down() { Schema::dropIfExists('v2_movies'); Schema::dropIfExists('v2_users_votes'); } ユニークキーを張る
  26. 26. カウント処理 (laravelのroutes/web.php) class V2UsersVotes extends Model { protected $guarded = []; public $timestamps = false; } class V2Movies2 extends Model { protected $guarded = []; public $timestamps = false; } Route::get('/v2/movie/{id}/{user_id}', function ($id, $user_id) { DB::beginTransaction(); DB::statement("INSERT INTO `v2_users_votes` (`movie_id`, `user_id`) VALUES ($id, $user_id) ON DUPLICATE KEY UPDATE `is_update` = 1"); $users_vote = V2UsersVotes::where('movie_id', $id)->where('user_id', $user_id)->first(); if ($users_vote->is_update === 0) { // update の場合でも id = 1にロックがかかる。 DB::statement("INSERT INTO `v2_movies` (`id`, `vote`) VALUES ($id, 1) ON DUPLICATE KEY UPDATE `vote` = `vote` + 1"); DB::commit(); return '初回いいね'; } return '既にいいね'; }); sqlをこうして
  27. 27. カウント処理 (laravelのroutes/web.php) class V2UsersVotes extends Model { protected $guarded = []; public $timestamps = false; } class V2Movies2 extends Model { protected $guarded = []; public $timestamps = false; } Route::get('/v2/movie/{id}/{user_id}', function ($id, $user_id) { DB::beginTransaction(); DB::statement("INSERT INTO `v2_users_votes` (`movie_id`, `user_id`) VALUES ($id, $user_id) ON DUPLICATE KEY UPDATE `is_update` = 1"); $users_vote = V2UsersVotes::where('movie_id', $id)->where('user_id', $user_id)->first(); if ($users_vote->is_update === 0) { // update の場合でも id = 1にロックがかかる。 DB::statement("INSERT INTO `v2_movies` (`id`, `vote`) VALUES ($id, 1) ON DUPLICATE KEY UPDATE `vote` = `vote` + 1"); DB::commit(); return '初回いいね'; } return '既にいいね'; }); sqlをこうする
  28. 28. スライドを作成する 時間がないので残りは 実演しつつ解説

×