2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【🔥炎䞊回避】DB蚭蚈のプロが教える「絶察やっおはいけない」アンチパタヌンず察策

Last updated at Posted at 2025-09-09

はじめに

  • 僕は30幎以䞊のSE人生で様々な業務システム開発にデヌタベヌススペシャリストやシステムアヌキテクトずしお参加しおきたした
  • その䞭で 「DB蚭蚈の倱敗」 がどれほど埌々たで圱響するかを䜕床も芋おきたした
    倚くの堎合、結合テスト以降で パフォヌマンスが出ずに問題が露芋🔥🔥 
  • 昔の僕は正芏化原理䞻矩でしたが、今は正芏化をあえお䞀郚で厩し 「意図的に正芏化を緩和」 しおたす

😖 既存のDBの蓋を開けるず 

❌ 圓然のように ER図が存圚しない
   蚭蚈曞の䞭で 最重芁 ず思っおたす 
❌ å…šç„¶ 参照敎合性制玄が無いからテヌブル間の関連は自力で党郚読み解くしかない
❌ 参照敎合性制玄が無いから、参照先のマスタに倀があるか保蚌は無い
  保蚌が無いから垞にleft join
❌ 削陀フラグに 0,1 だけでなく null や 9 など入っおいお、どう扱うべきか分からない
❌ ぀の列に耇数の意味を持たせおいる。削陀フラグ=9は匷制削陀など
❌ 列の名前が䞍十分で良く分からない
   䟋えば「請求フラグ」だけでは "請求枈み" なのか "請求察象" なのか分かりずらい

✅ こんな問題を匕き起こさないために

  • 最高の蚭蚈ポリシヌが぀だけあっおその他はダメ、ずいうこずではなく実際はケヌスバむケヌスなこずが倚いです
  • ただし、「分かっおはいるけれど既存デヌタが〇〇だからここは劥協する」のず、䜕も考えおいないのは党然違いたす

あなたの職堎やプロゞェクトに合わせおカスタマむズしおください






蚭蚈の基本

✅ DBの寿呜ず䟡倀

  • 開発するアプリよりDBの構造ずその䞭のデヌタの寿呜の方がはるかに長い
  • 䞍正なデヌタが混ざっおしたうず、そのデヌタは䜿えない

image.png

あなたの銀行残高が、「倚分なんずなく正しいはず」では困りたすよね

✅ 䞍正なデヌタが混ざらないようにできるこず

  • DBの列はできるだけnot nullで蚭蚈する
  • できる限り型を限定する
    • ON/OFFしかないならbool型にする
    • 日付は date 型を利甚する
  • 入力しお良い倀が限定されおいるならCHECK制玄を付ける

぀たり入っお良いデヌタをできるだけ限定する。
構造的に䞍正なデヌタが入れられないようにする
   → 型安党性ずデヌタ敎合性の確保

❓ なぜできるだけnot nullで蚭蚈するのか

  • selectする時に垞にnullの堎合に配慮するこずは合理的か
select isnull(氏名, '氏名䞍明') as 氏名
  • そもそも null の状態で登録する事は健党か
    少なくずも瀟員マスタの「氏名」がnullのデヌタを登録できおよいのか

僕のDB蚭蚈の垫匠は「日本にRDBを初めお導入普及した゚ンゞニアの䞀人」ずされおいる方です。
垫匠は昔「null の可胜性のある列は原則別テヌブルに分離しろ」ずいう超過激掟でした。

✅ 良いCREATE文

create table 瀟員 (
  ID int identity not null primary key
, 瀟員番号 varchar(10) not null unique
    constraint CK_瀟員_瀟員番号 check (len(瀟員番号) = 10)
, 氏名 nvarchar(50) not null
    constraint CK_瀟員_氏名 check (len(氏名) >= 2)
, 生幎月日 date not null
, 職玚 int not null
    constraint CK_瀟員_職玚 check (職玚 in (1, 2, 3))
)

💎 良い点

  • 瀟員番号は圚職䞭に倉曎されるケヌスもあるお客様の瀟内ルヌルに䟝存
    → Primary Keyにすべきではない
  • 瀟員番号は Primary Key ではないが unique にするこずで重耇は排陀できる
  • 瀟員番号ず氏名に長さの制玄があり、空文字などは登録できない
  • 氏名、生幎月日、職玚など not null 制玄により倀が保蚌される
  • 職玚は数倀の1,2,3しか取りえないこずが保蚌される

✅ 悪いCREATE文

create table 瀟員 (
  瀟員番号 nvarchar(10) primary key
, 氏名 nvarchar(50)
, 生幎月日 varchar(10)
, 職玚 char(1)
)

🚫 悪い点

  • 瀟員番号がPrimary Key
  • 瀟員番号や氏名に長さの制玄が無いので、空文字などありえる
  • 氏名、生幎月日、職玚など nullがありえる
  • 職玚に存圚しない 4 や A などがありえる

❓ なぜできるだけ日付型を利甚するのか

  • 日付ずしおの意味が保蚌される
  • 挔算・関数が䜿える(dateadd, datediff など)
  • ゜ヌトや比范が正しくできる
  • ストレヌゞ効率(date は固定3バむト、varchar(10) は最倧12バむト)

✅ CREATE文をさらに拡匵

先ほどの良いCREATE文の職玚の制玄で
「将来職玚が増えたらどうするの」ず思った方はセンスが良いです。

    constraint CK_瀟員_職玚 check (職玚 in (1, 2, 3))

将来倉曎される可胜性が高いビゞネスルヌルは、デヌタ構造で衚珟したす。

image.png

create table 職玚区分 (
  職玚 int not null primary key
, 職玚名 nvarchar(20) not null
    constraint CK_職玚区分_職玚名 check (len(職玚名) >= 2)
)

create table 瀟員 (
  ID int identity not null primary key
, 瀟員番号 varchar(10) not null unique
    constraint CK_瀟員_瀟員番号 check (len(瀟員番号) = 10)
, 氏名 nvarchar(50) not null
    constraint CK_瀟員_氏名 check (len(氏名) >= 2)
, 生幎月日 date not null
, 職玚 int not null
, constraint FK_瀟員_職玚 foreign key (職玚) references 職玚区分(職玚)
)

💎 良い点

  • 職玚を専甚のテヌブルに分離
  • 将来的な職玚の远加にデヌタ構造の倉曎なく察応できる

DB蚭蚈デヌタ構造でビゞネスルヌルを衚すのが最善
RDBずはリレヌショナルDB。リレヌションが倧切。

✅ 悪い拡匵の䟋実務では時々芋かける

create table 汎甚区分 (
  ID int identity not null primary key
, 区分皮類 varchar(10)
, 区分コヌド varchar(10) not null
, 区分名 nvarchar(20) not null
)

create table 瀟員 (
  ID int identity not null primary key
, 瀟員番号 varchar(10) not null unique
, 氏名 nvarchar(50) not null
, 生幎月日 date not null
, 職玚 int not null
)
  • キヌず名称の組み合わせを汎甚テヌブルで保持する蚭蚈
  • 䞀芋効率的に芋えるが、そもそも瀟員の職玚が取れる倀を制限する目的を実珟しおない

DBにできる限り䞍正なデヌタを入れさせない趣旚ず反しおいる

💎 参照敎合性制玄(foreign key)の䟡倀

✅ 参照敎合性制玄がある堎合のSELECT文

select
  s.瀟員番号
, s.氏名
, c.職玚名
from 瀟員 s
    join 職玚区分 c
        on s.職玚 = c.職玚

💎 良い点

  • 等結合できる
    瀟員マスタの職玚は、必ず職玚区分に存圚するこずが 「保蚌」 されおいる

✅ 参照敎合性制玄が「ない」堎合のSELECT文

select
  s.瀟員番号
, s.氏名
, isnull(c.職玚名, '職玚䞍明') as 職玚名
from 瀟員 s
    left join 汎甚区分 c
        on s.職玚 = c.職玚
        and c.区分皮類 = '職玚'

🚫 悪い点

  • DBに制玄が無い以䞊、倖郚結合するしかない
  • 保蚌されおいない以䞊、joinできなかった堎合のこずを垞に考える必芁がある
  • こちらのSELECT文の方が䞀般的に負荷が高く遅い






効率よくSELECTできるかを垞に意識しお蚭蚈する

  • デヌタは読んで利甚するために登録される
  • 登録や修正される回数より、読み取られる回数の方が圧倒的に倚い
  • ぀たり登録曎新しやすい構造よりも、読み取りやすい構造を優先する

パフォヌマンスの重芁床
     SELECT   >>>>>>>>   INSERT/UPDATE/DELETE






SELECT効率を悪化させるグレヌケヌス

3.1 ✅ グレヌケヌス(1) 論理削陀フラグ

  • 実務では頻繁にみかける蚭蚈
    image.png

🚫 悪い点

  • 生きおいるデヌタず削陀枈みのデヌタが混圚するので、KEYが重耇しUNIQUE制玄が利甚できない
  • デヌタの安党を保障する参照敎合性制玄の意味が薄くなる
  • select 時に垞に論理削陀フラグを意識する必芁がある
  • 過去の状態を残したいずいう保険が 党䜓ぞ悪圱響を䞎えるために本末転倒

🚀 改善策論理削陀結果は履歎テヌブルぞ移送する

image.png

  • 過去の状態は別テヌブルにする
  • 倉曎前や削陀前の状態をトリガヌやリポゞトリで別テヌブルぞ曞き蟌む

3.2 ✅ グレヌケヌス(2) マスタヌに有効期間

  • 実務では時々みかけるパフォヌマンス悪化に぀ながる極めお危険な蚭蚈

image.png

select
  s.氏名
, c.職玚名
from 瀟員 s
    left join 職玚区分 c
    on s.職玚 = c.職玚
    and GetDate() between c.有効開始日 and c.有効終了日

🚫 悪い点

  • SELECT文の耇雑さが党おを物語っおいる。パフォヌマンスが出るわけがない
    蚭蚈は積み重ねなので、この耇雑さがシステム党䜓ぞ波及する
  • もはや参照敎合性制玄を匵る意味がない

🚀 改善策①過去デヌタは履歎ぞ移送する

  • 論理削陀フラグず同様 別テヌブルで管理する

🚀 改善策②未来デヌタに぀いお

  • 来月から商品単䟡の倀䞊げなど、圱響が倧きく事前に倉曎される日付ず倉曎内容が分かるものもある
  • 基本的に未来デヌタは扱いが難しいので個別に蚭蚈する
  • 消費皎に関しおは次章参照

3.3 ✅ グレヌケヌス(3) 未来デヌタ消費皎の扱い

消費皎率が改蚂される日の朝䞀に、システム蚭定の消費皎率を倉曎しおも䞊手く行かないこずが倚いです。

䟋

  • 2019/10/1に 消費皎率が 8% → 10% ぞ倉曎された
  • 10/1の朝䞀にシステム蚭定の消費皎率を 10% ぞ倉曎した
  • 10/1に10/1の売䞊デヌタを登録する時 消費皎率は 10% で蚈算された → これは正しい
  • 10/1に「過去」の 9/30 の売䞊デヌタを登録したずきも 消費皎率は 10% で蚈算された → これはダメ 8%が正しい

🚀 お勧めのデヌタ構造

image.png

  • 消費皎率に関しおは期間毎に持たざるを埗ない
  • 売䞊デヌタなどに採甚した消費皎率を盎接保存する
    (消費皎率IDは保持しおも良いがほずんど意味はない)

image.png

  • 売䞊にデヌタを登録、修正するずき「売䞊日」から「消費皎率」を取埗
  • 売䞊に保持される「消費皎額」を蚈算した根拠ずなる「消費皎率」を売䞊にも蚘録
  • 今、消費皎率IDに関数埓属する消費皎率を売䞊に持぀こずで正芏化を倖れたした
  • マスタの構造は正芏化すべきですが トランザクションに関しおはその限りでは無いず思っおいたす

🚀 この考え方を発展させるず

✅ トランザクションぞマスタ情報を焌き付ける

  • 取匕先の「ハカマタ゜フト」ず長い付き合いがある
  • 「ハカマタ゜フト」は 2025/10/1 から屋号を 「チキチキ゜フト」 ぞ倉曎するずしたら・・・

✅ 䞀般的なデヌタ構造
image.png

✅ 取匕先の瀟名倉曎ぞの察応策

お勧め床 察応策 メリット デメリット
★☆☆ 倉曎日に請求先マスタの請求先名を倉曎 簡単 䜎コスト 過去の請求曞は過去の宛名で再珟できない
★☆☆ 倉曎埌の請求先名を請求先マスタに远加する 簡単 䜎コスト 請求先IDでの請求額の掚移確認が難しい
☆☆☆ 請求先を期間ごずに保持 過去の請求曞は過去の宛名で再珟できる システムが極めお耇雑になる
★★★ 先の消費皎率ず同じように請求デヌタに請求先名を蚘録 簡単 䜎コスト デヌタ量が増える無芖できるレベル
  • トランザクションは発生したタむミングでの履歎です
  • 履歎に必芁十分な情報マスタの情報含むを蚘録するのは自然ず思いたす

✅ トランザクションに必芁なマスタ情報を焌き付ける
image.png

仮にマスタを倉曎しおも、請求曞を再発行したら前回ず同じ請求曞が出るのが自然ず思いたす

昔はデヌタストレヌゞのコストが高く、できるだけデヌタを瞮小する発想が重芁でした。西暊ですら埌ろ2桁しか持たないなど
でも、今はストレヌゞのコストが圧倒的に安いので、必芁な情報はできるだけそのたた保管するメリットの方が倧きいず思いたす。






トランザクション管理の重芁性

✅ 銀行の入金凊理を䟋に考えたす。

image.png

① 取匕明现に入金の実瞟を蚘録する。
② 口座残高に入金額を远加しお曎新する。

image.png

この぀の凊理はセットで、

  • 䞡方が正しく実行された
  • どちらか䞀方が倱敗したら䞡方無かったこずにする

✅ 正しいトランザクション管理

begin try
    /* トランザクションの開始を宣蚀 */
    begin tran;

    insert into 取匕明现 (口座番号, 凊理, 実行日, 金額)
    values ('口座123', '入金', '2025-09-03', 3000);

    update 口座残高
    set 残高 = 残高 + 3000
    where 口座番号 = '口座123';

    /* デヌタ曎新を確定 */
    commit;
end try
begin catch
    /* ゚ラヌ発生時はロヌルバックを明瀺 */
    rollback;
end catch

✅ トランザクション管理をしおいない堎合

insert into 取匕明现 (口座番号, 凊理, 実行日, 金額)
values ('口座123', '入金', '2025-09-03', 3000);

update 口座残高
set 残高 = 残高 + 3000
where 口座番号 = '口座123';

image.png

入金実瞟のinser盎埌に障害が発生ネットワヌク回線の切断などするず、入金実瞟のみが蚘録され残高が曎新されたせん。

耇数のSQLが「セットで成功」か「なかったこず」にする堎合、必ずトランザクション管理したす






排他制埡

✅ ネット通販の圚庫管理を考えたす
image.png

排他制埡しなければ矛盟が発生しおしたう

✅ 改善方法楜芳的排他制埡の実装

  • 排他制埡甚にVersion列を远加したす
    image.png

image.png

/* 最初に圚庫数を確認するずきに Version も取埗 */
select 圚庫数, Version from 商品圚庫
where 商品番号 = '商品A'
;

/* Update時に同じ Version に察しおのみupdateを実行 */
update 商品圚庫
set 圚庫数 = 圚庫数 - 1
where 商品番号 = '商品A'
  and Version = 1;

✅ このupdate文の実行結果が

  • 1行なら曎新に成功
  • 0行なら他の方が圚庫数を曞き換えおいる同じVersionのデヌタが無い

image.png

Bさんの賌入は圚庫数の曎新結果=0行になるので倱敗です。
取匕明现のinsertもロヌルバックしたす。

✅ 悲芳的排他ロックに関しお

  • webを䞭心に珟代はステヌトレスな蚭蚈が䞻流です
  • そういう意味で「悲芳的排他ロック」を必芁ずする業務は限られるのでここでの説明は割愛したす






たずめ

  • 🗂 DB蚭蚈はアプリより寿呜が長い
  • 🔒 䞍正デヌタは構造で防ぐ
  • 🚀 SELECT効率を最優先
  • 🕑 履歎は履歎テヌブルに移す
  • 🔄 トランザクションず排他制埡は必須
2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?