はじめに
私は実務経験3年目にはいったバックエンドエンジニアです。
SQLアンチパターンを再度読み返す機会があったため、感想文を書かせていただきます。
再度と記載しましたが、実は1年目の時にも拝読しており、その時は経験もないことからほとんど理解できておりませんでした。
それから実務経験を経てから、読み返すことで理解できたことをいくつか章をピックアップして経験ベースで記事にさせていただきます。
論理設計
カンマ区切りの格納
今となっては列にカンマ区切りで値を格納するような設計は余程じゃない限りしませんが、最初はその深刻さが理解できていませんでした。なんなら、配列型をサポートしているデータベースがあることを知って、便利ではと思っていたほどです。
ただ、似たような設計に出会ったことがありました。
そこでは、色や雰囲気といったタグで商品を検索する要件を満たすために、アプリケーションで定義した整数の定数をカンマ区切りで格納していました。
こういった検索用途であれば、1ユースケースとして許容するのはパフォーマンス点などから考慮してありなのかなとも感じました。
個人的には、アプリケーションで制御できていないコードを埋め込むと、データの整合性が担保されていないことや、重複に気が付かないなどの問題が発生しそうといった理由から、列に複数の値を格納する設計はしない方が良いと感じました。
また、データベースだけで情報が完結しないのも扱いづらい気がします。
外部キー制約を使用しない
私の最初の携わったシステムでは外部キー制約を使用していなかったため、最初は外部キー制約は不要な制約という認識でした。
そのシステムで外部キー制約を使用していなかった理由として、経験上運用で柔軟な対応が困難になるからとのことでした。
親テーブルのレコード削除が大変、存在しない値を外部キーとして登録できないなどが困難となる要因だと考えられます。
あと、個人的には簡単な動作確認したいケースで若干ですが、不都合を感じました。
システムの要件やユースケースによりますが、私はやはり外部キー制約は使用した方が良いと思います。
データの整合性が保てるのはやはり大きいです。データの不整合が起きた時の問題の大きさと、リカバリーにはとても労力をついやますし、何より心にダメージを負います。そもそも柔軟な対応を可能とするテーブル設計にするべきですし、間違いを起こす操作が頻繁に起きるならば、UIを見直すべきであって、外部キー制約を使用しない理由にはならないという考えです。
複数列属性
どういった内容かは、書籍の例をあげるとレコードに対して、複数のタグ付けする場合にtag1
、tag2
、tag3
としてカラムを定義する設計のことです。明らかに順番や重複、存在確認などのアプリケーションで制御するのが大変そうですね。SQLも複雑になりそうです。
このような設計を拝見したことがありませんが、似たような事例でカテゴリーが増える毎にカラムを追加する設計を見かけたことがあります。
category
、category2
, ~ ,category21
まであり、各カテゴリーはアプリケーションで管理されており(category
→ 人気、category2
→ 限定 ...)、カテゴリーに属さない場合はデフォルト値に0、属する場合は100毎インクリメントしてソート番号として格納されておりました。
カラム名から推定するに、最初は1つのカテゴリーの想定で、後に次々とカテゴリーが複数に増えたことが考えられます。
カラム特有のルールが発生したり、レコードがカテゴリーに属するかと、ソート番号を示すといった複数の責務を体現しているカラムは、バグが発生する起因になりやすいことが想定されるので、こういった設計は避けるべきだと思います。
物理設計
丸め誤差
こちらは小数値のデータを扱う場合、float型
やdouble型
を使用するべきではないといった内容です。
私はこれまで脳死でfloat型
を使用してきてしまいました...。
主に合成用の座標や倍率だったためか、そこまで問題だと感じたことがありませんでした。もしくは、目で判断ができないレベルで発生していた可能性もあります。
ただ、正確な小数値を扱えないということを存じていなかったため、今後こういったケースがある場合は注意して脳死で定義するのはやめようと思いました。
幻のファイル
こちらは画像ファイルなどを保存する場合、ファイルパスではなく、バイナリデータをデータベースに保存するべきといった内容です。要するにデータベースの外部に画像ファイルを格納するべきではないという主張です。
逆にバイナリデータで画像ファイルを保存している設計を拝見したことがありませんでしたがどうなんでしょう...。
私も実ファイルは削除されているのに、データベースでは存在しているという乖離が発生したバグを何度か見かけたことがあります。和田さんのスライドでも「否定意見多し」とありましたので、必ずしもそうあるべきではなくメリット、デメリットを考えて取り入れる必要がありそうです。
クエリのアンチパターン
恐怖のunknown
Null
を一般値として扱うべきでないといった内容です。
アプリケーションと同様にデータベースのNull
もバグが発生する原因になりやすいです。
私の経験上でも、注文テーブルに出荷状態を管理するカラムがあり、出荷済みとなると出荷日時カラムも同時に更新されるが、出荷日時が記録されていないという報告を受けたことがあります。
また先ほどの例で、出荷前に注文がキャンセルとなった場合、出荷日時は更新されないまま(unknownな状態)ですので、Null
は単純にデータを参照するケースでも気を付ける必要があると感じました。
私としては、なるべくNot Null制約を使用して、データの制御を楽にしたいです。そのためには、念入りにテーブル設計を考える必要があります。
貧者のサーチエンジン
こちらは、LIKE %???%
といったパターンマッチ述語を使用して、曖昧検索を実装するべきではないといった内容です。
私はこれまで普通に使用していましたし、そういった実装を見かけたこともあります。
読み返して、そういったアンチパターンがあることを知りました。
ベンダー拡張を利用した全文検索機能は日本語に対応していなかったり、転置インデックスの自作は現実的ではないため、サードパーティの全文検索エンジン一択らしいです。
サードパーティの全文検索エンジンを使用した実装をしたことがまだないのでいつか挑戦してみたいです。
暗黙の列
SELECT *
といったワイルドカードを使用するべきではないといった内容です。
はい、ごめんなさい、今でも私はこちらを使用してしまうことがあります...。
そして、他のテーブルと結合してカラム名が衝突する現象も度々発生しているので、なるべく明示的に指定するように心がけます。
テーブルのカラム数が増えてくると、明示的に指定するのは面倒なことから、タイプ数を減らしたくてついつい使ってしまいます。そもそも面倒だと感じる時点で、テーブル設計を見直すべきなのかもしれませんが...。
今だとフレームワークがいい感じにやってくれることが多い印象があるので、そもそも出会うことが少ないケースな気がしました。
アプリケーション開発のアンチパターン
臭いものに蓋
こちらはデータベースからの戻り値や例外を無視するべきではないといった内容です。
私が携わったシステムでも、SQLの構文エラーでデータ取得が失敗しているのにAPIが成功したレスポンスが返っていることがありました。
私は勝手にデータベース接続失敗時や、クエリ文の実行失敗時はアプリケーション側で例外扱いとなると思っていました。
どうやらデフォルトの設定で例外扱いにはならない?らしくそのまま後続処理を実行していたため発生していた問題でした。
しかし、一元で変更すると、他の実装部分でデータベース接続失敗時や、クエリ文の実行失敗時で後続処理を許容している可能性があるので、アプリケーションで例外扱いとするかは注意が必要だと思いました。
最後に
今回SQLアンチパターンを読み返して、1年目とそれから実務経験を経てからで感想が大きく違いました。
ここに挙げられているアンチパターンを表現したテーブル構成で実際に携わることで、苦労や大変さを少し理解できたからだと思います。また、逆にこのアンチパターンを避けるためにしていた設計や実装に気づくことができ、知見が深まりました。恐らく、これから再び経験を経てから読み返すとで、感想が変わり、まだ出会ったことがないアンチパターンにその時には出会って共感しているかもしれないです。これから設計に携わる際には、改めて参考にさせていただきたい書籍でした!