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.

そこそこ速くて安全なRDBの使い方

2020/06/11 社内の勉強会で発表した、主にMySQLの資料です。
SQLの書き方やINDEXの使用方法は分かったけど、「もう少しクエリの速度を速くしたい!」「安全なクエリの書き方を知りたい!」等思っている方向けに、INDEXの仕組みやトランザクションについて解説しています。

  • Be the first to comment

そこそこ速くて安全なRDBの使い方

  1. 1. confidential Mobility Technologies Co., Ltd. そこそこ速くて安全なRDBの使い方 中村遵介
  2. 2. Mobility Technologies Co., Ltd. confidential 2 今日話すこと RDB を使ったアプリケーションでは  パフォーマンス  安全性 を気にかけた開発が行われがちなので、  RDB側でパフォーマンスと安全性の点で気をつけると良いこと  アプリケーション側で安全性の点で気をつけると良いこと をMySQLとRustを例に話します ※ MySQLとRustそのものには触れません
  3. 3. confidential Mobility Technologies Co., Ltd.3 01 データベースでの速くて安全な使用方法
  4. 4. Mobility Technologies Co., Ltd. confidential 4 4. テーブル設計について(時間があれば) Agenda 1. 実行計画について 2. INDEXについて 3.トランザクションについて 5. その他(時間があれば)
  5. 5. confidential Mobility Technologies Co., Ltd. 実行計画01 SELECT文はまずこれを確認してから投げる
  6. 6. confidential Mobility Technologies Co., Ltd. MySQLのOptimizerがクエリを受け取った際に「このクエリを解決するにはこうやってやるのが 一番コストが低そうだな」と見積もる計画 少し複雑なSELECT文を実行する際は、まず実行計画を確認してどの程度DBに負荷をかけそうかを 見ておくと良い 実行計画とは
  7. 7. Mobility Technologies Co., Ltd. confidential 7 client parser optimizer executor storage handler connection handler > select … from … where … ; 接続待ち / ユーザのログイン権限check クエリの構文check / ユーザのアクセス権限check 等 Storageから統計情報取得 / クエリの実行計画決定 実行計画通りにhandlerに指示 / 後処理担当 Storageエンジンにexecutorの指示を伝播 データの実操作
  8. 8. Mobility Technologies Co., Ltd. confidential 8 データの取得方法は最適化されているか? 後処理が重くなってないか? クエリの速度面でまず関心があるのはこの2つ これらは optimizer の実行計画から知ることができる client parser optimizer executor storage handler connection handler > select … from … where … ;
  9. 9. confidential Mobility Technologies Co., Ltd.  Optimizer はクエリとテーブル定義とデータの統計情報を元に、 そのクエリをどのように実行すれば最も高速かを計画を立てる  高速かどうかの判定は、複数の計画について実行時のコストを見積もることで判定している 実行計画の作成方法 optimizer クエリ テーブル定義 データ統計情報 実行計画A コスト5 実行計画B コスト3 実行計画C コスト8 実行計画B
  10. 10. confidential Mobility Technologies Co., Ltd. あるクエリ Q に対する optimizer の実行計画は EXPLAIN Q で見ることができる 実行計画の取得方法 id account password … 0 taro b99aef694a43… … 1 hanako 6fadd70764e9… … … … … … SELECT * FROM users WHERE account = “hanako”; EXPLAIN SELECT * FROM users WHERE account = “hanako”; 1. account が”hanako”の行を取得するクエリ 例: 何らかのサービスのユーザテーブル users 2. このクエリの実行計画は以下で取得できる Id select_ type table partitions type possible_ keys key key_len ref rows filtered Extra 1 SIMPLE users NULL const account_ UNIQUE account_ UNIQUE 62 const 1 100.0 NULL 実行計画
  11. 11. confidential Mobility Technologies Co., Ltd. 実行計画の読み方  id : 識別子  select_type : select の種類。12種類の中のどれかになる  table : どのテーブルから select するか  partitions : (パーティションで区切られている場合)どのパーティションから取得するか  type : 結合の型。非常に重要  possible_keys: クエリ解決のために使用できる index。重要  key : この実行計画で使用することにした index。非常に重要  key_len : 使用するインデックスの長さ。小さい程よい  ref : 使用するインデックスに対して比較されるカラム  rows : クエリ実行にあたってフェッチする必要のある行数の見積もり。小さい程よい  filtered : rowsに対して、フィルタ処理で通過する割合の見積もり。100に近い程よい  extra : 追加情報。非常に重要
  12. 12. confidential Mobility Technologies Co., Ltd.  type 結合の型。スキャンの方法とほぼ同義と思って良い。 実行計画の読み方 type: よく見るもの const Primary key もしくは unique index に対する定数の等価比較(=)を示す。最速。 取得された値は以降定数として扱われる。 eq_ref Primary key もしくは unique index に対する定数の等価比較を示す。 const に近いが、joinの内部表に対して用いられる。最速。 ref 非 unique な index に対する定数の等価比較を示す。 取得されるレコードは複数あって良い。Index の凝集度によって速度が変化する。 range Index に対する範囲比較(>, <, <=>, IS NULL, BETWEEN, LIKE, IN)を示す。 Index の凝集度や range scan の可/不可によって速度が変化する。 index Indexツリーに対する全スキャンを示す。 Extraカラムに using index が出ているならマシだが基本的には避けたい。 all Index が使用されないテーブルフルスキャンを示す。 よほどのことがない限り避けたい。
  13. 13. confidential Mobility Technologies Co., Ltd.  extra 実行計画の追加情報 実行計画の読み方 extra: よく見るもの using where 検索条件に対し、index だけで解決できなかったことを示す。 Executor が取得されたレコードを検査して条件外のものを除外している。 遅いが仕方のないこともある。 using filesort Order / Group by で指定されたカラムに対し index ではソートできないことを示す。 Executorが代わりにクイックソートを実行するので遅い。 using temporary 一時結果を保持するためのテンポラリテーブルを作成することを示す。 場合によってはディスク上で作成され、非常に遅くなる事がある。 using index Index ツリーの走査だけでクエリを解決できることを示す。 Index はメモリに乗っていることが多いので基本的に速い。 using index for group-by group by & min/maxを使用しているときに、Index ツリーの走査だけで クエリを解決できることを示す。Index の左端、右端にアクセスすれば良い。 Index はメモリに乗っていることが多いので基本的に速い。 using index condition インデックスコンディションプッシュダウン最適化が行われる。 Using index よりは遅いが、可能な限りindexでfilterしてくれるためそこそこ速い。
  14. 14. confidential Mobility Technologies Co., Ltd. INDEX02 15 常に最適なアクセスをするために
  15. 15. confidential Mobility Technologies Co., Ltd. INDEX とは RDBMSのデータを辞書に例えるとするなら、INDEX は索引である。 ただし、RDBMSは辞書とは違い、データをあいうえお順ではなく大体の作成日時順で格納する。 そのため、DBから「自動車」を検索するには全ページを順に見ていって探す必要がある。 しかしもし索引(INDEX)があれば、自動車は120ページに乗っている事がわかるので、 全ページアクセスの必要がなくなる。 また、「自動」で始まる単語の検索も、索引があれば該当ページだけの最小限の検索で済む。 逆に、「車」で終わる単語の検索は、索引からページ数を辿るのは難しく、INDEX は意味がない。 自動車バイク 自動詞飛行機 汽車 119 120 121 201 202 RDBMSで格納されているデータ 索引(INDEX) か行-き 汽車 202 さ行-し 自動詞 201 自動車 120 は行-は バイク 119 は行-ひ 飛行機 121
  16. 16. Mobility Technologies Co., Ltd. confidential 17 条件をどうINDEXを使って解決するか決める INDEXで解決できなかった条件はexecutorが 後処理として解決する client parser optimizer executor storage handler connection handler INDEXを使用して条件にマッチする可能性がある レコードを探索してexecutorに返す
  17. 17. confidential Mobility Technologies Co., Ltd.  あるカラムをもとにINDEXを作成できる 例)(アカウント名)カラムでINDEXを作成 ⇒ アカウント名での検索が高速化される  複数のカラムをもとにINDEXを作成できる 例)(性別, 年齢)カラムでINDEXを作成 ⇒ 性別=“男”, 年齢>=20のような検索が高速化される ⇒ 性別=“男”のような検索が高速化される ⇒ 年齢>=20のような検索は高速化されない INDEXの作成 18 ユーザID アカウント名 スクリーン名 年齢 性別 作成時間 1 aaa ほげ 10 男 2020/04/01 00:00:00 1 bbb ぴよ 20 女 2020/05/01 00:00:00 2 ccc ほげ 30 男 2020/06/01 00:00:00
  18. 18. confidential Mobility Technologies Co., Ltd. INDEXの構造  INDEX の実態は指定されたカラムでソートされているB+木 1. 例) ユーザテーブルの name カラムに INDEX を貼っているケース データそのものを保持すると、メモリ消費量が激しくなるため Innodb ではリーフノードは対応するレコードの ID(主キー)を格納している* Node 3 Alice Bob id: 20 id: 5 Cecily id: 9 Node 4 Cindy David id: 21 id: 33 Laura id: 2 Node 5 Nick Olive id: 18 id: 7 Sophia id: 15 Node 6 Tom Wendy id: 10 id: 35 Yoda id: 1 Node 0 Alice Nick Node 1 Node 2 Node 1 Alice Cindy Node 3 Node 4 Page 2 Nick Tom Node 5 Node 6 name INDEX
  19. 19. confidential Mobility Technologies Co., Ltd. INDEXの構造  INDEX の実態は指定されたカラムでソートされているB+木 1. 例) ユーザテーブルの name カラムに INDEX を貼っているケース IDが 7 のレコードが name = olive のレコード 実データの検索はこのレコードだけアクセスすれば良い Node 3 Alice Bob id: 20 id: 5 Cecily id: 9 Node 4 Cindy David id: 21 id: 33 Laura id: 2 Node 5 Nick Olive id: 18 id: 7 Sophia id: 15 Node 6 Tom Wendy id: 10 id: 35 Yoda id: 1 Node 0 Alice Nick Node 1 Node 2 Node 1 Alice Cindy Node 3 Node 4 Node 2 Nick Tom Node 5 Node 6 name = ‘olive’
  20. 20. confidential Mobility Technologies Co., Ltd. INDEXの構造  INDEX の実態は指定されたカラムでソートされているB+木 1. 例) ユーザテーブルの name カラムに INDEX を貼っているケース IDが 10, 15, 7であるレコードが対象のレコード INDEX のおかげで sort 処理をすることなく、ORDER BY name DESC を満たせる Node 3 Alice Bob id: 20 id: 5 Cecily id: 9 Node 4 Cindy David id: 21 id: 33 Laura id: 2 Node 5 Nick Olive id: 18 id: 7 Sophia id: 15 Node 6 Tom Wendy id: 10 id: 35 Yoda id: 1 Node 0 Alice Nick Node 1 Node 2 Node 1 Alice Cindy Node 3 Node 4 Node 2 Nick Tom Node 5 Node 6 name >= ‘o’ AND name < ‘u’ ORDER BY name DESC
  21. 21. confidential Mobility Technologies Co., Ltd. Indexは単一のカラムに対して作成できるだけでなく、複数のカラムでまとめてIndexを作成する ことも出来る Multi column index Node 3 Alice 21 Alice 34 id: 20 id: 5 Bob 19 id: 9 Node 4 Bob 21 Bob 50 id: 21 id: 33 Cindy 37 id: 2 Node 5 David 35 David 40 id: 18 id: 7 David 41 id: 15 Node 6 Laura 30 Nick 18 id: 10 id: 35 Nick 29 id: 1 Node 0 Alice 21 David 35 Node 1 Node 2 Node 1 Alice 21 Bob 21 Node 3 Node 4 Node 2 David 35 Laura 30 Node 5 Node 6 この場合、name = David and age > 40 のような検索ではINDEXで最小アクセスが可能 一方、name >= David and age > 40になるとINDEXを使用しても最小アクセスにならない さらに、age > 40になるとINDEXは使用できない name, ageでINDEXを作成した場合
  22. 22. confidential Mobility Technologies Co., Ltd. Indexの目的は、索引の力によって  ストレージにアクセスする数を最小限にする  Executorにフィルタ処理をさせない ことにある また、効果としては、Unique indexや重複が少ないカラムに貼られたindexが最も効果を発揮する 逆に、フラグカラムに貼られたindexはあまり意味がない。 例えば、患者の情報を登録するテーブルに性別カラムを設けたとして、そのカラムだけにindexを 作成しても、ストレージへのアクセスは半分にしかならない(し、更に言うならindexがなければ テーブルフルスキャンでシーケンシャルアクセスだったところを、ランダムアクセスになる分遅 くなるかもしれない) Indexの効果
  23. 23. confidential Mobility Technologies Co., Ltd. トランザクション03 データ処理を正しく行うために
  24. 24. confidential Mobility Technologies Co., Ltd. 1つ以上のクエリを1まとまりとして扱う処理単位 ここでいう1まとまりとは、 トランザクション内の全クエリは、「全て実行される」か「全て実行されない」かしかない ということを意味する 多くのRDBMSはトランザクションに関してACIDモデルと呼ばれる思想で実装されている トランザクションとは
  25. 25. confidential Mobility Technologies Co., Ltd. RDBMSでは多くがACIDモデルに基づいたデータ操作を行う。ACIDとは  Atomicity(原子性) トランザクション内の操作が全て成功するか、全て失敗するかしかない  Consistency(一貫性) データベース内のデータに矛盾がない  Isolation(分離性) トランザクション中の操作は他のトランザクションの操作に影響を与えない  Durability(永続性) 成功したトランザクションの結果が失われない なお、これらは実際には完璧には守られていません。原子性と一貫性はほとんど守られている気がしますが、 分離性は並列処理のためにある程度諦めることが多いですし、永続性はハードディスクを破壊すれば終わりです。 ACIDモデル
  26. 26. confidential Mobility Technologies Co., Ltd. トランザクション内の操作が「全て成功する」か、「全て失敗する」かしかない 例)物販サイトでユーザAが手持ちのポイントを使って品物Xを購入する手続き Atomicity(原子性) ユーザAの品物X購入処理開始 ユーザAのポイントから品物Xの値段を引く 品物Xの在庫数から1を引く ユーザAの購入履歴に品物Xを追加 処理終了 売上記録に品物Xを追加
  27. 27. confidential Mobility Technologies Co., Ltd. トランザクション内の操作が「全て成功する」か、「全て失敗する」かしかない 例)物販サイトでユーザAが手持ちのポイントを使って品物Xを購入する手続き Atomicity(原子性) ← ポイントが足りずに購入手続きに失敗する可能性 ← 在庫不足で購入手続きに失敗する可能性 ユーザAの品物X購入処理開始 ユーザAのポイントから品物Xの値段を引く 品物Xの在庫数から1を引く ユーザAの購入履歴に品物Xを追加 処理終了 売上記録に品物Xを追加
  28. 28. confidential Mobility Technologies Co., Ltd. トランザクション内の操作が「全て成功する」か、「全て失敗する」かしかない 例)物販サイトでユーザAが手持ちのポイントを使って品物Xを購入する手続き Atomicity(原子性) ポイント減算処理は成功したが、在庫不足で購入に失敗した場合 ユーザAのポイントはただ消費されることに… これら一連の処理はひとまとまりにしたい → トランザクション ← ポイントが足りずに購入手続きに失敗する可能性 ← 在庫不足で購入手続きに失敗する可能性 ユーザAの品物X購入処理開始 ユーザAのポイントから品物Xの値段を引く 品物Xの在庫数から1を引く ユーザAの購入履歴に品物Xを追加 処理終了 売上記録に品物Xを追加
  29. 29. confidential Mobility Technologies Co., Ltd. トランザクション内の操作が「全て成功する」か、「全て失敗する」かしかない 例)物販サイトでユーザAが手持ちのポイントを使って品物Xを購入する手続き Atomicity(原子性) ユーザAの品物X購入処理開始 ユーザAのポイントから品物Xの値段を引く 品物Xの在庫数から1を引く ユーザAの購入履歴に品物Xを追加 処理終了 COMMIT命令は • トランザクション内の処理を永続的なものにする • 他トランザクションから結果を見えるようにする という意味を持つ BEGIN COMMIT / ROLLBACK BEGIN命令はトランザクションの開始を示す ただし、実際にトランザクションIDが発行されるのは BEGINの次にクエリが初めて実行されるタイミング (いくつかの例外クエリを除く) ROLLBACK命令はトランザクション内の処理を すべて破棄することを意味する 売上記録に品物Xを追加 アプリケーションからBEGIN-COMMIT/ROLLBACKを 行うのは大抵明示的に命令を与える必要がある
  30. 30. confidential Mobility Technologies Co., Ltd. あるトランザクション内の処理が同時に実行されている他のトランザクションに影響を与えないこと これを守るには変更があるようなトランザクションを全て直列実行するしかないが、 isolationにレベルを設けることで分離性とパフォーマンスのトレードオフを行うRDBMSが多い ※ MySQLは後述するMVCCという仕組みによって REPEATBLE READでも、COMMITされた他のトランザクションの追加を参照できないようにしている Isolation(分離性) Isolation level 内容 READ UNCOMMITTED COMMITされていない他のトランザクションの変更を参照できる READ COMMITTED COMMITされた他のトランザクションの変更を参照できる REPEATABLE READ COMMITされた他のトランザクションの追加を参照できる※ SERIALIZABLE 全てのトランザクションが直列実行されているのと等価になる
  31. 31. confidential Mobility Technologies Co., Ltd.  ユーザAが品物Xを購入中に、今日の売上記録の集計処理が走ったとする Isolation levelによる問題 - READ UNCOMMITTED - ユーザAの品物X購入処理開始 ユーザAのポイントから品物Xの値段を引く 品物Xの在庫数から1を引く ユーザAの購入履歴に品物Xを追加 → 失敗! 処理終了 BEGIN ROLLBACK 売上記録に品物Xを追加 今日の売上記録の集計処理開始 レポートテーブルに集計結果を記録 今日の売上記録の集計を算出 処理終了 BEGIN COMMIT 実際には売られていない品物Xを 売上記録の集計に入れてしまっている! Dirty readと呼ばれる
  32. 32. confidential Mobility Technologies Co., Ltd.  ユーザAが品物Xを購入中に、ユーザAのポイントを複数回参照する処理が走ったとする Isolation levelによる問題 - READ COMMITTED - ユーザAの品物X購入処理開始 ユーザAのポイントから品物Xの値段を引く 200P→100P 品物Xの在庫数から1を引く ユーザAの購入履歴に品物Xを追加 処理終了 BEGIN COMMIT 売上記録に品物Xを追加 なんらかの集計処理開始 ユーザAのポイントを参照 ユーザAのポイントを参照 BEGIN 左の処理は未COMMITなので、200Pが 取得される COMMITされた変更を取得してしまい、 100Pを得る。先程の結果と矛盾する Non-repeatable readと呼ばれる
  33. 33. confidential Mobility Technologies Co., Ltd.  ユーザAが品物Xを購入中に、今日の購入履歴を複数回参照する処理が走ったとする Isolation levelによる問題 - REPEATABLE READ - ユーザAの品物X購入処理開始 ユーザAのポイントから品物Xの値段を引く 200P→100P 品物Xの在庫数から1を引く ユーザAの購入履歴に品物Xを追加 処理終了 BEGIN COMMIT 売上記録に品物Xを追加 なんらかの集計処理開始 今日の売上記録を全件取得 今日の売上記録を全件取得 BEGIN 左の処理は未COMMITなので、取得されない 先程存在しなかった品物Xの売上履歴が 追加される Phantom readと呼ばれる
  34. 34. confidential Mobility Technologies Co., Ltd.  MySQLではMulti-Version Concurrency Control(MVCC)によって、前述のような isolation問題を解決している  正確にはMySQLがデフォルトで採用しているinnodbストレージエンジンによる機能  トランザクションが開始された瞬間にcommitされていたデータのみを参照できるというもの MySQLでの解決方法 TRX A TRX B TRX C TRX D TRX Dは、開始時にcommitされていたデータのみを参照するため、 TRX A, TRX Cによる追加・変更・削除を参照することはない A commit C commitB commit
  35. 35. confidential Mobility Technologies Co., Ltd.  ユーザAが品物Xを購入中に、今日の購入履歴を複数回参照する処理が走ったとする MVCCによるトランザクション例 ユーザAの品物X購入処理開始 ユーザAのポイントから品物Xの値段を引く 200P→100P 品物Xの在庫数から1を引く ユーザAの購入履歴に品物Xを追加 処理終了 BEGIN COMMIT 売上記録に品物Xを追加 なんらかの集計処理開始 今日の売上記録を全件取得 今日の売上記録を全件取得 BEGIN 左の処理は未COMMITなので、取得されない このトランザクションの開始時点で左の トランザクションはCOMMITされてい ないのでやはり取得されない
  36. 36. confidential Mobility Technologies Co., Ltd. テーブル内の各レコードには隠しカラムが存在し、  DB_ROW_ID : Primary keyが作成されなかった場合に自動で付く  DB_TRX_ID : そのレコードが最後に作成・変更されたトランザクションID  DB_ROLL_PTR : そのレコードの過去の状態を保存する場所へのポインタ  DELETION_MARK : そのレコードが削除予定であるか否か Update / Insert / Delete は DB_TRX_IDを自身のトランザクションIDで上書きする これにより、どのレコードがどのタイミングで変更されたかが追跡できるので、 SELECTは自身のトランザクションIDとDB_TRX_IDを比較することで、読み取って良いデータか、もしくは DB_ROLL_PTRから過去のデータを引っ張ってくる必要があるかを判断する この方法の最大の利点はSELECT時にレコードへのロックを必要としないこと MVCCの仕組み
  37. 37. confidential Mobility Technologies Co., Ltd.  MySQLはトランザクション内でロックを取得するケースが有るが、トランザクションの終了時 に必ずロックは開放される  ロックの種類  共有ロック 複数トランザクションからかけることが出来るが、他のトランザクションからの書き込みを 拒絶する  排他ロック 他のトランザクションからの共有・排他ロックを拒絶する  ロックを取得するとき  Update / Insert / Delete 対象行に排他ロックを取得する(自分以外のトランザクションから完全に遮断)  SELECT FOR UPDATE / LOCK IN SHARE MODE それぞれ対象行に排他ロック / 共有ロックをかけ、MVCCを完全に無視してその瞬間に最新 のデータを取得してロックする MySQLにおけるロック
  38. 38. confidential Mobility Technologies Co., Ltd.  Dirty read / non-repeatable read / phantom read は防げるが、ロストアップデートを防げないと いう問題がある  ロストアップデート… 後から変更したほうが勝つ 例)Aさんが5000円の口座から2000円引き落としている時にBさんが1000円送金してきたとする MVCCの弱点 Aさんの引き落とし処理 Aさんの口座金額を取得 → 5000円 Aさんの口座金額を3000円にアップデート 処理終了 BEGIN COMMIT Aさんへの送金処理 Aさんの口座金額を取得 → 5000円 Aさんの口座金額を6000円にアップデート COMMIT…排他ロックの解放待ち 処理終了 BEGIN これらの処理が終わった後のAさんの口座残高は 3000円になる(送金処理がロストする)
  39. 39. confidential Mobility Technologies Co., Ltd. 解決方法1: SELECTせずに直接変更する Aさんの口座金額に関してアプリケーション側で何らかの操作を必要としないならこれが最良 どちらのupdateも排他ロックを要求するため、Aさんの口座金額への変更処理は直列になり、 ロストアップデートが起きない ロストアップデートの解決方法 Aさんの引き落とし処理 Aさんの口座金額を-2000する UPDATE kouza SET money = money – 2000 WHERE id = ‘A’; 処理終了 BEGIN Aさんへの送金処理 Aさんの口座金額を+1000する UPDATE kouza SET money = money + 1000 WHERE id = ‘A’; BEGIN COMMIT 処理終了 COMMIT
  40. 40. confidential Mobility Technologies Co., Ltd. 解決方法2: UPDATEのタイミングで変更の有無を確認する楽観ロックを行う UPDATE時以外にロックを取得する必要がないが、片方の処理は失敗する点に注意 ロストアップデートの解決方法 Aさんの引き落とし処理 処理終了 BEGIN Aさんへの送金処理 BEGIN COMMIT 処理終了 COMMIT Aさんの口座金額を取得 → 5000円 口座金額が5000円であれば Aさんの口座金額を3000円にアップデート UPDATE … WHERE id = ‘A’ AND money = 5000; Aさんの口座金額を取得 → 5000円 口座金額が5000円であれば Aさんの口座金額を6000円にアップデート UPDATE … WHERE id = ‘A’ AND money = 5000; 最後のUPDATEでの変更件数が1か0かで処理の成否を判断する
  41. 41. confidential Mobility Technologies Co., Ltd. 解決方法3: SELECTのタイミングでロックを取得する悲観ロックを行う トランザクション内で複数テーブルのロックを取得するときはデッドロックに注意 ロストアップデートの解決方法 Aさんの引き落とし処理 処理終了 BEGIN Aさんへの送金処理 BEGIN COMMIT 処理終了 COMMIT Aさんの口座金額を取得&ロック → 5000円 SELECT FOR UPDATE Aさんの口座金額を3000円にアップデート Aさんの口座金額を取得&ロック 左の処理のロックが開放されるまで待つ → 3000円が取得される (ロックを伴うSELECTはMVCCを無視する) Aさんの口座金額を4000円にアップデート
  42. 42. confidential Mobility Technologies Co., Ltd. テーブル設計04 43 最初が全て
  43. 43. confidential Mobility Technologies Co., Ltd. テーブル設計のリファクタ MySQLでテーブル設計を変更する際、以下のケースではオンラインDDLで 高速なノンブロッキング処理が行われる  INDEXの作成と削除  外部キー制約の作成と削除  カラム名の変更  カラムの作成と削除(テーブルコピーは発生するしコストも大きい) 他にもいくつかあるが、 ALTER TABLE … ALGORITHM=INPLACE, LOCK=NONE; を試してみて、 エラーになるようなものはオンラインDDLが適用できない なので、ある程度は柔軟に変更できるが、プログラム側の変更は難しいという問題が常に残る 誰がいつどこでどんなクエリを発行するかを追跡するのは容易ではないので、テーブル構造の 変更はかなりコストが高くなってしまう → なるべく最初に良い設計をしておく
  44. 44. confidential Mobility Technologies Co., Ltd. 上から順に大切だと思っています  データは1箇所だけに保存されている  結合関係が明らかである(外部キー制約が設定されている)  1対1、1対多、多対多がテーブルとして定義されている  Primary keyのサイズが小さい  可能な限りNULLが排除されている(諸説あり) → 程よく正規化されている 良いテーブル設計とは(個人的感想)
  45. 45. confidential Mobility Technologies Co., Ltd. データは1箇所だけに保存されている、という状態を作り出すための指針 レベルに応じて名前がついている 第一正規形 … すべてのカラムの値がスカラ値である(さようならJSON型) 第二正規形 … ある非キー属性の値が候補キーの一部によって定まることがない状態 第三正規形 … ある非キー属性の値が他の非キー属性によって定まることがない状態 BC正規系 … ある候補キーの値が非キー属性によって定まることがない状態 第四正規形 … 非キー属性への多値従属性があってはならない 第五正規形 … 第四正規形まで満たして第五正規形は満たさない、はほぼないので割愛 正規化とは
  46. 46. confidential Mobility Technologies Co., Ltd. ある非キー属性の値が候補キーの一部によって定まることがない状態  候補キーとはPrimary( Unique )keyのような、「これらで検索すれば1行だけになるカラム」  非キー属性とは、「候補キーではないカラム」 すなわち、Primary(Unique)keyの一部の値から分かってしまう値があってはいけない 例: 社員の配属情報テーブル 第二正規形 社員ID 名前 部署ID 部署名 配属日 1 AAA 10 営業 2019/04/01 1 AAA 11 開発 2020/04/01 2 BBB 11 開発 2020/04/01
  47. 47. confidential Mobility Technologies Co., Ltd. ある非キー属性の値が候補キー(の一部)によって定まることがない状態  候補キーとはPrimary( Unique )keyのような、「これらで検索すれば1行だけになるカラム」  非キー属性とは、「候補キーではないカラム」 すなわち、Primary(Unique)keyの一部の値から分かってしまう値(従属している値)が あってはいけない 例: 社員の配属情報テーブル 第二正規形 社員ID 名前 部署ID 部署名 配属日 1 AAA 10 営業 2019/04/01 1 AAA 11 開発 2020/04/01 2 BBB 11 開発 2020/04/01 候補キーは(社員ID, 配属日)。(社員ID, 部署ID)は、社員が複数回同じ部署に配属される可能性があるのでNG ここで、社員の名前は、社員IDから定まる(社員IDに従属している)ため、第二正規形を満たしていない
  48. 48. confidential Mobility Technologies Co., Ltd. 解決方法: テーブルの分割 第二正規形 社員ID 部署ID 部署名 配属日 1 10 営業 2019/04/01 1 11 開発 2020/04/01 2 11 開発 2020/04/01 社員ID 名前 1 AAA 2 BBB 社員の配属情報テーブル 社員テーブル 社員ID 名前 部署ID 部署名 配属日 1 AAA 10 営業 2019/04/01 1 AAA 11 開発 2020/04/01 2 BBB 11 開発 2020/04/01 社員の配属情報テーブル 社員IDに対する名前の情報が1箇所になった(元だと社員ID 1の名前が2箇所に保存されている)
  49. 49. confidential Mobility Technologies Co., Ltd. ある非キー属性の値が他の非キー属性によって定まることがない状態 すなわち、Primary(Unique)key以外のカラム同士で従属している値があってはならない 第三正規形 社員ID 部署ID 部署名 配属日 1 10 営業 2019/04/01 1 11 開発 2020/04/01 2 11 開発 2020/04/01 社員ID 名前 1 AAA 2 BBB 社員の配属情報テーブル 社員テーブル
  50. 50. confidential Mobility Technologies Co., Ltd. ある非キー属性の値が他の非キー属性によって定まることがない状態 すなわち、Primary(Unique)key以外のカラム同士で従属している値があってはならない 第三正規形 社員ID 部署ID 部署名 配属日 1 10 営業 2019/04/01 1 11 開発 2020/04/01 2 11 開発 2020/04/01 社員ID 名前 1 AAA 2 BBB 社員の配属情報テーブル 社員テーブル 部署名は部署IDに従属している!!
  51. 51. confidential Mobility Technologies Co., Ltd. 解決方法: テーブルの分割 第三正規形 部署IDに対する名前が1箇所に保存されるようになった → 社員の名前変更は社員テーブルを、部署の名前変更は部署テーブルを変更すればよく、他に影響しない 社員ID 部署ID 部署名 配属日 1 10 営業 2019/04/01 1 11 開発 2020/04/01 2 11 開発 2020/04/01 社員ID 名前 1 AAA 2 BBB 社員の配属情報テーブル 社員テーブル 社員ID 部署ID 配属日 1 10 2019/04/01 1 11 2020/04/01 2 11 2020/04/01 社員ID 名前 1 AAA 2 BBB 社員の配属情報テーブル 社員テーブル 部署ID 部署名 10 営業 11 開発 部署テーブル
  52. 52. confidential Mobility Technologies Co., Ltd.  テーブルは「エンティティ」と「リレーション」のみにする  エンティティ…ある物体を表す1まとまりの最小限の構成  リレーション…エンティティ同士の関連(リレーショナルモデルのリレーションとは違う) 先程の例だと、「社員」と「部署」がエンティティで、「配属」がリレーション リレーションテーブルは、自然言語に直すと「AはB」「AはBに属する」「BはAに属する」に なるはずで、「aというAはbというBに属する」のような表現になったら分割を考える (桑原というID1の社員は開発というID11の部署に属する)  エンティティは本当にその物体と切り離せないものなのかを考慮する 社員テーブルに社員メールアドレスをカラムとして保存する前に、本当にそれは一体かを考慮 メールアドレステーブルに社員IDとメールアドレスをペアで保存するほうが良いケースも  NULL/フラグカラムが出てくる時もテーブル分割を考慮する ユーザの端末IDを保存する際、iOS用にIDFVカラムと、android用にandroid_idカラムを追加し、 片方をNULLにするのか、最初からiOSユーザとandroidユーザのテーブルを分けるのか テーブル設計のコツ
  53. 53. confidential Mobility Technologies Co., Ltd. 雑多05 まとめきれなかった豆知識
  54. 54. confidential Mobility Technologies Co., Ltd. 豆知識 index編 • MySQLの評価順はWHERE -> GROUP BY → HAVING → ORDER BY → LIMIT 基本的にWHERE / GROUP BY / ORDER BYがindexで解決できるようにしておくと高速 • WHERE / GROUP BY / ORDER BYに使用しないカラムを敢えてindexにいれることがある 例えば、SELECT a, b, c FROM T WHERE a = “hoge” ORDER BY b; というクエリでは(a, b)というマルチカラムインデックスを貼るのが良いが、 (a, b, c)というindexを貼っておくと、ストレージにアクセスする必要が消える ORMだとselect_all系のクエリが発行されがちなのでこういうことはしづらいが、クエリを 直書きするような実装にすると細かな調整は可能 • テーブルの行数が少ないと、indexを使用するよりフルスキャンしたほうが低コストと 判断されることもある。いずれにせよ多少複雑なクエリを投げる時は実行計画を確認する • Primary keyはサイズが小さい(int系)の方が良い。Indexのleaf nodeに利用されるため、 サイズが大きいとindexが肥大する
  55. 55. confidential Mobility Technologies Co., Ltd.  動的にクエリを作る際は、必ずプリペアードステートメントを利用して、SQLインジェクション を防ぐ  MySQLは暗黙の型変換があるので文字列型の比較に数字を突っ込むのはNG MySQL セキュリティ編
  56. 56. confidential Mobility Technologies Co., Ltd. 豆知識 insert編 複数レコードをinsertするときは、for文でinsertを回さず、bulk insertを使用する 近年のORMはほとんどbulk insertに対応している クエリ直書きをする際は、インジェクションに十分留意する必要がある これに限らず、基本的にfor文でクエリを投げるのはパフォーマンスが悪い 主キーやユニークなINDEXへのJOINは十分高速なので、JOINを使用するべき
  57. 57. confidential Mobility Technologies Co., Ltd. 豆知識 CLI編 • CLI上で、数レコードだけ取得するクエリを投げる時は末尾を;ではなくGにすると見やすい • 取り敢えずこんなselectできるかな? を試す時はlimit 3をつける • GROUP BYとGROUP CONCATを使うと指定した区切り文字で一つにつなげてくれる
  58. 58. confidential Mobility Technologies Co., Ltd. 豆知識 ログ編 • スロークエリログはデフォルトでオフなのでオンにしておくと後で困らない [mysqld] slow_query_log=1 # ログの有効化 long_query_time=1 # 1秒以上かかっているクエリをロギング log_queries_not_using_indexes=1 # indexを使用していないクエリをロギング slow_query_log_file=/usr/local/var/mysql/slow_query.log # 出力先指定 • エラーログも同様 [mysqld] log-error= /usr/local/var/mysql/ error.log # 出力先指定 • 詳細ログも動揺 [mysqld] general_log=1 # ログの有効化 general_log_file=/ usr/local/var/mysql /general.log # 出力先指定
  59. 59. confidential 文章·画像等の内容の無断転載及び複製等の行為はご遠慮ください。 Mobility Technologies Co., Ltd. 60
  60. 60. confidential Mobility Technologies Co., Ltd.61 02 アプリケーションからの安全な使用方法
  61. 61. confidential Mobility Technologies Co., Ltd. 静的な型システムを持つコンパイラ言語 実行時の安全性・高速性・並列性に優れている というわけでアプリケーションの例としてRustを使用した場合のDBとの安全なやり取りについて話します が、Pythonであってもtypescriptであってもここらへんの話は変わらないと思います Rust とは
  62. 62. confidential Mobility Technologies Co., Ltd. Rustからの利用方法01 どうすれば安全な問い合わせができるのか
  63. 63. confidential Mobility Technologies Co., Ltd. アプリケーション側からDBに対して問い合わせをする際は、 1. アプリケーションの中でSQLを直書きする(文字列レベルで組み立てる) user_name = db.query(“SELECT name FROM users WHERE id = {}”.format(int(user_id))) 2. ライブラリ(ORM)にSQLを組み立ててもらう user = User.find_by_id(id) # 内部でDBに問い合わせを行ってくれる user_name = user.name が代表的 1.は自由にクエリを組み立てられるメリットがあるが、構文レベルでバグを生む、bind処理が甘く SQLインジェクションが起きるというデメリットがある 2.は構文エラーやセキュリティ問題が起きづらいがクエリの自由度は下がるというデメリットがある 安全な問い合わせとは
  64. 64. confidential Mobility Technologies Co., Ltd. クエリにパラメータを動的に渡す際、エスケープ処理をサボると任意のDB操作を許してしまうリスクがある user_id = config.user_id # 外部のconfigファイルから値を取得 user_name = db.query(“SELECT name FROM users WHERE id = {};”.format(user_id))  User_id = 1のとき 生成されるクエリ: SELECT name FROM users WHERE id = 1;  User_id = “1; DELETE FROM users”のとき 生成されるクエリ: SELECT name FROM users WHERE id = 1; DELETE FROM users; このようにクエリにパラメータをそのまま渡すと、故意でなくとも意図しないクエリを実行されてしまう → prepared statementで回避する query(“SELECT name FROM users WHERE id = ?”).bind(1) 危ない文字はすべてエスケープされる SQLインジェクションとは
  65. 65. confidential Mobility Technologies Co., Ltd.  mysqlクレート conn.query_map(“SELECT name FROM users WHERE id = ?”) .with((1, )) .map(||…)?; クエリを直書きしてデータベースに渡す。Prepared statementには対応しているため インジェクションリスクは低いが、クエリ構文が間違っていても気付けない → クエリの構文としての正しさは、テストを通して実際に実行して確認する → クエリの内容の正しさも、テストを通して実際に実行して確認する ※ Pythonで利用することの多いMySQLdbも似たようなものなので必須 直書き代表(Rustの場合) 66
  66. 66. confidential Mobility Technologies Co., Ltd.  sqlxクレート sqlx::query_as!(User, “SELECT name FROM users WHERE id = ?”, 1) .fetch(&conn)?; クエリを直書きしてデータベースに渡す。Prepared statementには対応している さらに、コードのビルド時にクエリをDBに投げ(実行はしない)、構文エラーをチェックする そのためクエリの動的組み立ては出来ない → クエリの構文としての正しさは、ビルド時に保証される → クエリの内容の正しさは、テストを通して実際に実行して確認する ※ Pythonでこういうのは見たことない 直書き特殊例(Rustの場合) 67
  67. 67. confidential Mobility Technologies Co., Ltd.  dieselクレート let user_name = users::table.fileter(users::id.eq(1)).select(users::name).load(&conn); クエリを直書きしない。Prepared statementも当然対応 クエリの構文としての正しさをRustの型システムで保証させてるのでビルドが成功した時点で正 しい → クエリの構文としての正しさは、ビルド時に保証される → クエリの内容の正しさは、テストを通して実際に実行して確認する ※ PythonだとSQLAlchemyがこれにあたる ORM代表 68
  68. 68. confidential Mobility Technologies Co., Ltd.  どの方法でもビルドまでで確認できるのは、「SQLの構文としての正しさ」だけ クエリの内容の正しさを保証するには必ずtestを書かなければならない  Prepared statementは基本的にどのライブラリでも提供されている クエリに動的に値を渡す時は必ずライブラリの機能を利用する 文字列結合でクエリを作る場合はホワイトリストチェックや型キャストなど、可能な限りの方法 で期待している値であることを確認する 結局安全に使うには 69

×