テックカリキュラム

高度なDB設計とパフォーマンスチューニング

高度なDB設計とパフォーマンスチューニング

本章では、VB.NET業務システムにおける高度なDB設計とパフォーマンスチューニングについて学習します。

業務システムでは、処理が遅い、画面が固まる、検索に時間がかかる、夜間バッチが終わらない、デッドロックが発生するなど、DB起因の性能問題が頻繁に発生します。 特にVB.NETのWindows Formsアプリケーションでは、画面からDBへ直接アクセスしていたり、大量データをDataTableへ一括取得していたりすることも多く、設計次第で大きな性能差が出ます。

DBパフォーマンス改善では、単にSQLを少し書き換えるだけでは不十分です。 SQL実行計画、インデックス設計、N+1問題、ロック競合、デッドロック、大量データ処理、バルクインサート、ページング、DB負荷分散などを総合的に理解する必要があります。

本章のゴールは、DB起因の性能問題を感覚で直すのではなく、原因を分析し、安全に改善できるようになることです。


1. DBパフォーマンス問題の基本

DBパフォーマンス問題は、単に「SQLが遅い」だけではありません。 アプリケーション設計、DB設計、インデックス、トランザクション、ネットワーク、データ量、同時実行数など、複数の要因が絡み合って発生します。

よくある性能問題

  • 検索画面の表示が遅い
  • DataGridViewへの一覧表示に時間がかかる
  • 大量CSV取込が終わらない
  • 帳票出力に数分かかる
  • 夜間バッチが朝までに終わらない
  • 特定時間帯だけDBが重い
  • 複数ユーザー操作時にロック待ちが発生する
  • デッドロックで処理が失敗する
  • DBサーバーのCPUやI/Oが高騰する

性能問題を調査するときの基本観点

  • どの画面・機能で遅いのか
  • どのSQLが遅いのか
  • データ件数はどの程度か
  • 特定条件だけ遅いのか
  • 実行計画はどうなっているか
  • インデックスは使われているか
  • ロック待ちは発生しているか
  • 同時実行時だけ遅いのか
  • アプリ側で不要なループSQLを発行していないか

DB性能改善では、まず「どこが遅いのか」を特定することが重要です。 画面が遅いからといって、必ずしもSQLだけが原因とは限りません。 DB、アプリケーション、ネットワーク、UI描画、ファイル出力などを切り分けて考える必要があります。


2. SQL実行計画

SQL実行計画とは、DBがSQLをどのような手順で実行するかを示す情報です。 どのテーブルから読み始めるか、どのインデックスを使うか、結合方法は何か、どの程度の件数を読む想定かなどを確認できます。

SQLが遅い場合、実行計画を見ることで、ボトルネックの原因を把握しやすくなります。

実行計画で確認する代表的な項目

  • Table Scan
  • Index Scan
  • Index Seek
  • Nested Loop Join
  • Hash Join
  • Sort
  • Key Lookup
  • 推定行数と実際の行数
  • コストの高い処理

Table Scan

Table Scanは、テーブル全体を読み込む処理です。 小さなテーブルであれば問題にならないこともありますが、数十万件、数百万件のテーブルで発生すると大きな性能問題につながります。

Index Seek

Index Seekは、インデックスを使って必要なデータだけを効率的に探す処理です。 一般的には、検索条件に適したインデックスが存在し、DBがそれを有効に使えている状態です。

Index Scan

Index Scanは、インデックス全体または広い範囲を読み込む処理です。 Table Scanより軽い場合もありますが、条件によっては大量の読み込みが発生します。

実行計画を見るべきSQLの例

SELECT
    OrderId,
    CustomerId,
    OrderDate,
    TotalAmount
FROM Orders
WHERE CustomerId = @CustomerId
  AND OrderDate >= @FromDate
  AND OrderDate <= @ToDate
ORDER BY OrderDate DESC

このSQLでは、CustomerId、OrderDate、ORDER BYに関係するインデックスが適切に設計されているかが重要です。

実行計画確認時のポイント

  • 想定外のTable Scanが発生していないか
  • 検索条件に合ったインデックスが使われているか
  • JOINの順序や方式が妥当か
  • Sort処理が重くなっていないか
  • Key Lookupが大量発生していないか
  • 推定行数と実際の行数が大きくずれていないか

実行計画は、DBの内部動作を知るための重要な情報です。 性能問題を感覚で判断するのではなく、実行計画を根拠に改善方針を決めることが重要です。


3. インデックス設計

インデックスは、DB検索を高速化するための仕組みです。 本の索引のように、必要なデータへ効率的にアクセスできるようにします。

ただし、インデックスは多ければ多いほど良いわけではありません。 検索は速くなる可能性がありますが、INSERT、UPDATE、DELETE時にはインデックス更新コストが増えます。

インデックスが効果を発揮しやすい列

  • WHERE句でよく使う列
  • JOIN条件で使う列
  • ORDER BYで使う列
  • GROUP BYで使う列
  • 検索対象件数を大きく絞り込める列

インデックス設計例

CREATE INDEX IX_Orders_CustomerId_OrderDate
ON Orders (CustomerId, OrderDate)

このインデックスは、CustomerIdとOrderDateを条件にする注文検索で効果が期待できます。

複合インデックスの順序

複合インデックスでは、列の順序が非常に重要です。 例えば以下のインデックスがあるとします。

CREATE INDEX IX_Orders_CustomerId_OrderDate
ON Orders (CustomerId, OrderDate)

この場合、CustomerIdを条件に含む検索では使いやすいですが、OrderDateだけを条件にした検索では効果が限定的になる場合があります。

インデックスが効きにくいSQLの例

SELECT *
FROM Users
WHERE LEFT(UserName, 1) = 'A'

列に関数を適用して検索すると、インデックスが効きにくくなる場合があります。

改善例

SELECT *
FROM Users
WHERE UserName LIKE 'A%'

前方一致検索であれば、条件やDBによってはインデックスを利用しやすくなります。

インデックス設計の注意点

  • 不要なインデックスを増やしすぎない
  • 更新頻度の高いテーブルではインデックス数に注意する
  • 複合インデックスの列順を意識する
  • 検索条件とORDER BYを考慮する
  • 実行計画で実際に使われているか確認する
  • SELECT * を避け、必要な列だけ取得する

インデックス設計は、SQL単体ではなく、業務でよく使われる検索条件、更新頻度、データ量を踏まえて考える必要があります。


4. N+1問題

N+1問題とは、一覧データを取得した後に、その件数分だけ追加SQLを発行してしまう問題です。 アプリケーション側のループ処理で発生しやすく、VB.NET業務システムでも非常によく見られます。

N+1問題の悪い例

Dim orders = _orderRepository.FindOrders()

For Each order In orders
    Dim customer = _customerRepository.FindById(order.CustomerId)
    order.CustomerName = customer.CustomerName
Next

この例では、最初に注文一覧を1回取得し、その後、注文件数分だけ顧客取得SQLを実行しています。 注文が1000件あれば、合計1001回SQLが実行されます。

N+1問題の影響

  • DBアクセス回数が増える
  • ネットワーク往復回数が増える
  • 画面表示が遅くなる
  • DBサーバーに余計な負荷がかかる
  • データ件数が増えるほど急激に遅くなる

JOINでまとめて取得する改善例

SELECT
    o.OrderId,
    o.OrderDate,
    o.TotalAmount,
    c.CustomerName
FROM Orders o
INNER JOIN Customers c
    ON o.CustomerId = c.CustomerId
WHERE o.OrderDate >= @FromDate
  AND o.OrderDate <= @ToDate

必要なデータをJOINでまとめて取得すれば、SQL実行回数を大幅に減らせます。

IN句でまとめて取得する例

SELECT
    CustomerId,
    CustomerName
FROM Customers
WHERE CustomerId IN (@CustomerId1, @CustomerId2, @CustomerId3)

JOINが適さない場合でも、対象IDをまとめて取得し、アプリ側でマッピングする方法があります。

N+1問題を防ぐ設計

  • ループ内でRepositoryを呼ばない
  • 一覧画面用の検索DTOを用意する
  • JOINや一括取得を活用する
  • 必要なデータを最初からまとめて取得する
  • SQL発行回数をログで確認する

N+1問題は、開発時の少量データでは気づきにくく、本番データ量になって初めて問題化することがあります。 一覧画面や帳票出力では、SQL発行回数を必ず意識する必要があります。


5. ロック競合

ロック競合とは、複数の処理が同じデータへ同時にアクセスしようとした際に、一方の処理が他方の処理完了を待つ状態です。 業務システムでは、更新処理、承認処理、在庫更新、締め処理などで発生しやすくなります。

ロック競合が起きやすい処理

  • 同じ注文データを複数ユーザーが更新する
  • 在庫数を同時に更新する
  • 月次締め処理中に画面から更新する
  • 夜間バッチと日中画面操作が同じテーブルを更新する
  • 大量UPDATE中に検索処理が走る

ロック競合を悪化させる原因

  • トランザクション時間が長い
  • ユーザー操作待ちをトランザクション内で行っている
  • 大量データを一括更新している
  • インデックス不足で不要な範囲までロックしている
  • 同じテーブルへの更新が集中している

悪い例:トランザクション中に時間のかかる処理を行う

Using transaction = connection.BeginTransaction()

    ' DB更新
    UpdateOrder(connection, transaction)

    ' ファイル出力
    ExportFile()

    ' 外部API通信
    CallExternalApi()

    transaction.Commit()

End Using

この例では、ファイル出力や外部API通信がトランザクション内に含まれています。 その間ロックが保持されるため、他の処理が待たされる可能性があります。

改善方針

  • トランザクション範囲を最小限にする
  • DB更新と外部API通信を分離する
  • 大量更新は分割して実行する
  • 適切なインデックスを設計する
  • 更新順序を統一する
  • 必要に応じて楽観ロックを導入する

楽観ロックの考え方

楽観ロックは、更新時に「自分が読み込んだ時点からデータが変更されていないか」を確認する方式です。 Version列やUpdatedAt列を使って実装されることが多いです。

UPDATE Orders
SET
    Status = @Status,
    Version = Version + 1
WHERE OrderId = @OrderId
  AND Version = @OriginalVersion

更新件数が0件の場合、他のユーザーによって先に更新されたと判断できます。


6. デッドロック解析

デッドロックとは、複数のトランザクションがお互いのロック解放を待ち続け、処理が進まなくなる状態です。 DBは通常、どちらか一方のトランザクションを強制的に失敗させてデッドロックを解消します。

デッドロックの例

処理A:
1. Ordersを更新
2. OrderDetailsを更新

処理B:
1. OrderDetailsを更新
2. Ordersを更新

処理AがOrdersをロックし、処理BがOrderDetailsをロックした状態で、 それぞれが相手のテーブルを更新しようとすると、デッドロックが発生する可能性があります。

デッドロックの主な原因

  • 複数テーブルの更新順序が統一されていない
  • トランザクション範囲が広すぎる
  • インデックス不足により広範囲をロックしている
  • 大量更新が同時に実行されている
  • 画面処理とバッチ処理が同じデータを更新している

デッドロック対策

  • 更新するテーブル順序を統一する
  • トランザクションを短くする
  • 適切なインデックスを追加する
  • 一括更新を分割する
  • 処理時間帯を分ける
  • デッドロック発生時のリトライを検討する

リトライ処理の例

Public Sub ExecuteWithRetry(action As Action)

    Dim maxRetryCount As Integer = 3

    For retry As Integer = 1 To maxRetryCount
        Try
            action()
            Return

        Catch ex As SqlClient.SqlException
            If IsDeadlock(ex) AndAlso retry < maxRetryCount Then
                Threading.Thread.Sleep(500 * retry)
            Else
                Throw
            End If
        End Try
    Next

End Sub

デッドロックは一時的な競合であることも多いため、処理によってはリトライが有効です。 ただし、二重登録や二重送信が起きないよう、処理の冪等性を考慮する必要があります。

デッドロック解析で確認すること

  • どのSQLが関係しているか
  • どのテーブル・インデックスで競合しているか
  • 更新順序は統一されているか
  • 同時実行される処理は何か
  • トランザクション時間は長すぎないか
  • 同じ時間帯にバッチが動いていないか

7. 大量データ処理

業務システムでは、大量データを扱う処理が多くあります。 CSV取込、売上集計、在庫更新、請求データ作成、ログ集計、帳票一括出力などです。

大量データ処理では、少量データでは問題なかった実装が、本番データ量で急激に遅くなることがあります。

悪い例:1件ずつDB登録する

For Each row In rows
    _repository.Insert(row)
Next

このコードでは、データ件数分だけSQLが実行されます。 10万件であれば10万回INSERTが発行されるため、非常に遅くなる可能性があります。

大量データ処理の改善方針

  • 1件ずつ処理しない
  • バルクインサートを利用する
  • 処理単位を分割する
  • 不要な列を取得しない
  • DataTableへ全件保持しすぎない
  • 途中経過をログに残す
  • 再実行可能にする
  • エラー行を退避できるようにする

チャンク処理の考え方

1. 1000件単位で読み込む
2. 1000件単位で検証する
3. 1000件単位でDB登録する
4. 失敗した単位をログに残す
5. 再実行できるようにする

大量データを一度にすべて処理すると、メモリ使用量やトランザクション時間が大きくなります。 一定件数ごとに分割して処理することで、安定性を高められます。

大量処理でログに残すべき情報

  • 処理開始日時
  • 処理終了日時
  • 対象ファイル名
  • 総件数
  • 成功件数
  • 失敗件数
  • エラー行番号
  • 処理時間
  • 再実行キー

大量データ処理では、性能だけでなく、失敗時にどこから再開できるかが重要です。 本番運用では、再実行性のない大量処理は大きなリスクになります。


8. バルクインサート

バルクインサートとは、大量データをまとめて高速にDBへ登録する方法です。 SQL ServerではSqlBulkCopyを使うことで、DataTableなどのデータを効率的に登録できます。

SqlBulkCopyの例

Public Sub BulkInsertUsers(table As DataTable)

    Using connection As New SqlClient.SqlConnection(_connectionString)
        connection.Open()

        Using bulkCopy As New SqlClient.SqlBulkCopy(connection)

            bulkCopy.DestinationTableName = "Users"

            bulkCopy.ColumnMappings.Add("UserName", "UserName")
            bulkCopy.ColumnMappings.Add("Email", "Email")
            bulkCopy.ColumnMappings.Add("CreatedAt", "CreatedAt")

            bulkCopy.BatchSize = 1000
            bulkCopy.BulkCopyTimeout = 300

            bulkCopy.WriteToServer(table)

        End Using
    End Using

End Sub

SqlBulkCopyを使うことで、1件ずつINSERTするよりも大幅に高速化できる場合があります。

バルクインサートのメリット

  • 大量登録が高速になる
  • DBへの往復回数を減らせる
  • CSV取込やデータ移行に向いている
  • バッチ処理の処理時間短縮に有効

バルクインサートの注意点

  • 入力データの事前検証が重要
  • 途中失敗時の扱いを設計する
  • トランザクション範囲を考慮する
  • 一時テーブルへの登録も検討する
  • 登録後の重複チェックや整合性チェックを設計する

一時テーブルを使う設計

1. CSVを読み込む
2. 一時テーブルへBulk Insertする
3. 一時テーブル上で形式チェック・重複チェックを行う
4. 正常データだけ本テーブルへ登録する
5. エラーデータをエラー管理テーブルへ保存する

大量取込では、いきなり本テーブルへ登録するよりも、一時テーブルを使う方が安全な場合があります。 検証、重複排除、差分更新、エラー管理を行いやすくなるためです。


9. ページング設計

検索結果が大量になる画面では、全件を一度に取得してDataGridViewへ表示すると、DB、ネットワーク、アプリケーションメモリ、UI描画のすべてに負荷がかかります。

そのため、一覧画面ではページング設計が重要です。

悪い例:全件取得

SELECT
    OrderId,
    OrderDate,
    CustomerName,
    TotalAmount
FROM Orders
ORDER BY OrderDate DESC

このSQLは、条件に一致する全件を取得します。 件数が数万件、数十万件になると、画面表示が非常に重くなります。

OFFSET / FETCHを使ったページング例

SELECT
    OrderId,
    OrderDate,
    CustomerName,
    TotalAmount
FROM Orders
ORDER BY OrderDate DESC
OFFSET @Offset ROWS
FETCH NEXT @PageSize ROWS ONLY

このSQLでは、指定した位置から指定件数だけ取得できます。 例えば、1ページ50件の一覧画面を作る場合に有効です。

VB.NET側の指定例

Dim pageSize As Integer = 50
Dim pageNumber As Integer = 1
Dim offset As Integer = (pageNumber - 1) * pageSize

command.Parameters.Add("@Offset", SqlDbType.Int).Value = offset
command.Parameters.Add("@PageSize", SqlDbType.Int).Value = pageSize

ページング設計のポイント

  • 1ページあたりの表示件数を決める
  • ORDER BYを必ず指定する
  • ORDER BYに適したインデックスを検討する
  • 総件数取得の負荷も考慮する
  • 検索条件を明確にする
  • 必要な列だけ取得する

総件数取得の注意点

SELECT COUNT(*)
FROM Orders
WHERE OrderDate >= @FromDate
  AND OrderDate <= @ToDate

総件数表示のためにCOUNTを実行する場合、条件によってはCOUNT自体が重くなることがあります。 大量データ画面では、「全件数を必ず表示する必要があるか」も業務要件として確認する必要があります。

キーセットページング

大量データでは、OFFSETが大きくなるほど遅くなる場合があります。 その場合、最後に表示したキーを基準に次ページを取得するキーセットページングも検討します。

SELECT TOP 50
    OrderId,
    OrderDate,
    CustomerName,
    TotalAmount
FROM Orders
WHERE OrderDate < @LastOrderDate
ORDER BY OrderDate DESC

キーセットページングは、深いページへ移動する用途には向きませんが、「次へ」「さらに表示」のようなUIでは高速に動作しやすいです。


10. DB負荷分散の考え方

DB負荷分散とは、DBサーバーへの負荷を分散し、システム全体の性能と可用性を高める考え方です。 ただし、アプリケーション側のSQLや設計が悪いままDBを増強しても、根本解決にならないことがあります。

DB負荷分散の前に確認すべきこと

  • 遅いSQLは特定できているか
  • 不要な全件取得をしていないか
  • N+1問題がないか
  • 適切なインデックスがあるか
  • 大量更新の時間帯は適切か
  • ロック競合は発生していないか
  • アプリ側でキャッシュできる情報はないか

代表的な負荷分散方法

  • 参照系と更新系の分離
  • 読み取り専用レプリカの利用
  • バッチ処理の時間帯分散
  • キャッシュの導入
  • 集計テーブルの作成
  • テーブルパーティショニング
  • アーカイブテーブルへの退避

参照系と更新系の分離

検索や帳票出力などの参照処理が多いシステムでは、読み取り専用DBやレプリカへ参照処理を逃がすことで、更新系DBの負荷を軽減できる場合があります。

キャッシュの活用

部署マスタ、権限マスタ、コードマスタなど、頻繁に変わらないデータは、アプリケーション側でキャッシュすることでDBアクセスを減らせます。

Public Class CodeMasterCache

    Private Shared _codes As List(Of CodeMaster)

    Public Shared Function GetCodes() As List(Of CodeMaster)

        If _codes Is Nothing Then
            _codes = LoadCodesFromDatabase()
        End If

        Return _codes

    End Function

End Class

ただし、キャッシュを使う場合は、データ更新時にいつ反映するかを設計する必要があります。 古いマスタを使い続けると業務不整合につながる可能性があります。

集計テーブルの活用

毎回大量データを集計するSQLは重くなりやすいです。 日次・月次で集計済みテーブルを作成し、画面では集計済みデータを参照する設計も有効です。

Sales
    → 日次集計バッチ
DailySalesSummary
    → 画面表示

アーカイブ設計

古い履歴データが大量に蓄積すると、検索や更新の性能に影響することがあります。 一定期間を過ぎたデータをアーカイブテーブルへ退避する設計も検討します。

  • 直近2年分は本テーブル
  • 2年以上前はアーカイブテーブル
  • 通常検索は本テーブルのみ
  • 過去検索時のみアーカイブを参照

DB負荷分散は、単なるインフラ対策ではありません。 アプリケーション設計、データ設計、業務要件、運用設計を含めて考える必要があります。


11. VB.NET側での性能改善ポイント

DB性能問題では、SQLやDBサーバーだけでなく、VB.NET側の実装が原因になっていることも多くあります。

VB.NET側でありがちな問題

  • ループ内でSQLを実行している
  • DataTableに大量データを全件読み込んでいる
  • DataGridViewへ大量行を一括表示している
  • SELECT * で不要な列まで取得している
  • 接続を開きっぱなしにしている
  • DataReaderを閉じ忘れている
  • 画面スレッドで重いDB処理を実行している
  • 検索条件なしで大量検索できてしまう

改善方針

  • 検索条件を必須にする
  • 取得列を必要最小限にする
  • ページングを導入する
  • ループ内SQLを一括取得へ変更する
  • UsingでConnection / Command / Readerを管理する
  • 重い検索は非同期化する
  • 画面表示用DTOを用意する

検索条件なしの大量検索を防ぐ例

If String.IsNullOrWhiteSpace(txtCustomerName.Text) AndAlso
   dtpFromDate.Value = Date.MinValue AndAlso
   dtpToDate.Value = Date.MinValue Then

    MessageBox.Show("検索条件を1つ以上入力してください。")
    Return

End If

業務画面では、全件検索が本当に必要かを確認することが重要です。 利用者の操作性を考慮しつつ、DBを守る設計が必要です。


12. 性能改善の進め方

性能改善では、いきなりSQLを書き換えるのではなく、原因を特定し、効果を測定しながら進めることが重要です。

性能改善の基本手順

1. 遅い機能を特定する
2. 処理時間を計測する
3. 遅いSQLを特定する
4. 実行計画を確認する
5. インデックスやSQLを見直す
6. アプリ側のSQL発行回数を確認する
7. ロックやデッドロックを確認する
8. 改善案を小さく適用する
9. 改善前後で処理時間を比較する
10. 副作用がないかリグレッションテストする

計測すべき情報

  • 画面表示にかかる時間
  • SQL単体の実行時間
  • SQL発行回数
  • 取得件数
  • CPU使用率
  • DB I/O
  • ロック待ち時間
  • バッチ全体の処理時間

ログ出力例

Dim stopwatch As New Stopwatch()

stopwatch.Start()

Dim result = _orderRepository.Search(condition)

stopwatch.Stop()

Logger.Info("注文検索 処理時間=" & stopwatch.ElapsedMilliseconds & "ms 件数=" & result.Count)

性能改善では、改善前後の数値比較が重要です。 「速くなった気がする」ではなく、実測値で判断します。


13. 実務でありがちなDB性能問題

インデックスを追加したのに速くならない

インデックスを追加しても、SQLの条件や実行計画によっては使われない場合があります。 実行計画で実際に使われているかを確認する必要があります。

開発環境では速いが本番で遅い

開発環境はデータ件数が少ないため、問題が見えにくいです。 本番相当のデータ量でテストしないと、性能問題を見逃します。

一覧画面で全件取得している

DataGridViewに数万件を表示しても、利用者がすべて見ることはほとんどありません。 検索条件とページングを導入するべきです。

夜間バッチがロックを長時間保持している

大量更新バッチが長時間トランザクションを保持すると、他処理に影響します。 チャンク処理や実行時間帯の調整が必要です。

デッドロック時に何も対策していない

デッドロックは完全にゼロにできない場合もあります。 発生原因の削減と、必要に応じた安全なリトライ設計が重要です。

DB負荷分散で根本原因をごまかしている

SQLやアプリ設計が悪いままDBサーバーを増強しても、根本解決にはなりません。 まずSQL、インデックス、SQL発行回数、ロック状況を分析する必要があります。


14. 演習課題

演習1:SQL実行計画を読む

注文検索SQLの実行計画を確認し、Table Scan、Index Seek、Sort、Key Lookupが発生しているかを整理してください。

演習2:インデックスを設計する

Ordersテーブルに対して、CustomerId、OrderDate、Statusを条件に検索する画面を想定し、適切なインデックスを設計してください。

演習3:N+1問題を改善する

注文一覧取得後に、ループ内で顧客名を1件ずつ取得している処理を、JOINまたは一括取得に修正してください。

演習4:楽観ロックを実装する

Version列を使って、他ユーザーが先に更新した場合に更新エラーとする仕組みを実装してください。

演習5:バルクインサートを実装する

CSVから読み込んだユーザーデータをDataTableへ変換し、SqlBulkCopyでUsersテーブルへ一括登録してください。

演習6:ページング検索を実装する

注文一覧画面で、1ページ50件ずつ表示するSQLとVB.NET側のページ番号計算処理を実装してください。

演習7:DB性能改善レポートを作成する

遅い検索画面を対象に、原因、実行計画、改善案、改善前後の処理時間、リスク、テスト結果をまとめた性能改善レポートを作成してください。


15. まとめ

本章では、VB.NET業務システムにおける高度なDB設計とパフォーマンスチューニングについて学習しました。

DB性能問題は、SQLだけでなく、アプリケーション設計、インデックス、トランザクション、ロック、データ量、同時実行数など複数の要因で発生します。 そのため、まずは遅い処理を特定し、実行時間、SQL発行回数、実行計画、ロック状況を確認することが重要です。

SQL実行計画を読むことで、Table Scan、Index Scan、Index Seek、Sort、Key Lookupなど、DB内部で何が起きているかを把握できます。 感覚でSQLを直すのではなく、実行計画を根拠に改善することが重要です。

インデックス設計では、WHERE句、JOIN、ORDER BY、GROUP BYで使われる列を考慮します。 ただし、インデックスを増やしすぎると更新性能が低下するため、検索性能と更新性能のバランスを取る必要があります。

N+1問題は、VB.NET業務システムで非常に起きやすい性能問題です。 ループ内でSQLを発行せず、JOINや一括取得を活用してSQL実行回数を減らすことが重要です。

ロック競合やデッドロックは、データ整合性と同時実行性に関わる重要な問題です。 トランザクション範囲を短くし、更新順序を統一し、必要に応じて楽観ロックやリトライ制御を設計します。

大量データ処理では、1件ずつDB処理するのではなく、チャンク処理やバルクインサートを活用します。 また、処理結果、失敗件数、エラー行、再実行キーをログに残し、本番運用に耐えられる設計にする必要があります。

一覧画面では、全件取得を避け、検索条件とページングを導入します。 DataGridViewへ大量データを一括表示する設計は、DBだけでなくUI性能にも悪影響を与えます。

DB負荷分散では、参照系と更新系の分離、読み取り専用レプリカ、キャッシュ、集計テーブル、アーカイブなどを検討します。 ただし、その前にSQL、インデックス、SQL発行回数、ロック状況といった基本的なボトルネックを解消することが重要です。

本章のゴールは、DB起因の性能問題を解析し、安全に改善できるようになることです。 性能改善は、単なる高速化ではなく、業務システムを安定して長期運用するための重要な設計スキルです。