...

SQL Server 2014 実践シリーズ No.1

by user

on
Category: Documents
412

views

Report

Comments

Transcript

SQL Server 2014 実践シリーズ No.1
SQL Server 2014 実践シリーズ No.1
インメモリ OLTP 機能の実践的な利用方法
Published: 2014 年 6 月 30 日
有限会社エスキューエル・クオリティ
SQL Server 2014 実践 No.1 インメモリ OLTP
この文章に含まれる情報は、公表の日付の時点での Microsoft Corporation の考え方を表しています。市場の変化に応える必要
があるため、Microsoft は記載されている内容を約束しているわけではありません。この文書の内容は印刷後も正しいとは保障で
きません。この文章は情報の提供のみを目的としています。
Microsoft、SQL Server、Visual Studio、Windows、Windows XP、Windows Server、Windows Vista は Microsoft Corporation
の米国およびその他の国における登録商標です。
その他、記載されている会社名および製品名は、各社の商標または登録商標です。
この文章内での引用(図版やロゴ、文章など)は、日本マイクロソフト株式会社からの許諾を受けています。
© Copyright 2014 Microsoft Corporation. All rights reserved.
2
SQL Server 2014 実践 No.1 インメモリ OLTP
目次
STEP 1.
インメモリ OLTP 機能の 実際の利用例..................................................................... 6
1.1
インメモリ OLTP 機能の概要 .................................................................................... 7
1.2
インメモリ OLTP 機能による実際の性能向上(早期導入事例) ......................................... 9
1.3
インメモリ OLTP の性能効果を期待できるシステム .......................................................13
1.4
大量のユーザーによる同時更新が発生するシステムに最適 ...............................................15
1.5
インメモリ OLTP はシングル実行でも性能向上を期待可能 ..............................................18
1.6
RDB での主な性能低下をインメモリ OLTP で解決 ........................................................22
1.7
INSERT 中心のシステムでも効果を発揮 ......................................................................24
1.8
bwin 社における利用例 ...........................................................................................30
1.9
SBI リクイディティ・マーケット株式会社での利用例 .....................................................35
1.10
Edgenet 社での利用例 .........................................................................................39
1.11
TPP 社での利用例 ...............................................................................................41
1.12
弊社のお客様 A 社の「ポイントカード システム」での検証結果 ....................................42
1.13
この章のまとめ ...................................................................................................57
STEP 2.
ポイントカード システムにおける インメモリ OLTP 機能の実装のポイント ....................61
2.1
ポイントカード システムの概要.................................................................................62
2.2
テーブル構成 .........................................................................................................63
2.3
シングル実行時の性能(ネイティブ コンパイル SP で 1.74 倍) .....................................64
2.4
bw-tree インデックスの利用 ....................................................................................73
2.5
SELECT ステートメントの具体的な性能向上例 .............................................................75
2.6
メモリ最適化テーブル変数の利用(IN、OR、UNION ALL の代替) ..................................78
2.7
UPDATE ステートメントの具体的な性能向上例 .............................................................81
2.8
INSERT ステートメントの具体的な性能向上例 .............................................................84
2.9
各ステートメントの性能に関するまとめ ......................................................................87
2.10
ネイティブ コンパイル SP を簡単に作成する方法 ......................................................89
2.11
ネイティブ コンパイル SP の実行方法の違いによる性能差 ..........................................99
2.12
char/varchar へ変更した場合の注意点 ................................................................. 102
2.13
ADO.NET の AddWithValue、JDBC の場合の注意点 ............................................... 110
2.14
char/varchar 型を利用する場合のメモリ使用量 ..................................................... 113
2.15
ネイティブ コンパイル SP での更新競合への対応 .................................................... 117
2.16
ネイティブ コンパイル SP での SELECT 時の BIN2 照合順序 .................................. 121
2.17
ネイティブ コンパイル SP にするとできなくなること .............................................. 125
2.18
ネイティブ コンパイル SP を利用するときのポイント .............................................. 128
STEP 3.
ポイントカード システムにおける インメモリ OLTP 機能への移行手順 ........................ 130
3.1
実際に移行時に行った作業 ...................................................................................... 131
3.2
バックアップとリストアによる DB 移行 ................................................................... 132
3.3
データベース移行後のデータベース設定の変更 ............................................................ 134
3.4
メモリ最適化テーブルへ移行する手順 ....................................................................... 140
3.5
既存テーブルからデータのコピー ............................................................................. 152
3.6
ネイティブ コンパイル SP の作成(オプション) ....................................................... 153
3
SQL Server 2014 実践 No.1 インメモリ OLTP
STEP 4.
インメモリ OLTP の その他の役立つ利用方法 ......................................................... 155
4.1
ELEVATE_TO_SNAPSHOT で自動的にスナップショット分離へ ..................................... 156
4.2
スナップショット分離レベルでの考え方 .................................................................... 159
4.3
更新競合が多発する場合の考え方 ~ナンバリング処理など~ ......................................... 162
4.4
メモリ使用量を制限したい場合 ~リソース プールの作成~ ........................................... 171
4.5
SELECT の取得データ件数が異なる場合の性能差 ........................................................ 174
4.6
JOIN がある場合の性能差 ...................................................................................... 183
4.7
bw-tree インデックスの選択基準、範囲スキャン ........................................................ 186
4.8
フル スキャンを避ける(インメモリ OLTP の苦手な処理) ........................................... 191
4.9
BUCKET_COUNT の違いによる性能差 ..................................................................... 200
4.10
性能検証をする上での注意点 ................................................................................ 207
4.11
メモリ最適化テーブルを作成するときの制限事項 ...................................................... 217
4.12
メモリ最適化アドバイザーによる移行チェック/変換 ................................................ 219
4.13
メモリ最適化テーブルの機能的な制限事項 ............................................................... 224
4.14
ネイティブ コンパイル SP の制限事項 .................................................................. 226
4.15
ネイティブ コンパイル アドバイザー .................................................................... 228
4.16
AMR ツール(データ コレクションによる分析/提案機能) ....................................... 230
4.17
ASP.NET Session State provider for SQL Sever In-Memory .................................... 232
4.18
1 秒間に 100 万件のデータ挿入も可能 ~Codeplex サンプル~ ................................... 237
4
SQL Server 2014 実践 No.1 インメモリ OLTP
はじめに
本ドキュメントは、SQL Server の最新バージョンである SQL Server 2014 で提供された「イ
ンメモリ OLTP」
(In-Memory OLTP)機能について、現場のエンジニアの方々が "即実践" で利
用できるスキルを身につけることを目的としたものです。
単なる機能解説やアーキテクチャを説明したドキュメントではなく、実際にどう役立つのか?、実
際にどうやって利用すれば良いのか? などを一番に考えて作成しています。

インメモリ OLTP を利用すると本当に速くなるのか?

どういったシステムなら、インメモリ OLTP の性能メリットを享受できるのか?

インメモリ OLTP を実際に使うにはどうしたら良いのか?

インメモリ OLTP で性能向上をする上でのコツは?

ネイティブ コンパイル ストアド プロシージャを作成するときのコツは?

インメモリ OLTP を検証/テストする上でのポイントは?

インメモリ OLTP が苦手な処理は?
など、ノウハウを満載したドキュメントになっています。
相互補完ドキュメント
本ドキュメントで扱っていない内容としては、インメモリ OLTP のアーキテクチャ(ハッシュ イ
ンデックスや bw-tree インデックス、データ ファイル、デルタ ファイルなどのアーキテクチャ)
がありますが、これらについては、以下のドキュメントに記載されています。
SQL Server In-Memory OLTP Internals Overview
http://download.microsoft.com/download/5/F/8/5F8D223F-E08B-41CC-8CE5-95B79908
A872/SQL_Server_2014_In-Memory_OLTP_TDM_White_Paper.pdf
本ドキュメントと相互補完することによって、より深くインメモリ OLTP を理解できるようにな
るので、お勧めのドキュメントになっています。
5
SQL Server 2014 実践 No.1 インメモリ OLTP
STEP 1. インメモリ OLTP 機能の
実際の利用例
第1章では、「インメモリ OLTP」機能の実際の利用例を説明します。既に早期導
入/検証を行っている企業では、どのようにインメモリ OLTP 機能を利用して、
どのように性能向上を実現しているのか、具体的な利用例を説明します。
この章の目次は、次のとおりです。

インメモリ OTLP 機能の概要

インメモリ OLTP の性能効果を期待できるシステムは?

シングル実行時の性能向上(どの部分で性能が向上するのか)

RDB での性能低下をインメモリ OLTP で解決

bwin 社でのインメモリ OLTP 機能の利用例

SBI リクイディティ・マーケット株式会社での利用例(FX 取引)

Edgenet 社での利用例(商品データのデータ サービスを提供)

ポイントカード システム(弊社のお客様)での検証結果

ラッチ待ちとは? ラッチ待ちを調べる方法

チェックポイントの負荷

インメモリ OLTP のガベージ コレクションの影響
6
SQL Server 2014 実践 No.1 インメモリ OLTP
1.1
インメモリ OLTP 機能の概要
「インメモリ OLTP」機能(開発コード名:Hekaton)は、SQL Server の最新バージョンである
SQL Server 2014 から提供された、OLTP(On-Line Transaction Processing:オンライン ト
ランザクション処理)向けの新しいデータベース エンジンです。
このエンジンの一番の利点は、
「インメモリ」で動作することで、非常に高速に処理できる点です。
テーブル内のデータを、全てメモリ内に載せることができるので、従来のディスク ベースのデー
タベース エンジンよりも非常に高速に動作させることができます。
本ドキュメントでは、実際にどれぐらい速くできるのか? また速くさせるためのコツはあるの
か? といった観点で、既に早期導入/検証を行っている企業での利用例をベースに、より実践的
な解説をしていきます。
SQL Server と完全に統合されている
インメモリ OLTP 機能は、SQL Server のデータベース エンジンと完全に統合されていること
も大きなメリットです。性能を向上させたい特定のテーブルのみをインメモリ化して、他のテーブ
ルは今まで同じように利用する、といった使い方も可能で、インメモリとディスク ベースのテー
ブルをハイブリッドに利用/共存させることができます。
インメモリ OLTP 機能を利用するには、SQL Server を通常どおりインストールするだけです。
SQL Server 2014 のインストール時の[機能の選択]ページで、次のように「データベース エ
ンジン サービス」を選択すれば、インメモリ OLTP 機能を利用することができます。
後は、テーブルの作成時に、次のように「MEMORY_OPTIMIZED = ON」キーワードを付ける
7
SQL Server 2014 実践 No.1 インメモリ OLTP
ことで、テーブルをインメモリ化することができます。
CREATE TABLE t1_InMem
( col1 int NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1000000)
,col2 nvarchar(100) )
WITH ( MEMORY_OPTIMIZED = ON )
このようにインメモリ化したテーブルと、従来のディスク ベースのテーブルは、同じデータベー
ス内に共存させることができるので、パフォーマンスが問題になっているテーブルのみをインメモ
リ化する、といった使い方ができます。
インメモリ化したテーブルは、ディスク ベースのテーブルと同様、INSERT や UPDATE、
DELETE、SELECT ステートメントを利用して操作することができます。次のように、普段利用
している Management Studio から同じように操作できます。
インメモリ化した
テーブル
INSERT
UPDATE
DELETE
SELECT
通常のディスク ベースのテーブルを
同じデータベース内に作成可能
ハイブリッドに利用できる
GUI 操作
も可能
通常のテーブルと
インメモリ化したテーブルを
JOIN することも可能
追加コスト不要
インメモリ OLTP 機能は、SQL Server 2014 Enterprise エディション X64 版の標準機能と
して提供されているので、追加コストがかからない点も大きなメリットです。特別なハードウェア
を購入する必要はなく、通常通り、SQL Server 2014 を動作させることができるハードウェアが
あれば利用することができます。後は、インメモリ化するテーブルのデータ量に応じて、メモリ搭
載量を決定するだけです。データベース内の全てのテーブルをメモリに載せる必要はないので、デ
ータベースの全使用量分(全テーブルのデータ量分)のメモリが必要になるわけではありません。
性能を向上させたいテーブルだけをインメモリ化することができます。
8
SQL Server 2014 実践 No.1 インメモリ OLTP
1.2
インメモリ OLTP 機能による実際の性能向上(早期導入事例)
既にインメモリ OLTP 機能を早期導入/検証を行っている企業では、次のような性能向上を実現
しています。
社名
性能向上
bwin 社
約 16.7倍の性能向上
15,000 バッチ要求数/sec が 250,000 バッチ要求数/sec へ向上。
最新の Lab Test では 450,000 バッチ要求数/sec を確認
(約 30倍の性能向上を確認)
SBIリクイディティ・マーケット
株式会社
約 2.5倍の性能向上*
処理件数が 52,080 件/sec から 131,921 件/sec へ向上。
従来は、トランザクションが集中する時間帯での
Latency(遅延)が約 4秒だったのを、1秒以下に短縮
Edgenet 社
約 8~11倍の性能向上
2時間 20分かかっていたバッチ処理を、わずか 20分に短縮。
更新にブロックされない読み取りにより、読み取り性能も向上
TPP 社
約 7倍の性能向上
34,700 トランザクション/sec が、
数十万 トランザクション/sec へ向上
弊社のお客様 A社
ポイントカード システム
約 2.8倍の性能向上
*マイクロソフトでは、「TAP」(早期検証プロジェクト)と呼ばれる、製品完成前のプレビュー版の段
階から検証を行うプログラムを展開しています。
日本における FX 取引で有名な SBIリクイディティ・マーケット株式会社では、2012年 8月に日本マイ
クロソフトから TAP への参加オファーを受けて、各種の検証を実施。本番稼働とほぼ同等の環境を構築
して入念な検証を重ね、インメモリ OLTP を中心として、さまざまなパフォーマンス チューニング(アー
キテクチャ変更など)を実施することで、元々のシステムと比較すると約 30倍もの処理パフォーマンス
向上を実現しています(インメモリ OLTP の部分では約 2.5倍の性能向上を実現)。
bwin 社では 16.7 倍の性能向上 ~45 万 Batch Reqeust/sec も~
既にこのエンジンを早期導入している「bwin」社(欧州サッカーでの大手スポンサーとしてもお
馴染みの、オンライン ゲームなどを提供している会社)では、従来 1 秒あたり 1 万 5 千件の
Batch Request(SQL Server に対するバッチ要求)を処理していたところを、インメモリ OLTP
機能を採用/移行することで、1 秒あたり 25 万件もの Batch Request を処理をできるように
なりました(16.7 倍もの性能向上)
。
* 左図は、SQL Server 2014 データシートより引用
SQL Server 2014 データシート
http://download.microsoft.com/download/9/8/0/980B9E8B-01474730-9AD1-964D06A7095A/SQL_Server_2014_Datasheet.pdf
9
SQL Server 2014 実践 No.1 インメモリ OLTP
また、Lab 環境での最新の検証では、450,000 Batch Requests/sec も達成していて、約 30
倍もの性能向上を確認しています。
458,937 Batch Requests/sec
を確認
*上の画面は、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより引用。吹出しは筆者が追加。
bwin 社での導入事例については、1.8 で詳しく説明します。
SBI リクイディティ・マーケット株式会社では 2.5 倍 ~国内事例~
日本における FX 取引で有名な「SBI リクイディティ・マーケット株式会社」では、インメモリ
OLTP 機能を利用することで約 2.5 倍の性能向上(1 秒あたりの処理件数が 52,080 件から
131,921 件へ向上)を実現しています(詳しくは、1.9 で説明します)
。
*上のグラフは、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより数値を引用
10
SQL Server 2014 実践 No.1 インメモリ OLTP
SBIリクイディティ・マーケット株式会社での FX 取引システム
*上の画面は、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより引用。写真(右)は、日本マイクロ
ソフト導入事例(http://www.microsoft.com/ja-jp/casestudies/sbilm2.aspx)より引用
また、同社では、SQL Server 2014 への移行に伴って、アプリケーションのアーキテクチャ変更
も実施し、これによって、元々のシステムと比較すると約 30 倍もの処理パフォーマンスの向上を
実現しています。
Edgenet 社では 8~11 倍の性能向上 ~DWH 環境での利用~
海外では、商品データのデータ サービスを提供している「Edgenet」社が DWH(データ ウェ
アハウス)環境での「ステージング テーブル」へインメモリ OLTP 機能を採用することで、各種
のバッチ処理で 8 倍~11 倍の性能向上を実現しています。
ETL(データの抽出/変換/ロード)処理においては、1,000 万件のデータを処理する際に、従来
のシステムでは 2 時間 40 分もかかっていたところを、
インメモリ OLTP 機能を採用することで、
わずか 20 分で処理が完了するようになっています(以下)。
従来システムでは
2時間 40分
インメモリ OLTP
は 20分で完了
1,000万件
*上のグラフは、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより数値を引用。吹出しは筆者が追加。
詳細については、1.10 で説明します。
11
SQL Server 2014 実践 No.1 インメモリ OLTP
TPP 社では 7 倍の性能向上 ~数十万トランザクション/sec~
UK(United Kingdom)では、
「TPP」社が、同社の提供している臨床ソフトウェア「SystemOne」
にインメモリ OLTP 機能を採用することで、7 倍の性能向上を実現しています。このソフトウェ
アは、主となるトランザクション データベースは 8 テラ バイト、1 日あたり 6 億 4000 万件の
トランザクション、ピーク時には、1 秒あたり 34,700 件のトランザクションを処理しています。
これを SQL Server 2014 のインメモリ OLTP へ移行することで、1 秒あたり数十万件ものトラ
ンザクションを処理できるように性能向上しています。
詳細については、1.11 で説明します。
弊社のお客様 A 社のポイントカード システム ~2.8 倍の性能向上~
弊社のお客様「A 社」の「ポイントカード システム」では、約 2.8 倍の性能向上という検証結果
を確認しています。
本番想定の 100多重の負荷テスト実施時の 1秒あたりの処理量
約 2.8倍
の性能向上
このシステムは、流通系企業でお馴染みの「ポイントカード」における、
「ポイントの入金」や「出
金」、
「残高照会」などを行うのが主なトランザクション処理になります。この負荷テストは、本番
のピーク時を想定して、1 時間継続して負荷をかけ続けたときのものです(グラフは、1 秒あたり
の処理量)
。
1 時間で処理できた件数は、次の表のとおりです。
1秒
1分
1時間
On Disk
1,743
104,569
6,274,117
In Memory
4,795
287,707
17,262,415
1時間あたり
1,726万もの処理
を実行できる
インメモリ OLTP を採用することで、1 時間あたり 1,726 万もの処理を実行できることを確認で
きました(詳しくは、1.12 で説明します)。
12
SQL Server 2014 実践 No.1 インメモリ OLTP
1.3
インメモリ OLTP の性能効果を期待できるシステム
bwin 社や SBI リクイディティ・マーケット株式会社、Edgenet 社、TPP 社、弊社のお客様 A
社など、実際に早期導入/検証を行った企業でのインメモリ OLTP 機能の利用方法をまとめると、
次のようになります。
社名/システム概要
システムの特性
導入効果
bwin 社
オンライン ゲームなど
を提供
大量のユーザーによる同時更新。
トランザクションとしては小さい。
毎日 15万人以上のアクティブ ユー
ザー。毎年 100万人以上の新規ユー
ザーが増加。
ASP.NET のセッション状態データ
ベースで利用。
約 16.7倍の性能向上
15,000 バッチ要求/sec が
250,000 バッチ要求/sec へ向上。
450,000 バッチ要求/sec も確認
(約 30倍の性能向上)
SBIリクイディティ・
マーケット株式会社
FX 取引(オンライン
トレード)
大量のユーザーによる同時更新。
トランザクションとしては小さい。
顧客のトレーディング データ(取引
履歴)をリアルタイムに集計
約 2.5倍の性能向上
52,080 件/sec が
131,921 件/sec へ向上。
ピーク時の Latency(遅延)は、
約 4秒だったのを、1秒以下に短縮
Edgenet 社
商品データを提供する
データ プロバイダー
DWH 環境でのステージング テーブル
で利用。ETL 処理など。
バッチ処理での大量のデータ更新。
更新中の参照アクセスあり
約 8~11倍の性能向上
2時間 20分かかっていたバッチ処
理が、わずか 20分に短縮。
更新中の参照アクセスが、
更新によってブロック(ロック待
ちなど)されることがなくなり、
読み取り性能も向上
TPP 社
臨床ソフトウェアの提供
大量のユーザーによる同時更新。
トランザクションとしては小さい。
1秒あたり 72,000 ユーザーが
同時アクセス(ピーク時)
約 7倍の性能向上
34,700 Transaction/sec が
数十万 Transaction/sec へ向上
弊社のお客様 A社
ポイントカード システム
大量のユーザーによる同時更新。
トランザクションとしては小さい。
ポイントカードにおける
ポイントの入金や出金、残高照会処理
約 2.8倍の性能向上
オンライン ゲームや、FX 取引、データ プロバイダー、オンライン サービス、ポイントカード シ
ステムなど、大量のユーザーによる同時更新およびトランザクションとしては小さい(=実行され
るステートメントが単純で、トランザクションが短い)、1 秒あたりのバッチ要求数が多いシステ
ムで、多く採用されています。これらは、まさに OLTP(オンライン トランザクション処理)シ
ステムの典型例で、そういったシステムで大きな効果を発揮できるのが インメモリ OLTP 機能で
す。
Edgenet 社では、DWH 環境でのステージング テーブルで利用したことで、バッチ処理の性能向
上
(8 倍~11 倍の性能向上)
を実現しています。
また、インメモリ OLTP 機能では、
更新中(UPDATE
/INSERT/DELETE ステートメントの実行中)に、読み取り操作(SELECT ステートメント)が
ブロックされることもないので、その分の性能向上も実現しています。
13
SQL Server 2014 実践 No.1 インメモリ OLTP
インメモリ OLTP の性能効果を期待できるシステム
インメモリ OLTP の性能効果を期待できるシステムは、次のとおりです。

大量のユーザーによる同時更新が発生するシステム = OLTP システムの典型例
(ラッチ待ちやロック待ちが多発しているシステム)

Latency(遅延)を小さくしたいシステム
SLA(サービスレベル契約)で数秒以内に応答を返すことを定義しているシステムなど

INSERT 中心のシステム(ほとんどの処理が INSERT)

トランザクション ログへの書き込みがボトルネックになっているシステム

更新にブロックされない読み取りを実現したい場合(Edgenet 社の例)

ASP.NET のセッション状態データベースとして SQL Server を利用している場合
(bwin 社では 16.7 倍の性能向上、Lab 環境では 45 万バッチ要求/sec を確認)
以降では、これらの状況について、もう少し詳しく見ていきます。
14
SQL Server 2014 実践 No.1 インメモリ OLTP
1.4
大量のユーザーによる同時更新が発生するシステムに最適
インメモリ OLTP 機能が最も効果を発揮するのは、大量のユーザーからの多数の更新要求が発生
するシステムです。オンラインゲームや、FX 取引、オンライン ショッピング サイト、チケット
予約、ポイントカード システムなど、トランザクションとしては小さく(実行されるステートメ
ントが単純で、短いトランザクション)、多数の同時実行によるブロッキングが発生しやすいシス
テムで最も効果を発揮します。
従来のディスク ベースのデータベース エンジンでは、データの更新時(INSERT/UPDATE/
DELETE 時)に、ロック待ち(Lock Wait)やラッチ待ち(Latch Wait)が原因で、同時実行性が
低下することがありましたが、インメモリ OLTP 機能では、このようなブロッキングは発生しま
せん。インメモリ OLTP 機能は、ロックおよびラッチを利用しないアーキテクチャ(Lock Free
/Latch Free)を採用しているからです。
ロック フリーな楽観的同時実行制御(tempdb 不使用のスナップショット分離)
インメモリ OLTP 機能では、ロックを利用しないマルチ バージョンの楽観的同時実行制御
(Optimistic Concurrency Control)を採用しています。これは、SQL Server 2005 からサポー
トされているスナップショット分離機構をインメモリ OLTP 向けに改良したものです。この新し
いスナップショット分離機構は、tempdb を利用しない、完全なインメモリ アーキテクチャです。
インメモリ OLTP 機能の楽観的同時実行制御では、次の図のように更新中のデータでも、ロック
待ちが発生することなく、データを読み取ることが可能です。
トランザクション
X
ロック待ちなし
で読み取り可能!
トランザクション
Y
【 col1=1 を更新 】
UPDATE t1_InMem
SET col2='1111'
WHERE col1=1
:
処理中
:
COMMIT or
ROLLBACK
更新
1
t1_InMemテーブル
col1
col2
1
1111
2
AAAAA
3
AAAAA
:
:
更新中のデータ。
未コミット(まだ確定
していない)データ
【 col1=1 を検索 】
更新前データ
(スナップショット)
1
AAAAA
2
SELECT *
FROM t1_InMem
WHERE col1=1
トランザクション開始時点
での正しいデータが返る
= 読み取り一貫性
スナップショットの格納先
として tempdb は利用しない
ラッチ待ちとは? ~スループットの低下~
従来ながらのディスク ベースのテーブルでは、大量のユーザーからの多数の更新要求がある場合
には、ラッチ待ちによるブロッキングが問題となり得ます。ラッチ(Latch)には、主にページ ラ
ッ チ ( PAGELATCH_SH 、 PAGELATCH_EX ) と
IO ラ ッ チ ( PAGEIOLATCH_SH 、
PAGEIOLATCH_EX)がありますが、前者は、ページへの同時アクセスを制御するための、SQL
Server が内部的にページに対してかけるロックのようなもの、後者は、データ ファイル(.mdf)
15
SQL Server 2014 実践 No.1 インメモリ OLTP
からメモリ内のデータ バッファへページを取り出すとき/書き出すときにかけるロックのような
ものです。
ページ ラッチ(PAGELATCH_SH や PAGELATCH_EX)は、次の図のように、同じページに対
して、多数のユーザーからの同時アクセスが発生した場合を制御するためのもので、同時に同じペ
ージを操作させないように、後からきたラッチを "待ち" にします(ページ ラッチ待ちの発生)。
同じページへの同時アクセスは、ページ ラッチ待ちが発生する
1
INSERT
3
INSERT
5
UPDATE
ページ
獲得
UPDATE
2
INSERT
4
SELECT
6
PageLatch_EX
PageLatch_EX
待ち
PageLatch_EX
PageLatch_EX
待ち
待ち
PageLatch_EX
PageLatch_SH
待ち
待ち
更新系のステートメント(INSERT/UPDATE/DELETE)では、PAGELATCH_EX(排他ページ
ラッチ。EX は Exclusive:排他の略)をかけにいき、SELECT ステートメントによる参照時には
PAGELATCH_SH(共有ページラッチ。SH は Shared:共有の略)をかけにいきます。このとき、
先にアクセスしている処理がある場合は(ラッチが既にかかっている場合には)、それが解放され
るまで、"待ち" が発生します。このような、同時アクセスによって待ちが発生する状態は、ラッ
チ競合(Latch Contention)とも呼ばれています。
このように、ラッチ待ちが発生すると、ユーザー数が増えれば増えるほど、ラッチ待ちも増えるこ
とになるので、スループットが低下していきます。
ラッチ待ちが発生するとスループットが低下
ページ
性能
ユーザー数が増えるほど
スループットが低下。
性能が頭打ちになる
ユーザー数
同時アクセスが
ブロックされる
これでは、同時実行数が増えれば増えるほど、システムの処理能力は頭打ちになり、スケールしな
くなってしまいます。
これに対して、インメモリ OLTP が採用している、ラッチを利用しない「ラッチ フリー」のアー
キテクチャであれば、ユーザー数が増えてたとしても性能低下は発生しにくくなります。
ラッチ フリーならユーザー数が増えても性能低下を抑えられる
性能
ユーザー数が増えても
性能が低下しにくくなる
ユーザー数
同時アクセス可能
16
SQL Server 2014 実践 No.1 インメモリ OLTP
ラッチ待ちが発生しているかどうかを調べる ~Latch Waits/sec~
現在のシステムで、ラッチ待ちが発生しているかどうかは、パフォーマンス モニター(システム モ
ニター)の Latch Waits/sec カウンターを利用して調べることができます。この値が大きい値
を示している場合には、ラッチ待ちが多発しているので、インメモリ OLTP 機能による性能向上
を享受できる可能性が高くなります。
ラッチ待ちが多発している場合には、次のグラフのように Latch Waits/sec カウンターが跳ね
上がります(赤の折れ線がラッチ待ちで、定期的に 15,000 ぐらいまで跳ね上がっています)。
Batch Requests/sec と Latch Waits/sec
Batch Request
(バッチ要求数)
50,000
45,000
1秒あたりの数
40,000
35,000
30,000
25,000
ラッチ待ち
20,000
15,000
10,000
5,000
OnDisk Latch Waits/sec
45:0
43:30
44:15
42:0
42:45
40:30
41:15
39:0
39:45
37:30
38:15
36:0
36:45
34:30
35:15
33:0
33:45
31:30
32:15
30:0
30:45
28:30
29:15
27:0
27:45
25:30
26:15
24:0
24:45
22:30
23:15
21:0
21:45
19:30
20:15
18:0
18:45
16:30
17:15
15:0
15:45
0
OnDisk Batch Requests/sec
弊社のお客様 A社「ポイントカード システム」での負荷テスト中のラッチ待ちの様子
インメモリ OLTP 化する前のディスク ベースのテーブルでの負荷テスト時。
このグラフのように Batch Requests/sec(バッチ要求数)と Latch Waits/sec(ラッチ待ち)
が連動している場合(跳ね上がる場所が同じ場合)は、複数のユーザーによる同時アクセスが発生
していて、ラッチ待ちが原因で性能が頭打ちになっている可能性があるので、このような場合に、
インメモリ OLTP 機能を利用することで性能向上を期待することができます。
17
SQL Server 2014 実践 No.1 インメモリ OLTP
1.5
インメモリ OLTP はシングル実行でも性能向上を期待可能
インメモリ OLTP 機能は、多数のユーザーによる同時実行時だけでなく、シングル実行(1 ユー
ザーによる単体実行)でも性能向上を期待できます。これについて、次のアーキテクチャをベース
に説明します(図は、TechEd North America 2014 のセッションより引用)
。
Application
TDS Handler and Session Management
Few improvements
in comm layers
20-40x more
efficient
Real Apps see 2-30x
Reduced log
contention; Low
latency still critical
for performance
Parser,
Catalog,
Optimizer
Hekaton
Compiler
NativeCompiled SPs
and Schema
Key
Execution Plan cache for
ad-hoc T-SQL and SPs
Existing SQL
Component
T-SQL Interpreter
Hekaton
Component
Access Methods
Non-durable
Table option
Query
Interop
Hekaton Engine: Memory_optimized
Tables & Indexes
Memory-optimized Table
Filegroup
SQL Server.exe
T
1
T
2
T
3
Tables
T
1
T
2
T
3
Indexes
Buffer Pool
T
1
T
2
T
3
T
1
T
2
T
3
T
1
T
2
T
3
T
1
T
2
T
3
Transaction Log
Generated
.dll
Data Filegroup
* 上の図は、TechEd North America 2014 での
「DBI-B287 SQL Server 2014: In-Memory OLTP Overview」セッションより引用
図の水色と緑の部分が、インメモリ OLTP のコンポーネントです(名称にある「Hekaton」は、
インメモリ OLTP の開発コード名です)
。
「SQL Server.exe」メモリ空間内に Hekaton Engine:
Memory-optimized Table&Indexes(インメモリ OLTP のエンジンと、メモリ最適化された
テーブルとインデックス)が常駐され、緑の部分の Native Compiled SP(ネイティブ コンパ
イル ストアド プロシージャ)は、ストアド プロシージャを事前コンパイルして DLL 化するこ
とができる機能です。
図では、この Hekaton Engine と Native Compiled SP の部分で、20-40x more efficient(20
~40 倍効率化)されて、Real Apps see 2-30x(リアル アプリケーションでは 2~30 倍の効
率化)と記載されています。
また、Non durable Table option は、データを永続化(durable)しないオプションで、
SCHEMA_ONLY オプションとも呼ばれ、メモリ内にのみテーブルとインデックスを配置します。
このオプションでは、トランザクション ログへの書き込みを行わず、図の左下にある
Memory-optimized Table Filegroup(インメモリ OLTP 用のファイル グループ)も利用しませ
ん(正確には、データを保存せずに、スキーマのみを保存するので、SCHEMA_ONLY と呼ばれて
います)
。
これに対して、データを永続化するオプションがあり、これは SCHEMA_AND_DATA と呼ばれ
ています。このオプションの場合は、トランザクション ログへの書き込みや、Memory-optimized
Table Filegroup への書き込みを行って、データを永続化します(永続化することで、電源断など
があったとしても、コミット済みのデータを復旧させることができます)。トランザクション ログ
18
SQL Server 2014 実践 No.1 インメモリ OLTP
への書き込みは、従来ながらのディスク ベースのテーブルよりも効率化されていますが、これに
ついては後述します。
どの部分で性能が向上するのか? ~接続、実行、ログ書き込み~
インメモリ OLTP 機能を利用することで、どの部分で性能が向上するのか、次の図で説明します。
No improvement
2-10X improvement
Same latency
Less volume
* 上の図は、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014:
In-Memory OLTP Customer Deployments and Lessons Learned」セッションより引用
こ の 図 も 、 TechEd North America 2014 の セ ッ シ ョ ン か ら 引 用 し た も の で す が 、 左 側 が
Traditional execution stack(従来ながらのディスク ベースの実行スタック)、右側がインメモ
リ OLTP の実行スタック、真ん中の Perfomance gain が性能向上に関してです。
上から、Client Connectivity(クライアント接続)に関しては、No Improvement(向上なし)
で、アプリケーションからの接続と切断(ADO.NET の場合は SqlConnection の Open と
Close)に関しては、ディスク ベースでもインメモリでも性能は変わりません。
性能が大きく変わるのは、Query Execution(クエリ実行)と Data Access(Buffer Pool)
の部分で、2-10x improvement(2~10 倍の性能向上)となっています。インメモリ OLTP で
は、ネイティブ コンパイル ストアド プロシージャを作成/利用することで、クエリ実行部分が
Procedure Execution(事前コンパイルした DLL によるプロシージャ実行)に代わって、デー
タ アクセスに関しては、メモリ最適化(Memory-Optimized)されることで、このような性能向
上を期待できます。
トランザクション ログへの書き込みは、Same Latency(遅延時間は同じ)とありますが、これ
はデータを永続化(SCHEMA_AND_DATA オプションを利用)した場合です。データを永続化
する場合には、ディスク ベースと同様、トランザクション ログへの書き込みが発生します。
ディスク ベースとの大きな違いは、インメモリ OLTP の方が Less volume(ログへの書き込み
量が少ない)という部分です。インメモリ OLTP では、インデックスに関する情報はログへは書
き込まないので、その分ログへの書き込み量を減らすことができます。インメモリ OLTP でのイ
ンデックスは、メモリ内にのみ配置し、永続化をしないからです(電源断などがあって、復旧が必
要な場合には、インデックスは再作成されます)
。
また、SQL Server 2014 からは、Delayed Durability(遅延書き込み)というオプションが提
供されて、ログへの書き込みを非同期で行えるようになったので、これでログへの書き込みの性能
19
SQL Server 2014 実践 No.1 インメモリ OLTP
向上を図ることもできます。このオプションを利用しない場合は、トランザクションのコミット時
にログへの書き込みを行って、書き込みが完了したことがコミットとなり、データの損失は発生し
ません。一方、Delayed Durability オプションを利用した場合は、ログへの書き込みが完了する
のを待たないで、コミットとすることができます(ログへの書き込みは遅延で行います)。したが
って、コミットしたにも関わらず、データの損失の可能性があり、コミット直後に電源断などが発
生した場合にはデータの損失が起こり得ます(データ損失のリスクと性能向上のトレード オフが
あります)。後述の Edgenet 社では、このオプションを利用することで性能向上を実現していま
す。
トランザクション ログへの書き込みは、データを永続化しない SCHEMA_ONLY オプションで
あれば、書き込みを行わないので、その分大きく性能を向上させることができます(電源断などが
発生した場合には、完全にデータが損失してしまうので、それとのトレード オフになります)
。後
述の bwin 社では、このオプションを利用することで、16.7 倍もの性能向上を実現しています(最
新のテストでは 30 倍もの性能向上も確認しています)
。インメモリ OLTP 機能の性能メリットを
最大限に活かすことができるのがこのオプションです。
ネイティブ コンパイル ストアド プロシージャによる性能向上
ネイティブ コンパイル ストアド プロシージャは、詳しくは第 2 章以降で説明しますが、通常の
SQL ステートメントのようなインタプリタ形式(その都度コンパイルする形式)ではなく、事前
にコンパイル済みの DLL を作成しておくことができる機能です。ネイティブ コンパイル ストア
ド プロシージャを作成する一番のメリットは、性能向上を期待できることです。
弊社のお客様では、次のような効果を確認しています。
100多重で実行した場合の性能差
ネイティブ SP を
作成/利用することで
2.8倍の性能向上
インメモリ化
で 1.8倍
弊社のお客様の A社「ポイントカード システム」でのネイティブ コンパイル SP の効果
SCHEMA_AND_DATA でデータの永続化有り。一部 Delayed Durability を利用(詳しくは後述)
アプリケーションを一切修正することなく、インメモリ OLTP 化しただけで 1.8 倍の性能向上、
ネイティブ コンパイル ストアド プロシージャ(グラフ内は Native SP と表記)を作成して、こ
れを利用するようにアプリケーションを修正することで 2.8 倍もの性能向上を確認することがで
20
SQL Server 2014 実践 No.1 インメモリ OLTP
きました。このように、大きな性能向上を期待できるのが、ネイティブ コンパイル ストアド プ
ロシージャです。
このグラフは、100 多重で実行した場合の負荷テストの結果ですが、シングル実行した場合の性能
差は、次の表のようになっています。
単位はマイクロ秒
INSERT
SELECT
UPDATE
合計
個数
3
6
3
割合
25%
50%
25%
12
On Disk In Memory
549.9
218.4
262.9
123.8
492.6
408.0
1,305.3
差
331.4
139.1
84.6
向上率% 何倍か
60.3%
2.5
52.9%
2.1
17.2%
1.2
750.3 555.1 42.5%
1.74
* ベンチマーク結果の公開は、使用許諾契約書で禁止されていますが、ハードウェア構成やテーブル構造などの詳細を明記しないことで、
同じ条件でのテストを実行できないようにすることで数値を掲載しております。
詳しくは後述しますが、ネイティブ コンパイル ストアド プロシージャを作成/利用することで、
全体として 1.74 倍の性能向上を確認することができました(12 個のステートメントで 1.3 ミリ
秒かかっていた処理を、750 マイクロ秒に短縮できました)
。
ネイティブ コンパイル ストアド プロシージャの作成概要
ネイティブ コンパイル ストアド プロシージャは、通常のストアド プロシージャと同様、
CREATE PROC ステートメントを利用して、簡単に作成することができます。次のように、WITH
句で「NATIVE_COMPILATION」オプションを付けることで、ネイティブ コンパイル ストア
ド プロシージャとして作成することができます。
CREATE PROC p_カードマスター検索
@p1 nchar(16), @p2 nchar(10)
WITH
NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'japanese' )
SELECT col1, col2, col3, col4, …
FROM dbo.カードマスター
WHERE カードID = @p1 AND 企業コード = @p2
END
BEGIN ATOMIC で囲んで、スナップショット分離レベル(ISOLATION LEVEL = SNAPSHOT)
を指定している以外は、見慣れた Transact-SQL ステートメントだと思います。このネイティブ
コンパイル ストアド プロシージャは、ポイントカード システムで実際に利用したもので、
「カー
ド ID」列が PRIMARY KEY の「カード マスター」テーブルから 1 件分のデータを取得するた
めのものです。これを作成/利用することで、約 1.8 倍の性能向上を確認しています(インメモリ
OLTP 化する前は、.NET アプリケーションから ADO.NET を利用して、直接 SELECT ステー
トメントを実行していました)
。
このように、インメモリ OLTP 機能では、ネイティブ コンパイル ストアド プロシージャを作成
することで、シングル実行でも性能向上を期待することができます。
21
SQL Server 2014 実践 No.1 インメモリ OLTP
1.6
RDB での主な性能低下をインメモリ OLTP で解決
従来ながらのディスク ベースの RDB(リレーショナル データベース)では、性能低下を引き起
こす原因として、主に次の 5 つがあります。
リレーショナル データベースでの主な性能低下の原因
ログ書き込み
ロック待ち
インデック
ラッチ待ち
ス管理
バッファ管理
・インデックスの断片化
・b-tree 構造の維持 etc
・データ バッファへの読み込み
・チェックポイント時の書き込み etc
これらが原因で性能が低下している場合は、インメモリ OLTP 機能を採用することで、性能向上
を期待することができます。
ロック待ちやラッチ待ちは、前述したように、多数のユーザーが同時に同じデータ(やページ)を
操作することで発生する "待ち" ですが、インメモリ OLTP 機能では、ロックおよびラッチを利
用しないアーキテクチャ(Lock Free/Latch Free)を採用しているので、こういった待ちに悩ま
されることがなくなります。
バッファ管理は、ディスクからデータ バッファ(メモリ)への読み込みとチェックポイント時の
データ ファイル(.mdf)への書き込みがありますが、前者はメモリを追加することで対応できま
す。インメモリ OLTP では、テーブル内の全てのデータをメモリへ配置(インメモリ化)します。
その分のメモリが必要になりますが、データベース内のすべてのテーブルをインメモリ化する必要
はなく、性能を向上させたいテーブルのみをインメモリ化することができます。
チェックポイント時のデータ ファイルへの書き込みは、詳しくは後述しますが、従来のデータベ
ース エンジンよりも、インメモリ OLTP 機能でのデータの書き込みのほうが負荷が軽いことを確
認しています。また、インメモリ OLTP 機能では、SCHEMA_ONLY(データの永続化なし)オ
プションを利用して、データの書き込みを行わないようにすることもできます。
インデックス管理は、従来のデータベース エンジンにおける通常のインデックス(b-tree 構造)
でのメンテナンス コストです。b-tree インデックスでは、データが追加/更新されて、データが
増えていくと、断片化が発生して、速度低下を引き起こします。これを解消するには、インデック
スを再構築(ReBuild)または再構成(ReOrganize)しなければなりません。この処理は、処理の
履歴をトランザクション ログへ書き込むので、データベース ミラーリングや、AlwaysOn 可用
性グループを利用している場合には、その処理履歴をミラー サーバーへ転送する負荷もかかりま
す。
インメモリ OLTP 機能では、ハッシュ インデックスがサポートされたことで、断片化に悩まされ
ることがなくなり、再構築や再構成は必要ありません。また、インデックスに関する更新情報は、
22
SQL Server 2014 実践 No.1 インメモリ OLTP
トランザクション ログに記録しないアーキテクチャを採用しているので、ログへの書き込み量を
削減できるというメリットも得られます。
ログへの書き込みは、前述したように、インメモリ OLTP 機能でも、SCHEMA_AND_DATA(デ
ータの永続化有り)オプションを利用している場合には行いますが、インデックスに関する更新情
報をログへ記録しない分、性能面でのメリットが得られます。また、Delayed Durability(遅延
書き込み)オプションを利用することで、ログへの書き込みを非同期で行えるようになるので、性
能向上を実現することができます。
SCHEMA_ONLY(データの永続化なし)オプションを利用した場合であれば、ログへの書き込み
を行わないこともできるので、その分大きく性能を向上させることができます。
Tips: ファイルの自動拡張は大きな性能低下の原因
RDB の速度低下の原因としては、
「ファイルの自動拡張」も存在します。例えば、次のようにフ
ァイル サイズを指定しないでデータベースを作成したとします。
CREATE DATABASE DB1
この場合は、5MB のデータ ファイル(.mdf)と 2MB のトランザクション ログ ファイル(.ldf)
が作成されて、容量が足りなくなった場合にはファイルが自動拡張していきます(データ ファ
イルは 1MB ずつ、ログ ファイルは現在のサイズの 10%ずつ拡張)。
しかし、ログ ファイルの自動拡張が発生した場合には、その間(拡張が完了するまで)
、ユーザ
ーのトランザクションが完全にブロックされることに注意しなければなりません。例えば、現在
のログ ファイルのサイズが 10GB で、10%の自動拡張で 1GB 分の拡張が必要だったとしま
す。このとき、ディスクの書き込み性能が 100MB/sec 程度である場合には、1GB 分の拡張
に 10 秒かかることになります(ディスクの性能が 500MB/sec なら 2 秒)
。そして、この間
はユーザーのトランザクションが完全にブロックされるわけです。もし、SLA(サービス レベ
ル保証)で 3 秒以内に結果を返す、と定義している場合には、10 秒間の処理待ちが発生したと
したらアウトになるわけです。
このような問題に対処するには、ログ ファイルのサイズを、あらかじめ大きめのサイズへ変更
しておくことです。インメモリ OLTP 機能でも、SCHEMA_AND_DATA(データの永続化有
り)オプションを利用している場合には、ログ ファイルへの書き込みを行うので、サイズを大
きくしておかないと、同じ問題が起こり得ます。十分大きなサイズへ設定しておくことをお勧め
します。なお、SCHEMA_ONLY(永続化なし)オプションを利用した場合には、ログへの書き
込みは行わないので、この問題は発生しません。
■ データ ファイルの場合は瞬時初期化によって瞬時に拡張される
データ ファイルの場合は、瞬時初期化機能を有効化しておくことで、ファイルの自動拡張が発
生しても、瞬時に拡張が完了するので、ログ ファイルのように大きな問題とはなりません(ロ
グ ファイルは瞬時初期化機能が効きません)。
瞬時初期化機能を有効化しておくことは、インメモリ OLTP 機能でも SCHEMA_AND_DATA
オプションを利用する場合に重要になるので、有効化する方法については第 3 章で説明します。
23
SQL Server 2014 実践 No.1 インメモリ OLTP
1.7
INSERT 中心のシステムでも効果を発揮
インメモリ OLTP 機能は、INSERT が中心のシステムでも効果を発揮します。例えば、センサー
系のデータを常に INSERT し続けるような状況や、大規模イベントでの「登録」や「予約」、
「投
票」のみを受け付けるようなシステムなどです。
インメモリ OLTP 機能では、次のような INSERT 性能を出すことができます。
On Disk の処理時間は
多重度が上がるほど遅くなる
400多重では
OnDisk と Durable は
2倍の差
100多重では
1.6倍の差
Delayed Durable なら
OnDisk より 2.2倍速い
SCHEMA_ONLY なら
OnDisk より 2.5倍速い
* ベンチマークの結果の公表は、使用許諾契約書で禁止されていますが、その効果をわかりやすく表現するため、
日本マイクロソフト株式会社の監修のもと、数値を掲載しております。
このテストは、サーバー機ではなく、約 15万円で購入したデスクトップ PC(普通のパソコン)で行っているので
サーバー機であれば、さらに良い性能になります。
テスト PC の構成: Core i7 3770K 3.5GHz 4コア、メモリ 32GB、HDD Western Digital WD30EZRX
多重度を上げた負荷テストには、弊社作成のテスト ツールを利用
テストに使用したテーブル、ネイティブ コンパイル ストアド プロシージャ
インメモリ OLTP
のテーブル
HASH インデックス
データの永続化の設定
テーブルには
10個の列
従来ながらの
ディスク ベース
のテーブル
1件のデータを INSERT する
ネイティブ コンパイル SP
24
SQL Server 2014 実践 No.1 インメモリ OLTP
テスト実行後に格納されるデータ(実際のアプリケーションを想定して、乱数を利用し、同じ値にならないように考慮)
col7 datetime
現在の日付
col5 nvarchar(50)
0~38バイト(乱数)の文字
col1
int
連番
col2 int
0~10000
の乱数
col3 int
0~1000
の乱数
col4 nvarchar(20)
0~19バイト(乱数)の文字
col8 datetime
現在の日付+1年
col6 nvarchar(100)
0~95バイト(乱数)の文字
col9 int
1 のみ
col10 nchar(1)
”0” のみ
この性能テストは、10 個の列を持ったテーブルを作成して、そのテーブルへデータを 1 件ずつ
INSERT を行って、5,000 万件分を INSERT したときの実行時間を測定したものです(データ
には、実際のアプリケーションを想定して、乱数を利用し、同じ値が格納されないようにしていま
す)
。
このテストは、Bulk Insert や SELECT INTO などの一括操作を利用したものではなく、またネ
イティブ コンパイル ストアド プロシージャの中でループ処理を記述して、複数件をまとめて
INSERT するようなものでもなく、実際のアプリケーションを想定して、1 件ずつの INSERT 処
理を行った場合の実行時間を測定しています。ネイティブ コンパイル ストアド プロシージャ内
の処理も、前述の図のように 1 件の INSERT を行うものしか記述していません。接続に関しても、
1 件の INSERT ごとに Open と Close を行っています。
実行結果は、従来のディスク ベースと比較して、インメモリ OLTP では、100 多重で 1.6 倍の
性能向上、400 多重では 2 倍の性能向上を確認することができました(この検証は、約 15 万円
のデスクトップ PC で行っているので、サーバー機であればさらに良い性能になります)
。
ディスク ベースのテーブルでは、多重度が上がっていくと、性能が頭打ちになってしまう(多重
度が上がるほど遅くなってしまう)ところが、インメモリ OLTP では、多重度が上がっても、性
能低下があまり発生していません(∵ラッチ フリー、ロック フリーのアーキテクチャであるため)。
また、Delayed Durability(遅延書き込み)オプションを利用した場合は、400 多重では 2.2
倍の性能向上、SCHEMA_ONLY(データの永続化なし/ログ書き込みなし)オプションを利用し
た場合は、400 多重では 2.5 倍の性能向上を確認することができました。
このように、インメモリ OLTP 機能は、常に INSERT をし続けるようなシステムでも大きな効
果を発揮します。センサー系のデータであれば、元々サンプリング(定期的な間隔)でデータを取
得していたりするので、Delayed Durability オプションで遅延書き込みを行っても、多少のデー
タのロスが許さるので、性能向上を実現できる有効なオプションです。
また、データの永続化が必要ないのであれば、SCHEMA_ONLY オプションを利用することで、
2.5 倍の性能向上を実現できるので、一時的なデータ領域として利用するときに便利なオプション
になります。
なお、このテストで利用したアプリケーションは、実際のアプリケーションを想定して、次のよう
に .NET(VB+ADO.NET)で作成しています(C#で作成してもほとんど同じコードになります)
。
25
SQL Server 2014 実践 No.1 インメモリ OLTP
乱数を利用して、
同じ値にならないよう
に配慮
ADO.NET の SqlConnection
ADO.NET の SqlCommand
SqlParameter を利用
このコードは、ディスク ベースのテーブルへ INSERT を行う場合のものですが、インメモリ
OLTP のネイティブ コンパイル ストアド プロシージャを利用する場合には、次のように 2 ヶ所
を修正しています。
CommandType を変更
ネイティブ コンパイル ストアド
プロシージャの名前に変更
SqlCommand の CommandType を変更して、CommandText をネイティブ コンパイル
ストアド プロシージャの名前に変更するだけで、ネイティブ コンパイル ストアド プロシージャ
を実行することができます。
以上のコードを、多重実行(並行実行)して、実行時間を計測しています。
26
SQL Server 2014 実践 No.1 インメモリ OLTP
ラッチ待ちの様子 ~インデックスの最終ページがホットスポット~
このテストで利用したテーブルは、col1 列を IDENTITY(1, 1) の PRIMARY KEY へ設定し
ているので、ディスク ベースのテーブルでは、多数のユーザーが同時にデータを追加することに
よって、次のようにインデックスの最終ページにアクセスが集中します(ディスク ベースのテー
ブルの場合は、PRIMARY KEY 制約を作成することで、自動的にクラスター化インデックスが作成
されています)
。
インデックスの最終ページでページ ラッチ待ちが多発
インデックスの最後のページ
がホットスポットになる
多数の同時追加
連番系の列(IDENTITY を設定した列や、シーケンスを設定した列)など、データを追加するた
びに連続した値(1、2、3、…)が格納されていくような場合には、このようなインデックスの最
終ページでページ ラッチ待ちが多発する(最終ページがホット スポットになる)ことがよくあり
ます。これは、シングル実行(1 人のユーザーによる単体実行)では、発生しないものですが、多
数のユーザーが同時にデータを追加する場合には発生してしまいます。
実際に、400 多重のときのラッチ待ちの様子をパフォーマンス カウンターで取得すると、次のよ
うになります。
400多重で 5,000万件を INSERT しているときのラッチ待ちの様子(On Disk)
ラッチ待ちが
Batch Request の
3倍近く発生している
Batch Request
(バッチ要求数)
Batch Request(バッチ要求数)の数の 3 倍近いラッチ待ち(Latch Waits)が発生してしまっ
ています。
次に、40 多重の場合のラッチ待ちの様子をパフォーマンス カウンターで確認すると、次のように
なります。
27
SQL Server 2014 実践 No.1 インメモリ OLTP
40多重で 5,000万件を INSERT しているときのラッチ待ちの様子
ラッチ待ちが
Batch Request の
1.7倍ぐらい発生
Batch Request
(バッチ要求数)
40 多重では、Batch Request の 1.7 倍ぐらいのラッチ待ちが発生しています。これに対して、
400 多重では 3 倍近いラッチ待ちが発生していたことからも、多重度が上がることで、ラッチ待
ちが増えてしまうことを確認できると思います。
なお、このように、ラッチ待ちが多発している場合には、CPU 利用率(%Processor Time)が
低い値になり、フル活用されていない状態になります(100% 利用されていない状態)
。
CPU 利用率を追加したグラフ(400多重の On Disk)
CPU 利用率が
60% 近辺を推移
CPU 利用率
これに対して、インメモリ OLTP の Durable では、次のように CPU 利用率が推移しています。
Durable での CPU 利用率の推移(400多重の In Memory)
インメモリでは
Batch Request が安定している
CPU 利用率が
80% 近辺を推移
ラッチ待ちは
発生しない
28
SQL Server 2014 実践 No.1 インメモリ OLTP
インメモリ OLTP ではラッチ待ちが発生しない分、Batch Request が高い値で安定しています。
また、CPU 利用率は 80%近辺を推移して、ディスク ベースのときよりも CPU を活用していま
す。100% 近くまでフル活用していない理由としては、Durable ではログ書き込みがあること、
ログの配置先が HDD(約1万円の 3TB HDD:Western Digital WD30EZRX、RAID なしのシ
ングル構成)であること、多重度が上がりすぎていること(今回のテストで利用した 4 コアの CPU
に対しては 400 多重では負荷が高すぎること)
、
1 件の INSERT ごとに 接続の Open と Close
を繰り返していることなどがあります。
したがって、今回の環境では、多重度を 100 に下げて、ログ書き込みをしない(SCHEMA_ONLY
を利用)
、接続をキープする(100 個の接続を作った後に、その接続をキープする)ことで、CPU を
フル活用に近い状態まで持っていくことができます(以下のグラフ)
。
100多重で 接続をキープしたままの場合の SCHEMA_ONLY での CPU 利用率
CPU 利用率が
95% 近辺を推移
Batch Request が
9万近辺を推移
1秒間に 9万件の INSERT を実現
(約15万円の普通のパソコンでのテスト)
このように、接続をキープできるような状況であれば、CPU をさらに活用することができるので、
ディスク ベースとインメモリ OLTP の性能差をさらに広げることができます(以下のグラフ)
。
400多重では
OnDisk と Durable は
2.4倍の差
100多重では
1.9倍の差
Delayed Durable なら
OnDisk より 2.9倍速い
SCHEMA_ONLY なら
OnDisk より 3.3倍速い
* テストに利用したマシンは、前のテストと同様、約 15万円のデスクトップ PC
テスト PC の構成: Core i7 3770K 3.5GHz 4コア、メモリ 32GB、HDD Western Digital WD30EZRX
多重度を上げた負荷テストには、弊社作成のテスト ツールを利用
29
SQL Server 2014 実践 No.1 インメモリ OLTP
1.8
bwin 社における利用例
ここからは、早期導入/検証を行った企業では、どのようにインメモリ OLTP を利用したのか、
具体的に見ていきます。
bwin 社(オンライン ゲームなどを提供する会社)では、従来 1 秒あたり 1 万 5 千件の Batch
Request(SQL Server に対するバッチ要求)を処理していたところを、インメモリ OLTP 機能
を採用/移行することで、1 秒あたり 25 万件もの Batch Request を処理をできるようになり
ました(16.7 倍もの性能向上)。
* 左図は、SQL Server 2014 データシートより引用
SQL Server 2014 データシート
http://download.microsoft.com/download/9/8/0/980B9E8B-01474730-9AD1-964D06A7095A/SQL_Server_2014_Datasheet.pdf
bwin 社での早期導入の詳細については、以下の米マイクロソフトの事例(Case Studies)サイト
に記載されています。
http://www.microsoft.com/casestudies/Microsoft-SQL-Server-2014/bwin.party/GamingSite-Can-Scale-to-250-000-Requests-Per-Second-and-Improve-Player-Experience/71000
0003117
30
SQL Server 2014 実践 No.1 インメモリ OLTP
また、2014 年 5 月に米ヒューストンで開催された Microsoft TechEd North America 2014 で
の以下のセッションでも詳細が説明されていたので、ここではその内容を要約します。
Microsoft SQL Server 2014: In-Memory OLTP Customer Deployments and Lessons Learned
http://channel9.msdn.com/Events/TechEd/NorthAmerica/2014/DBI-B313#fbid=
Web サイトの利用状況 ~15 万/日、100 万新規ユーザー/年~
bwin 社が運営する Web サイト(オンライン エンターテイメントを提供している Web サイト)
では、1 日あたり 15 万人以上のアクティブ ユーザーがアクセスし、毎年 100 万人以上の新規ユ
ーザーが登録されています。年間収益は 6.5 億ユーロにもなります。
ASP.NET のセッション状態データベースで負荷集中 ~ラッチ待ちの多発~
Web サイトの構成は、次のとおりです。
SessionState
WebServer Farm
* 上の図は、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより引用
Web サーバー ファーム(多数の Web サーバーによるフロントエンド)に、ASP.NET のセッシ
ョン状態(セッション変数)を格納するためのデータベースとして、SQL Server が利用されてい
ます。Web サーバー上のそれぞれの Web ページからは、SQL Server に対して「2」個のバッ
チ要求が発生していて、ピーク時には SQL Server への負荷が集中していました(これは、同時
に 1 万ページ分のアクセスがある場合に、2 万ものバッチ要求が発生する状況です)。
インメモリ OLTP を導入する前のシステムでは、ラッチ待ちが多発することによって、最大スル
ープットが「15,000 バッチ要求/秒」で頭打ちになっていました。これは、RamDrive を利用
(メモリをストレージとして利用)している場合にも同様でした。また、Latency(遅延、クライ
アントへの応答時間)も加速度的に増えていっており、これを解決する必要がありました。
ラッチ フリー、SCHEMA_ONLY の利用
この問題を解決するために、同社では SQL Server 2014 のインメモリ OLTP を採用することで、
31
SQL Server 2014 実践 No.1 インメモリ OLTP
1 秒あたり 25 万件ものバッチ要求を処理をできるようになりました(16.7 倍もの性能向上)
。
このような性能向上が実現できた理由の 1 つは、インメモリ OLTP がラッチ フリー(ラッチを
利用しない同時実行制御)のアーキテクチャであるためです。これまでのシステムでは、多数の同
時アクセスが行われた場合に、ラッチ待ちが多発して、スループットに限界がありましたが、イン
メモリ OLTP では同時アクセス数が増えてもラッチ待ちが発生しないからです。
性能向上の理由には、ASP.NET のセッション状態に関するテーブルに、
「SCHEMA_ONLY」オプ
ションを採用したことも大きく関係しています。このオプションでは、データを永続化することな
く、メモリ内にのみデータを配置します。これにより、トランザクション ログへの書き込みが必
要なくなるので、インメモリ OLTP 機能の性能を最大限に引き出すことができます。
クライアント コードは未修正、互換性を保つ ~BLOB への対応~
今回、インメモリ OLTP の採用を決定したチーム(インフラ チーム)には、クライアント コー
ドをコントロールすることができなかったので、インメモリ OLTP 化にあたっては、クライアン
ト コードへの 100%の互換性を保つことも要求されていました。
クライアント コードへの互換性を保つためには、セッション状態を格納するための列が BLOB
(image/varbinary(max) データ型)であることが課題でしたが(∵インメモリ OLTP 機能
では BLOB へネイティブに対応していないため)、これに対しては、データを分割することで対応
しています。この BLOB データの分割については、オンライン ブックの以下のトピックに実装
例が記載されているので、参考になると思います。
メモリ最適化テーブルへの LOB 列の実装
http://msdn.microsoft.com/ja-jp/library/dn296676.aspx
なお、後述の「Microsoft ASP.NET Session State provider for SQL Sever In-Memory
1.0.1」を利用した場合には、自動で BLOB への対応が行われたセッション状態データベースを
作成することもできます。
32
SQL Server 2014 実践 No.1 インメモリ OLTP
Write-Write 競合への対応 ~エラー 41302~
インメモリ OLTP 機能は、ロック フリー(ロックを利用しない同時実行制御)のアーキテクチャ
なので、同じデータへの同時更新が発生した場合には、Write-Write 競合(更新競合)が発生し
ます。この場合は、先に更新した方が勝つ方式(First Writer Win)をとり、負けた方(後から更
新した側)には、エラー 41302 が通達されます。bwin 社では、エラー 41302 が発生した場
合には、次のように再試行を行うロジックを利用することで、Write-Write 競合への対応を行っ
ています(Write-Write 競合については、第 2 章と第 4 章でも説明しています)
。
@IsDone = 0 の間
繰り返す。
成功するまで再試行を行う
成功なら @IsDone = 1
で WHILE ループを抜ける
41302(更新競合)が発生
した場合は 1ミリ秒待機して
再試行を行う
* 上のコードは、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより引用。吹出しは筆者が追加。
1 秒あたり 450,000 バッチ要求も達成 ~約 30 倍の性能向上~
bwin 社では、Lab 環境での最新の検証では、450,000 バッチ要求/sec を処理できることも確
認しています(元々は 15,000 バッチ要求/sec だったので、約 30 倍の性能向上)
。
458,937 Batch Requests/sec
を確認
*上の画面は、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより引用。吹出しは筆者が追加。
33
SQL Server 2014 実践 No.1 インメモリ OLTP
ASP.NET Session State provider for SQL Sever In-Memory
ASP.NET のセッション状態データベースのインメモリ化は、NuGet(.NET 用のパッケージ マネ
ージャー)から取得できる「Microsoft ASP.NET Session State provider for SQL Sever
In-Memory 1.0.1」を利用すると、簡単に実装することができます。
http://www.nuget.org/packages/Microsoft.Web.SessionState.SqlInMemory/
このプロバイダーをインストールすると、次のようにインメモリ化するためのスクリプトが提供さ
れているので、簡単にインメモリ化することができます(詳しくは、第 4 章で説明します)
。
BLOB 対応
データを分割して格納
するためのテーブル
インメモリ化した
セッション変数格納用
のテーブル
BLOB ではない場合は
こちらのテーブルに格納
34
SQL Server 2014 実践 No.1 インメモリ OLTP
1.9
SBI リクイディティ・マーケット株式会社での利用例
日本における FX 取引で有名な「SBI リクイディティ・マーケット株式会社」では、インメモリ
OLTP 機能を利用することで約 2.5 倍の性能向上を実現しています(1 秒あたりの処理件数が
52,080 件から 131,921 件へ向上)
。
*上のグラフは、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより数値を引用
SBIリクイディティ・マーケット株式会社での FX 取引システム
*上の画面は、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより引用。写真(右)は、日本マイクロ
ソフト導入事例(http://www.microsoft.com/ja-jp/casestudies/sbilm2.aspx)より引用
また、同社では、SQL Server 2014 への移行に伴って、アプリケーションのアーキテクチャ変更
も実施し、これによって、元々のシステムと比較すると約 30 倍もの処理パフォーマンスの向上を
実現しています。
SBI リクイディティ・マーケット株式会社での早期導入の詳細については、以下の日本マイクロソ
フトの事例サイトに記載されています。
SBI リクイディティ・マーケット株式会社の事例
http://www.microsoft.com/ja-jp/casestudies/sbilm2.aspx
35
SQL Server 2014 実践 No.1 インメモリ OLTP
この記事の中で、同社の代表取締役社長 重光 達雄 氏は、次のように語っています。
「今や FX の取引高は、日本が世界のトップに立っています。当社はその業界のトップ ランナ
ーとしての責務を果たすべく、日本マイクロソフトの協力の下、SQL Server を中心とした取引シス
テムのパフォーマンスや可用性、信頼性の向上に大きな力を注いできました。ちなみに当社の取
引金額は、2013 年 1 ~ 12 月の 1 年間で約 610 兆円に達しています。日本の 2013 年度
一般会計予算、約 92.6 兆円の約 6.6 倍、東証一部上場企業の 2013 年度の株式売買代金、
約 640 兆円 (出典:株式会社東京証券取引所) と、ほぼ同等な取引金額を扱うシステムと言え
ば、規模の大きさやミッション クリティカルの度合いを感じていただけるでしょうか」
「今後、FX 取引は、アジア圏でも大きく伸びることが確実と予想されています。そうなれば、取
引量も今の何倍、何十倍にも膨らんでいくでしょう。当社でもその市場規模に応え得る強力なパ
フォーマンスや取引精度を提供できる、より速く可用性に優れた取引システムを早急に実現しな
くてはなりません。その意味で SQL Server 2014 のインメモリ OLTP 機能は大きな可能性を秘
めており、しかもこれまで築き上げてきた環境を生かしながらシステムの能力を強化できると確信
したのが、TAP 参加に踏み切った理由でした」(ここまで事例記事より引用)
なお、「TAP」は、マイクロソフトが展開している ”早期検証プロジェクト” で、製品完成前のプ
レビュー版の段階から検証を行うプログラムです。SBI リクイディティ・マーケット株式会社では、
2012 年 8 月に日本マイクロソフトから TAP への参加オファーを受けて、各種の検証を実施、本
番稼働とほぼ同等の環境を構築して入念な検証を重ね、インメモリ OLTP を中心として、さまざ
まなパフォーマンス チューニングを実施しました。
36
SQL Server 2014 実践 No.1 インメモリ OLTP
システム構成 ~売買集計システムでインメモリ OLTP を採用~
SBI リクイディティ・マーケット株式会社では、「カバー取引」と呼ばれる処理のためのシステム
である「売買集計システム」
(顧客の取引履歴のリアルタイム集計)に、インメモリ OLTP 機能を
採用しています。このシステムの構成は、次のとおりです。
*上の図は、日本マイクロソフト導入事例(http://www.microsoft.com/ja-jp/casestudies/sbilm2.aspx)
より引用
この売買集計システムには、顧客の行った FX 取引の情報(取引履歴)が絶えず送られ、それを即
時に集計し、その集計結果をもとにカバー取引を行っています。これは、同社の収支に直結する非
常に重要な処理になりますが、従来のシステムでは、トランザクションが集中する時間帯に集計処
理に時間がかかってしまう、Latency(遅延)が発生してしまうという問題がありました。
また、トランザクション数は日々増えており、将来的な、加速度的なトランザクションの増大にも
対応していく必要がありました。前項の社長のコメントのように、現在、FX の取引高は、日本が
世界のトップ、アジア圏でも大きく伸びることが確実と予想され、取引量は、現在の何倍、何十倍
にもなることが予想されています。
更新系処理でのロック/ラッチ待ちを解消するためにインメモリ OLTP を採用
売買集計システムでは、リアルタイムでの集計を行うために、集計のための元データ(顧客の取引
履歴)を、売買集計システムへ INSERT する処理や、集計結果を UPDATE する処理など、更
新系の処理でも高い処理能力が要求されます。しかし、従来のシステムでは同時更新が発生した場
合の「ロック待ち」や「ラッチ待ち」が問題となっていました。
これらの問題を解決するために採用したのが、インメモリ OLTP 機能です。インメモリ OLTP 機
能は、ロック フリー/ラッチ フリーのアーキテクチャなので、ロック待ちやラッチ待ちに悩まさ
れることがなくなります。
インメモリ OLTP 機能を利用することによって、冒頭の約 2.5 倍の性能向上を実現することがで
きました(グラフを以下に再掲)。
37
SQL Server 2014 実践 No.1 インメモリ OLTP
*上のグラフは、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより数値を引用
このグラフは、カバー取引のためのリアルタイム集計(顧客の取引履歴の集計)での性能向上の結
果で、処理内容の具体的な内訳(実際に実行している SQL ステートメントの内訳)は、次のとお
りです。

INSERT*1

UPDATE*1

SELECT*1

UPDATE または INSERT*1
これらの 1 秒あたりの処理件数は、従来のシステムでは 52,080 件だったところを、インメモリ
OLTP を採用することによって、131,921 件もの処理ができるように性能向上しています。
また、従来のシステムでは、トランザクションが集中する時間帯での Latency(遅延)に問題が
ありましたが(約 4 秒の遅延が発生)、インメモリ OLTP を採用することによって、1 秒未満に
抑えられるようになりました。
データの永続化に関しては、前項の bwin 社では、SCHEMA_ONLY オプションを利用して、デ
ータを永続化していませんでしたが、SBI リクイディティ・マーケット株式会社では、扱っている
データが非常にクリティカルなので、SCHEMA_AND_DATA オプションを利用して、データを
永続化しています。このオプションでは、ログへの書き込みが発生しますが、電源断などがあって
も、SQL Server が再起動されても、コミット済みのデータが失われることがありません。
また、同社では、インメモリ OLTP と AlwaysOn 可用性グループと組み合わせて利用して、デ
ータの保護(データベースの複製/二重化)も行って、可用性を大きく高めています。なお、イン
メモリ OLTP と AlwaysOn 可用性グループを組み合わせるためには、SCHEMA_AND_DATA
オプションを利用するのが必須になります。
38
SQL Server 2014 実践 No.1 インメモリ OLTP
1.10 Edgenet 社での利用例
商品データのデータ サービスを提供している、米 Edgenet 社では、DWH(データ ウェアハウ
ス)環境での「ステージング テーブル」へインメモリ OLTP 機能を採用することで、各種のバッ
チ処理で 8 倍~11 倍の性能向上を実現しています。
ETL(データの抽出/変換/ロード)処理においては、1,000 万件のデータを処理する際に、従来
のシステムでは 2 時間 40 分もかかっていたところを、
インメモリ OLTP 機能を採用することで、
わずか 20 分で処理が完了するようになっています(以下)。
従来システムでは
2時間 40分
インメモリ OLTP
は 20分で完了
1,000万件
*上のグラフは、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより数値を引用。吹出しは筆者が追加。
詳細については、米マイクロソフトの事例サイト(Case Studies)に記載されています。
Edgenet 社
http://www.microsoft.com/casestudies/Microsoft-SQL-Server-2014-Enterprise/Edgenet/
Data-Services-Firm-Gains-Real-Time-Access-to-Product-Data-with-In-Memory-Technolog
y/710000003026
また、bwin 社と同様、Microsoft TechEd North America 2014 でのセッション DBI-B313 で
39
SQL Server 2014 実践 No.1 インメモリ OLTP
も詳細が説明されていたので、ここではその内容を要約します。
Microsoft SQL Server 2014: In-Memory OLTP Customer Deployments and Lessons Learned
http://channel9.msdn.com/Events/TechEd/NorthAmerica/2014/DBI-B313#fbid=
サービス概要 ~最適化された商品データの提供~
Edgenet 社のサービス概要は、次のとおりです。
* 上の図は、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより引用
Edgenet 社は、Supplier(製造業者)や Retailer(小売業者)、Bing や Google などの検索エン
ジン向けに、最適化された商品データを提供しています(消費者が、製品を購入するにあたって、
比較に役立つ商品データを提供)。従来のシステムでは、データの件数が多い場合に、バッチ処理
に時間がかかってしまう(データの遅延が発生してしまう)、参照データにおけるロック待ちを回
避するために、アプリケーション層でデータ キャッシュを用意している、といった課題がありま
した。
Delayed Durability の採用、更新にブロックされない読み取り
同社では、これらの課題を解決するべく、インメモリ OLTP 機能を採用し、商品データをできる
限りリアルタイムで配信できるように改善しました。商品データを生成するための各種のバッチ処
理の性能は、8 倍~11 倍に向上し、従来のシステムでは 2 時間 40 分もかかっていた処理を、わ
ずか 20 分で完了するようになりました(前掲のグラフ)
。
インメモリ化したテーブルで、データ件数が一番大きいものは 1.15 億件、SCHEMA_AND_
DATA オプションを利用してデータを永続化しています。そして、性能向上を実現するために、
Delayed Durability(遅延永続化)オプションをデータベース レベルで設定しています(Delayed
Durability の設定方法や利用方法については、第2章以降で説明します)。
また、インメモリ OLTP 機能を採用することで、バッチ処理中(データ更新中)のデータを参照
できるようになったので(ロック待ちが発生しなくなったので)、アプリケーション層でのデータ
キャッシュも用意する必要がなくなりました。
40
SQL Server 2014 実践 No.1 インメモリ OLTP
1.11 TPP 社での利用例
UK(United Kingdom)では、
「TPP」社が、同社の提供している臨床ソフトウェア「SystemOne」
にインメモリ OLTP 機能を採用することで、7 倍の性能向上を実現しています。
詳細については、米マイクロソフトの事例サイト(Case Studies)に記載されています。
http://www.microsoft.com/casestudies/Microsoft-SQL-Server-2014/TPP/Clinical-Softwar
e-Easily-Supports-Thousands-of-New-Users-and-Helps-Doctors-Save-Lives/7100000034
30
この事例の内容を要約すると、従来の SystemOne(臨床ソフトウェア)は、SQL Server 2008
R2 をベースに作られていて、3,000 万人分の患者レコード(イングランドの人口の半分以上にも
なる数)を管理することができ、200,000 ものアクティブ ユーザー(UK の NHS:National
Health Service)がいて、ピーク時には 72,000 ものユーザーが同時にアクセスしています。
また、700 テラ バイト以上(主となるトランザクション データベースは 8 テラ バイト)
、1 日
あたり 6 億 4000 万件のトランザクション、ピーク時には、1 秒あたり 34,700 件のトランザク
ションを処理しています。
これを SQL Server 2014 のインメモリ OLTP へ移行することによって、1 秒あたり数十万件も
のトランザクションを処理できるように性能向上しました。
41
SQL Server 2014 実践 No.1 インメモリ OLTP
1.12 弊社のお客様 A 社の「ポイントカード システム」での検証結果
弊社のお客様「A 社」の「ポイントカード システム」では、インメモリ OLTP 機能を採用するこ
とで、2.8 倍の性能向上を実現できることを確認しました。
本番想定の 100多重の負荷テスト実施時の 1秒あたりの処理量
約 2.8倍
の性能向上
このお客様のポイントカード システムは、流通系企業でお馴染みの「ポイントカード」における、
「ポイントの入金」や「出金」、
「残高照会」などを行うのが主なトランザクション処理になります。
これまでの環境は、SQL Server 2005 Enterprise エディション X64 版と Windows Server
2003 R2 X64 版で構成されており、1 秒あたり 1,743 個の処理量でした(ポイント入出金や残
高照会などのトランザクション処理を 1 秒間に何回できるかの処理量)。
これを SQL Server 2014 Enterprise + Windows Server 2012 R2 環境へ移行して、インメモ
リ OLTP 機能を利用することで 1 秒あたり 4,795 個の処理ができることを確認できました(2.8
倍の性能向上)
。これは、1 秒間の間に、4,795 人のユーザーが同時にポイントカードを利用して
も問題がない、という処理量になります(これまでのシステムでは、1 秒間に 1,743 人のユーザ
ーが同時に処理を行おうとすると、遅延が発生する可能性がありましたが、対応可能なユーザー数
が 2.8 倍に増えました)
。
今回の負荷テストは、本番のピーク時を想定しているので、1 時間継続して負荷をかけ続けていま
す。この 1 時間で処理できた件数が次のとおりです。
1秒
1分
1時間
On Disk
1,743
104,569
6,274,117
In Memory
4,795
287,707
17,262,415
1時間あたり
1,726万もの処理
を実行できる
インメモリ OLTP では、1 秒あたり 4,795 個の処理が可能で、1 分では 28.7 万、1 時間では
1,726 万もの処理を実行できることを確認できました。
42
SQL Server 2014 実践 No.1 インメモリ OLTP
検証の背景 ~将来のアクセス増への対応~
このお客様のポイントカード システムでは、利用者が日々増え続けており、このままでは将来の
アクセス増に耐えられるか不安がありました。そこで、
「SQL Server 2014 のインメモリ OLTP 機
能を利用すれば、性能が上がりそうなので、実際にどのくらいの効果があるのか? 将来のアクセ
ス増に耐えられそうか? 性能効果を調査したい」ということで、今回の検証を実施しました。ま
た、今回は、ハードウェアをリプレイスすることなく、性能向上を実現したい、という希望もあっ
たので、同じハードウェア スペックのマシンを利用して、検証を行っています。
処理の内容
このシステムでは、
「ポイントの入出」や「出金」、
「残高照会」が日中トランザクションの 9 割以
上の処理になり、各処理内ではステートメントが 12 個実行されています。その内訳は、次のとお
りです。
1処理内のステートメントの割合と処理概要
個数
割合
INSERT
3
25%
SELECT
6
50%
UPDATE
3
25%
合計
処理の概要
トランザクション テーブルへの Insert
顧客利用履歴への Insert * 2回
メッセージ マスターの参照
顧客マスターの参照
カード マスターの参照
カード種別マスターの参照 * 2回
ID 値の参照
カード マスターの更新
顧客マスターの更新 * 2回
12
1 回のポイント入金では、上記の 12 個のステートメントが実行されて、Insert と Update ス
テートメントが 1/4 ずつ(3 個ずつ)
、Select ステートメントが 1/2(6 個)の割合で実行さ
れています。出金処理や、残高照会処理についても、ほぼ同様のステートメントが 12 個ずつ実行
されています。
テーブル構成
このシステムでの主なテーブルは、次の 6 つで、マスター系のテーブル(カードやカード種別、
顧客、メッセージ)と、処理の履歴を格納するトランザクション テーブル、顧客の利用履歴を格
納するテーブルなどがあります。
各テーブルの概要
テーブル名
カード マスター
カード種別マスター
顧客マスター
メッセージ マスター
トランザクション テーブル
顧客利用履歴テーブル
列数
32
24
7
8
30
24
SELECT
1
2
1
1
0
0
UPDATE
1
0
2
0
0
0
* テーブル名は、実際のテーブル名から変更しています。
43
INSERT
0
0
0
0
1
1
テーブルの概要
カード情報を格納
カードの種別情報を格納
顧客情報を格納
メッセージ情報を格納
処理の履歴を格納
顧客の利用履歴を格納
SQL Server 2014 実践 No.1 インメモリ OLTP
これらのテーブルは、データの永続化が必要になるので、インメモリ
OLTP の
SCHEMA_AND_DATA(Durable)で作成しています。また顧客履歴テーブルに関しては、
SCHEMA_AND_DATA でデータを永続化しますが、Delayed Durability(遅延永続化)を利用
可能だったので、これを採用しました。
シングル実行でも性能向上(1.74 倍の性能向上)
各処理を、多重実行ではなく、シングル実行(シングル スレッドで単体実行)したときの性能は、
次のようになりました。
単位はマイクロ秒
INSERT
SELECT
UPDATE
合計
個数
3
6
3
12
割合
25%
50%
25%
On Disk In Memory
549.9
218.4
262.9
123.8
492.6
408.0
1,305.3
差
331.4
139.1
84.6
向上率% 何倍か
60.3%
2.5
52.9%
2.1
17.2%
1.2
750.3 555.1 42.5%
1.74
* ベンチマークの結果の公表は、使用許諾契約書で禁止されていますが、その効果をわかりやすく表現するため、
日本マイクロソフト株式会社の監修のもと、数値を掲載しております。
シングル実行でも、全体として 1.74 倍の性能向上(1.3 ミリ秒から 750 マイクロ秒へ向上)す
ることを確認できました。また、INSERT ステートメント 3 個では 2.5 倍、SELECT ステート
メント 6 個では 2.1 倍、UPDATE ステートメント 3 個では 1.2 倍の性能向上を確認すること
ができました。
このように、インメモリ OLTP 機能は、シングル実行でも性能向上を確認することができ、さら
に多重度を上げた場合に大きな性能向上に繋がることが理解できました(前述の 2.8 倍)。なお、
各処理内で実行しているステートメントは、ネイティブ コンパイル ストアド プロシージャへ変
換していますが、その詳細については、第 2 章で説明します。
多重実行時のオーバーヘッド(ラッチ待ち、ロック待ち、チェックポイントなど)
冒頭のグラフのように、本番を想定した 100 多重での負荷テストでは、約 2.8 倍の性能向上を確
認しています。
本番想定の 100多重の負荷テスト実施時の 1秒あたりの処理量
約 2.8倍
の性能向上
44
SQL Server 2014 実践 No.1 インメモリ OLTP
このように、多重度を上げた場合に、シングル実行よりも性能差が大きく出る(シングル実行では
1.74 倍だったところが、100 多重では 2.8 倍になった)のは、ディスク ベースのテーブルでは、
「ラッチ待ち」や「ロック待ち」など、多重実行時のオーバーヘッドが非常に大きく、またチェッ
クポイント時の書き込みの負荷がインメモリ OLTP よりも大きいことが、今回の負荷テストで確
認することができたためです。
パフォーマンス カウンターの様子(ラッチ待ちとバッチ要求数)
この 100 多重での負荷テストを行ったときの様子をパフォーマンス モニター(データ コレクタ
ーで収集したパフォーマンス カウンター)で確認すると、次のようになります。
インメモリでの
Batch Request数は
安定している
ディスクベースでは
ラッチ待ちが多発。
Batch Request数が低く、
安定していない
ラッチ待ち
このグラフは、負荷テストを 1 時間実施し続けているときの、その間の 15 分~45 分の 30 分間
を抜き出したものです。
グラフ内の赤色の折れ線がディスク ベースでのラッチ待ち(Latch Waits/sec)で、多発してい
ることが分かります(1 秒あたり 2,000~16,000 のラッチ待ちが発生)
。
青色の折れ線が、ディスク ベースでの Batch Requests/sec(1 秒あたりのバッチ要求数)で、
1 秒あたり 10,000~45,000 ぐらいの要求数を処理していることが分かります。また、Batch
Requests/sec が増えたタイミングで、ラッチ待ちも増加、減ったタイミングで、ラッチ待ちも減
少する、といった具合に連動しています。
インメモリ OLTP では、緑色の折れ線が Batch Requests/sec で 50,000 近辺の値を安定し
て推移していることが分かります。黄色の折れ線がラッチ待ちで 0(ラッチ待ちなし)を推移して
います。
また、このグラフの 30 分間でのラッチ待ちとバッチ要求数の平均を計算すると、次のようになり
ます。
ラッチ待ち/sec 要求数/sec
On Disk
In Memory
7,331
0
差
23,945
51,707
2.2
45
SQL Server 2014 実践 No.1 インメモリ OLTP
ディスク ベースでは、ラッチ待ちが多発する(平均 7,331/sec)ことによって、1 秒あたりのバ
ッチ要求数が伸びていません(平均 23,945/sec)。これに対して、インメモリ OLTP では、バ
ッチ要求数が安定して高い値を推移しています(平均 51,707/sec)。
ラッチ待ちによるスループットの低下
前述したように、ラッチ待ちが多発している場合には、スループットが低下します。
ラッチ待ちが発生するとスループットが低下
ページ
性能
ユーザー数が増えるほど
スループットが低下。
性能が頭打ちになる
ユーザー数
同時アクセスが
ブロックされる
ラッチ待ちが発生する場合には、ユーザー数が増えれば増えるほど、スループットが低下していき
ます(同時実行数が増えるほど、システムの処理能力が頭打ちになります)。
これに対して、インメモリ OLTP が採用している、ラッチを利用しない「ラッチ フリー」のアー
キテクチャであれば、ユーザー数が増えても性能低下は発生しにくくなります。
ラッチ フリーならユーザー数が増えても性能低下を抑えられる
性能
ユーザー数が増えても
性能が低下しにくくなる
ユーザー数
同時アクセス可能
今回は、本番を想定した 100 多重でのテストでしたが、インメモリ OLTP 機能を利用すれば、今
後同時接続ユーザー数が増えた場合にも、有効なアーキテクチャになっています。
ラッチ待ちの割合を調べる ~Latch Waits/sec~
パフォーマンス モニターの Latch Waits/sec カウンターを利用するときのポイントは、この値
が、バッチ要求数(Batch Requests/sec)と比較してどれぐらいなのかを見ることです。これ
で、ラッチ待ちの頻度を推測することができます。今回の結果では、ラッチ待ちが 7,331/sec に
対して、バッチ要求数が 23,945/sec なので、ラッチ待ちの割合は約 30%になりますが、この
システムでは、前述したように、処理のうち SELECT が 50%、INSERT が 25%、UPDATE が
25%で実行されているので、更新系でラッチ待ちが発生しているのではないか、と推測できます。
46
SQL Server 2014 実践 No.1 インメモリ OLTP
ラッチ待ちの詳細を調べるには ~Wait Stats、Index Stats~
ラッチ待ちの詳細調査には、dm_os_wait_stats と dm_db_index_operational_stats 動
的管理ビューが役立ちます。
dm_os_wait_stats は、SQL Server に関する内部的な "待機" の情報を取得することができる
便利なビューです。SQL Server が起動してから、現在までの累積値を参照することができ、この
結果は、Wait Stats とも呼ばれています(Wait:待機 の Statistics:統計)。
このビューでは、さまざまな待機の種類を参照することができますが、次のように wait_type(待
機の種類)で LATCH という文字列を含んだもののみに絞り込むことで、ラッチ待ちがどれぐら
い発生していたかを確認することができます。
SELECT * FROM sys.dm_os_wait_stats
WHERE wait_type LIKE '%LATCH%'
ORDER BY waiting_tasks_count DESC
ページ ラッチ待ちが
多発していることが分かる
ORDER BY 句で waiting_tasks_count が大きい順に並べ替えることで、待機の発生した回数
の多い順に並べ替えて表示、wait_time_ms は、該当する待機(wait_type)によって、どれ
ぐらいの時間の待機時間(ミリ秒単位)が発生していたのかを確認することができます。
上の画面は、ディスク ベースで 100 多重で 1 時間実行した後に取得したときのもので、排他ペ
ージ ラッチ(PAGELATCH_EX)や共有ページ ラッチ(PAGELATCH_SH)が上位に表示され
ることから、ページ ラッチ待ちが多発していることが分かります。
dm_os_wait_stats ビューの結果は、あくまでも SQL Server を起動してから、現在までの累
積値であることを忘れてはいけません。上の画面は、多重テストの実行前に SQL Server を再起
動しているので、多重テストで発生したラッチ待ちを確認することができていますが、通常は、調
べたい操作の前(多重テストの前など)に、ビューの結果を取得しておき、多重テスト後に、もう
一度ビューの結果を取得して、その 2 つの結果の差分を参照する、という使い方をします。
47
SQL Server 2014 実践 No.1 インメモリ OLTP
Index ごとのラッチ待ちを確認可能 ~dm_db_index_operational_stats~
dm_os_wait_stats ビューでは、SQL Server 全体としての待機に関する情報でしたが、
dm_db_index_operational_stats 動的管理ビューを利用すれば、インデックスごとのラッチ
待ち(やロック待ち)に関する情報を参照することができます。
これは、次のように利用できます。
SELECT
OBJECT_NAME(object_id) AS テーブル名,
index_id,
page_latch_wait_count,
page_latch_wait_in_ms,
page_lock_wait_count,
page_lock_wait_in_ms
FROM
sys.dm_db_index_operational_stats(DB_ID(), NULL, NULL, NULL)
ORDER BY page_latch_wait_count DESC
ラッチ待ちが
非常に多いインデックス
dm_db_index_operational_stats ビューの第 1 引数には DB_ID() を指定して、現在接続
中のデータベースを指定し、第 2~第 4 引数へ NULL を指定することで、データベース内のすべ
てのテーブルの、すべてのインデックスを参照できるようになります(第 2 引数はオブジェクト
ID、第 3 引数はインデックス ID、第 4 引数はパーティション番号を指定することで、特定のオブ
ジェクトの特定のインデックスのみを参照することも可能)。
page_latch_wait_count と page_latch_wait_in_ms で、インデックスごとのページ ラッ
チ 待 ち の 発 生 回 数 と 待 機 時 間 の 合 計 ( SQL Server が 起 動 し て か ら の 累 積 値 )、
page_lock_wait_count と page_lock_wait_in_ms で、ロック待ちの発生回数と待機時間
の合計を確認することができます。
上の画面は、ディスク ベースで 100 多重で 1 時間実行した後に取得したときのもので、顧客利
用履歴テーブルの index_id = 1 とトランザクション テーブルの index_id = 1 で多くのペー
ジ ラッチ待ちが発生していることを確認できます。index_id に対応したインデックスの名前を
48
SQL Server 2014 実践 No.1 インメモリ OLTP
確認するには、sys.indexes システム ビューを利用しますが、index_id = 1 はクラスター化
インデックスに割り当てられている番号と決まっています。
詳しくは後述しますが、顧客利用履歴テーブルとトランザクション テーブルのクラスター化イン
デックスは、どちらも IDENTITY(1,1) へ設定した PRIMARY KEY(主キー)列になっていて、
多数のユーザーが同時にデータ追加を行っていることで、次のようにインデックスの最終ページへ
のアクセスが集中している状態です。
インデックスの最終ページでページ ラッチ待ちが多発
インデックスの最後のページ
がホットスポットになる
多数の同時追加
連番系の列(IDENTITY を設定した列や、シーケンスを設定した列)など、データを追加するた
びに連続した値が格納されていくような場合には、このようなインデックスの最終ページでページ
ラッチ待ちが多発する(最終ページがホット スポットになる)ことがよくあります。これは、シ
ングル実行(1 人のユーザーによる単体実行)では、発生しないものですが、多数のユーザーが同
時にデータを追加する場合には発生し得ます。
今回のシステムでは、本番を想定した 100 多重でのテストを行っていますが、このように多重度
が高いとページ ラッチ待ちが多発することになります。改めて、以下のパフォーマンス カウンタ
ーの様子を見てみてください。
インメモリでの
Batch Request数は
安定している
ディスクベースでは
ラッチ待ちが多発。
Batch Request数が低く、
安定していない
ラッチ待ち
ラッチ待ち/sec 要求数/sec
On Disk
In Memory
7,331
0
差
23,945
51,707
2.2
ディスク ベースでは、ラッチ待ちが多発することで、Batch Requests/sec(1 秒あたりのバッ
49
SQL Server 2014 実践 No.1 インメモリ OLTP
チ要求数)がインメモリ OLTP よりも低くなっています。インメモリ OLTP では、Max 6 万ぐ
らいまで出るのに対して、ディスク ベースでは Max 4.8 万ぐらいまでしか出ていません。
ディスク ベースでの Batch Requests/sec の値が安定していない(ガクンと定期的に下がる)
理由については、別の理由があるので(ディスクへの書き込み待ちなど)、以降では、それを見て
いきます。
Processor Time(CPU 利用率)と Disk Queue(ディスク待ち)
多重テスト時の %Proccessor Time と Disk Write Queue Length(ディスクへの書き込み
待ちの、待ち行列の長さ)は、次のようになりました。
ディスクベースでは
CPU 利用率が安定しない
インメモリでの
CPU 利用率は
安定している
インメモリでは
Disk Queue
(ディスク待ち)
は少ない
CPU 利用率が下がるタイミン
グで Disk Queue が上がる
CPU利用率 WriteQueue
On Disk
In Memory
38.5
71.7
2.2
18.0
差
8.3
グラフ内の青色の折れ線は、インメモリ OLTP での CPU 利用率で、70~80% ぐらいを安定し
て推移し(平均は 71.7%)
、黄色の折れ線が Disk Write Queue Length でほとんどキューが
溜まっていないことが分かります(平均 2.2。第 2 軸)
これに対して、グラフ内の緑色の折れ線がディスク ベースでの CPU 利用率で、定期的にガクン
と下がるタイミングがあって安定していないことが分かります(平均すると、CPU が 38.5% し
か活用されていません)。また、赤色の折れ線が Disk Write Queue Length で、CPU 利用率が
ガクンと下がったタイミングで、キュー(ディスク待ち)がいっきに 50 以上に増えてしているこ
とが分かります(平均 18。第 2 軸)
。このグラフには、Batch Requests/sec を含めていませ
んが、この値が安定しなかったのも同じ理由で、Batch Requests/sec がガクンと下がるタイミ
ングで、ディスク待ちが発生して(キューが溜まって)いました。
ディスクへの書き込み待ちが発生することによって、他のリソース(CPU など)は余力を残して
しまう、というのは典型的なボトルネックの例です(ディスク ボトルネックが原因で、CPU がフ
ル活用されていない状態です)。ディスクへの書き込み待ちが発生している理由は、チェックポイ
ント処理によるものですが、以降では、これについて詳しく見ていきます。
50
SQL Server 2014 実践 No.1 インメモリ OLTP
チェックポイント処理の負荷 ~復旧間隔、Checkpoint pages/sec~
チェックポイント処理は、メモリ内のデータ バッファの内容を、データ ファイル(.mdf)へフラ
ッシュする(書き込む)処理です。これを定期的に行っておくことで、万が一の障害発生時に、復
旧時間を短くできるというメリットがあります。チェックポイントが発生するタイミングは、[復
旧間隔]
(recovery interval)で設定することができ、これは、次のようにサーバーのプロパティ
の[データベースの設定]ページから設定できます。
[復旧間隔]は、既定では「0」に設定されていて、この場合は「1」分に設定した場合と同等の
動作になって、復旧時間が 1 分程度で済むようにチェックポイントを発生させる、という設定値
になります。この場合、データ更新が 1 分間ずっと続いているような状態であれば、約 1 分でチ
ェックポイントが発生して、データ更新がほとんど行われない状態であればチェックポイントはほ
とんど行われません。
今回の負荷テストでは、処理のうち SELECT が 50%、INSERT が 25%、UPDATE が 25%の
割合で、データ更新(INSERT/UPDATE)の割合が 50% で実行されているので、1 分 20 秒~
1 分 40 秒ぐらいのタイミングで、
チェックポイントが定期的に実行されています(後半になって、
処理できる量が減っていくと、チェックポイントのタイミングも遅くなっていきます)。
チェックポイントが発生したかどうかは、Checkpoint pages/sec(1 秒あたりにチェックポイ
ントで処理したページ数)カウンターで、次のように調べることができます。
Batch Requests/sec
が下がるタイミングで
チェックポイント
が発生している
51
SQL Server 2014 実践 No.1 インメモリ OLTP
Batch Requests/sec がガクンと下がるタイミングで、チェックポイント処理が行われて(デー
タファイルへの書き込みが発生して)、このときにログ書き込みとチェックポイント処理が競合す
ることによって、ディスクへの書き込み待ちが多数発生してしまっています。また、後半になれば
なるほど、データ量が増えていくので、チェックポイントにかかる時間も延びていっています。
Tips: データ ファイルとログ ファイルを別 RAID セットへ配置して性能向上
チェックポイント時の書き込み負荷のオーバーヘッドを軽減するには、データ ファイル(.mdf)
とログ ファイル(.ldf)を別々のディスク(異なる RAID セット)へ配置するようにします。
今回のお客様は、RAID 6 で構成された 1 本の RAID セットのみしか利用できなかったため、
データ ファイルとログ ファイルを異なる RAID セットへ分けることはできなかったのです
が、次にハードウェアをリプレイスする際には、RAID セットを複数作成するようにして、別々
の RAID セットへファイルを配置する予定です。
私の経験上は、ファイルを別 RAID セットへ配置することで、10~30% の性能向上を実現す
ることができます。ログへの書き込み量が多い場合には、特に性能メリットが得られます。
ちなみに、私がコンサルティングをしていて、よく聞かれる質問があるのですが、同じ RAID セ
ット内で複数の論理ドライブを作成している場合に(例えば、1 つの RAID 10 の中で、C ドラ
イブと D ドライブと分けている場合に)
、ドライブごとにデータ ファイルとログ ファイルを分
ける効果はありますか? というものです。答えは、私の経験上は No で、同じ RAID セットの
中でファイルを分散配置しても、性能向上を確認できたことはありません。
あくまでも、異なる RAID セットへデータ ファイルとログ ファイルを配置することで、性能
向上を実現することができます。もちろん、同じ RAID セット内の論理ドライブごとにファイ
ルを分けることを否定しているわけではなく、(データベース管理者にとって)見た目が分かり
やすくなって、管理/監視がしやすいというメリットはあるので、ドライブごとに分けることに
は賛成です。
■ 複数サーバーで共有利用するストレージの注意点
ここ数年流行しているストレージ システム(複数のサーバーで共有して利用するストレージ)
では、サーバーごとに 1 つの RAID セットを提供、という形態をよく見かけます。この場合は、
複数の RAID セットを利用することができないか検討してみることをお勧めします。また、複
数のサーバーで共有するストレージの場合は、サーバーとストレージ間のネットワークがボトル
ネックになる可能性があります(実際に、これが原因で性能が頭打ちになっているお客様がいら
っしゃいました)
。どこで性能が出ていないのか見極めることは、非常に重要です。
■ チェックポイント処理の負荷軽減には SSD などのフラッシュ型ストレージがお勧め
チェックポイント処理は、データ ファイルへのランダム書き込み(Random Write)で行われ
るため、HDD(ハードディスク)よりも、ランダム書き込みの性能が桁違いに速い SSD(Solid
State Drive)やフラッシュ メモリ型の半導体ストレージ(Fusion-io ioDrive2 など)を利用す
るのがお勧めです。これで、チェックポイントの負荷を大幅に軽減することができます。
52
SQL Server 2014 実践 No.1 インメモリ OLTP
インメモリ OLTP ではチェックポイントの負荷が軽い
今回の負荷テストでは、ディスク ベースでは、チェックポイント処理が大きなボトルネックとな
っていますが、インメモリ OLTP 機能の場合は、チェックポイント処理の負荷が軽いことを確認
することができました。
また、インメモリ OLTP 機能では、マルチバージョンの楽観的同時実行制御を実現するために、
利用しなくなった古いバージョンのデータを定期的に削除するために「ガベージ コレクション」
(GC:Garbage Collection)が動作するのですが、これもディスク ベースでのチェックポイン
ト処理ほどの負荷にはならないことを確認できました。
インメモリ OLTP 機能では、チェックポイント処理が発生したかどうかは「XTP Checkpoint
Completed」カウンター、ガベージ コレクションが発生したかどうかは「XTP Main GC work
items/sec」カウンターで調べることができます。これを取得したときの様子が次のグラフです。
青の折れ線がチェックポイントの完了(完了ごとに1ずつインクリメントされる)で、30 秒~1
分ごとぐらいに定期的に完了していて、30 分間で 41 回完了しています。赤の折れ線がガベージ
コレクションによって 1 秒間に処理されたアイテム数で、30 分間で 27 回実行されています(1
回の GC で 20 万~35 万ぐらいのアイテムが処理されています。第 2 軸)
。
インメモリ OLTP 機能では、チェックポイントは、ログ サイズが 512MB 増えるたびに実行さ
れ、ガベージ コレクションは、1 分に 1 回実行されるアーキテクチャになっています。詳しくは、
オンライン ブックの以下のトピックが参考になると思います。
メモリ最適化テーブルのチェックポイント操作
http://msdn.microsoft.com/ja-jp/library/dn553124.aspx
インメモリ OLTP ガベージ コレクション
http://msdn.microsoft.com/ja-jp/library/dn643768.aspx
53
SQL Server 2014 実践 No.1 インメモリ OLTP
ガベージ コレクションの影響
ガベージ コレクションと Batch Requests/sec をグラフにしてみると、次のようになります。
Batch Requests/sec
が下がるタイミングで
ガベージ コレクション
が発生している
Batch Requests/sec が少し下がったタイミングで、ガベージ コレクションが実行されている
ことが分かると思います。ディスク ベースでは、チェックポイントのタイミングで Batch
Requests/sec がガクンと下がりましたが(5,000~10,000 ぐらいまで下がる)、インメモリ
OLTP では、ガベージ コレクションが実行されても 30,000 ぐらいの Batch Requests/sec
を処理することができ、安定したパフォーマンスを実現できることを確認することができました。
このように、インメモリ OLTP では、チェックポイント処理の負荷は非常に軽く、ガベージ コレ
クションに関しては、オーバーヘッドがある、ということが分かりました。チェックポイント処理
のアーキテクチャについては、冒頭で紹介した以下のドキュメントが参考になると思います。
SQL Server In-Memory OLTP Internals Overview
http://download.microsoft.com/download/5/F/8/5F8D223F-E08B-41CC-8CE5-95B79908
A872/SQL_Server_2014_In-Memory_OLTP_TDM_White_Paper.pdf
ディスク ベースでは断片化も発生する
ディスク ベースのインデックス(b-tree 構造)では、データを追加していけばいくほど、断片化
が発生していき、性能低下に繋がります。断片化は、dm_db_index_physical_stats 動的管理
ビューを利用して確認することができます。
これは、次のように利用できます。
SELECT
OBJECT_NAME(object_id) AS テーブル名,
index_id,
avg_fragmentation_in_percent,
page_count
FROM sys.dm_db_index_physical_stats ( DB_ID(), NULL, NULL, NULL , 'LIMITED')
WHERE index_type_desc <> 'HEAP'
54
SQL Server 2014 実践 No.1 インメモリ OLTP
91.4%の断片化
21.6%の断片化
86.5%の断片化
dm_db_index_physical_stats ビューの第 1 引数には DB_ID() を指定して、現在接続中の
データベースを指定し、第 2~第 4 引数へ NULL を指定することで、データベース内のすべての
テーブルの、すべてのインデックスを参照できるようになります(第 2 引数はオブジェクト ID、
第 3 引数はインデックス ID、第 4 引数はパーティション番号を指定することで、特定のオブジェ
クトの特定のインデックスのみを参照することも可能)
。第 5 引数で LIMITED を指定することで、
断片化の度合いを簡易チェックすることができます(DETAILED を指定することで詳細チェック
も可能)
。
avg_fragmentation_in_percent で断片化の割合、page_count で、インデックスの使用ペ
ージ数を確認することができます。上の画面は、ディスク ベースで 100 多重で 1 時間実行した
後に取得したときのもので、トランザクション テーブルの index_id = 2 と 3、顧客利用履歴テ
ーブルの index_id = 2 で大きな断片化が発生していることが分かります。
このような断片化は、速度低下を引き起こすので、これを解消するためには、インデックスの再構
築(ReBuild)または再構成(ReOrganize)をしなければなりません(このお客様では、1 日1回、
夜中に再構成を実施しています)。この処理は、インデックス サイズが大きい場合には、非常に時
間がかかるので、その間のサーバー処理能力が低下するという問題があります。また、再構築/再
構成の処理の履歴はトランザクション ログへ書き込まれるので、データベース ミラーリングや、
AlwaysOn 可用性グループを利用している場合には、その処理の履歴をミラー サーバーへ転送す
る負荷もかかってしまうという問題もあります。
インメモリ OLTP 機能では、ハッシュ インデックスがサポートされたことで、断片化に悩まされ
ることがなくなります(再構築や再構成は必要ありません)。また、インデックスに対する更新情
報は、トランザクション ログへ記録しないアーキテクチャを採用しているので、ログの書き込み
量を削減できるメリットもあります。
ログ書き込み量の削減 ~インデックスに関してはログ記録しない~
今回のシステムでは、SCHEMA_AND_DATA(データの永続化有り)オプションを利用している
55
SQL Server 2014 実践 No.1 インメモリ OLTP
ので、ログへの書き込みを行っていますが、インデックスに対する更新情報は、トランザクション
ログへ記録しないので、ログ書き込み量が少なくなっています。
1 時間の負荷テストを実行した後のログ書き込み量は、次のようになりました。
処理量
On Disk
In Memory
ログ使用量
(MB)
1処理あたり
(KB)
6,274,117
37,287
6.1
17,262,415
46,276
2.7
差
45.1%
* ログ使用量は、DBCC SQLPERF(LOGSPACE) を実行前後で取得して、その差分から計算したもの
インメモリ OLTP 機能では、倍以上の処理を行っているのに対して、ログの使用量は倍にはなっ
ていません。
1 件あたりのログ使用量を計算すると、45% も削減できていることを確認できます。
このシステムでは、トランザクション テーブルへ 2つ、顧客利用履歴テーブルへ 1 つのハッシ
ュ インデックスを追加で作成していて(PRIMARY KEY のハッシュ インデックスとは別に作成)、
1 回の処理で、前者へは 1 回の INSERT、後者へは 2 回の INSERT を行っているので、その分
のログ出力量の差が、このような大きな差となって出ています。
まとめ、今後の展望
このように、ディスク ベースのテーブルでは、ラッチ待ちやロック待ちなど、多重実行時のオー
バーヘッドが非常に大きく、またチェックポイント時の書き込みの負荷や、ログ書き込みの負荷、
断片化の発生などもインメモリ OLTP よりも大きいことが、今回の負荷テストで確認することが
できました。
今回は、できる限りハードウェアをリプレイスすることなく、SQL Server 2014 へアップグレー
ドするだけで、性能向上できないか、という当初のもくろみがありましたが、期待以上の性能向上
を確認することができました。また、ハードウェアをより良いものへ変更することで、さらなる性
能向上を期待できることも分かったので、最新のハードウェアを導入すれば、4~5 年後のアクセ
ス増にも耐えられるのではないか、という期待も出てきました。
インメモリ OLTP 実装のポイント(次の章で説明)
このシステムでは、次のことを実践していますが、詳しくは次章以降で説明します。

ネイティブ コンパイル ストアド プロシージャの作成して性能向上を実現

ネイティブ コンパイル ストアド プロシージャを作成しないと速くならなかったのか?

インメモリ化で逆に遅くなってしまった SELECT ステートメントへの対処方法

一部の処理で Delayed Durability を利用して大幅な性能向上を実現

ハッシュ インデックスと bw-tree インデックスの比較

データ型でハマったこと、データ型の違いによるメモリ使用量の差

実行方法の違いによる性能差 など
56
SQL Server 2014 実践 No.1 インメモリ OLTP
1.13 この章のまとめ
この章では、インメモリ OLTP 機能がとういった場面で役に立つのか、早期導入/検証を行った
企業での実際の利用例をベースに説明しました。利用例をまとめると、次のようになります。
インメモリ OLTP 機能の
設定など
社名/システム概要
システムの特性
導入効果
bwin 社
オンライン ゲームなど
ASP.NET のセッション状態データ
ベースで利用。
大量のユーザーによる同時更新。
トランザクションとしては小さい。
毎日 15万人以上のアクティブ
ユーザー。毎年 100万人以上の新
規ユーザーが増加
約 16.7倍の性能向上
15,000 バッチ要求/sec が
250,000 バッチ要求/sec へ向上。 SCHEMA_ONLY
450,000 バッチ要求/sec も確認
(約 30倍の性能向上)
SBIリクイディティ・
マーケット株式会社
FX 取引(オンライン
トレード)
約 2.5倍の性能向上
大量のユーザーによる同時更新。
52,080 件/sec が
トランザクションとしては小さい。
131,921 件/sec へ向上。
顧客のトレーディング データ(取
ピーク時の Latency(遅延)は、
引履歴)をリアルタイムに集計
約 4秒だったのを、1秒以下に短縮
Edgenet 社
商品データの提供
DWH 環境でのステージング テー
ブルで利用。バッチ処理での大量
のデータ更新。
更新中の参照アクセスあり
TPP 社
臨床ソフトウェアの提供
大量のユーザーによる同時更新。
約 7倍の性能向上
トランザクションとしては小さい。
34,700 Transaction/sec が
1秒あたり 72,000 ユーザーが同
数十万 Transaction/sec へ向上
時アクセス(ピーク時)
-
弊社のお客様 A社
ポイントカード システム
大量のユーザーによる同時更新。
トランザクションとしては小さい。
約 2.8倍の性能向上
ポイントカードにおけるポイント
の入金や出金、残高照会処理
SCHEMA_AND_DATA
Delayed Durability(遅
延永続化)を一部で利用
INSERT 中心システム
弊社環境でのテスト
一括操作による大量 INSERT では
なく、1件ずつ INSERT をし続け
るシステム
SCHEMA_AND_DATA で
は 2倍、Delayed
Durability は 2.2倍、
SCHEMA_ONLY は 2.5倍
約 8~11倍の性能向上
2時間 20分かかっていたバッチ処
理が、わずか 20分に短縮。
更新中の参照アクセスが、
更新によってブロック(ロック待
ちなど)されることがなくなり
読み取り性能も向上
約 2~2.5倍の性能向上
約 15万円のデスクトップ PC
での検証
SCHEMA_AND_DATA、
AlwaysOn 可用性グルー
プと組み合わせて利用し、
高可用性を実現
SCHEMA_AND_DATA
Delayed Durability(遅
延永続化)を利用
オンライン ゲームや、FX 取引、データ プロバイダー、オンライン サービス、ポイントカード シ
ステムなど、大量のユーザーによる同時更新およびトランザクションとしては小さい(=実行され
るステートメントが単純で、トランザクションが短い)、1 秒あたりのバッチ要求数が多いシステ
ムで、多く採用されています。これらは、まさに OLTP(オンライン トランザクション処理)シ
ステムの典型例で、こういったシステムでは、同時実行数が増えていくと、「ラッチ待ち」や「ロ
ック待ち」が多発して、性能が頭打ちになってしまいます。インメモリ OLTP 機能であれば、ラ
ッチおよびロックを利用しないアーキテクチャ(ラッチ フリー、ロック フリー)なので、同時実
行数が増えても性能低下を抑えられます。こうしたラッチ待ちやロック待ちに悩まされているシス
テムで大きな効果を発揮できるのが インメモリ OLTP 機能です。
bwin 社では、ASP.NET のセッション状態データベースに対して、SCHEMA_ONLY(データの
永続化なし)オプションを利用することで、インメモリ OLTP 機能での性能メリットを最大限に
活かしています。このオプションでは、メモリ内にのみデータを配置して、トランザクション ロ
グへの書き込みを行わないことで、大きな性能向上を実現することができます(データを永続化し
ないことによる、電源断時のデータ損失のリスクと性能向上のトレード オフ)。ASP.NET のセッ
ション状態(セッション変数)は、既定では 20 分間で破棄されるもの(一時的なデータ)なので、
こういった一時的なデータ領域として、インメモリ OLTP + SCHEMA_ONLY が多いに役立ち
57
SQL Server 2014 実践 No.1 インメモリ OLTP
ます。
Edgenet 社では、DWH 環境でのステージング テーブルで利用していますが、バッチ処理の性能
向上(8 倍~11 倍の性能向上)を実現しています。また、インメモリ OLTP 機能では、更新中
(UPDATE/INSERT/DELETE ステートメントの実行中)に、読み取り操作(SELECT ステート
メント)がブロックされることもないので、その分の性能向上も実現しています
Edgenet 社では、Delayed Durability(遅延書き込みのよるデータの永続化)オプションを利用
して、性能向上も実現しています。このオプションでは、ログへの書き込みが完了するのを待たな
いで、コミットとすることができます(ログへの書き込みは遅延で行い、アプリケーションへはコ
ミットが通達されます)。したがって、コミットしたにも関わらず、データの損失の可能性があり、
コミット直後に電源断などが発生した場合にはデータの損失が起こり得ます。その分、大幅な性能
向上を実現できるというメリットを得られます(トレード オフ)
。
多少のデータ損失が許容できるような場合には、Delayed Durability オプションによる性能メリ
ットを享受できるので、利用できる場面がないかどうか、検討してみることを強くお勧めします。
例えば、もともと一定の間隔(1 分間隔など)でその瞬間のデータのみを保存する(サンプリング
している)、といった処理の場合などであれば、多少のデータ損失が許容できます。こうした処理
であれば、もともと間のデータを取得していないので、1 分間隔であれば、最大1分分のデータは
損失する可能性があるわけです。それを許容して(性能とのトレードオフで)システムを設計して
いるので、そういったテーブルでは、Delayed Durability を採用しても、もともとデータの損失
の可能性があったものなので、何の問題もありません。ぜひ、活用してみてください(利用方法に
ついては、次章以降で説明します)
。
ディスク ベースのテーブルでの性能低下の主な原因
従来ながらのディスク ベースのテーブルで、性能低下を引き起こす主な原因をまとめると、次の
ようになります。
リレーショナル データベースでの主な性能低下の原因
ログ書き込み
ロック待ち
インデック
ラッチ待ち
ス管理
バッファ管理
・インデックスの断片化
・b-tree 構造の維持 etc
・データ バッファへの読み込み
・チェックポイント時の書き込み etc
これらが原因で性能が低下している場合は、インメモリ OLTP 機能を採用することで、性能向上
を期待することができます。
58
SQL Server 2014 実践 No.1 インメモリ OLTP
従来型の RDB
(ディスク ベース)
インメモリ OLTP
ロック待ち
多数のユーザーの同時実行時に性能低下の
可能性。ロック競合が多発する可能性
ロック フリーのアーキテクチャで、
ロック競合は発生しない。
ロックを利用しないマルチ バージョンの楽
観的同時実行制御、tempdb を利用しない
インメモリ対応のスナップショット分離
ラッチ待ち
多数のユーザーの同時実行時に性能低下の
可能性。ラッチ競合が多発する可能性
ラッチ フリーのアーキテクチャで、
ラッチ競合は発生しない
データ バッファ
への読み込み
データ バッファへの読み込み
不要。常にメモリに常駐
チェックポイント
時のフラッシュ
チェックポイント時のデータ ファイルへの
書き込み
チェックポイント処理の負荷が軽い
インデックス管理
・b-tree 構造の維持
・インデックスの断片化
インデックスの断片化、それを解消するた
めに再構築と再構成を定期的に行う必要が
ある。
ハッシュ インデックスでは断片化に悩まさ
れることはない。再構築と再構成も不要。
インデックスはメモリ内にのみ配置される
ログ書き込み
ログへの書き込みを行う。
インデックスに関する情報もログへの書き
込みを行う。
インデックスの再構築や再構成に関する
処理履歴もログへ書き込む。
Delayed Durability(遅延書き込み)オプ
ションを利用することも可能
SCHEMA_ONLY ならログへの書き込みを
行わない。
SCHEMA_AND_DATA では、ログへの
書き込みを行うが、インデックスに関する
情報は書き込まない分、ログ出力量は減る
SCHEMA_AND_DATA でも Delayed
Durability(遅延書き込み)オプションを
利用することで、大幅な性能向上が可能
バッファ管理
インメモリ OLTP の性能効果を期待できるシステム
インメモリ OLTP の性能効果を期待できるシステムは、次のとおりです。

大量のユーザーによる同時更新が発生するシステム = OLTP システムの典型例
(ラッチ待ちやロック待ちが多発しているシステム)

Latency(遅延)を小さくしたいシステム
SLA(サービスレベル契約)で数秒以内に応答を返すことを定義しているシステムなど

INSERT 中心のシステム(ほとんどの処理が INSERT)

トランザクション ログへの書き込みがボトルネックになっているシステム

更新にブロックされない読み取りを実現したい場合(Edgenet 社の例)

ASP.NET のセッション状態データベースとして SQL Server を利用している場合
(bwin 社では 16.7 倍の性能向上、Lab 環境では 45 万バッチ要求/sec も確認)
インメモリ OLTP 機能を利用するにあたっては、性能を向上させるためのコツ(アプリケーショ
ンの記述方法や、ネイティブ コンパイル ストアド プロシージャの利用方法など)もあるので、
これらについては、次章以降で、弊社のお客様の「ポイントカード システム」での実装例をベー
スに説明します。
59
SQL Server 2014 実践 No.1 インメモリ OLTP
Note: SQL Server のインメモリ技術(CEP、BI、ビッグデータもカバー)
SQL Server は、今回のインメモリ OLTP 機能が初めて提供されたインメモリ技術ではなく、
既に多くのインメモリ技術が提供されています。2 つ前のバージョンの SQL Server 2008 R2
から提供された StreamInsight および PowerPivot に始まり、以下のインメモリ技術がありま
す。
インメモリ機能の名称
提供開始時の
SQL Server のバージョン
役割
StreamInsight
SQL Server 2008 R2
CEP(複合イベント処理)向けの高速
なイベント処理が可能なエンジン
PowerPivot for Excel
PowerPivot for SharePoint
SQL Server 2008 R2
BI 向けのインメモリ エンジン
(列指向データベース)
xVelocity エンジン
SQL Server 2012
xVelocity 列ストア インデックス
SQL Server 2012
インメモリ OLTP
SQL Server 2014
OLTP 向けの DB エンジン
更新可能な
xVelocity 列ストア インデックス
SQL Server 2014
更新可能な列ストア インデックス
(列指向 DB の SQL Server 実装)
PowerPivot を Analysis Services で
利用できるように改良
xVelocity エンジンを RDB へ応用した
もの。DWH 環境などの集計演算での
クエリを大幅に性能向上が可能
このように、SQL Server のインメモリ技術は、CEP から BI、ビッグデータ、OLTP まで、幅
広くカバーしています。
■ 更新可能な列ストア インデックス(カラム指向データベース)での大幅な性能向上
SQL Server 2014 から提供された「更新可能な列ストア インデックス」
(カラム指向データベ
ースの SQL Server 実装)は、BI/DWH 環境や、集計処理で大きな性能向上を実現すること
ができる機能です。
例えば、数多くのソーシャル ゲームを提供している「株式会社 gloops」では、この機能をイ
チ早く導入して、12 分かかっていた集計処理をわずか 3 秒に短縮、テーブル容量は 1/20 に
まで圧縮しています。詳しくは、以下の日本マイクロソフトの事例サイトに記載されています。
株式会社 gloops の事例
http://www.microsoft.com/ja-jp/casestudies/gloops.aspx
これらについては、本実践シリーズの「SQL Server 2014 移行/アップグレードの実践」編
でも詳しく説明する予定なので、こちらもぜひご覧いただければと思います。
60
SQL Server 2014 実践 No.1 インメモリ OLTP
STEP 2. ポイントカード システムにおける
インメモリ OLTP 機能の実装のポイント
第 2 章では、弊社のお客様の A 社の「ポイントカード システム」をインメモリ
OLTP 化するにあたって、どのように実装したのか?、実装にあたっていろいろと
試行錯誤したこと、ハマったところは? など実際に実装するにあたってのでポイ
ントを説明します。
この章の目次は、次のとおりです。

ポイントカード システムでの主な処理内容、テーブル構成

シングル実行時の性能

各ステートメントの具体的な処理内容、どのくらい性能向上したのか?

性能が出なかった SELECT ステートメントへの対処方法

ネイティブ コンパイル SP を簡単に作成する手順

ネイティブ コンパイル SP を利用するためのアプリケーションの修正方法

ネイティブ コンパイル SP の実行方法の違いによる性能差

char/varchar を利用する場合の注意点、メモリ使用量の差

更新競合への対応方法(再試行ロジックの実装)

BIN2 照合順序を利用する場合の注意点
61
SQL Server 2014 実践 No.1 インメモリ OLTP
2.1
ポイントカード システムの概要
前章では、弊社のお客様「A 社」の「ポイントカード システム」で、インメモリ OLTP 機能を採
用することで、2.8 倍の性能向上を実現できることを説明しました。
本番想定の 100多重の負荷テスト実施時の 1秒あたりの処理量
約 2.8倍
の性能向上
この 2.8 倍の性能向上を実現するにあたっては、いろいろと苦労/試行錯誤したことがありまし
たので、この章では、それらの作業内容について説明します。ネイティブ コンパイル ストアド プ
ロシージャ(以降、ネイティブ コンパイル SP と表記)を作成する必要があったのか? 作成し
なかった場合には性能が向上しなかったのか? ネイティブ コンパイル SP を作成する上でのポ
イントは? ハッシュ インデックスや bw-tree インデックスを選択するポイントは? データ型
でハマったこと、などを説明します。
ポイントカード システムの主な処理内容(SQL ステートメント)
このお客様のポイントカード システムでは、「ポイントカード」における、「ポイントの入金」や
「出金」
、
「残高照会」などを行うのが主なトランザクション処理になり、これらが日中トランザク
ションの 9 割以上の処理になります。
各処理内では、SQL ステートメントが 12 個実行されていて、その内訳は、次のとおりです。
1処理内のステートメントの割合と処理概要
個数
割合
INSERT
3
25%
SELECT
6
50%
UPDATE
3
25%
合計
処理の概要
トランザクション テーブルへの Insert
顧客利用履歴への Insert * 2回
メッセージ マスターの参照
顧客マスターの参照
カード マスターの参照
カード種別マスターの参照 * 2回
ID 値の参照
カード マスターの更新
顧客マスターの更新 * 2回
12
1 回のポイント入金では、上記の 12 個のステートメントが実行されて、Insert と Update ス
テートメントが 1/4 ずつ(3 個ずつ)
、Select ステートメントが 1/2(6 個)の割合で実行さ
れています。出金処理や、残高照会処理についても、ほぼ同様のステートメントが 12 個ずつ実行
されています。
62
SQL Server 2014 実践 No.1 インメモリ OLTP
2.2
テーブル構成
このシステムでの主なテーブルは、次の 6 つです。
カード
マスター
顧客
マスター
トランザクション
テーブル
カード種別
マスター
顧客利用履歴
テーブル
メッセージ
マスター
* テーブル名や列名は、実際の名前から変更しています。
上記の 6つのテーブルが、日中トランザクションの 9割を占める「ポイント入金」や「出金」「残高照会」処理で利用されます。
データベース内には、他の処理のためのテーブルが、これらの他に 20個以上存在していますが、これらは省略しています。
マスター系のテーブル(カードやカード種別、顧客、メッセージ)と、処理の履歴を格納するトラ
ンザクション テーブル、顧客の利用履歴を格納するテーブルなどがあります。
各テーブルの概要は、次のとおりです。
各テーブルの概要
テーブル名
カード マスター
カード種別マスター
顧客マスター
メッセージ マスター
トランザクション テーブル
顧客利用履歴テーブル
列数
32
24
7
8
30
24
SELECT
1
2
1
1
0
0
UPDATE
1
0
2
0
0
0
INSERT
0
0
0
0
1
1
テーブルの概要
カード情報を格納
カードの種別情報を格納
顧客情報を格納
メッセージ情報を格納
処理の履歴を格納
顧客の利用履歴を格納
* テーブル名は、実際のテーブル名から変更しています。
これらのテーブルは、データの永続化が必要になるので、インメモリ
OLTP の
SCHEMA_AND_DATA(Durable)で作成しています。また、顧客利用履歴テーブルに関しては、
SCHEMA_AND_DATA でデータを永続化しますが、Delayed Durability(遅延永続化)を利用
可能だったので、これを採用しました。
63
SQL Server 2014 実践 No.1 インメモリ OLTP
2.3
シングル実行時の性能(ネイティブ コンパイル SP で 1.74 倍)
前の章では、シングル実行(多重実行ではなく、シングル スレッドでの単体実行)でも、性能が
向上することを説明しました。
単位はマイクロ秒
INSERT
SELECT
UPDATE
合計
個数
3
6
3
割合
25%
50%
25%
12
On Disk In Memory
549.9
218.4
262.9
123.8
492.6
408.0
1,305.3
差
331.4
139.1
84.6
向上率% 何倍か
60.3%
2.5
52.9%
2.1
17.2%
1.2
750.3 555.1 42.5%
1.74
* ベンチマークの結果の公表は、使用許諾契約書で禁止されていますが、その効果をわかりやすく表現するため、
日本マイクロソフト株式会社の監修のもと、数値を掲載しております。
この値は、1 スレッドでアプリケーションを実行して、過去のシステム利用状況をシミュレートし
て、1,000 回の処理を実行したときの「平均値」を比較したものです(SQL Server Profiler ツー
ルを利用して処理をトレース/キャプチャして、その結果から平均値を計算しています)
。
全体として 1.74 倍の性能向上(1.3 ミリ秒から 750 マイクロ秒へ向上)することを確認できま
した。また、INSERT ステートメント 3 個では 2.5 倍、SELECT ステートメント 6 個では 2.1
倍、UPDATE ステートメント 3 個では 1.2 倍の性能向上になりました。
このシングル実行での性能向上は、ネイティブ コンパイル SP を作成することで実現しています。
ネイティブ コンパイル SP ではアプリケーションの修正が必要になる
ネイティブ コンパイル SP 化するにあたっては、ネイティブ コンパイル SP の作成と、それを
利用するようにアプリケーションを修正する必要も出てきます。とは言っても、アプリケーション
が .NET(VB + ADO.NET)で作られているのであれば、修正は簡単です。例えば、次のような
コードで SQL Server へアクセスしているとします。
Using cn As New SqlConnection(cnstr)
Using cmd As New SqlCommand()
cn.Open()
cmd.Connection = cn
Dim sqlStr As String
sqlStr = "SELECT * FROM table1 WHERE col1 = @p1"
Dim p1 As SqlParameter = cmd.Parameters.Add("@p1", SqlDbType.Int)
p1.Value = 123
cmd.CommandText = sqlStr
End Using
End Using
このようにアプリケーションから実行している SELECT ステートメントを、次のようにネイティ
ブ コンパイル SP 化したとします。
CREATE PROC p_table1_検索
@p1 int
WITH NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
64
SQL Server 2014 実践 No.1 インメモリ OLTP
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'japanese' )
SELECT col1, col2, col3 FROM dbo.table1 WHERE col1 = @p1
END
このネイティブ コンパイル SP を利用するには、次のように SQL 文字列の部分(sqlStr 変数)
を、ネイティブ コンパイル SP 名に変更して、SqlCommand オブジェクトの CommandType
を「CommandType.StoredProcedure」へ変更するだけです。
cn.Open()
cmd.Connection = cn
cmd.CommandType = CommandType.StoredProcedure
' この行を追加
Dim sqlStr As String
sqlStr = "p_table1_検索"
' この行をネイティブ コンパイル SP 名に変更
Dim p1 As SqlParameter = cmd.Parameters.Add("@p1", SqlDbType.Int)
:
このようにアプリケーションの修正は、簡単に行えますが、簡単とは言っても、修正後にきちんと
動作するのかのテストなどが必要になるので、まずは、ネイティブ コンパイル SP を作成しない
で、できる限りアプリケーションを修正することなく、インメモリ化するだけで、性能向上を実現
できないか? と考え、テーブルだけを、メモリ最適化テーブルへ変更することを試みてみました。
ネイティブ コンパイル SP を作成しないと効果がない??
ネイティブ コンパイル SP を作成しないで、各テーブルをメモリ最適化テーブルへ変更しただけ
の場合に、シングル実行したときの性能は、次のようになりました。
個数
INSERT
SELECT
UPDATE
3
6
3
合計
12
OnDisk
In Memory
アプリ修正なし
差
向上率
何倍か
32.2
-25.7
7.6
0.06
-0.10
0.02
1.06
0.91
1.02
1,291.3 14.0
0.01
1.01
549.9
262.9
492.6
1,305.3
517.7
288.6
485.0
In Memory
NativeSP
218.4
123.8
408.0
750.3
この方法は、アプリケーションを修正しなくても済むのが非常に大きなメリットで、各テーブルの
インデックスを(メモリ最適化テーブルを利用するために)ハッシュ インデックスへ変更したり、
ハッシュ インデックスを作成するために照合順序を Japanese_BIN2 へ変更したりするなど
を行っていますが、今回対象となるテーブルが 6 個のみであったこともあり、これらの変更は半
日もあれば完了しました(具体的な作業方法については後述します)
。
結果は、ディスク ベースのものと 14 マイクロ秒しか差がでないものとなってしまいました(数
値は、前項と同様、1 スレッドで 1,000 回の処理を実行したときの「平均値」を計算したもの)。
しかし、この結果は、あくまでもシングル実行(シングル スレッドでの単体実行)での結果です。
本番を想定した 100 多重での負荷テスト(1 時間負荷をかけ続けるテスト)を実施すると、次の
65
SQL Server 2014 実践 No.1 インメモリ OLTP
ように 1.83 倍の性能向上を確認することができました。
本番想定の 100多重の負荷テスト実施時の 1秒あたりの処理量
1.83倍
の性能向上
* 負荷テストは、このお客様のために、弊社が独自に作成したツール(負荷テスト アプリケーション)
を利用して、実際の本番での処理をシミュレートできるようにしたもので実施しています。
In Memory
アプリ修正なし
1,743
3,189
104,569
191,314
6,274,117
11,478,864
On Disk
1秒
1分
1時間
In Memory
NativeSP
4,795
287,707
17,262,415
1時間あたり
の処理量
ディスク ベースでは、1 秒あたり 1,743 個の処理量であったところを、テーブルをメモリ最適化
テーブルへ変更するだけで、1 秒あたり 3,189 個の処理ができるようになりました(1.83 倍の
性能向上)
。
なんといっても、アプリケーションを修正することなく、テーブルを変更しただけで、1.83 倍も
の性能向上を実現できたことに驚きでした。従来ながらのディスク ベースのテーブルでは、多重
度が上がったときに、ラッチ待ちやロック待ち、チェックポイント時のデータ ファイルへのフラ
ッシュ、ログ書き込みの負荷、断片化の発生など、多くのオーバーヘッドがあるので、このような
性能差を確認することができました。
シングル実行も実は速くなるのでは?
上記のように多重実行で速くなることは確認できましたが、シングル実行でも、何かを変えれば速
くなるのかもしれない、という期待があり、改めて、以下の表を見て、気が付くことがありました。
個数
INSERT
SELECT
UPDATE
3
6
3
合計
12
OnDisk
549.9
262.9
492.6
1,305.3
In Memory
アプリ修正なし
差
向上率
何倍か
32.2
-25.7
7.6
0.06
-0.10
0.02
1.06
0.91
1.02
1,291.3 14.0
0.01
1.01
517.7
288.6
485.0
66
In Memory
NativeSP
218.4
123.8
408.0
750.3
SQL Server 2014 実践 No.1 インメモリ OLTP
INSERT と UPDATE は若干速くなっていて、足を引っ張っているのは SELECT ステートメン
トなのではないかという疑問が沸いてきたので、SELECT ステートメントについて詳しく調べて
みることにしました。
各 SELECT ステートメントの実行時間は?
シングル実行での足を引っ張っているのが SELECT ステートメントなのではないか、という疑問
から、各ステートメントの実行時間を確認してみました。
INSERT 顧客履歴 * 2
INSERT トランザクション テーブル
SELECT 顧客マスター
SELECT カード マスター
SELECT メッセージ マスター
SELECT カード種別マスター * 2
UPDATE 顧客マスター * 2
UPDATE カード マスター
SELECT ID値の取得
354.7
195.2
58.0
43.7
37.9
76.7
317.2
175.4
46.7
In Memory
アプリ修正なし
339.9
177.7
93.8
37.6
35.5
74.6
315.4
169.7
47.1
合計
1,305.3
1,291.3
ステートメント
OnDisk
4.2%
8.9%
-61.8%
13.8%
6.4%
2.7%
0.6%
3.3%
-1.0%
In Memory
NativeSP
77.5
141.0
31.2
23.9
23.5
45.2
268.8
139.2
0.0
1.1%
750.3
向上率
すると、「顧客マスター」の SELECT ステートメントのみが倍近く遅い結果(58 マイクロ秒が
93.8 マイクロ秒)になっていて、そのほかは性能向上していることが分かりました。
遅かった SELECT ステートメント(IN 演算子を利用)
遅かった SELECT ステートメントは、次のような形をとっていました。
SELECT
WHERE
AND
ORDER
* FROM 顧客マスター
カードID = '12345'
カード種別 IN ('A1', 'A2', 'A3')
BY カード種別
この顧客マスターは、次のようなテーブル構成です(列名を伏せるために col3 や col4 など、実
際の名前とは異なる名前へ変更しています)
。
CREATE TABLE 顧客マスター
( カードID nchar(16) COLLATE Japanese_BIN2 NOT NULL,
カード種別 nvarchar(2) COLLATE Japanese_BIN2 NOT NULL,
col3 datetime NULL,
col4 int NOT NULL,
col5 datetime NOT NULL,
col6 datetime NULL,
col7 nchar(1) NOT NULL DEFAULT ('0')
,CONSTRAINT PK_顧客マスター PRIMARY KEY NONCLUSTERED
HASH (カードID, カード種別) WITH (BUCKET_COUNT = 20000000)
) WITH ( MEMORY_OPTIMIZED = ON
, DURABILITY = SCHEMA_AND_DATA )
67
SQL Server 2014 実践 No.1 インメモリ OLTP
(カード ID, カード種別) を複合主キーとして PRIMARY KEY 制約に設定して、ハッシュ イン
デックスを作成しています。また、ハッシュ インデックスを作成するには、BIN2 の照合順序が
必須になるので、Japanese_BIN2 を指定しています(メモリ最適化テーブルを作成するための
要件については、第 3 章で説明します)。
遅かった SELECT ステートメントの「実行プラン」を確認してみると、次のようになりました。
Index Seek
カードID に統計がない
という主旨の警告
Index Seek(インデックスを利用したピンポイント検索)が選択されているので、実行プランを
見る限りは問題がなさそうに見えます。黄色い警告が 1 つ表示されていますが、これは、カード
ID 列に統計が作成されていないということで表示されています。
ディスク ベースのテーブルでは、複合インデックスの場合は、一番左に指定した列に対して、自
動的に統計が作成されますが、メモリ最適化テーブルでは作成されないようです(2 つ目の列のカ
ード種別に対しては、クエリ実行後に自動作成されました)。
クエリ実行後にカード種別
には統計が自動作成される
_WA_Sys_~
PK(2列)としての
統計は作成されている
68
SQL Server 2014 実践 No.1 インメモリ OLTP
統計の作成 ~CREATE STATISTICS~
カード ID 単体には、統計が作成されていないようだったので、CREATE STATISTICS ステート
メントを利用して、次のように統計を作成してみました。
-- カードID へ統計の作成
CREATE STATISTICS カードID_stats
ON 顧客マスター(カードID)
WITH FULLSCAN, NORECOMPUTE
このように統計を作成することによって、次のように実行プランに警告は表示されなくなりました
が、実行時間に変化はありませんでした(やはりディスク ベースの b-tree インデックスよりも
遅い結果となりました)
。
警告はなくなったが...
クエリを分割して考えてみる ~ハッシュ インデックスの考え方~
統計を作成しても効果がなかったので、この問題を解決するために、クエリを分割して考えてみま
した。まず、「カード ID」列のみで検索をしてみます。
SELECT * FROM 顧客マスター
WHERE カードID = '12345'
実行プランを確認してみると、次のように「Table Scan」になります。
Table Scan
私たちは、これを見たときに一瞬とまどってしまったのですが、b-tree インデックス的な発想だ
と、一番左の列に指定している列なら Index Seek になるわけです。しかし、Table Scan(全ス
キャン)になってしまっています。
これは、次のようにハッシュ インデックスをイメージすると分かりやすいと思います。
69
SQL Server 2014 実践 No.1 インメモリ OLTP
(カードID) に Hash Index を作成している場合
(12345)
ハッシュ値
(22222)
ハッシュ値
(77777)
ハッシュ値
(54321)
ハッシュ値
(カードID, カード種別) に Hash Index を作成している場合
0
1
2
3
4
5
6
:
(12345, A1)
ハッシュ値
(12345, A2)
ハッシュ値
(12345, A3)
ハッシュ値
(77777, A1)
ハッシュ値
0
1
2
3
4
5
6
:
以下の検索は、Table Scan になる
SELECT * FROM 顧客マスター
WHERE カードID = '12345'
以下の検索は、Index Seek になる
SELECT * FROM 顧客マスター
WHERE カードID = '12345'
ペアを指定すれば、Index Seek になる
SELECT * FROM 顧客マスター
WHERE カードID = '12345'
AND カード種別 = 'A1'
もし、「カード ID」列にのみハッシュ インデックスを作成しているのであれば、左図のように
Index Seek で検索できますが、(カード ID, カード種別) のハッシュ インデックスでは、カー
ド ID とカード種別がペアになってハッシュ値を計算しているため、カード ID の指定だけでは、
インデックスを Seek(ピンポイント検索)できないのです(右図)
。
これをヒントに、先ほどの遅かったクエリを改めてみてみます。
SELECT
WHERE
AND
ORDER
* FROM 顧客マスター
カードID = '12345'
カード種別 IN ('A1', 'A2', 'A3')
BY カード種別
カード ID とカード種別を指定していて、実行プランも Index Seek でプラン的にも問題ない、
統計を作成しても変わらない、そこで思い浮かんだのが、次の 2 点です。

IN 演算子が遅い?

(カード ID, カード種別) のペアがうまく認識されていない?
そこで、以下のように IN 演算子を利用しないで、カード種別を1つだけ指定した検索を行ってみ
ました。
SELECT
WHERE
AND
ORDER
* FROM 顧客マスター
カードID = '12345'
カード種別 = 'A1'
BY カード種別
この検索は、ディスク ベースの検索と遜色ないスピード(むしろディスク ベースよりも少し速
い?)という感触が得られました。
ようやく希望が見えてきたので、
次は、IN 演算子を UNION ALL
を使って分割してみました。
IN 演算子を UNION ALL への変更
IN 演算子は、UNION で置換することができるので、次のクエリは、同じ結果を取得することが
できます。
70
SQL Server 2014 実践 No.1 インメモリ OLTP
-- IN 演算子を使う場合
SELECT * FROM table1
WHERE col1 IN ('A', 'B')
-- UNION を使う場合
SELECT * FROM table1
WHERE col1 = 'A'
UNION
SELECT * FROM table1
WHERE col1 = 'B'
UNION は、重複データを除去する効果があるので、IN 演算子(OR)と等価になります。しか
し、重複データを除去する部分は、余計なオーバーヘッドになるので、もし事前に重複データが発
生することがあり得ないことが分かっているのであれば、UNION の代わりに UNION ALL を利
用して、性能向上を計ることができます。今回のカード ID とカード種別の関係は、まさにこの状
況なので、UNION ALL が使える状況でした。
そこで、さきほどの SELECT ステートメントの IN 演算子を、次のように UNION ALL を使っ
て、分割してみました。
SELECT * FROM (
SELECT * FROM 顧客マスター
WHERE カードID = '12345' AND カード種別 = 'A1'
UNION ALL
SELECT * FROM 顧客マスター
WHERE カードID = '12345' AND カード種別 = 'A2'
UNION ALL
SELECT * FROM 顧客マスター
WHERE カードID = '12345' AND カード種別 = 'A3' ) t1
ORDER BY カード種別
この SELECT ステートメントは、同じカード ID でカード種別が異なるもの(A1、A2、A3)を
取得する、という検索なので、重複値が発生することはないので、UNION ALL を利用することが
できます。これを利用することで、次のようにディスク ベースよりも 28.9% の性能向上を実現
することができました。
実行時間
58.0
93.8
41.2
31.2
On Disk
In Memory アプリ修正なし
In Memory UNION ALL 対応
In Memory Native SP
vs.OnDisk
-61.8%
28.9%
46.2%
ここにたどり着くまでに丸々1日ぐらい費やしてしまいましたが、ハッシュ インデックスへの理
解が深まったことは大きな収穫になりました。
この結果を、全体の結果へ当てはめてみると、次のように 5.1% の性能向上になりました。
71
SQL Server 2014 実践 No.1 インメモリ OLTP
INSERT 顧客履歴 * 2
INSERT トランザクション テーブル
SELECT 顧客マスター
SELECT カード マスター
SELECT メッセージ マスター
SELECT カード種別マスター * 2
UPDATE 顧客マスター * 2
UPDATE カード マスター
SELECT ID値の取得
354.7
195.2
58.0
43.7
37.9
76.7
317.2
175.4
46.7
In Memory
アプリ修正なし
339.9
177.7
41.2
37.6
35.5
74.6
315.4
169.7
47.1
合計
1,305.3
1,238.7
ステートメント
OnDisk
4.2%
8.9%
28.9%
13.8%
6.4%
2.7%
0.6%
3.3%
-1.0%
In Memory
NativeSP
77.5
141.0
31.2
23.9
23.5
45.2
268.8
139.2
0.0
5.1%
750.3
向上率
UNION ALL を利用することで、アプリケーションの修正が必要になりましたが、インメモリ化し
ただけで、シングル実行でも性能向上を達成できたことは大きな驚きでした。
また、本番を想定した 100 多重での負荷テストでは、次のように 1.93 倍もの性能向上を確認す
ることができました。
本番想定の 100多重の負荷テスト実施時の 1秒あたりの処理量
1.93倍の
性能向上
多重実行では、ラッチ待ちやロック待ち、チェックポイントの負荷などがかかるので、より大きな
差になることを確認できました。なによりも、アプリケーションをほとんど修正することなく、
1.93 倍(2 倍近い)の性能向上を実現できたことが驚きでした。
なお、ネイティブ コンパイル SP を作成すると、UNION ALL を利用するよりも、さらに速い実
行時間で処理できていますが、ネイティブ コンパイル SP では IN 演算子や UNION ALL が利
用できない、という制限があるところを、「メモリ最適化テーブル変数」を利用する、というテク
ニックを使っていますので、これについては後述します。
Note: IN を UNION へ変換する場合の注意点
今回のシステムでは、
IN 演算子で指定するカード種別が 3 つと固定だったので、UNION ALL へ
変更できましたが、もし IN で指定する値が可変の場合は、簡単に UNION へ変更することが
できません。このような場合は、後述する「bw-tree インデックス」を利用することで、今回
のような問題へ対処することが可能です。
72
SQL Server 2014 実践 No.1 インメモリ OLTP
2.4
bw-tree インデックスの利用
顧客マスターの SELECT では、UNION ALL を利用することで性能向上を実現しましたが、
bw-tree インデックスを利用することも検討しました。インメモリ OLTP では、ハッシュ イン
デ ッ ク ス だ け で な く 、「 メ モ リ 最 適 化 非 ク ラ ス タ ー 化 イ ン デ ッ ク ス 」( Memory-optimized
Non-Clustered Index)という種類のインデックスを作成することも可能で、このインデックスは
bw-tree という内部構造をとるので、bw-tree インデックスとも呼ばれています。
bw-tree インデックスは、従来のディスク ベースの非クラスター化インデックスの b-tree 構造
のインデックスを、インメモリ OLTP に対応させたもので、一番左の列でソートされた、ツリー
形式のインデックスになります。これにより、範囲スキャン(Range Scan)に強いインデックス
を作成することができます(これに対して、ハッシュ インデックスは、範囲スキャンには弱いと
いう特性がありますが、詳しくは、第 4 章で説明します)
。
先ほどの「顧客マスター」テーブルで、bw-tree インデックスを利用すると、次のようになりま
す。
CREATE TABLE 顧客マスター
( カードID nchar(16) COLLATE Japanese_BIN2 NOT NULL,
カード種別 nvarchar(2) COLLATE Japanese_BIN2 NOT NULL,
col3 datetime NULL,
col4 int NOT NULL,
col5 datetime NOT NULL,
col6 datetime NULL,
col7 nchar(1) NOT NULL DEFAULT ('0')
,CONSTRAINT PK_顧客マスター PRIMARY KEY NONCLUSTERED (カードID, カード種別)
) WITH ( MEMORY_OPTIMIZED = ON
, DURABILITY = SCHEMA_AND_DATA )
( カ ー ド ID, カ ー ド 種 別 ) を 複 合 主 キ ー と し て PRIMARY KEY 制 約 に 設 定 し て 、
NONCLUSTERED とだけ指定することで、bw-tree インデックスを作成することができます。
このインデックスを作成している場合は、今回問題となった以下のクエリも問題にはなりません。
SELECT
WHERE
AND
ORDER
* FROM 顧客マスター
カードID = '12345'
カード種別 IN ('A1', 'A2', 'A3')
BY カード種別
73
SQL Server 2014 実践 No.1 インメモリ OLTP
実行プランは Index Seek になり、統計の警告も表示されず、実行時間も次のように問題ありま
せんでした。
On Disk
In Memory
In Memory
In Memory
In Memory
アプリ修正なし
UNION ALL 対応
bw-treeインデックス
Native SP
実行時間
58.0
93.8
41.2
41.4
31.2
vs.OnDisk
-61.8%
28.9%
28.6%
46.2%
シングル実行した結果は、UNION ALL を利用した場合とほとんど同じになりました。
しかし、
「顧客マスター」テーブルは、SELECT ステートメントで参照されるだけでなく、一連の
処理の中で UPDATE ステートメントで 2 回の更新を行っています。この UPDATE ステートメ
ントを含めた実行結果(シングル実行)をまとめると、次のようになります。
ステートメント
SELECT 顧客マスター
UPDATE 顧客マスター * 2
合計
In Memory
アプリ修正なし
93.8
315.4
In Memory
Union 対応
41.2
315.4
409.1
356.6
In Memory
In Memory
bw-tree
NativeSP
41.4
31.2
327.5
268.8
368.9
300.0
結果は、ハッシュ インデックスを利用した場合よりも、若干の性能低下(12.3 マイクロ秒遅い結
果)となってしまいました。この結果を受けて、今回のシステムでは、bw-tree インデックスは
利用しませんでしたが、従来と同様の b-tree 形式で検索できることは大きなメリットになり得る
場面がありそうなので、別のシステムでは利用する可能性がありそうです(bw-tree インデック
スについては、第 4 章で詳しく説明します)
。
74
SQL Server 2014 実践 No.1 インメモリ OLTP
2.5
SELECT ステートメントの具体的な性能向上例
次に、残りの SQL ステートメントの具体的な内容について説明します。各ステートメントをシン
グル実行したときの実行時間と性能向上率の詳細は、次のとおりです。
種別
INSERT
SELECT
UPDATE
SELECT
INSERT 顧客履歴 * 2
INSERT トランザクション テーブル
SELECT 顧客マスター
SELECT カード マスター
SELECT メッセージ マスター
SELECT カード種別マスター * 2
UPDATE 顧客マスター * 2
UPDATE カード マスター
SELECT ID値の取得
354.7
195.2
58.0
43.7
37.9
76.7
317.2
175.4
46.7
In Memory
Union対応
339.9
177.7
41.2
37.6
35.5
74.6
315.4
169.7
47.1
合計
1,305.3
1,238.7
ステートメント
OnDisk
向上率
4.2%
8.9%
28.9%
13.8%
6.4%
2.7%
0.6%
3.3%
-1.0%
In Memory
NativeSP
77.5
141.0
31.2
23.9
23.5
45.2
268.8
139.2
5.1%
向上率
750.3
何倍か
78.2%
27.8%
46.2%
45.2%
38.0%
41.1%
15.3%
20.6%
4.6
1.4
1.9
1.8
1.6
1.7
1.2
1.3
42.5%
1.74
ネイティブ コンパイル SP を利用することで、
全体として 1.74 倍の性能向上(42.5% の向上)
を実現できています。
SELECT ステートメントは 1.6~1.9 倍の性能向上 ~PK を利用した参照~
SELECT ステートメントに関しては、顧客マスターやカード マスター、メッセージ マスター、
カード種別マスターの参照など、マスター系テーブルの参照で、すべてキー値(PRIMARY KEY)
が含まれている検索を行っています。これらは、ネイティブ コンパイル SP 化することで、38~
46%の性能向上(1.6~1.9 倍の性能向上)を実現できることが分かりました。それぞれの
PRIMARY KEY やデータ型は、次のとおりです。
各テーブルの概要
列数
Primary Key
PK の
データ型
追加の
Hash Index
SELECT
UPDATE
INSERT
32
カードID
nchar(16)
0
1
1
0
カード種別マスター
24
カードID
種別ID
nchar(16)
int
0
2
0
0
顧客マスター
7
カードID
カード種別
nchar(16)
nvarchar(2)
0
1
2
0
メッセージ マスター
8
MessageID
nchar(10)
0
1
0
0
bigint
2
0
0
1
bigint
1
0
0
1
カード マスター
トランザクション テーブル
30
顧客利用履歴テーブル
24
Seq
IDENTITY(1,1)
Seq
IDENTITY(1,1)
カード マスターの「カード ID」やメッセージ マスターの「MessageID」列などは、数値デー
タのみですが、char データ型で定義されています(char データ型の ID 列やコード列を利用さ
れているという方は多いのではないでしょうか? 弊社のお客様にもたくさんいらっしゃいます)。
カード マスターの検索例
カード マスターの検索は、次のように行っています。
SELECT col1, col2, col3, col4, …(32列分の列が列挙されています)
FROM カードマスター
WHERE カードID = @p1 AND 企業コード = @p2
75
SQL Server 2014 実践 No.1 インメモリ OLTP
WHERE 句の検索条件には、
「カード ID」と「企業コード」の 2 つの列がありますが、カード ID
列が PRIMARY KEY で、この列にはハッシュ インデックスを作成しているので、Index Seek
で検索することができています。この検索は、ネイティブ コンパイル SP 化することで、1.8 倍
の性能向上を確認しています。
ネイティブ コンパイル SP の作成方法については後述しますが、次のように作成しています。
CREATE PROC p_カードマスター検索
@p1 nchar(16), @p2 nchar(10)
WITH NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'japanese' )
SELECT col1, col2, col3, col4, …(32列分の列が列挙されています)
FROM dbo.カードマスター
WHERE カードID = @p1 AND 企業コード = @p2
END
NATIVE_COMPILATION や BEGIN ATOMIC など、新しいキーワードがいくつかありますが、
ネイティブ コンパイル SP を作成するのは、それほど難しいことではないのがお分かりいただけ
るのではないでしょうか。これを作成するだけで、シングル実行で 1.8 倍の性能向上を実現でき
るわけですから、インメモリ OLTP 機能のスゴさを体感することができました。
メッセージ マスターの検索例
メッセージ マスターの検索は、次のように行っています。
SELECT MessageID, col2, col3, col4, …(8列分の列が列挙されています)
FROM メッセージマスター
WHERE MessageID = @p1
メッセージ マスターは、
「MessageID」列が PRIMARY KEY で、この列にはハッシュ インデ
ックスを作成しているので、Index Seek で検索することができています。この検索は、ネイティ
ブ コンパイル SP 化することで、1.6 倍の性能向上を確認することができました。
ネイティブ コンパイル SP は、次のように作成しています。
CREATE PROC p_メッセージマスター検索
@p1 nchar(10)
WITH NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'japanese' )
SELECT MessageID, col2, col3, col4, …(8列分の列が列挙されています)
FROM dbo.メッセージマスター WHERE MessageID = @p1
76
SQL Server 2014 実践 No.1 インメモリ OLTP
END
カードマスターの検索のときとほとんど同じ形のネイティブ コンパイル SP であることがお分か
りいただけるのではないでしょうか。
カード種別マスターの検索例
カード種別マスターの検索は、次のように行っています。
SELECT col1, col2, col3, col4, …(24列分の列が列挙されています)
FROM カード種別マスター
WHERE カードID = @p1 AND 種別ID = @p2
カード種別マスターは、(カード ID, 種別 ID) の 2 つの列で構成される複合主キー(PRIMARY
KEY)で、この列にはハッシュ インデックスを作成しているので、Index Seek で検索すること
ができています。この検索は、ネイティブ コンパイル SP 化することで、1.7 倍の性能向上を確
認することができました。
ネイティブ コンパイル SP は、次のように作成しています。
CREATE PROC p_カード種別マスター検索
@p1 nchar(16), @p2 int
WITH NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'japanese' )
SELECT col1, col2, col3, col4, …(24列分の列が列挙されています)
FROM dbo.カード種別マスター
WHERE カードID = @p1 AND 種別ID = @p2
END
顧客マスターの検索例(IN/UNION ALL をメモリ最適化テーブル変数へ)
顧客マスターは、前の項で IN 演算子を UNION ALL へ変更したものでした。しかし、ネイティ
ブ コンパイル SP 内では、IN 演算子や OR 演算子、UNION ALL が利用できない、という制限
があるので、これを回避するために、「メモリ最適化テーブル変数」を利用しています。これにつ
いては、次の項で説明します。
77
SQL Server 2014 実践 No.1 インメモリ OLTP
2.6
メモリ最適化テーブル変数の利用(IN、OR、UNION ALL の代替)
顧客マスターの検索は、前項で説明したように、次のように (カード ID, カード種別) の複合主
キー(PRIMARY KEY)を利用したものでした。
SELECT
WHERE
AND
ORDER
* FROM 顧客マスター
カードID = '12345'
カード種別 IN ('A1', 'A2', 'A3')
BY カード種別
しかし、これでは性能向上を実現できなかったので、次のように UNION ALL へ変更しました。
SELECT * FROM (
SELECT * FROM 顧客マスター
WHERE カードID = '12345' AND カード種別 = 'A1'
UNION ALL
SELECT * FROM 顧客マスター
WHERE カードID = '12345' AND カード種別 = 'A2'
UNION ALL
SELECT * FROM 顧客マスター
WHERE カードID = '12345' AND カード種別 = 'A3' ) t1
ORDER BY カード種別
ネイティブ コンパイル SP 内では IN、OR、UNION が利用できない
ネイティブ コンパイル SP は、性能が向上する一方で、制限事項もあります(詳しくは後述しま
す)
。そのうちの 1 つが、ネイティブ コンパイル SP 内では、IN や OR、UNION を利用でき
ない、というものです。例えば、IN を含めたものを作成しようとすると、次のようにエラーが返
されます。
IN を含めると...
メモリ最適化テーブル変数の利用
IN や OR、UNION は、メモリ最適化テーブル変数を利用することで、代用できる場合がありま
78
SQL Server 2014 実践 No.1 インメモリ OLTP
す。これは、従来ながらのテーブル変数(table データ型の変数)をメモリ最適化テーブルのよう
に扱えるようにしたものです。使い方は、table データ型と同様、CREATE TYPE を利用して作
成し、違いは、最後に「MEMORY_OPTIMIZED = ON」を付けることと、ハッシュ インデック
スを付与する点です。
これは、次のように作成することができます。
CREATE TYPE 顧客マスター型 AS TABLE
(
カードID nchar(16) COLLATE Japanese_BIN2 NOT NULL,
カード種別 nvarchar(2) COLLATE Japanese_BIN2 NOT NULL,
col3 datetime NULL,
col4 int NOT NULL,
col5 datetime NOT NULL,
col6 datetime NULL,
col7 nchar(1) NOT NULL DEFAULT ('0')
,INDEX idx1 NONCLUSTERED HASH (カードID) WITH ( BUCKET_COUNT = 10 )
) WITH ( MEMORY_OPTIMIZED = ON )
顧客マスターの検索では、テーブルの全ての列を取得するので、顧客マスター テーブルの列定義
と全く同じものを定義しています(ハッシュ インデックスはカード ID へ設定)。このように、
CREATE TYPE で AS TABLE を指定して、
「MEMORY_OPTIMIZED = ON」を付けたものは、
メモリ最適化テーブル変数用の table データ型として利用することができます(DURABILITY
の指定はありませんが、メモリ最適化テーブル変数の場合は SCHEMA_ONLY で作成されます)。
このように定義した table データ型には、次のように INSERT ステートメントでデータを格納
することができます。
-- メモリ最適化テーブル変数の宣言
DECLARE @retValue dbo.顧客マスター型
-- メモリ最適化テーブル変数へデータの INSERT
INSERT INTO @retValue
SELECT カードID, カード種別, col3, col4, col5, col6, col7
FROM dbo.顧客マスター
WHERE カードID = N'12345' AND カード種別 = N'A1'
これを利用することで、UNION ALL で連結しているそれぞれの SELECT ステートメントの結果
をメモリ最適化テーブル変数へ格納して、その結果を返すようにすれば、ネイティブ コンパイル
SP を作成することができます。これは、次のように実装できます。
CREATE PROC p_顧客マスター検索
@p1 nchar(16)
WITH NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH (
TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'japanese')
79
SQL Server 2014 実践 No.1 インメモリ OLTP
-- メモリ最適化テーブル変数の宣言
DECLARE @retValue dbo.顧客マスター型
-- カード種別 A1 の結果を格納
INSERT INTO @retValue
SELECT カードID, カード種別, col3, col4, col5, col6, col7
FROM dbo.顧客マスター
WHERE カードID = @p1 AND カード種別 = N'A1'
-- カード種別 A2 の結果を格納
INSERT INTO @retValue
SELECT カードID, カード種別, col3, col4, col5, col6, col7
FROM dbo.顧客マスター
WHERE カードID = @p1 AND カード種別 = N'A2'
-- カード種別 A3 の結果を格納
INSERT INTO @retValue
SELECT カードID, カード種別, col3, col4, col5, col6, col7
FROM dbo.顧客マスター
WHERE カードID = @p1 AND カード種別 = N'A3'
-- メモリ最適化テーブル変数に格納した結果を SELECT。ORDER BY でソート
SELECT カードID, カード種別, col3, col4, col5, col6, col7
FROM @retValue
ORDER BY カード種別
END
go
この ネイティブ コンパイル SP を作成することで、顧客マスターの検索は、46.2%の性能向上
(1.9 倍の性能向上)を実現することができました。
このように、UNION ALL は、メモリ最適化テーブル変数で代用できる場合があるので、OR や IN
演算子の場合も、この方法を利用することができます。ただし、値が可変の場合への対応が難しい
ところがありますが、今回のシステムでは、カード種別が 3 種類と決定している状況でしたので、
この方法を採用することができました。
80
SQL Server 2014 実践 No.1 インメモリ OLTP
2.7
UPDATE ステートメントの具体的な性能向上例
UPDATE ステートメントは、
「顧客マスター」と「カード マスター」に対する更新があります。
INSERT 顧客履歴 * 2
INSERT トランザクション テーブル
SELECT 顧客マスター
SELECT カード マスター
SELECT
SELECT メッセージ マスター
SELECT カード種別マスター * 2
UPDATE 顧客マスター * 2
UPDATE
UPDATE カード マスター
SELECT SELECT ID値の取得
354.7
195.2
58.0
43.7
37.9
76.7
317.2
175.4
46.7
In Memory
Union対応
339.9
177.7
41.2
37.6
35.5
74.6
315.4
169.7
47.1
合計
1,305.3
1,238.7
種別
ステートメント
OnDisk
INSERT
向上率
4.2%
8.9%
28.9%
13.8%
6.4%
2.7%
0.6%
3.3%
-1.0%
In Memory
NativeSP
77.5
141.0
31.2
23.9
23.5
45.2
268.8
139.2
5.1%
750.3
向上率
何倍か
78.2%
27.8%
46.2%
45.2%
38.0%
41.1%
15.3%
20.6%
4.6
1.4
1.9
1.8
1.6
1.7
1.2
1.3
42.5%
1.74
UPDATE ステートメントは 1.2~1.3 倍の性能向上 ~PK を利用した更新~
顧客マスターとカード マスターに対する更新は、どちらもキー値(PRIMARY KEY)を利用した
更新を行っていて、これらは、ネイティブ コンパイル SP 化することで、15~20%の性能向上
(1.2~1.3 倍の性能向上)を実現できることが分かりました。
顧客マスターとカード マスターは、SCHEMA_AND_DATA(データの永続化有り)に設定して
おり、ログへの書き込みがある分、SELECT ステートメントほどの性能向上とはなりませんでし
たが、ネイティブ コンパイル SP を作成することの重要性を感じることができました(作成しな
い場合は、0.6~3.3%の性能向上のみでした)
。
顧客マスターの更新
顧客マスターの更新は、次のように行っています。
UPDATE 顧客マスター
SET col3 = @p3, col4 = @p4, col5 = @p5, col6 = @p6, col7 = @p7
WHERE カードID = @p1 AND カード種別 = @p2
WHERE 句の検索条件に、複合主キー(PRIMARY KEY)である「カード ID」と「カード種別」
を指定して、その他の列を更新しています。この更新は、ネイティブ コンパイル SP 化すること
で、1.2 倍の性能向上を確認することができました。
ネイティブ コンパイル SP は、次のように作成しています。
CREATE PROC p_顧客マスター更新
@p1 nchar(16), @p2 nvarchar(2),
@p3 datetime, @p4 int, @p5 datetime, @p6 datetime, @p7 nchar(1)
WITH
NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'japanese')
81
SQL Server 2014 実践 No.1 インメモリ OLTP
UPDATE dbo.顧客マスター
SET col3 = @p3, col4 = @p4, col5 = @p5, col6 = @p6, col7 = @p7
WHERE カードID = @p1 AND カード種別 = @p2
END
go
こうした単純な UPDATE を行うステートメントでも、ネイティブ コンパイル SP を作成するだ
けで、シングル実行で 1.2 倍の性能向上を実現できるわけですから、インメモリ OLTP 機能はや
はり便利です。
カード マスターの更新
カード マスターの更新は、次のように行っています。
UPDATE カードマスター
SET col2 = @p2, col3 = @p3, col4 = @p4, …(31列分の更新が列挙されています)
WHERE カードID = @p1
WHERE 句の検索条件に、PRIMARY KEY である「カード ID」列を指定して、その他の列を更新
しています。この更新は、ネイティブ コンパイル SP 化することで、1.3 倍の性能向上を確認す
ることができました。
ネイティブ コンパイル SP は、次のように作成しています。
CREATE PROC p_カードマスター更新
@p1 nchar(16), @p2 データ型, @p2 データ型, …(32列分のパラメーター)
WITH
NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'japanese')
UPDATE dbo.カードマスター
SET col2 = @p2, col3 = @p3, col4 = @p4, …(31列分の更新が列挙されています)
WHERE カードID = @p1
END
go
Tips: 全列の更新は必要??
上記のカード マスターの更新のように、PRIMARY KEY を指定して、残りの列をすべて更新す
る(32 列中、キーを除いた 31 列を更新する)
、という処理を行っているユーザーは多いのでは
ないでしょうか?
特に、データ アクセスのためのフレームワークなどを利用していると、自動的にこのような更
新を行う(特に更新が必要がない列まで含めて、全ての列を更新してしまう)場合が多々あり、
82
SQL Server 2014 実践 No.1 インメモリ OLTP
これでは余計なオーバーヘッドがかかってしまいます(ログの書き込み量も増えてしまいます)
。
そういう利用方法をされているお客様は、弊社にもたくさんいらっしゃいました。これを必要な
列のみの更新へ変更することができれば、さらなる性能向上を実現することができます...
Tips: 主キー列まで更新してしまう? は NG!
利用しているフレームワークによっては、主キー(PRIMARY KEY)列まで更新してしまうと
いう場合もあると思います(弊社のお客様にもいらっしゃいました)
。これは、次のような状況
です。
UPDATE dbo.カードマスター
SET カードID = @p1, col2 = @p2, col3 = @p3, …(すべての列の更新)
WHERE カードID = @p1
カードマスターの PRIMARY KEY である「カード ID」を WHERE 句の検索条件に入れている
にも関わらず、カード ID 列まで更新してしまっています。カード ID であれば、更新されるこ
とはあり得ない(更新が必要な場合は、番号の変更でなく、新しいカードを発行して、新しいカ
ード ID を割り当てる)、
という運用をしていることが多いのではないでしょうか? このように、
主キー列は、本来更新される可能性がほとんどないにも関わらず、フレームワークが全ての列を
更新するようになっていて、この形式で更新をしているという場合があります。
しかし、メモリ最適化テーブルでは、PRIMARY KEY 列の更新がサポートされていないので、こ
のような更新を行った場合は、エラーとなってしまいます。
PRIMARY KEY 列の更新は
サポートされていない
したがって、このような更新がある場合は、PRIMARY KEY 列を外したステートメントが実行さ
れるように、変更しなければなりません。
83
SQL Server 2014 実践 No.1 インメモリ OLTP
2.8
INSERT ステートメントの具体的な性能向上例
INSERT ステートメントは、「トランザクション テーブル」と「顧客利用履歴テーブル」への挿
入があります。
種別
INSERT
SELECT
UPDATE
SELECT
INSERT 顧客履歴 * 2
INSERT トランザクション テーブル
SELECT 顧客マスター
SELECT カード マスター
SELECT メッセージ マスター
SELECT カード種別マスター * 2
UPDATE 顧客マスター * 2
UPDATE カード マスター
SELECT ID値の取得
354.7
195.2
58.0
43.7
37.9
76.7
317.2
175.4
46.7
In Memory
Union対応
339.9
177.7
41.2
37.6
35.5
74.6
315.4
169.7
47.1
合計
1,305.3
1,238.7
ステートメント
OnDisk
向上率
4.2%
8.9%
28.9%
13.8%
6.4%
2.7%
0.6%
3.3%
-1.0%
5.1%
In Memory
NativeSP
77.5
141.0
31.2
23.9
23.5
45.2
268.8
139.2
750.3
向上率
何倍か
78.2%
27.8%
46.2%
45.2%
38.0%
41.1%
15.3%
20.6%
4.6
1.4
1.9
1.8
1.6
1.7
1.2
1.3
42.5%
1.74
INSERT ステートメントは 1.4~4.6 倍の性能向上
INSERT ステートメントは、ネイティブ コンパイル SP 化することで、27~78%の性能向上
(1.4~4.6 倍の性能向上)を実現できることが分かりました。
どちらのテーブルも、SCHEMA_AND_DATA(データの永続化有り)に設定していますが、顧客
利用履歴テーブルへの INSERT は、若干のタイムラグ(遅延)が許される状況だったので、
Delayed Durability(遅延書き込み/非同期書き込み)を採用しました。これによって、大きな
性能向上(4.6 倍もの性能向上)を実現することができ、Delayed Durability の強力さを確認す
ることができました。
これに対して、トランザクション テーブルへの INSERT は、ログへの通常書き込みがある分、
1.4 倍の性能向上に留まりました。UPDATE ステートメントが 1.2~1.3 倍の性能向上であった
ことを考えると、SCHEMA_AND_DATA の場合に、ネイティブ コンパイル SP 化することによ
って、1.2~1.4 倍程度の性能向上(シングル実行)が期待できるのでは、と考えることができそ
うです。
トランザクション テーブルへの INSERT
トランザクション テーブルへの INSERT は、次のように行っています。
INSERT INTO トランザクションテーブル (col2, col3, col4, …(29列分の列名))
VALUES(@p1, @p2, @p3, …(29列分のパラメーター) )
テーブルには、30 個の列があり、IDENTITY(1,1) に設定された「Seq」列が PRIMARY KEY
です。INSERT 時は、この列を除いた 29 列へ値を指定して、挿入しています。この INSERT は、
ネイティブ コンパイル SP 化することで、1.4 倍の性能向上を確認することができました。ネイ
ティブ コンパイル SP は、次のように作成しています。
CREATE PROC p_トランザクションテーブルへの挿入
@p1 データ型, @p2 データ型, @p2 データ型, …(29列分のパラメーター)
84
SQL Server 2014 実践 No.1 インメモリ OLTP
WITH
NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'japanese')
INSERT INTO dbo.トランザクションテーブル(col2, col3, col4, …(29列分の列名))
VALUES(@p1, @p2, @p3, …(29列分のパラメーター) )
SELECT SCOPE_IDENTITY() AS ID値
END
このように、単純なデータの INSERT でも ネイティブ コンパイル SP を作成するだけで、シン
グル実行で 1.4 倍の性能向上を実現することができました。
SCOPE_IDENTITY での ID の取得をネイティブ コンパイル SP へ含める
今回、ネイティブ コンパイル SP 化するにあたって、SCOPE_IDENTITY() をストアド プロシ
ージャの中に含めるように修正しています(今まではアプリケーションから SCOPE_IDENTITY
を実行)
。これを行わないと、IDENTITY によって生成された ID 値を取得できないからなのです
が、これは次のような状況です。
Native SP の実行
Native SP の実行
実行後に SCOPE_IDENTITY
Native SP の中で
SCOPE_IDENTITY を利用して
いれば ID 値を取得できる
Native SP の実行後に
SCOPE_IDENTITY を利用する
と、ID 値が取得できない
この動作は、通常のストアド プロシージャでも同様ですが、INSERT の実行後に、アプリケーシ
ョンから SCOPE_IDENTITY を利用している場合には、ストアド プロシージャ化することによ
って値が取れなくなってしまうので、SCOPE_IDENTITY はネイティブ コンパイル SP の中に
含めるように修正しなければなりません。
顧客利用履歴テーブルへの INSERT ~Delayed Durability~
顧客利用履歴テーブルへの INSERT は、次のようにネイティブ コンパイル SP を作成していま
す。
85
SQL Server 2014 実践 No.1 インメモリ OLTP
CREATE PROC p_顧客利用履歴への挿入
@p1 データ型, @p2 データ型, @p2 データ型, …(23列分のパラメーター)
WITH
NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( DELAYED_DURABILITY = ON,
TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'japanese')
INSERT INTO dbo.顧客利用履歴テーブル(col2, col3, col4, …(23列分の列名))
VALUES(@p1, @p2, @p3, …(23列分のパラメーター) )
SELECT SCOPE_IDENTITY() AS ID値
END
顧客利用履歴テーブルには、24 個の列があり、IDENTITY(1,1) に設定された「Seq」列が
PRIMARY KEY です。INSERT 時は、この列を除いた 23 列へ値を指定して、挿入を行っていま
す。この INSERT を、ネイティブ コンパイル SP 化するときに、
「DELAYED_DURABILITY =
ON」を WITH 句で追加することによって、遅延書き込み(非同期でのログへの書き込み)を行
えるになります。これによって、4.6 倍もの性能向上を実現できています。
DELAYED_DURABILITY = ON を指定しない場合は、トランザクションのコミット時にログへ
の書き込みを行って、書き込みが完了したことがコミットと見なされて、データの損失は発生しま
せんが、DELAYED_DURABILITY = ON を指定した場合は、ログへの書き込みが完了するのを
待たないで、コミットと見なします(ログへの書き込みは遅延で行います)。したがって、コミッ
トしたにも関わらず、データの損失の可能性があります(コミット直後に電源断などが発生した場
合にはデータの損失が起こり得ます)。その分、大幅な性能向上を実現できるというメリットを得
られます。
顧客利用履歴テーブルは、ユーザーが直接参照するテーブルではなく、ユーザーの利用状況を分析
するためのデータになるので、多少のデータ損失が許容できるものでした。そこで、Delayed
Durability を採用して、性能向上のメリットを享受することができました。こうした多少のタイム
ラグが許されるテーブルは、意外と多いのではないでしょうか?
例えば、もともと一定の間隔(1 分間隔など)でその瞬間のデータのみを保存する、といった処理
の場合などです。これであれば、もともと間のデータを取得していないので、1 分間隔であれば、
最大1分分のデータは損失する可能性があるわけです。それを許容して(性能とのトレードオフで)
システムの設計を行っているので、そういったテーブルでは、Delayed Durability を採用しても、
もともとデータの損失の可能性があったものなので、何の問題もありません。
Delayed Durability は、大幅な性能向上を実現することができるので、利用できる場面がないか
どうか、検討してみることを強くお勧めします。
86
SQL Server 2014 実践 No.1 インメモリ OLTP
2.9
各ステートメントの性能に関するまとめ
ここまでは、シングル実行時(シングル スレッドでの単体実行時)の各ステートメントにブレー
クダウンして、性能をみてきたので、いったんここまでの内容をまとめます。
種別
INSERT
SELECT
UPDATE
SELECT
INSERT 顧客履歴 * 2
INSERT トランザクション テーブル
SELECT 顧客マスター
SELECT カード マスター
SELECT メッセージ マスター
SELECT カード種別マスター * 2
UPDATE 顧客マスター * 2
UPDATE カード マスター
SELECT ID値の取得
354.7
195.2
58.0
43.7
37.9
76.7
317.2
175.4
46.7
In Memory
Union対応
339.9
177.7
41.2
37.6
35.5
74.6
315.4
169.7
47.1
合計
1,305.3
1,238.7
ステートメント
OnDisk
向上率
4.2%
8.9%
28.9%
13.8%
6.4%
2.7%
0.6%
3.3%
-1.0%
5.1%
In Memory
NativeSP
77.5
141.0
31.2
23.9
23.5
45.2
268.8
139.2
750.3
向上率
何倍か
78.2%
27.8%
46.2%
45.2%
38.0%
41.1%
15.3%
20.6%
4.6
1.4
1.9
1.8
1.6
1.7
1.2
1.3
42.5%
1.74
最初に、ディスク ベースのテーブルをメモリ最適化テーブルへ変更するだけ(アプリケーション
の修正はほとんどなし)で、5.1%の性能向上を確認することができました(顧客マスターの
SELECT では、IN 演算子を UNION ALL へ変更することによって、性能向上を実現することが
できました)。
本番を想定した 100 多重での負荷テストでは、次のように約 2 倍もの性能向上を確認することが
できました。
本番想定の 100多重の負荷テスト実施時の 1秒あたりの処理量
1.93倍の
性能向上
1.83倍の
性能向上
多重実行では、ラッチ待ちやロック待ち、チェックポイントの負荷などがかかるので、より大きな
差になることを確認できました。なによりも、アプリケーションをほとんど修正することなく(IN
演算子を UNION ALL へ変更しただけで)
、約 2 倍の性能向上を実現できたことが大きな成果でし
た。
ネイティブ コンパイル SP による性能向上
次に、ネイティブ コンパイル SP を作成することで、シングル実行で 1.74 倍もの性能向上を確
87
SQL Server 2014 実践 No.1 インメモリ OLTP
認することができました。顧客マスターの SELECT で、メモリ最適化テーブル変数を利用した以
外は、特にテクニックらしいテクニックを使ったものはなく、PRIMARY KEY 列を利用した
SELECT や UPDATE、単純な INSERT ステートメントであったにも関わらず、ネイティブ コ
ンパイル SP を作成することで性能向上を実現することができました。
それぞれのステートメントの性能向上は、次のとおりです。

PRIMARY KEY 列を利用した SELECT ステートメントは 1.6~1.9 倍の性能向上

PRIMARY KEY 列を利用した UPDATE ステートメントは 1.2~1.3 倍の性能向上

単純な INSERT ステートメントは 1.4 倍の性能向上

Delayed Durability(遅延書き込み)を利用した INSERT は、4.6 倍の性能向上
特筆すべきは、やはり Delayed Durability の効果です。非常に強力な機能なので、ぜひ利用で
きないか検討してみることをお勧めします。
ネイティブ コンパイル SP の作成によって、本番を想定した 100 多重での負荷テストでは、次
のように 2.8 倍もの性能向上を確認することができました。
本番想定の 100多重の負荷テスト実施時の 1秒あたりの処理量
2.8倍の
性能向上
1.93倍
1.83倍
ネイティブ コンパイル SP の作成は、約2日
今回は、ネイティブ コンパイル SP 化するものが 11 個でしたので、約2日ぐらいで作成するこ
とができました。ただ、作成した後に、何故か性能が出ない! など、性能が出るように動作させ
るまでに、何日かハマったところがありましたので、これについては、次の項で説明します。
88
SQL Server 2014 実践 No.1 インメモリ OLTP
2.10 ネイティブ コンパイル SP を簡単に作成する方法
ここでは、今回のポイントカード システムで、私たちがネイティブ コンパイル SP を作成したと
きに利用した方法を説明します。これを利用すれば、簡単にネイティブ コンパイル SP を作成す
ることができるので、参考になると思います。
大まかな手順は次のとおりです。
1.
アプリケーションから実行される SQL を、SQL Server Profiler でキャプチャする
2.
キャプチャした SQL(sp_executesql)をクエリ エディターへ貼り付けて、パラメーター
定義や実行ステートメントからネイティブ コンパイル SP を作成する
3.
パラメーターで (var)char データ型を利用している場合は、n 付きの n(var)char データ
型へ変更する
4.
ステートメント内で単一引用符(文字リテラル)を利用している場合は、N プレフィックスを
付ける
5.
SELECT ステートメントで SELECT * を利用している場合は、列名を列挙するように変更
する
実際の手順
次の .NET アプリケーション(VB.NET+ADO.NET)を例に、ネイティブ コンパイル SP を作
成する方法を説明します。
Using cn As New SqlConnection(cnstr)
Using cmd As New SqlCommand()
cn.Open()
cmd.Connection = cn
cmd.CommandText = "INSERT INTO t1 VALUES(@p1, @p2, @p3)"
Dim p1 As SqlParameter = cmd.Parameters.Add("@p1", SqlDbType.Int)
p1.Value = 1
89
SQL Server 2014 実践 No.1 インメモリ OLTP
Dim p2 As SqlParameter = cmd.Parameters.Add("@p2", SqlDbType.NVarChar, 200)
p2.Value = "A"
Dim p3 As SqlParameter = cmd.Parameters.Add("@p3", SqlDbType.DateTime)
p3.Value = DateTime.Now
cmd.ExecuteNonQuery()
cn.Close()
End Using
End Using
これは、「t1」テーブル(int、nvarchar(200)、datetime データ型の 3 つの列がある場合)
にデータを INSERT するコードです。
SQL Server Profiler ツールで SQL をキャプチャする
上記のアプリケーションを実行するときに、SQL Server Profiler ツールを利用して、内部的に
実行された SQL をキャプチャ(トレース)しますが、SQL Server Profiler ツールは、次のよう
に Management Studio の[ツール]メニューから「SQL Server プロファイラー」をクリッ
クして、起動することができます。
SQL Server Profiler
ツールの起動
起動後、
[サーバーへの接続]ダイアログが表示されたら、接続したい SQL Server の名前を入力
して、[接続]ボタンをクリックします。
SQL Server への
接続
接続が完了すると、次のように[トレースのプロパティ]ダイアログが表示されるので、[実行]
ボタンをクリックします。
90
SQL Server 2014 実践 No.1 インメモリ OLTP
これで、SQL Server に対して実行されたすべての SQL をキャプチャできるようになるので、こ
こでアプリケーションを実行します。
アプリケーションを実行すると、次のように「Audit Login」
(ログイン成功)や「RPC:Completed」
(リモート プロシージャ コールの完了)が表示されます。
RPC:Completed
アプリケーションから
実行された SQL
「RPC:Completed」をクリックすると、アプリケーションから内部的に実行された SQL を確認
することができます。次のように sp_executesql でパラメーター化されたものになっているこ
とを確認できます。
exec sp_executesql N'INSERT INTO t1 VALUES(@p1, @p2, @p3)'
,N'@p1 int, @p2 nvarchar(200), @p3 datetime'
,@p1=1, @p2=N'A', @p3='2014-06-24 16:38:06.853'
次に、この SQL をクエリ エディターへ貼り付けて、これをもとにネイティブ コンパイル SP を
作成していきます。
91
SQL Server 2014 実践 No.1 インメモリ OLTP
ネイティブ コンパイル SP の作成
ネイティブ コンパイル SP を作成するときは、次の基本構文を利用します。
-- ネイティブ コンパイル SP の雛形
CREATE PROC プロシージャ名
パラメーター定義
WITH
NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'japanese')
-- ここに SQL ステートメントを貼り付ける
END
この構文の中へ、プロファイラーでキャプチャした sp_executesql の第 2 引数のパラメーター
定義をプロシージャ名の後ろのパラメーター定義へ追加して、第 1 引数の SQL ステートメント
を BEGIN .. END ブロック内へ貼り付けます。
パラメーター定義へ貼り付け
BEGIN .. END ブロックへ貼り付け
オブジェクト名にスキーマ名を付与する
次に、オブジェクト名(画面は t1)へスキーマ名を付与して、
「dbo.t1」のように変更しますが、
もしスキーマ名を省略している場合は、次のように「スキーマ バインドできません」エラーが表
示されます。
スキーマ名を
省略している場合
スキーマ名を付与した後、プロシージャ名へ任意の名前を付ければ、ネイティブ コンパイル SP の
作成が完了です。
92
SQL Server 2014 実践 No.1 インメモリ OLTP
任意のプロシージャ名へ変更
スキーマ名を付与
コマンドは正常に完了しました。
と表示されれば、作成が完了
アプリケーションの修正(ADO.NET の場合)
ネイティブ コンパイル SP を作成した後は、アプリケーション側を変更しますが、次のように、
SqlCommand オブジェクトの CommandText をネイティブ コンパイル SP の名前へ変更
して、CommandType を「CommandType.StoredProcedure」へ指定するだけで完了です。
cn.Open()
cmd.Connection = cn
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "ネイティブ コンパイル SP の名前へ変更"
' この行を追加
' この行を修正
Dim p1 As SqlParameter = cmd.Parameters.Add("@p1", SqlDbType.Int)
p1.Value = 1
Dim p2 As SqlParameter = cmd.Parameters.Add("@p2", SqlDbType.NVarChar, 200)
p2.Value = "A"
Dim p3 As SqlParameter = cmd.Parameters.Add("@p3", SqlDbType.DateTime)
p3.Value = DateTime.Now
cmd.ExecuteNonQuery()
ここを修正
するだけ
93
SQL Server 2014 実践 No.1 インメモリ OLTP
パラメーター(SqlParameter)に関しては、同じものを利用しているので、そのまま利用するこ
とができます。このように記述することで、ネイティブ コンパイル SP を実行することができ、
内部的には次のように変換されて実行されています。
アプリケーションから
実行された SQL
EXEC で、ストアド プロシージャを呼び出して、パラメーターのデータ型が nvarchar の場合
は、N プレフィックスが付与された文字列に変換されて、実行されています。
Note: CommandType を Text にした場合は?
上の例では、SqlCommand の CommandType を「CommandType.StoredProcedure」
へ変更して、CommandText へネイティブ コンパイル SP の名前を指定する方法をとりまし
たが、CommandType の既定値である「CommandType.Text」を利用しても、次のように
ネイティブ コンパイル SP を実行することができます。
CommandType.Text
を利用した場合
しかし、この方法は、
「CommandType.StoredProcedure」を利用するよりも性能が出ない
ので、利用しないことをお勧めします。詳しい性能比較については、次の項で説明します。
94
SQL Server 2014 実践 No.1 インメモリ OLTP
パラメーターに char/varchar が利用できない ~ n 付きへ変更する~
データベースの照合順序に Japanese_CI_AS(日本語版の SQL Server の既定の照合順序)な
ど、コードページが「1252」以外のものを利用している場合には、ネイティブ コンパイル SP の
パラメーターで char/varchar データ型を利用することができません。
パラメーターのデータ型に
varchar を利用した場合
「コード ページが 1252 以外では
char および varchar はサポートされません」エラー
このように、ネイティブ コンパイル SP では、データベースの照合順序が Japanese_CI_AS の
場合には、パラメーターのデータ型に char/varchar データ型を利用することができません。
代わりに、nchar/nvarchar データ型(n 付きのデータ型)へ変更する必要があります。
なお、コード ページが「1252」の照合順序(Latin_~ など)を利用することで、char/varchar
データ型を利用することもできますが、これについては後述します。
プロシージャ内の単一引用符には N プレフィックスが必要
データベースの照合順序に Japanese_CI_AS など、コードページが「1252」以外の照合順序
を利用している場合には、ネイティブ コンパイル SP 内の単一引用符(文字列リテラル)には、
N プレフィックスを付ける必要があります。もし、N プレフィックスを付けていない場合は、次の
ようにエラーになります。
単一引用符に
N プレフィックスを付けなかった場合
「コード ページが 1252 以外では
char および varchar はサポートされません」エラー
95
SQL Server 2014 実践 No.1 インメモリ OLTP
このエラーを回避するには、N'AAA' のように、文字列を N プレフィックスで囲むようにします。
N プレフィックスは、nchar/nvarchar データであることを示すためのものなので、N プレフィ
ックスがない場合は、char/varchar データと見なされてしまって、先ほどと同じエラーが発生
してしまいます。このエラー メッセージは、私自身がよく目にして、エラー メッセージだけだと
理由が一瞬分からないことがあったので、覚えておくことをお勧めします。例えば、日付データを
次のように単一引用符で囲んだ場合にも同様のエラーが表示されます。
日付データの単一引用符に
N プレフィックスを付けなかった場合
「コード ページが 1252 以外では
char および varchar はサポートされません」エラー
この場合も、日付を N'2014/06/14' のように N プレフィックスで囲まなければなりません。
SELECT * を利用できない ~列名を列挙するように変更する~
SELECT ステートメントをネイティブ コンパイル SP 化する場合は、「SELECT *」(* を利用
して全ての列の取得)を利用することができません。ネイティブ コンパイル SP では、次のよう
に「SELECT *」を利用すると、構文エラーになります。
SELECT * を
利用した場合
「スキーマ バインド オブジェクト
では許可されません」エラー
SELECT ステートメントの選択リストは、
「SELECT col1, col2, …」のように、取得したい列名
をすべて列挙しなければなりません。
96
SQL Server 2014 実践 No.1 インメモリ OLTP
* が利用できないので、
列名を列挙する必要がある
すべての列を手書きで列挙するのは、取得したい列がたくさんある場合には面倒なので、私はこれ
を回避するために、オブジェクト エクスプローラーで次のように該当テーブルを右クリックして、
[上位 1000 件の選択]をクリックして、列名を自動生成させたものを利用しました。
TOP 1000 付きの
すべての列名が列挙された
SELECT ステートメントが
自動生成される
これを利用すれば、簡単に列名を列挙することができます。
97
SQL Server 2014 実践 No.1 インメモリ OLTP
Note: ネイティブ コンパイル SP の dll が作成される場所
ネイティブ コンパイル SP は、CREATE PROC でストアド プロシージャを作成したときに、
コンパイルされた DLL が作成されます。この DLL は、SQL Server をインストールしたフォ
ルダーの Data\xtp フォルダー配下に格納されています。
C 言語に変換された
ソース
コンパイルされた DLL
SQL Server をインストール
したフォルダーの
Data の下の xtp フォルダー
* xtp は、Extreme Transaction Processing の略で、インメモリ OLTP 機能の通称
「xtp_p_7_405576483.dll」形式のファイルが、コンパイルされた DLL です。_7 の部分
はデータベース ID、_405576483 はオブジェクト ID で、オブジェクト ID は、次のように
sysobjects システム テーブルを参照して確認することもできます。
オブジェクトID
「.c」拡張子のファイルは、ネイティブ コンパイル SP が C 言語に変換されたソース コード
になっていて、次のように中身を参照することも可能です。
ネイティブ コンパイル SP が
C 言語に変換されたソース
98
SQL Server 2014 実践 No.1 インメモリ OLTP
2.11 ネイティブ コンパイル SP の実行方法の違いによる性能差
アプリケーション(ADO.NET)からネイティブ コンパイル SP を実行するときは、次のように
SqlCommand オブジェクトの CommandText をネイティブ コンパイル SP の名前を指定
して、CommandType を「CommandType.StoredProcedure」へ変更すると説明しました。
Using cn As New SqlConnection(cnstr)
Using cmd As New SqlCommand()
cn.Open()
cmd.Connection = cn
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "p_t1_insert"
' ネイティブ コンパイル SP の名前
Dim p1 As SqlParameter = cmd.Parameters.Add("@p1", SqlDbType.Int)
p1.Value = 1
Dim p2 As SqlParameter = cmd.Parameters.Add("@p2", SqlDbType.NVarChar, 200)
p2.Value = "A"
Dim p3 As SqlParameter = cmd.Parameters.Add("@p3", SqlDbType.DateTime)
p3.Value = DateTime.Now
cmd.ExecuteNonQuery()
cn.Close()
End Using
End Using
このように記述すると、内部的には、次のように EXEC に変換されて実行されています。
EXEC に変換されて
実行される
この他にもネイティブ コンパイル SP を実行する方法があるので、ここではそれらの実行方法の
違いによる性能差を説明します。結論から言うと、上のように実行するのが最も性能が出る実行方
法になります。
実行方法の違い
アプリケーション(ADO.NET)からネイティブ コンパイル SP を実行するには、次の 3 つの方
法があります。
99
SQL Server 2014 実践 No.1 インメモリ OLTP
概要
実行方法1
CommandType.StoredProcedure で、SqlParameter を使用
実行方法2
CommandType.Text で、SqlParameter を使用
実行方法3
CommandType.Text で、SqlParameter を使用しないで文字列連結で実行
実行方法1 は、最初に説明したコードです。
実 行 方 法 2 は 、 CommandType の 既 定 値 で あ る 「 CommandType.Text 」 を 利 用 し て 、
SqlParameter(パラメーター)も利用する方法です(前述の Note で少し紹介しました)。具体
的には、次のように利用します。
cmd.CommandType = CommandType.Text
' 省略可能
cmd.CommandText = "p_t1_insert @p1, @p2, @p3" ' p_t1_insert はネイティブ SP の名前
Dim p1 As SqlParameter = cmd.Parameters.Add("@p1", SqlDbType.Int)
p1.Value = 1
:
このように実行した場合は、内部的には、次のように sp_executesql に変換されます。
sp_executesql
に変換される
実行方法 3 は、実行方法2と同じく「CommandType.Text」を利用しますが、実行方法3では
SqlParameter を利用しないで、文字列連結を利用します。具体的には、次のように記述します。
cmd.CommandType = CommandType.Text
' 省略可能
cmd.CommandText = "p_t1_insert @p1 = 1, @p2 = N'A', @p3 = '" & DateTime.Now & "'"
:
この方法では、パラメーターを文字列として埋め込んで、「&」で文字列連結して、実行していま
す。このように実行した場合は、内部的には、次のようにそのまま実行されます。
そのまま実行される
100
SQL Server 2014 実践 No.1 インメモリ OLTP
性能比較
3 つの実行性能を比較すると、次のようになります(3 列構成のメモリ最適化テーブルへ Insert
を行った場合)
。
20% 遅くなる
Native SP を作成しないよりも
遅くなってしまう
Native SP を作成しないで
INSERT を直接実行した場合
実行時間は、「実行方法1」の結果を 100 とした場合の相対値。
この値は、1スレッドでアプリケーションを実行して、2,000回繰り返し実行したときの「平均値」を比較
(SQL Server Profiler ツールを利用して処理をトレースして、その結果から平均値を計算)。
結果は、実行方法1 が一番速く実行することができ、実行方法2は、約 7%遅くなり、実行方法
3 では、約 20% 遅くなることを確認することができました。実行方法3は、ネイティブ コン
パイル SP を作成しな い で、 INSERT ス テートメ ントを 直接実行する(CommandText へ
INSERT を記述したもの)よりも遅い結果となってしまいました。
このように、せっかくネイティブ コンパイル SP を作成しても、SqlParameter を利用しなか
ったり、CommandType をきちんと指定していない場合には、ネイティブ コンパイル SP の性
能メリットを享受することができず、実行方法3を選択してしまうと、逆に遅くなってしまうこと
もあるので、実行方法3 は選択しないようにしましょう。
私は、当初、アプリケーションの修正をできる限り少なくするためにと思って、実行方法2 を選
択してしまっていたのですが、これで性能テストを行ったときに、性能メリットをあまり感じるこ
とができず、せっかくネイティブ コンパイル SP を作成したのに何故???と何日も悩んでしま
いました。実行方法1と2では、修正箇所は 1 行増えるかどうかしか変わらないのですが、1 行コ
メントして、1 行変更したほうがコード上の修正が見やすいと思って、誤選択をしてしまいまし
た...
実行方法1 を利用すれば、性能メリットを享受することができるので、実行方法1 を利用するこ
とをお勧めします。
101
SQL Server 2014 実践 No.1 インメモリ OLTP
2.12 char/varchar へ変更した場合の注意点
今回のポイントカード システムでは、もともとのテーブルのデータ型は char や varchar を利
用していて、
「カード ID」や「カード種別」、
「MessageID」、
「企業コード」などは、英数字のデ
ータしか格納されない(日本語は格納されない)ので、n 付きの nchar や nvarchar(Unicode
対応のデータ型)へ変更する必要はなかったので、最初は、次のようなテーブル構成でテストを行
っていました。
カードID が char
カード種別 が varchar
col7 が char(1)
nchar/nvarchar(n 付き)を利用した場合は、英数字データでも 2 バイトを消費することにな
るので、データ量(メモリ使用量)が増えてしまって、遅くなるのではないか? という私の推測
があって、最初に char/varchar を選択していました。
しかし、char/varchar を利用する上では、次のような大きな問題が 2 つありました。

データベースの照合順序が、Japanese_CI_AS だと、ネイティブ コンパイル SP の
パラメーターのデータ型に char/varchar を利用することができない(パラメーター
に nchar/nvarchar を利用した場合は、桁違いに遅くなってしまう)

データベースの照合順序に、コード ページ 1252 の照合順序(例えば、英語版の既定
値である SQL_Latin1_General_CP1_CI_AS など)を利用する場合は、日本語デー
タがある場合に、N プレフィックスが必須になり、ADO.NET で SqlDbType.Char を
利用している場合は、SqlDbType.NChar へ変更しなければいけなくなる(変更しない
と性能が出ない)
データベースの照合順序に何を利用しているかによって、問題が変わってきますが、今回のシステ
ムでは、既定の照合順序の Japanese_CI_AS を利用していました。この場合は、ネイティブ コ
ンパイル SP のパラメーターのデータ型に char/varchar を利用することができないというの
が大きなネックになりました。これは、前々項で紹介した以下のエラー メッセージです。
パラメーターのデータ型に
char を利用した場合
「コード ページが 1252 以外では
char および varchar はサポートされません」エラー
102
SQL Server 2014 実践 No.1 インメモリ OLTP
これを回避するには、パラメーターのデータ型に nchar/nvarchar を採用することですが、こ
れを利用すると、検索処理で、桁違いに性能が低下するという大問題が発生してしまいました(後
述)
。
データベースの照合順序に Japanese_CI_AS を利用している場合
データベースの照合順序に、Japanese_CI_AS を利用している場合は、char/varchar データ
型にコード ページ 1252 以外の照合順序を利用することができないという制限もあります。これ
を回避するには、次のように Latin1_General_BIN2 など、1252 コード ページの照合順序を
使用するようにします。
Latin1_General_BIN2
照合順序を利用
Latin1_General_BIN2
照合順序を利用
ただし、この 1252 コード ページの照合順序では、日本語データを格納することができないので、
日本語データを格納したい列には、nchar/nvarchar データ型を利用しなければなりません。ま
た、一番の問題は、前述したように、ネイティブ コンパイル SP のパラメーターのデータ型に
char/varchar を利用することができないことで、これでは性能が出ないことでした。
例えば、上記のように char で定義した顧客マスターに対して、検索を行う SELECT ステート
メントを ネイティブ コンパイル SP 化すると次のようになります。
データベースの照合順序が Japanese_CI_AS
だと、パラメーターのデータ型に char
が利用できないので nchar を利用
nchar のパラメーターが検索条件
実際の SELECT 処理は、前述したように、メモリ最適化テーブル変数を利用して、
カード種別を 3パターン検索しますが、説明を簡略化するために、1つのカード種別へ絞っています。
検索条件である「カード ID」へ与えるパラメーターには、nchar(16) を利用しなければいけま
せん。このように nchar(16) を利用した場合は、次のように実行プランが「Index Scan」(イ
ンデックスの全スキャン)になってしまい、桁違いに性能が悪くなります。
103
SQL Server 2014 実践 No.1 インメモリ OLTP
Index Scan
(インデックスの全スキャン)
になってしまう
ツールバーの「推定実行プラン」ボタンで、実行プランを確認することができます。
後述しますが、ネイティブ コンパイル SP では、「実際の実行プラン」を利用することはできません。
暗黙の型変換による大幅な性能低下 ~ char に対して nchar を利用するとき~
列のデータ型が char/varchar で、ネイティブ コンパイル SP のパラメーターのデータ型が
nchar/nvarchar(n 付き)の場合には、暗黙の型変換(Convert Implicit)が発生して、大幅
な性能低下が発生します。前述の実行プランで確認したように、データ型が異なると、暗黙の型変
換が発生して、Index Scan(インデックスの全スキャン)になってしまうからです。型が正しけ
れば Index Seek(インデックスを利用したピンポイント検索)で済みます。
この場合の実行時間を比較すると、次のようになります。
暗黙の型変換で
Index Scan
型は合っていない
が Index Seek
型が正しければ
Index Seek
型が正しければ
Index Seek
顧客マスターの「カードID」列のデータ型を nchar(16) にした場合と、char(16) にした場合の性能比較
実行時間は、「Native SP nchar」の結果を 100 とした場合の相対値。
この値は、1スレッドでアプリケーションを実行して、1,000回繰り返し実行したときの「平均値」を比較
(SQL Server Profiler ツールを利用して処理をトレースして、その結果から平均値を計算)。
列のデータ型を nchar へ設定している場合は(グラフの左3つ)、型が正しければ、もちろん
Index Seek で良い性能が出ますが、型が合わずに暗黙の型変換が発生した場合でも Index
Seek になって、性能低下は発生しませんでした。
グラフ内の直接 SQL は、ネイティブ コンパイル SP を利用せずに、次のように ADO.NET か
ら SQL を実行した場合です。
104
SQL Server 2014 実践 No.1 インメモリ OLTP
ネイティブ コンパイル SP
を作成せずに、直接 SQL を
実行した場合
パラメーター
A1 を nchar として
扱う場合は N'A1' にする
パラメーターへ与える
データ型が char(16)
nchar(16) を与える場
合はこちら
なお、このように、直接 SQL を実行した場合は、ネイティブ コンパイル SP を利用するよりも
1.4 倍程度遅くなります。
グラフの右3つは、列のデータ型を char へ設定している場合ですが、この場合は、ネイティブ コ
ンパイル SP のパラメーターに char を利用することができないので、nchar を利用することで、
暗黙の型変換が発生して、Index Scan(インデックスの全スキャン)になってしまっています。
実行速度は、1 万倍も遅い結果になりました(これは、仮に Index Seek が 10 マイクロ秒で完
了したとすると、Index Scan だと 100 ミリ秒もかかってしまうという意味になります)
。
列のデータ型を char へ設定している場合に、唯一、暗黙の型変換が発生しないのは、直接 SQL
を実行するときで、次のように SqlParameter のデータ型に char を指定した場合のみです。
cmd.CommandText = "SELECT カードID, カード種別, col3, col4, col5, col6, col7 " _
& " FROM dbo.顧客マスター" _
& " WHERE カードID = @p1 AND カード種別 = 'A1'"
Dim p1 As SqlParameter = cmd.Parameters.Add("@p1", SqlDbType.Char, 16)
このように型が合っていれば、Index Seek になってくれますが、直接 SQL を実行する方法は、
ネイティブ コンパイル SP を利用するよりも、性能が劣るのが惜しいところです(1.4 倍くらい
低速)。
このように、データベースの照合順序に Japanese_CI_AS を利用して、列のデータ型を char
にするのは難しいことが分かったので、次は、データベースの照合順序を Latin_~(コード ペ
ージ 1252)にすることを試してみました。
データベースの照合順序に 1252 コード ページを利用する場合(Latin_~)
データベースの照合順序に、コード ページ 1252(例えば、英語版の既定値である SQL_Latin1
105
SQL Server 2014 実践 No.1 インメモリ OLTP
_General_CP1_CI_AS など)を利用した場合は、ネイティブ コンパイル SP のパラメーター
のデータ型に char/varchar を利用することができます。
ただし、日本語データを格納したい場合には、nchar/nvarchar データ型へ変更しなければなり
ません(Japanese_CI_AS であれば、char/varchar でも日本語データを格納可能です)。また、
日本語データを扱うときには、N プレフィックスも必須になり、ADO.NET で SqlParameter を
SqlDbType.Char として利用している場合は、SqlDbType.NChar へ変更する必要もあります
(後述しますが、変更しないと性能が出ません)
。
データベースの照合順序は、次のようにデータベースを作成するときに指定することができます
(データベース名を db1_Latin とする場合)。
CREATE DATABASE db1_Latin
ON PRIMARY ( NAME = db1_Latin_data,
FILENAME = 'D:\testDB\db1_Latin_data.mdf',
SIZE = 102400MB ),
FILEGROUP fg1 CONTAINS MEMORY_OPTIMIZED_DATA
( NAME = db1_Latin_InMem,
FILENAME = 'D:\testDB\db1_Latin_InMem' )
LOG ON
( NAME = db1_Latin_log,
FILENAME='D:\testDB\db1_Latin_log.ldf',
SIZE = 10240MB )
COLLATE SQL_Latin1_General_CP1_CI_AS
CREATE DATABASE ステートメントの末尾に、COLLATE 句を付けて、照合順序を指定するこ
とができます(COLLATE 句を省略した場合は、サーバーの照合順序に設定されます)。なお、コ
ード ページ 1252 の照合順序の一覧は、次のように fn_helpcollations を利用して、取得す
ることもできます。
SELECT * FROM sys.fn_helpcollations()
WHERE collationproperty(name, 'codepage') = 1252
このように、データベースの照合順序を SQL_Latin1_General_CP1_CI_AS へ設定して、次
のように「顧客マスター」テーブルを作成(カード ID 列を char(16) へ設定)してテストを行
いました。
106
SQL Server 2014 実践 No.1 インメモリ OLTP
カードID が char
カード種別 が varchar
col7 が char(1)
ネイティブ コンパイル SP は、次のように char(16) のパラメーターで作成しました。
データベースの照合順序が
コード ページ 1252 なら、
パラメーターのデータ型に char を利用できる
N を付けないようにして、
char として認識させる
char のパラメーター
このように、列のデータ型と、パラメーターのデータ型が合っていれば(同じであれば)、次のよ
うに Index Seek で検索できるようになります。
データ型が同じなので
Index Seek
この場合の実行時間を比較すると、次のようになりました。
暗黙の型変換で
Index Scan
型が正しければ
Index Seek
「カードID」列のパラメーターのデータ型を char(16) にした場合と、nchar(16) にした場合の性能比較
実行時間は、「Native SP char」の結果を 100 とした場合の相対値。
この値は、1スレッドでアプリケーションを実行して、1,000回繰り返し実行したときの「平均値」を比較
(SQL Server Profiler ツールを利用して処理をトレースして、その結果から平均値を計算)。
107
SQL Server 2014 実践 No.1 インメモリ OLTP
列のデータ型とパラメーターのデータ型が正しければ(同じであれば)、Index Seek で良い性
能が出ますが、パラメーターに nchar を利用してしまうと、暗黙の型変換が発生して、Index
Scan(インデックスの全スキャン)になってしまいます(1 万倍遅い結果)
。
Japanese_CI_AS のときは、直接 SQL を実行する方法でしか、データ型を合わせることがで
きませんでしたが、コード ページ 1252 を利用している場合は、ネイティブ コンパイル SP の
パラメーターに char/varchar を利用することができるので、(データ型さえ間違えなければ)
性能面でのロスがなくなります。
実際に、今回のポイントカード システムでの各ステートメントをシングル実行したときの実行時
間を計測すると、次のようになりました。
種別
J a pa nes e_ CI_ A S
N a ti v eSP
ncha r/nv a rcha r
ステートメント
La ti n_ ~
N a ti v eSP
cha r/v a rcha r
差
INSERT 顧客履歴 * 2
INSERT トランザクション テーブル
SELECT 顧客マスター
SELECT カード マスター
SELECT
SELECT メッセージ マスター
SELECT カード種別マスター * 2
UPDATE 顧客マスター * 2
UPDATE
UPDATE カード マスター
77.5
141.0
31.2
23.9
23.5
45.2
268.8
139.2
75.4
140.1
30.0
23.3
28.6
57.1
267.8
138.8
2.1
0.9
1.2
0.6
-5.0
-11.9
1.0
0.4
合計
750.3
761.1
-10.8
INSERT
結果は、Japanese_CI_AS でも、Latin_~ でも、ほとんど差が出ませんでした。
テスト前は、私の経験上、char のほうが速くなるだろうと予想していたのですが、今回のテスト
では、その差を感じることができませんでした。しかし、char/varchar データ型を利用するこ
とで、メモリ使用量を削減することができるので、このメリットは非常に大きく、今回は利用しま
せんでしたが、メモリ使用量を抑えたい場合に、非常に有用なオプションであると感じました(メ
モリ使用量については、後述します)
。
108
SQL Server 2014 実践 No.1 インメモリ OLTP
暗黙の型変換のまとめ ~ char のときに nchar だと遅くなる~
暗黙の型変換について、いろいろなパターンを試しましたので、ここでまとめておきます。
DB の照合順序
列のデータ型
nchar
Japanese_CI_AS
(既定値)
char
nchar
コードページ 1252
Latin_~ など
char
パラメーターの
データ型
Native SP nchar
実行プラン
実行速度
最速
同じ型
Index Seek
Native SP char
利用できない
-
-
直接 SQL nchar
同じ型
Index Seek
1.4倍遅い
直接 SQL char
暗黙の型変換
Index Seek
1.4倍遅い
Native SP nchar
暗黙の型変換
Index Scan
1万倍遅い
Native SP char
利用できない
-
-
直接 SQL nchar
暗黙の型変換
Index Scan
1万倍遅い
直接 SQL char
Native SP nchar
同じ型
同じ型
Index Seek
Index Seek
1.4倍遅い
最速
暗黙の型変換
Index Seek
最速
Native SP char
直接 SQL nchar
同じ型
Index Seek
1.4倍遅い
直接 SQL char
暗黙の型変換
Index Seek
1.4倍遅い
Native SP nchar
1万倍遅い
暗黙の型変換
Index Scan
Native SP char
同じ型
Index Seek
最速
直接 SQL nchar
暗黙の型変換
Index Scan
1万倍遅い
同じ型
Index Seek
1.4倍遅い
直接 SQL char
遅くならない
Japanese_CI_AS
で char だと
直接 SQL にする
しか方法がない
遅くならない
遅くならない
直接 SQL は、ネイティブ コンパイル SP を利用せずに、ADO.NET 内に SQL を記述して、
SqlParameter のデータ型に何を利用したか(SqlDbType.Char や SqlDbType.NChar)でテスト
この表のポイントは、次のとおりです。

列のデータ型に nchar/nvarchar を利用している場合は、暗黙の型変換が発生しても、
遅くはならない(Index Scan にはならず、Index Seek で検索してくれる)

列のデータ型に char/varchar を利用していて、ネイティブ コンパイル SP のパラ
メーターや、ADO.NET の SqlParameter で nchar/nvarchar を利用している場合
は、暗黙の型変換が発生して、Index Scan になってしまい、1 万倍遅くなる

Japanese_CI_AS で、列のデータ型に char/varchar を利用している場合は、ネイ
ティブ コンパイル SP のパラメーターに char/varchar を利用できないので、直接
SQL を実行する方法を利用せざるを得ない(他に回避策がない)
したがって、Japanese_CI_AS を利用している場合には、列のデータ型には、char/varchar を
利用しないようにして、nchar/nvarchar を利用するように変更するのがお勧めになります。今
回のポイントカード システムでは、char/varchar をすべて nchar/nvarchar へ変換してい
ます。列のデータ型が nchar/nvarchar である場合は、パラメーターや SqlParameter で型を
間違ったとしても、遅くはならないので、そのままアプリケーションを利用できる可能性が高くな
ります(実際、今回のポイントカード システムでの、ネイティブ コンパイル SP を利用しないテ
ス ト で は 、 ADO.NET ア プ リ ケ ー シ ョ ン は 修 正 せ ず に 、 SqlDbType.Char や
SqlDbType.VarChar を利用したものでした)
。
こ れ に 対 し て 、 列 の デ ー タ 型 に char / varchar を 利 用 す る 場 合 は 、 パ ラ メ ー タ ー や
SqlParameter で型を間違えると、桁違いに遅くなってしまうので、細心の注意が必要になります。
このように遅くなってしまっては、せっかくインメモリ化しても、ディスク ベースよりも遅い結
果になり、インメモリ OLTP の性能メリットを享受することができません。
109
SQL Server 2014 実践 No.1 インメモリ OLTP
2.13 ADO.NET の AddWithValue、JDBC の場合の注意点
ADO.NET や JDBC から、直接 SQL を実行している場合に、パラメーターのデータ型を意識し
ないで作られている場合が意外と多くあります(弊社のお客様にもたくさんいらっしゃいます)。
例えば、ADO.NET であれば、次のように AddWithValue を利用して、SqlParameter のデー
タ型を指定せずに、値をセットしている場合があります。
cmd.CommandText = "SELECT カードID, カード種別, col3, col4, col5, col6, col7 " _
& " FROM dbo.顧客マスター" _
& " WHERE カードID = @p1 AND カード種別 = 'A1'"
cmd.Parameters.AddWithValue("@p1", cardID)
このように、データ型を指定せずにパラメーターを追加した場合は、値が String 型なら、その文
字列の長さ分の nvarchar(x) に変換されます。cardID 変数が 16 文字の String 型なら
nvarchar(16) に変換されて実行されます。このとき、顧客マスターのカード ID 列のデータ型
を char(16) に設定していたとすると、暗黙の型変換が発生して、Index Scan になり、桁違い
に遅くなってしまいます。したがって、このように AddWithValue を利用しないようし、次の
ようにデータ型を明示的に指定するのがお勧めになります。
Dim p1 As SqlParameter = cmd.Parameters.Add("@p1", SqlDbType.Char, 16)
p1.Value = cardID
JDBC の setString メソッドは nvarchar に変換されることに注意
JDBC を利用している場合は、prepareStatement の setString メソッドが、既定では、
nvarchar(4000) に変換されることに注意する必要があります。これは、次のようなコードです。
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
String connectionUrl = "jdbc:sqlserver://localhost:1433;"
+ "databaseName=db1;user=sa;password=password;";
Connection cn = DriverManager.getConnection(connectionUrl);
String query = "SELECT カードID, カード種別, col3, col4, col5, col6, col7"
+ " FROM dbo.顧客マスター"
+ " WHERE カードID = ? AND カード種別 = 'A1'";
PreparedStatement stmt = cn.prepareStatement(query);
stmt.setString(1, "12345");
ResultSet rs = stmt.executeQuery();
110
SQL Server 2014 実践 No.1 インメモリ OLTP
? がパラメーター
SetString でパラメーターへ
値を与える
このように実行した場合は、setString メソッドは nvarchar(4000) に変換されてしまいます。
このアプリケーションを実行したときに、SQL Server Profiler ツールを利用してキャプチャす
ると、次のようになります。
nvarchar(4000) に
自動変換される
sp_prepexec が実行されて、nvarchar(4000) に自動変換されていることを確認できます。こ
のときに、顧客マスターのカード ID 列のデータ型を char(16) に設定していたとすると、暗黙
の型変換が発生して、Index Scan になってしまいます。
これを回避するには、次のように「sendStringParametersAsUnicode」を「false」へ設定す
るようにします。
String connectionUrl = "jdbc:sqlserver://localhost:1433;"
+ "databaseName=db1;user=sa;password=password;"
+ "sendStringParametersAsUnicode=false";
Connection cn = DriverManager.getConnection(connectionUrl);
こうすることで、パラメーターを varchar(8000) へ変換できるようになります。
111
SQL Server 2014 実践 No.1 インメモリ OLTP
varchar(8000) に
変換される
varchar(8000) であれば、カード ID 列のデータ型が char(16) に設定されていたとしても、
暗黙の型変換が発生することはなく、Index Seek で検索できるようになります。
なお、Index Seek になっているかどうかは、次のように、単純な SQL で、N を付けた場合と、
付けなかった場合の推定実行プランを確認する方法がお勧めです。
ツールバーの[推定実行プラン]
ボタンをクリック
N を付けない
N を付けない
Index Seek
char(16)
N を付けて、推定実行プランを確認
N を付ける
N を付ける
Table Scan
になってしまう
N を付けた場合は、nvarchar として扱われるので、このような確認に利用することができます。
このような暗黙の型変換による性能低下は、インメモリ OLTP に限らず、通常のディスク ベース
のテーブルを利用している場合にも起こり得るので(過去の弊社のお客様にも数社いらっしゃいま
した)、
「何故か性能が出ない」という場合には、これをチェックしてみることをお勧めします。
112
SQL Server 2014 実践 No.1 インメモリ OLTP
2.14 char/varchar 型を利用する場合のメモリ使用量
前項では、char/varchar データ型を利用する場合の注意点を説明しましたが、再度まとめると、
次のようになります。

データベースの照合順序に Japanese_CI_AS を利用している場合は、列のデータ型に
char/varchar を利用すると、ネイティブ コンパイル SP のパラメーターに char/
varchar を利用できないので、直接 SQL を実行する方法を利用せざるを得ない(これ
だと、ネイティブ コンパイル SP を利用するよりも約 1.4 倍遅い)

データベースの照合順序に Latin_~ など、コードページ 1252 を利用している場合
は、列のデータ型に char/varchar を利用したとき、ネイティブ コンパイル SP の
パラメーターにも char/varchar を利用すること(型を合わせること)が重要になる
(型が合わない場合は、暗黙の型変換によって、性能が桁違いに遅くなる)
。
列のデータ型に char/varchar を利用する場合は、日本語データが格納できなくなる
(格納するには nchar/nvarchar を利用する必要がある)
今回の負荷テストでは、列のデータ型に char/varchar を利用した場合と、nchar/nvarchar
を利用した場合の性能差を体感することはできませんでしたが、char/varchar データ型を利用
することで、メモリ使用量を削減することができることが、非常に大きなメリットです。
メモリ使用量の確認 ~ dm_db_xtp_table_memory_stats ~
メモリ最適化テーブルが使用しているメモリの量は、dm_db_xtp_table_memory_stats」動
的管理ビューを利用して確認することができます。
USE データベース名
SELECT OBJECT_NAME(object_id) AS テーブル名, *
FROM sys.dm_db_xtp_table_memory_stats
テーブルごとの
メモリ使用量
また、データベースを右クリックして、[レポート]メニューの[標準レポート]から[メモリ最
適化オブジェクトによるメモリ使用量]レポートを利用しても、メモリ使用量を確認することがで
きます。
113
SQL Server 2014 実践 No.1 インメモリ OLTP
データベース全体での
メモリ使用量
テーブルごとの
メモリ使用量
メモリ使用量の差 ~英数字データなら半分になる~
nchar/nvarchar(n 付き)のデータ型では、Unicode でデータを格納するので、英数字データ
(A、B、C や 1、2、3 など)を格納する場合にも 2 バイトを消費します。これに対して、char
/varchar を利用する場合は、英数字データは、1 バイトしか消費しません。
例えば、今回のポイントカード システムの「カード ID」列には、数値データ(1111 2222 3333
4444 形式の 16 桁)しか格納されませんが、nchar(16) を利用しています。これでは、16 桁
の数値を格納するために、32 バイトが必要になります。一方、char(16) にすれば、16 バイト
で済みます。これを次のように確認してみましょう。
nchar(16)
nvarchar(2)
nchar(1)
実際のデータから 1万件分
だけ INSERT
メモリ使用量の確認
顧客マスターの「カード ID」と「カード種別」、「col7」を nchar/nvarchar で作成して、1
114
SQL Server 2014 実践 No.1 インメモリ OLTP
万件のデータを格納したときのメモリ使用量を確認しています。
次に、char/varchar で作成して、1 万件のデータを格納したときのメモリ使用量も確認してみ
ます。
char(16)
varchar(2)
char(1)
実際のデータから 1万件分
だけ INSERT
メモリ使用量の確認
実際のデータの
使用サイズ
結果のうち「memory_used_by_table_kb」が実際にデータが使用しているサイズ(KB 単位)
で、nchar のときは 1,093KB、char のときは 937KB で、差は 156KB になりました。
このテーブルに対して、実際の 10 万件のデータを入れた場合は、次のような差になります。
10万件の場合は
1,562KB の差
10 万件の場合は、1 万件のときのちょうど 10 倍の差 1,562KB になりました。
このようなメモリ使用量の差は、次のように、おおよその値を計算で求めることができます。
nchar の
char の
差
格納文字数 場合の使用 場合の使用
(バイト)
サイズ
サイズ
16
32
16
16
2
4
2
2
1
2
1
1
列名
カードID
カード種別
col7
合計
データ件数
1,000
10,000
100,000
1,000,000
10,000,000
19
38
19
19
1,000 * 19バイト
サイズ(KB)
18.6
10,000 * 19バイト
185.5
1,855.5
100,000 * 19バイト
18,554.7 18MB
185,546.9 185MB
115
nchar と char
での差は 19バイト
SQL Server 2014 実践 No.1 インメモリ OLTP
char は、格納文字数分を消費するのに対して、nchar では 2 倍の消費量として計算すれば、ど
れだけの差がでるのかを推測することができます。「カード ID」と「カード種別」、「col7」は、
19 文字分を格納するので、19 バイト分の差が出て、データ件数が 1 万件なら 1 万*19 バイト
=185KB の差が出るのではないかと推測できます(実際は 156KB の差でした)
。
これが、100 万件のデータ量なら 18.5MB、1,000 万件なら 185MB もの差になるので、メモ
リ使用量を削減するという目的としては、char/varchar を利用することは、非常に重要になり
ます。
今回のシステムでは、char/varchar を利用しませんでしたが、メモリ使用量を抑えたい場合に
は、非常に有用なオプションになるので、お勧めです。
なお、メモリ使用量の見積もり方法については、オンライン ブックの以下のトピックが参考にな
ります。
メモリ最適化テーブルのテーブルと行のサイズ
http://msdn.microsoft.com/ja-jp/library/dn205318.aspx
メモリ最適化テーブルのメモリ必要量の推定
http://msdn.microsoft.com/ja-jp/library/dn282389.aspx
116
SQL Server 2014 実践 No.1 インメモリ OLTP
2.15 ネイティブ コンパイル SP での更新競合への対応
インメモリ OLTP は、楽観的同時実行制御が実装されているので、同じデータを更新した場合に
は、更新競合(41302 エラー)が発生します。これは、次のような状況です。
-- Native SP で顧客マスターの更新
CREATE PROC p_顧客マスター_Update
@cardID nchar(16), @c3 datetime, @c4 int
WITH NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'japanese')
UPDATE dbo.顧客マスター
SET col3 = @c3, col4 = @c4
WHERE カードID = @cardID AND カード種別 = N'A1'
END
同時更新が発生した
場合の例外
実際の顧客マスターの更新は、前述したように主キー列を除いた全ての列データを更新しています
が、説明を簡略するために、col3 と col4 の更新のみにしています。また、この更新競合は、同じ
(カード ID, カード種別) に対する更新を、全く同時に行った場合にのみ発生するため、通常の運
用では発生し得ない競合です。このお客様のポイントカード システムでは、同じカード ID の操
作は、特定の店舗(端末)での利用に絞られていて、同時に他の店舗から同じカードが利用される
ことはないからです。したがって、顧客マスターの更新においては、更新競合への対応をする必要
はないのですが、他の処理で利用する場面はあり得るので、これを例に説明します。
117
SQL Server 2014 実践 No.1 インメモリ OLTP
更新競合への対応
更新競合への対応は、オンライン ブックの以下のトピックがそのまま利用できます。
メモリ最適化テーブルでのトランザクションの再試行ロジックのガイドライン
http://msdn.microsoft.com/ja-jp/library/dn169141(v=sql.120).aspx
このトピックでは、次のようにストアド プロシージャを作成する例が掲載されています。
ネイティブ コンパイル SP の
再試行を行うための
通常のストアド プロシージャ
再試行回数
10 なら 10回再試行
BEGIN TRY の中で
ネイティブ コンパイル SP を実行する
実行成功なら @retry=0 にして、
WHILE ループを抜ける
ネイティブ コンパイル SP でエラー(更新競合)が
発生すると CATCH へ飛ぶ。CATCH に飛んで来たら、
@retry(何回目の再試行なのか)を +1 する
更新競合が発生したときのエラーは
41302、41305、41325、41301 など
更新競合が発生したらロールバック
更新競合以外のエラーが発生した場合は
そのままエラーを通達(THROW)
118
SQL Server 2014 実践 No.1 インメモリ OLTP
ネイティブ コンパイル SP を実行するための、通常のストアド プロシージャを作成して、BEGIN
TRY でネイティブ コンパイル SP を実行して、実行が成功したのか、失敗したのかを判断して、
成功したら WHILE ループを抜けて終了、失敗した場合は、CATCH ブロックでエラー番号が更
新競合かどうか(41302、41305、41325、41301、1205 かどうか)をチェックして、これ
らの番号なら再試行を行う、そうでない場合は THROW で、該当エラーをそのまま通達する、と
いう流れになっています。
CATCH ブロックで処理しているエラー番号の意味は、次のとおりです。
エラー番号
エラーの内容
41302
更新競合
41305
REPEATABLE READ 分離レベル時の検証の失敗
41325
SERIALIZABLE 分離レベル時の検証の失敗
41301
依存トランザクションのエラー
1205
デッドロック エラー
これを、先ほどの顧客マスターの更新へ当てはめると、次のようになります。
ネイティブ コンパイル SP のパラメーターと同じ
ものを定義した通常のストアド プロシージャ
BEGIN TRY の中で
ネイティブ コンパイル SP を実行
後は、ADO.NET 側で、次のように、このストアド プロシージャを呼び出すように変更すれば完
了です。
119
SQL Server 2014 実践 No.1 インメモリ OLTP
再試行ロジックを入れた
ストアド プロシージャへ変更
Tips:更新競合(41302)が多発する場合は、WAITFOR DELAY を設定
オンライン ブックのトピックにも記載されていますが、更新競合(41302 エラー)が多発す
る場合には、WAITFOR DELAY を設定して、タイミングを遅らせることも考慮することをお勧
めします。
更新競合が多発する場合は
WAITFOR DELAY で調整すると
良いと記載されている
上記のように「WAITFOR DELAY '00:00:00.001'」と記述した場合は、1 ミリ秒待機して、
再試行が行えるようになります。
詳しくは第 4 章で説明しますが、ナンバリング処理を手動で行っている(連番を手動で生成して
いる)ような場合には、同時実行の多重度が上がった場合に(50 多重や 100 多重になった場
合に)、1 ミリ秒待機したとしても更新競合が発生してしまうことがあるので、「'00:00:00.
002'」のように 2 ミリ秒へ変更したり、再試行回数(@retry)を 10 から 100 などへ増や
したりするなどの対応が必要な場合があります。
もちろん、これによって実行性能は低下することになるので、これを行うかどうかは処理の継続
性とのトレードオフになります。そもそもナンバリング処理を手動で行うことを止めるという方
法(IDENTITY やシーケンスを利用するなど)もあるので、更新競合が多発するような状況は、
現在のロジックを見直して、更新競合を避けるようなアプリケーション設計へ変更するチャンス
とも言えます。
120
SQL Server 2014 実践 No.1 インメモリ OLTP
2.16 ネイティブ コンパイル SP での SELECT 時の BIN2 照合順序
ネイティブ コンパイル SP 内の SELECT ステートメントで、文字列の比較や並べ替えを行う場
合には、BIN2 照合順序が必須になります。
文字列の比較や並べ替えは、BIN2 照合順序が必要
ネイティブ コンパイル SP では、BIN2 照合順序ではない文字データ列で比較演算を行おうとす
ると、次のようにエラーになります。
BIN2 照合順序ではない文字データ列で
比較演算(=)で利用した場合
「BIN2 照合順序を使用しない文字列の比較、並べ替え、
操作はサポートされません」エラー
データベースの照合順序が
Japanese_CI_AS の 場 合 は 、 列 の 照 合 順 序 も 既 定 で
Japanese_CI_AS になるので、このようなエラーになります。
このエラーは、次のように COLLATE 句を付けることで回避することが可能ですが、この場合は、
Index Scan(Index Seek ではなく、インデックスの全スキャン)になってしまうので、お勧
めの方法ではありません。
COLLATE 句で対応する
ことも可能だが...
お勧めの方法は、テーブルの作成時に、列の照合順序を Japanese_BIN2 などへ変更しておく
ことですが、これを利用する場合の注意点もあるので、後述します。
Japanese_BIN2 への変更が必要な列 ~ PK/インデックスにも必須 ~
メモリ最適化テーブルでは、PRIMARY KEY 制約や、インデックスを付与する列には、BIN2 照
合順序を使用するのが必須になります。また、上の例のように、ネイティブ コンパイル SP 内で
比較演算(=)や並べ替え(ORDER BY)操作を行う場合にも、BIN2 照合順序が必須になりま
す。
したがって、既存のディスク ベースのテーブルで char 型の PRIMARY KEY を利用している
121
SQL Server 2014 実践 No.1 インメモリ OLTP
場合には、メモリ最適化テーブルへ移行するにあたって、多くの列で BIN2 照合順序への変更が
発生することになります。今回のポイントカード システムでは、カード ID やカード種別、
MessageID などで char 型の PRIMARY KEY を利用していたので、全て Japanese_BIN2
照合順序へ変更しました。
Japanese_BIN2 へ変更した場合の注意点 ~大文字・小文字の区別など~
Japanese_BIN2 照合順序へ変更した場合には、カード ID や MessageID などのように、格
納されているデータが数字(0、1、2 など)のみである場合は、特に問題はありませんが、カー
ド種別のように英数字(A1 や A2 など、英語が混ざっている場合)や、日本語を格納している
場合には、次のことに注意する必要があります。
まず、BIN2 ではなく、Japanese_CI_AS(既定値)を利用している場合は、文字を比較すると
きに、次のように動作します。

大文字と小文字を区別しない(CI:Case Insensitive)

アクセントを区別する(AS:Accent Sentitive)

カタカナとひらがなを区別しない(KI:Kana Insensitive)

半角と全角を区別しない(WI:Wide Insensitive)
Japanese_CI_AS では、大文字と小文字を区別しないので、「A1」も「a1」も同じデータとし
て処理をします。したがって、カード種別に「A1」というデータが格納されている場合に、次の
ように「a1」で検索しても、データを取得することができます。
a1(a が小文字)で
検索してもヒットする
実際のデータは
A1(A が大文字)
これに対して、Japanese_BIN2 では、バイナリ(文字コード)で文字の比較を行うので、上記
をすべてを区別する(CS、AS、KS、WS に相当する)形に変わります。実際に格納されている
データが「A1」であれば、「a1」で検索すると、データを取得することができません。
Japanese_BIN2
だとヒットしない
このように、Japanese_BIN2 では、大文字と小文字を厳密に区別するようになり、カタカナと
ひらがなも区別、半角と全角も区別するようになるので、アプリケーション側で、意図的に大文字
と小文字を区別しないような検索などを行っている場合には、アプリケーションの修正が必要にな
122
SQL Server 2014 実践 No.1 インメモリ OLTP
ります。
データの格納時に大文字・小文字を統一してしまう
Japanese_BIN2 を利用して、大文字と小文字を区別しない検索を行いたい場合には、データの
格納時(INSERT や UPDATE 時)に、大文字または小文字を統一してしまうという方法がお勧め
です。例えば、「カード種別」列のデータであれば、データを INSERT するときに、次のように
UPPER 関数(.NET なら ToUpper メソッド)で大文字に統一してしまいます。
UPPER 関数で大文字に変換
して格納してしまう
実際のデータが D1
(D が大文字)で格納される
このようにデータを格納するときに、大文字か小文字のどちらかに統一をしてしまえば、検索をす
るときにも、同じ処理をすることで、大文字と小文字を区別することなく、データを取得できるよ
うになります。
UPPER 関数で大文字に変換
して検索すればヒットする
このように、格納するデータをどちらかに統一しておけば、検索時に入力された値を、その統一し
た値へ変換することで、大文字と小文字を区別しない検索が行えるようになります。
Tips: WHERE UPPER(列名) = UPPER('入力値') を避ける
大文字・小文字を区別しない検索方法としては、次のように列データに対して、UPPER 関数を
かける方法もあります。
列データを UPPER 関数処理
Index Scan になり
低速になる
このように、列データに対して関数処理を行ってしまう場合は、インデックスが有効利用されず
(Index Seek でのピンポイント検索ができなくなり)
、Index Scan(インデックスの全スキ
ャン)での検索になってしまうので、非常に低速になります。
123
SQL Server 2014 実践 No.1 インメモリ OLTP
したがって、大文字と小文字を区別しない検索を行いたい場合には、本文中で説明したようにデ
ータの格納時に、どちらかへ統一するという方法がお勧めになります。
Tips: 数値のみが格納されているのであれば int や decimal へ変更するのがお勧め
今回のポイントカード システムでは、カード ID や MessageID などは、数値データのみが
格納されているにも関わらず、char データ型を利用していますが、数値データのみが格納され
る場合は、int や bigint、decimal など数値系のデータ型を利用するのがお勧めになります。
例えば、char(16) で 16 桁分のデータを格納するには、16 バイトが必要になりますが
(nchar(16) なら 32 バイトが必要)、bigint であれば 8 バイトで済みます。その分、メモ
リ使用量を削減することができるので、数値データのみの場合は、数値データ型へ変更できない
か検討してみることをお勧めします。
数値データに意味があって(例えば、数値の先頭4桁を企業を識別するために利用しているな
ど)
、char データ型を利用している、という場合も多いと思いますが(弊社のお客様でもそうい
った利用方法がほとんどです)
、性能面を考慮すると、できる限り小さいデータ型を選択するの
がお勧めです。
Tips: char(1) のフラグ列は、bit 型へ変更するのがお勧め
今回のポイントカード システムでは、char(1) の列がありますが、これはフラグ列です(状態
に応じて、0 か 1 のどちらかがセットされる列)
。このような char(1) のフラグ列は、多くの
方が利用しているのではないでしょうか(弊社のお客様にもたくさんいらっしゃいます)。
フラグ列が、2 種類の状態しか格納しない(0 と 1 や、1 と 2 など、どちらかの値しかとら
ない)ような場合には、bit データ型を利用するのがお勧めです。char(1) では 1 バイトを消
費しますが(nchar(1) なら 2 バイト消費)
、bit 型であれば 1 ビットで済むからです。その
分、メモリ使用量を削減することができるので、できる限り小さいデータ型を利用することをお
勧めします。
124
SQL Server 2014 実践 No.1 インメモリ OLTP
2.17 ネイティブ コンパイル SP にするとできなくなること
ネイティブ コンパイル SP を作成すると、次のことができなくなるので、注意する必要がありま
す。

実際の実行プラン(SET STATISTICS XML)が確認できなくなる。
推定実行プラン(SET SHOWPLAN_XML)は確認できる

別バッチから @@ROWCOUNT を取得できなくなる。
ネイティブ コンパイル SP 内であれば取得可能

ExecuteNonQuery で結果件数を取得できなくなる
実際の実行プランが確認できなくなる
ネイティブ コンパイル SP を作成すると、実際の実行プラン(SET STATISTICS XML)が確認で
きなくなります。これは次のような状況です。
ツールバーの[実際の実行プランを
含める]ボタンをクリック
Native SP
の実行
[実行プラン]タブ
が表示されない
実際の実行プランは、ストアド プロシージャやステートメントを実行して、実行後に、実際に利
用された実行プランを確認できる機能ですが、ネイティブ コンパイル SP の実行時は、確認する
ことができません。
その代わりに、推定実行プラン(SET SHOWPLAN_XML)であれば確認することができます(実
行前に、推定された実行プランを確認することは可能)
。
2
ツールバーの[推定実行プラン]
ボタンをクリック
1
125
ステートメント
を選択
SQL Server 2014 実践 No.1 インメモリ OLTP
別バッチから @@ROWCOUNT を取得できなくなる
ネイティブ コンパイル SP を作成すると、別バッチから @@ROWCOUNT が取得できなくなり
ます。これは次のような状況です。
通常ストアド プロ
シージャの実行
Native SP
の実行
@@ROWCOUNT
の結果
@@ROWCOUNT が 取得できない
(0 になってしまう)
@@ROWCOUNT は、1 つ前に実行されたステートメントの対象行数を取得できるシステム関数
ですが、ネイティブ コンパイル SP を実行したときは、確認することができません(0 が返りま
す)。ネイティブ コンパイル SP で @@ROWCOUNT を利用したい場合には、次のようにネイ
ティブ コンパイル SP の中に、@@ROWCOUNT を含めるようにします。
@@ROWCOUNT
を中に含める
Native SP
の実行
@@ROWCOUNT
の結果
ExecuteNonQuery で結果件数を取得できなくなる
ネイティブ コンパイル SP を作成すると、ADO.NET の ExecuteNonQuery で結果件数が取
得できなくなります。これは次のような状況です。
' ネイティブ コンパイル SP
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "p_t1_InMem_update"
Dim p1 As SqlParameter = cmd.Parameters.Add("@c1", SqlDbType.Int)
p1.Value = 1
Dim p2 As SqlParameter = cmd.Parameters.Add("@c2", SqlDbType.NVarChar, 200)
p2.Value = "BBB"
Dim rowcnt As Integer = cmd.ExecuteNonQuery()
126
' 結果件数を取得
SQL Server 2014 実践 No.1 インメモリ OLTP
Native SP
ExecuteNonQuery
で Native SP を実行
-1 が返ってしまう
このように、ExecuteNonQuery でネイティブ コンパイル SP を実行した場合は、結果件数で
は な く 、 -1 が 返 っ て し ま う こ と に 注 意 し ま し ょ う 。 弊 社 の 別 の お 客 様 で は 、 次 の よ う に
ExecuteNonQuery の結果によって、トランザクションの成否を判断(コミットまたはロールバ
ックのジャッジ)をしていることがありました(C# コード)
。
// 処理件数が 0件ならロールバック
if( cmd.ExecuteNonQuery() <= 0 ) {
txn.Rollback();
cn.Close();
return ("update error");
}
txn.Commit();
ExecuteNonQuery は、ネイティブ コンパイル SP の実行では -1 が返ってしまうので、この
ようなロジックだと、正常終了でもロールバックしてしまうことになるので、このような処理を利
用している場合は、ロジックを修正しなければなりません。
Note: SQL Server Profiler の RowCounts も取得できなくなる
ネイティブ コンパイル SP では、SQL Server Profiler の「RowCounts」データ列も取得で
きなくなります。
通常の SP なら
対象行数が返る
Native SP だと
0 が返る
127
SQL Server 2014 実践 No.1 インメモリ OLTP
2.18 ネイティブ コンパイル SP を利用するときのポイント
この章の後半では、ネイティブ コンパイル SP を利用するにあたってのポイントを説明してきま
したが、これらをまとめると、次のようになります。

今回のポイントカード システムでは、ネイティブ コンパイル SP を作成することで、
シングル実行で 1.2~4.6 倍の性能向上を確認。
PRIMARY KEY 列を利用した SELECT ステートメントは 1.6~1.9 倍の性能向上、
PRIMARY KEY 列を利用した UPDATE ステートメントは 1.2~1.3 倍の性能向上、
単純な INSERT ステートメントは 1.4 倍の性能向上、
Delayed Durability(遅延書き込み)を利用することで、4.6 倍の性能向上を確認

アプリケーションから実行される SQL を SQL Server Profiler でキャプチャするこ
とで、簡単にネイティブ コンパイル SP を作成できる

ADO.NET であれば、ネイティブ コンパイル SP を実行するための修正は、わずか 2 行

ネイティブ コンパイル SP の実行方法の違い(SqlCommand の CommandType
の指定や、文字列連結実行)で、性能差が出るので注意する。CommandType には
CommandType.StoredProcedure を指定し、SqlParameter を利用するのが最速

ネイティブ コンパイル SP 内では、オブジェクト名にスキーマ名を付与する必要がある
(dbo.テーブル名 形式で記述する)

SELECT * が利用できないので、列名を列挙する必要がある

データベースの照合順序が Japanese_CI_AS の場合は、ネイティブ コンパイル SP
内の単一引用符(文字リテラル)には、N プレフィックスを付ける必要がある

データベースの照合順序に Japanese_CI_AS を利用して、列のデータ型に char/
varchar を利用している場合は、ネイティブ コンパイル SP のパラメーターに char
/varchar を利用することができないので、直接 SQL を実行する方法を利用せざるを
得ない(これだと、ネイティブ コンパイル SP を利用するよりも約 1.4 倍遅い)

データベースの照合順序に Latin_~ など、コードページ 1252 を利用している場合
は、列のデータ型に char/varchar を利用したとき、ネイティブ コンパイル SP の
パラメーターにも char/varchar を利用すること(型を合わせること)が重要になる
(型が合わない場合は、暗黙の型変換によって、性能が桁違いに遅くなる)
。
列のデータ型に char/varchar を利用する場合は、日本語データが格納できなくなる
(格納するには nchar/nvarchar を利用する必要がある)

ADO.NET の AddWithValue は、nvarchar(X) に自動変換されるので、暗黙の型変
換が発生しないように注意する。char/varchar を利用するには、SqlParameter で
データ型を明示指定する(SqlDbType.Char などを利用する)ようにする

JDBC の prepareStatement の SetString は、既定で nvarchar(4000) に自動
変換されるので、暗黙の型変換が発生しないように注意する。varchar(8000) に自動
変換されるようにするには、sendStringParametersAsUnicode を false へ設定する
128
SQL Server 2014 実践 No.1 インメモリ OLTP

できる限り、小さいデータ型を利用することで、メモリ使用量を削減することができる。
英数字データのみなら nchar よりも char、
数値のみのデータなら char よりも int や bigint、decimal、
フラグ列なら char(1) よりも bit を利用するなど

メモリ使用量は、dm_db_xtp_table_memory_stats 動的管理ビューで確認できる

ADO.NET の ExecuteNonQuery は、結果件数を取得できなくなることに注意する

SCOPE_IDENTITY や @@ROWCOUNT を別バッチから取得している場合は、ネイ
ティブ コンパイル SP 内へ含めるようにする

メモリ最適化テーブルでは、PRIMARY KEY 制約やインデックスを付与する文字データ
列に、BIN2 照合順序が必須になる(Japanese_BIN2 などを利用)

ネイティブ コンパイル SP 内での文字データの比較演算(=)や並べ替え(ORDER BY)
を行うには BIN2 照合順序が必須になる。BIN2 照合順序の場合は、大文字と小文字を
区別することに注意する。
大文字と小文字を区別しないように検索したい場合は、データの格納時に大文字または小
文字に統一して格納するようにすると良い

ネイティブ コンパイル SP での更新競合は、TRY .. CATCH で再試行ロジックを記述
することで対応する

実際の実行プランが確認できなくなるので、推定実行プランを利用するようにする
129
SQL Server 2014 実践 No.1 インメモリ OLTP
STEP 3. ポイントカード システムにおける
インメモリ OLTP 機能への移行手順
第 3 章では、第 2 章に引き続き、弊社のお客様の「ポイントカード システム」を
題材に、ディスク ベースのテーブルを、インメモリ OLTP 化するにあたって、実
際に弊社が行った作業手順(移行方法)を具体的に説明します。
この章の目次は、次のとおりです。

実際に行った移行方法の概要

旧環境から新環境へのデータベースの移行方法

メモリ最適化テーブルを作成するためのスクリプト生成

メモリ最適化テーブルへのデータの移行(コピー)

ネイティブ コンパイル SP の作成
130
SQL Server 2014 実践 No.1 インメモリ OLTP
3.1
実際に移行時に行った作業
弊社のお客様の「ポイントカード システム」をインメモリ OLTP 化(ディスク ベースのテーブ
ルをメモリ最適化テーブルへ移行)するにあたって、実際に行った作業の具体的な手順を説明しま
す。
行った作業のおおまかな流れは、次のとおりです。
1.
既存の環境(SQL Server 2005 + Windows Server 2003 R2)でバックアップを取得
2.
バックアップを新環境(SQL Server 2014 + Windows Server 2012 R2)でリストア
3.
互換性レベルを SQL Server 2014(120)へ上げる(オプション)
4.
データベースの所有者を変更する
5.
瞬時初期化を有効化する
6.
メモリ最適化テーブル用のファイル グループの作成/追加する
7.
Delayed Durability を有効化する
8.
ELEVATE SNAPSHOT を有効化する(オプション)
9.
既存のテーブルの名前を変更する(sp_rename を利用)
10. 既存のテーブルをスクリプト化する(CREATE TABLE ステートメントを自動生成可能)
11. インデックスを作成する列には、NOT NULL が必須になるので、NOT NULL を付与する
12. データベースの照合順序に Japanese_CI_AS(日本語版の SQL Server の場合の既定値)
を利用している場合は、char/varchar データ型を nchar/nvarchar データ型へ変換す
る(詳しくは 2.12 を参照)
13. PRIMARY KEY 制約のインデックスを、ハッシュ インデックスまたは bw-tree インデッ
クスへ変更する
14. PRIMARY KEY 制約を設定する列が char 系のデータ型の場合には、BIN2 照合順序
(Japanese_BIN2 や Latin1_General_BIN2 など)へ変更する
15. ハッシュ インデックスのバケット数(BUCKET_COUNT)を決定する(バケット数の決定
基準については、第 4 章で説明)
16. PRIMARY KEY 以外にインデックスを作成している場合は、既存のインデックスを右クリッ
クして、スクリプト化する(CREATE INDEX ステートメントを自動生成可能)
17. インデックスのスクリプトをもとに、ハッシュ インデックスまたは bw-tree インデックス
へ変更して、CREATE TABLE ステートメントの列定義へ追加する
18. メモリ最適化テーブルを作成するための CREATE TABLE ステートメントを実行する
19. データを既存のテーブルからメモリ最適化テーブルへコピーする(INSERT .. SELECT を利
用して、全データをコピーする)
20. ネイティブ コンパイル SP を作成する(オプション)
131
SQL Server 2014 実践 No.1 インメモリ OLTP
3.2
バックアップとリストアによる DB 移行
最初に行う作業は、SQL Server の標準のバックアップとリストア機能を利用して、データベース
を丸ごと移行します。
既存の環境(SQL Server 2005)でバックアップを取得
まずは、既存の環境(SQL Server 2005 + Windows Server 2003 R2)でバックアップを取
得します。バックアップは、通常のバックアップと同様、BACKUP DATABASE ステートメント
で行います。
BACKUP DATABASE データベース名
TO DISK = 'C:\temp\DBフルバックアップ.bak' WITH STATS
WITH STATS は必須ではありませんが、バックアップの進行状況を確認するために便利なオプシ
ョンなので、付けておくことをお勧めします。
なお、既存の環境が SQL Server 2008 以上を利用している場合は、
「WITH COMPRESSION,
STATS」を付けて実行して、バックアップ圧縮を利用するのがお勧めです。圧縮することによって、
バックアップ/リストアにかかる時間や、ファイルをコピーする時間を短縮することができます。
バックアップを新環境(SQL Server 2014)でリストア
次に、既存の環境で取得したバックアップ ファイル(.bak)を、新環境(SQL Server 2014 +
Windows Server 2012 R2)へ Windows エクスプローラーでファイル コピーします。
ファイルのコピーが完了 したら 、リストアを実行 します。これは、次のよ うに RESTORE
DATABASE ステートメントで行います。
RESTORE DATABASE データベース名
FROM DISK = 'C:\temp\DBフルバックアップ.bak'
WITH MOVE 'mdfの論理名' TO 'C:\data\mdfファイル名.mdf'
,MOVE 'ldfの論理名' TO 'C:\data\ldfファイル名.ldf', STATS
SQL Server 2005 のデータベースが
SQL Server 2014 で利用できるように
自動的にアップグレードされる
132
SQL Server 2014 実践 No.1 インメモリ OLTP
このように SQL Server 2005 上で取得したバックアップは、簡単に SQL Server 2014 上へリ
ストアすることができます(SQL Server 2008 や 2008 R2、2012 のバックアップでも、同じ
手順で SQL Server 2014 上へリストアすることができます)
。
MOVE .. TO '新パス' で新しいリストア先のパス(mdf や ldf ファイルの作成場所)を指定して
いますが、旧環境と同じドライブおよびフォルダー構成がある場合には、指定は不要です。また、
新パスを指定するときの「論理名」が分からない場合は、次のように RESTORE FILELISTONLY
ステートメントを実行して確認することもできます。
LogicalName で 表示 さ れた 名前 を MOVE .. TO の .. へ指 定し ま す。 なお 、RESTORE
DATABASE を実行するときに MOVE .. TO を指定しなかった場合は、上記の PhysicalName
(旧環境で元々使っていた場所)へデータベースが復元されるようになります(新環境で該当パス
が存在しない場合は、復元エラーになります)。
133
SQL Server 2014 実践 No.1 インメモリ OLTP
3.3
データベース移行後のデータベース設定の変更
データベースの移行が完了した後は、データベースの設定を変更します。
次の作業を行います。

互換性レベルを SQL Server 2014(120)へ上げる(オプション)

データベースの所有者を変更する(重要)

瞬時初期化を有効化する(健全な性能のために非常に重要)

メモリ最適化テーブル用のファイル グループの作成/追加する(必須)

Delayed Durability を有効化する(Delayed Durability を利用するために必須)

ELEVATE SNAPSHOT を有効化する(オプション)
互換性レベルを SQL Server 2014(120)へ上げる(オプション)
SQL Server 2005 のバックアップをリストアすると、データベースの互換性レベルは 100(SQL
Server 2008 レベル)へ自動的に上がります。
互換性レベルは
2008(100)に上がる
SQL Server 2014 では、互換性レベルは 100(SQL Server 2008)以降がサポートされている
ので、90(SQL Server 2005)のデータベースをリストアした場合には、100 へ自動的に上がり
ます。なお、この[プロパティ]ダイアログでは、次のように 90(SQL Server 2005)も表示さ
れますが、選択して、
[OK]ボタンをクリックすると、エラーになって 90 レベルへ変更すること
はできません。
134
SQL Server 2014 実践 No.1 インメモリ OLTP
2005(90)も
表示されるが...
90 はサポート
されていない
互換性レベルは、100(SQL Server 2008 レベル)のまま利用しても、メモリ最適化テーブルを
利用することができますが、より良い性能を考慮するのであれば、120(SQL Server 2014 レベ
ル)へ上げておくことをお勧めします。120 へ変更していれば、SQL Server 2014 からの新し
い「基数推定」(Cardinarity Estimate)機能を利用することもできるからです。基数推定は、
クエリ オプティマイザーが推定行数を予測するためのアルゴリズムを改良したものです。
100 と 120 レベルでは、細かい修正はありますが、基本的な Transact-SQL ステートメントで
あれば同じように利用することができるので、多くの環境では問題が出ないと思います(弊社のお
客様では、今のところ 4 社ほど SQL Server 2005 から SQL Server 2014 へのデータベースの
移行を試していますが、何の問題もなく、アプリケーションを実行することができています)。こ
のあたりの移行に関する詳細については、別途ドキュメントを執筆していますので、そちらもご覧
いただければと思います。
データベース所有者の変更 ~ ALTER AUTHORIZATION ~
データベースのバックアップおよびリストアを行った環境が、同じ Active Directory ドメイン内
のマシンで、ドメイン ユーザーを利用してデータベースを操作している場合には、この作業は不
要になるのですが、異なるドメインのマシンへデータベースをリストアしたり、ワークグループな
どのスタンドアロン環境へリストアする場合には、リストア後にデータベースの所有者を変更して
おく必要があります。
異なるドメインやスタンドアロン環境だと、旧環境でのデータベースの所有者が、新しい環境には
存在しないことになるので、リストア先のデータベースでは、次のように所有者が「空」になって
しまいます。
データベースの所有者が ”空”
になってしまう
135
SQL Server 2014 実践 No.1 インメモリ OLTP
データベースの所有者を変更するには、次のように ALTER AUTHORIZATION ステートメント
を実行します。
ALTER AUTHORIZATION ON DATABASE::データベース名 TO [管理者アカウント名]
[マシン名¥Administrator]と指定することで、
ローカルの Administrator を所有者へ設定できる
管理者アカウント名には、管理者アカウントとしてローカルの Administrator を利用している
場合には「マシン名\Administrator」と指定し、sa を利用している場合には「sa」と指定しま
す。
データベースの所有者が空のままの場合には、ネイティブ コンパイル SP の作成時に、次のよう
にエラーとなってしまうので、必ず行っておいてください。
データベースの所有者が ”空” だと
ネイティブ コンパイル SP の作成ができない
瞬時初期化の有効化 ~「ボリュームの保守タスクを実行」権利を付与~
インメモリ OLTP 機能では、SCHEMA_AND_DATA(データの永続化有り)の場合に、ファイ
ルへの書き込みを行います(データ ファイルやデルタ ファイルなどが、ファイル グループ内へ
作成されていきます)
。このときに、
「瞬時初期化」機能が有効になっていないと、ファイルを作成
するときの性能低下が発生することになるので、有効化しておくことが非常に重要です。
瞬時初期化は、SQL Server のサービス アカウントが Administrators グループのメンバーで
ある場合には、自動的に有効になっているので、設定をする必要はありません。Administrators
グループのメンバーではない場合は、次のように「ローカル セキュリティ ポリシー」ツールを利
用して、「ユーザー権利の割り当て」から「ボリュームの保守タスクを実行」を与えておく必要が
あります。
136
SQL Server 2014 実践 No.1 インメモリ OLTP
3
4
SQL Server のサービス アカウントへ
「ボリュームの保守タスクを実行」
権利を付与する
1
2
権利を与えた後は、SQL Server サービスを再起動しておく必要があります。
メモリ最適化テーブル用のファイル グループの作成/追加
メモリ最適化テーブルを利用するには、データベースに、メモリ最適化テーブル用のファイル グ
ループを作成/追加しておく必要があります。これは、次のように行えます。
ALTER DATABASE データベース名
ADD FILEGROUP ファイルグループ名 CONTAINS MEMORY_OPTIMIZED_DATA
ALTER DATABASE データベース名
ADD FILE ( NAME = InMemoryName,
FILENAME = 'C:\data\InMemoryName' )
TO FILEGROUP ファイルグループ名
ALTER DATABASE ステートメントで、ADD FILEGROUP に続けて任意のファイル グループ
名を指定して、「CONTAINS MEMORY_OPTIMIZED_DATA」と記述することで、メモリ最適
化テーブルを格納できるファイル グループを作成することができます。
2 つ目の ALTER DATABASE ステートメントの ADD FILE では、ファイル グループ内にファ
イル(実際に作成されるのはフォルダー)を作成しますが、FILENAME に指定するファイル名は、
NAME で指定する論理ファイル名(上記では InMemoryName)と同じ名前に設定します。
このように、通常のデータベースに、メモリ最適化テーブル用のファイル グループを追加するこ
とで、通常のテーブル(従来どおりのディスク ベースのテーブル)と、メモリ最適化テーブルを
同じデータベース内に共存させることができます(インメモリ OLTP 機能が SQL Server と完全
に統合されている利点の 1 つです)。
作成したファイル グループには、メモリ最適化テーブルを作成していくと、次のようにファイル
137
SQL Server 2014 実践 No.1 インメモリ OLTP
が作成されています。
NAME および FILENAME で指定した
フォルダー(画面は InMemoryName)
にファイルが作成されていく
Delayed Durability の有効化
今回のポイントカード システムの「顧客利用履歴」テーブルでは、Delayed Durability(遅延書
き込み)機能を利用しましたが、これを利用するには、事前にデータベースで Delayed Durability
を有効化しておく必要があります。これは、次のように ALTER DATABASE ステートメントの
SET オプションで設定することができます。
ALTER DATABASE データベース名
SET DELAYED_DURABILITY = ALLOWED
DELAYED_DURABILITY に ALLOWED(許可)を指定することで、Delayed Durability を
利用できるようになります。ここで DISABLED を指定した場合は、無効化して、元に戻すこと
ができます。また、FORCED を指定した場合は、どんなトランザクションでも強制的に Delayed
Durability で実行するという設定にすることもできます。
ALLOWED を指定した場合は、次のようにトランザクションの COMMIT TRAN 時に、WITH
オプションで DELAYED_DURABILITY を ON に指定することで、Delayed Durability を利
用することができます。
BEGIN TRAN
:
COMMIT TRAN
WITH ( DELAYED_DURABILITY = ON )
ネイティブ コンパイル SP の場合は、次のように BEGIN ATOMIC の WITH オプションで
「DELAYED_DURABILITY = ON」と指定することで、Delayed Durability を有効化するこ
とができます。
CREATE PROC ストアド プロシージャ名
WITH NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH (
DELAYED_DURABILITY = ON,
TRANSACTION ISOLATION LEVEL = SNAPSHOT,
138
SQL Server 2014 実践 No.1 インメモリ OLTP
LANGUAGE = N'japanese')
:
END
ELEVATE_TO_SNAPSHOT を有効化(オプション)
今回のポイントカード システムでは利用しませんでしたが、次のようにステートメントの分離レ
ベルを自動的に SNAPSHOT(スナップショット分離レベル)へ昇格(ELEVATE)することがで
きる便利なオプションがあります。
USE データベース名
ALTER DATABASE CURRENT
SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT = ON
このように、MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT を ON に指定すれば、ト
ランザクション内のすべてのステートメントを、スナップショット分離レベルで実行することがで
きます。
インメモリ OLTP では、スナップショット分離レベルでステートメントを実行するのが基本にな
り(既定は、READ COMMITTED 分離レベル)、トランザクション内では各ステートメントへ
「WITH(SNAPSHOT)」付けて実行するのが基本になります(詳しくは、第 4 章で説明します)
。
あるいは、ネイティブ コンパイル SP であれば、WITH 句で「TRANSACTION ISOLATION
LEVEL = SNAPSHOT」を記述して、スナップショット分離レベルを利用します。
しかし、トランザクション内の各ステートメントへ WITH(SNAPSHOT) を付けるとなると、ア
プリケーションの修正が必要になるので、これをしなくても済むようになる、便利なオプションが
ELEVATE_TO_SNAPSHOT です。
139
SQL Server 2014 実践 No.1 インメモリ OLTP
3.4
メモリ最適化テーブルへ移行する手順
データベースの設定が完了した後は、既存のディスク ベースのテーブルを、メモリ最適化テーブ
ルへ移行する作業になります。
メモリ最適化テーブルへ移行する手順の概要は、次のとおりです。
1.
既存のテーブルの名前を変更する(sp_rename を利用)
2.
既存のテーブルをスクリプト化する(CREATE TABLE ステートメントを自動生成可能)
3.
インデックスを作成する列には、NOT NULL が必須になるので、NOT NULL を付与する
4.
データベースの照合順序に Japanese_CI_AS(日本語版の SQL Server の場合の既定値)
を利用している場合は、char/varchar データ型を nchar/nvarchar データ型へ変換す
る(詳しくは 2.12 を参照)
5.
PRIMARY KEY 制約のインデックスを、ハッシュ インデックスまたは bw-tree インデッ
クスへ変更する
6.
PRIMARY KEY 制約を設定する列が char 系のデータ型の場合には、BIN2 照合順序
(Japanese_BIN2 や Latin1_General_BIN2 など)へ変更する
7.
ハッシュ インデックスのバケット数(BUCKET_COUNT)を決定する(バケット数の決定
基準については、第 4 章で説明)
8.
PRIMARY KEY 以外にインデックスを作成している場合は、既存のインデックスを右クリッ
クして、スクリプト化する(CREATE INDEX ステートメントを自動生成可能)
9.
インデックスのスクリプトをもとに、ハッシュ インデックスまたは bw-tree インデックス
へ変更して、CREATE TABLE ステートメントの列定義へ追加する
10. メモリ最適化テーブルを作成するための CREATE TABLE ステートメントを実行する
11. データを既存のテーブルからメモリ最適化テーブルへコピーする(INSERT .. SELECT を利
用して、全データをコピーする)
12. ネイティブ コンパイル SP を作成する(オプション)
140
SQL Server 2014 実践 No.1 インメモリ OLTP
既存のテーブルの名前を変更(sp_rename を利用)
まずは、既存のディスク ベースのテーブルを sp_rename システム ストアド プロシージャを
利用して、名前を変更しておきます。「顧客マスター」テーブルの場合は、次のように名前を変更
できます。
USE データベース名
EXEC sp_rename '顧客マスター', '顧客マスター_OnDisk'
sp_rename では、第 1 引数に名前を変更したいテーブル、第2引数に変更後の名前を指定する
ので、この例では「顧客マスター」を「顧客マスター_OnDisk」という名前へ変更できます。
既存のテーブルをスクリプト化(CREATE TABLE を自動生成)
次に、既存のディスク ベースのテーブルをスクリプト化します(CREATE TABLE ステートメン
トを自動生成します)
。こを行うには、次のようにオブジェクト エクスプローラーで、該当テーブ
ルを右クリックして、
[テーブルをスクリプト化]から[新規作成]の[新しいクエリ エディター
ウィンドウ]をクリックします。
スクリプト化したいテーブル
を右クリック
141
SQL Server 2014 実践 No.1 インメモリ OLTP
CREATE TABLE が
自動生成される
PRIMARY KEY 制約
CLUSTERED なら
クラスター化インデックス
PRIMARY KEY 制約に
自動作成されるインデックスの
オプション設定(WITH 句)
ON [PRIMARY] は、テーブルを
どのファイル グループへ作成するかの指定。
既定では PRIMARY ファイル グループへ作成
DEFAULT 制約
CHECK 制約
* 実際の顧客マスター テーブルでは CHECK 制約は利用していませんが、スクリプト生成の例として、生成しています。
CREATE TABLE ステートメントが生成されて、PRIMARY KEY 制約や、PRIMARY KEY 制約
に自動作成されるインデックス(既定ではクラスター化インデックス)
、各種制約(DEFAULT 制
約や CHECK 制約など)がスクリプト化されることを確認できます。
このスクリプトをもとに、メモリ最適化テーブルを作成するための CREATE TABLE ステートメ
ントへ修正していきます。
Tips: 列ごとに照合順序を変更している場合はスクリプト生成ウィザードを利用
既存のテーブルで、列ごとに異なる照合順序を設定している場合には、スクリプトを生成すると
きに、照合順序も一緒に生成する必要があります。これを行うには、スクリプト生成ウィザード
を利用します。このウィザードを起動するには、次のように該当データベースを右クリックして、
[タスク]メニュー[スクリプト生成]をクリックします。
142
SQL Server 2014 実践 No.1 インメモリ OLTP
ウィザードの[オブジェクトの選択]ページでは、スクリプト化をしたいテーブルを選択します。
スクリプト化したい
テーブルを選択
次の[スクリプト作成オプションの設定]ページでは、
[詳細設定]ボタンをクリックします。
1
3
2
[詳細オプション]ダイアログが表示されたら、
[スクリプトの照合順序]を「True」へ変更す
ることで、照合順序の付きのスクリプトを生成できるようになります。あとは、
[新しいクエリ エ
ディター ウィンドウに保存]をクリックしておけば、次のようにクエリ エディターへスクリプ
トを生成することができます。
143
SQL Server 2014 実践 No.1 インメモリ OLTP
照合順序付きの
スクリプトが生成される
スクリプト(CREATE TABLE ステートメント)の修正
次に、生成したスクリプトをもとに、メモリ最適化テーブルを作成するための CREATE TABLE ス
テートメントへ修正していきます。修正にあたっては、メモリ最適化テーブルを作成するためのル
ールを把握しておく必要があります。これは、次のとおりです。

メモリ最適化テーブルでは、後からテーブル定義を変更することができない(ALTER
TABLE ステートメントがサポートされていない)

メモリ最適化テーブルでは、後からインデックスを追加することができない(CREATE
INDEX ステートメントがサポートされていないので、CREATE TABLE でテーブルを
作成するときに、インデックスを一緒に作成する必要がある)

メモリ最適化テーブルでは、1 つ以上の非クラスター化(NONCLUSTERED)のインデ
ックスを作成するのが必須になる(SCHEMA_AND_DATA の場合は、PRIMARY KEY
制 約 が 必 須 に な り 、 PRIMARY KEY 制 約 に は イ ン デ ッ ク ス が 必 須 に な る 。
SCHEMA_ONLY の場合は、インデックスが必須)

メモリ最適化テーブルでは、ハッシュ インデックスまたは bw-tree インデックスがサ
ポートされる(どちらも非クラスター化が必須。1 つのテーブルで最大 8 個まで)

インデックスを作成する列には、NOT NULL が必須になる

データベースの照合順序に Japanese_CI_AS(日本語版の SQL Server の場合の既定
値)を利用している場合は、char/varchar データ型を nchar/nvarchar データ型
へ変換する(∵より良い性能を出すため。詳しくは 2.12 を参照)

PRIMARY KEY 制約を設定する列が char 系のデータ型の場合には、BIN2 照合順序
144
SQL Server 2014 実践 No.1 インメモリ OLTP
(Japanese_BIN2 や Latin1_General_BIN2 など)へ変更する

ハッシュ インデックスを利用する場合は、バケット数(BUCKET_COUNT)を決定す
る(後からバケット数を変更することができない。バケット数の決定基準は後述)
以降では、上記の修正を 1つ1つ説明していきます。
NOT NULL を付与 ~インデックスのキー列は NOT NULL が必須~
メモリ最適化テーブルでは、ハッシュまたは bw-tree インデックスを作成する列には、NOT
NULL が必須になるので、生成されたスクリプトへ NOT NULL を付与しなければなりません。
また、PRIMARY KEY 制約には、インデックスの作成が必須になるので、PRIMARY KEY 制約を
設定する列に対しても NOT NULL を付与する必要があります。
複合主キーなど、複数の列で PRIMARY KEY 制約やインデックスを構成する場合には、それらの
列すべてに NOT NULL を付与する必要もあります。例えば、
「顧客マスター」テーブルであれば、
「カード ID」と「カード種別」列で複合主キーを構成しているので、この 2 つの列に NOT NULL
を付与する必要があります。
CREATE TABLE [dbo].[顧客マスター]
( [カードID] [char](16) NOT NULL,
[カード種別] [varchar](2) NOT NULL,
[col3] [datetime] NULL,
:
-- NOT NULL を付与
-- NOT NULL を付与
Note: NULL 値がある場合
NOT NULL を付与したということは、NULL 値を格納できなくなってしまうので、NOT NULL
を設定した列には NULL 値があるかどうかをチェックしばければなりません。NULL 値がある
場合には、それを別の値へ変更する必要も出てきます。また、アプリケーション側で NULL 値
(ADO.NET なら DBNull.Value)を INSERT や UPDATE してる場合などは、それも修正し
ていく必要があります。
今回のポイントカード システムでは、インデックスのキー列では NULL 値を利用していなかっ
たので、特に修正は必要ありませんでしたが、別のお客様では、NULL 値を許可して、DEFAULT
制約(既定値)として NULL 値を挿入している場合がありました。このような列に対してイン
デックスを作成する場合には、DEFAULT 制約を変更したり、NULL 値の扱いについて、新しい
ルールを決定/変更していかなければなりません。
char/varchar を nchar/nvarchar データ型へ変換(n 付きへ変更)
メモリ最適化テーブルでは、(var)char データ型は、1252 コードページの照合順序でのみサポ
ートされるので、
「Japanese_CI_AS」照合順序では、(var)char データ型を利用することがで
きません。(var)char データ型を利用したい場合には、COLLATE 句で 1252 コードページの
145
SQL Server 2014 実践 No.1 インメモリ OLTP
照合順序(Latin1_General_CI_AS や Latin1_General_BIN2 など)を指定する必要があり
ますが、この照合順序では日本語データを格納することができません(詳しくは、2.12 を参照)。
したがって、
「Japanese_CI_AS」を利用するには n(var)char データ型(n 付きのデータ型)
を利用しなければなりません。今回のポイントカード システムでは、char 系の列には (var)char
データ型を利用していたので、すべて n(var)char データ型へ変更しました。例えば、
「顧客マス
ター」テーブルでは、「カード ID」が char(16)、「カード種別」が varchar(2) だったので、
nchar(16)、nvarchar(2) へ変更しています。
CREATE TABLE [dbo].[顧客マスター]
( [カードID] [nchar](16) NOT NULL,
[カード種別] [nvarchar](2) NOT NULL,
[col3] [datetime] NULL,
:
-- nchar へ変更
-- nvarchar へ変更
私は、クエリ エディターの「クイック置換」
(Ctrl+H キー)機能を利用して、
「char」を「nchar」
へすべて置換して、その後「varnchar」(varchar が varnchar になってしまったもの)を
「nvarchar」へすべて置換することで、この変更に対応しました。
PRIMARY KEY 制約の設定(ハッシュまたは bw-tree インデックスの設定)
次に、PRIMARY KEY 制約を設定しますが、メモリ最適化テーブルでは、 非クラスター化
(NONCLUSTERED)のハッシュ インデックスまたは bw-tree インデックスの作成が必須に
なります。今回のポイントカード システムでは、すべての PRIMARY KEY 制約でハッシュ イ
ンデックスを利用しています。
ハッシュ インデックスを作成する構文は、次のとおりです。
HASH (キー1, キー2, …) WITH (BUCKET_COUNT = バケット数)
HASH の後に、カッコで囲んでキー列を指定し、WITH 句で BUCKET_COUNT(バケット数)
を指定します。バケット数の指針は、後述しますが、今回のポイントカード システムでは、マス
ター系のテーブル(カード マスターや顧客マスターなど)は、データ件数の約 3~4 倍に設定し
ました(∵マスター系のテーブルは、データ件数の変動が少ないため)。トランザクション系テー
ブルに関しては、データ件数がどんどん増えていくので、将来増えるであろうデータ件数分の大き
さに設定しました。
スクリプト生成では、
「顧客マスター」テーブルは、次のように PRIMARY KEY 制約が生成され
ています(カード ID とカード種別の複合主キーで、クラスター化インデックスを作成)
。
CONSTRAINT [PK_顧客マスター] PRIMARY KEY CLUSTERED
(
[カードID] ASC,
[カード種別] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
146
SQL Server 2014 実践 No.1 インメモリ OLTP
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
このうち、WITH 以下のオプション(PAD_INDEX や STATISTICS~、ON [PRIMARY] な
ど)は、不要になるのですべて削除します。また、
「カード ID」と「カード種別」列の隣の「ASC」
キーワード(b-tree インデックスの並び順を昇順に指示するキーワード)も不要になるので、削除
します。
メモリ最適化テーブルでは、PRIMARY KEY 制約には非クラスター化(NONCLUSTERED)の
インデックス(ハッシュまたは bw-tree)が必須になるので、次のように PRIMARY KEY 制約
を設定します。
CREATE TABLE [dbo].[顧客マスター]
( [カードID] [nchar](16) NOT NULL,
[カード種別] [nvarchar](2) NOT NULL,
[col3] [datetime] NULL,
:
CONSTRAINT [PK_顧客マスター_2] PRIMARY KEY NONCLUSTERED
HASH ( [カードID], [カード種別] ) WITH (BUCKET_COUNT = 20000000)
変更しているのは、「CLUSTERED」を「NONCLUSTERED」、制約の名前(CONSTRAINT の
隣)に「_2」を付与、キー列の前に HASH キーワードを入れてハッシュ インデックスを作成、
WITH 句では BUCKET_COUNT のみを指定しています(バケット数は 2000 万へ設定)
。
制約の名前を変更している部分は、不要と思われる方もいらっしゃるかもしれませんが、今回は、
既存のテーブルを残していて、そのテーブルに同じ名前の制約が存在しているので、違う名前へ設
定しないと、テーブルの作成時にエラーとなってしまうため、
「_2」を付与しています(付与する
名前は任意なので、分かりやすい名前を付けておくことをお勧めします)。
インデックスを作成する列が char 系の場合は BIN2 照合順序へ変更
メモリ最適化テーブルでは、インデックスを作成する列が char 系のデータ型の場合には、BIN2
照合順序を利用しなければなりません。nchar/nvarchar データ型(n 付き)を利用している場
合には「Japanese_BIN2」
、char/varchar データ型(1252 コードページ)を利用している
場合には「Latin1_General_BIN2」などを利用します。
「顧客マスター」テーブルでは、「カード ID」と「カード種別」列を「Japanese_BIN2」へ変
更しています。
CREATE TABLE [dbo].[顧客マスター]
( [カードID] [nchar](16) COLLATE Japanese_BIN2 NOT NULL,
-- 照合順序を変更
[カード種別] [nvarchar](2) COLLATE Japanese_BIN2 NOT NULL, -- 照合順序を変更
[col3] [datetime] NULL,
:
147
SQL Server 2014 実践 No.1 インメモリ OLTP
Japanese_BIN2 へ変更した場合は、既定の Japanese_CI_AS とは比較や並べ替え時の動作
が変わることに注意が必要です(詳しくは 2.16 が参考になると思います)
。
DEFAULT 制約を設定、名前の変更
DEFAULT 制約を設定している場合は、次のように CREATE TABLE とは別途、ALTER TABLE
ステートメントとしてスクリプト生成されることがあるので、これを CREATE TABLE の中の列
定義へ移動します。
DEFAULT 制約
メモリ最適化テーブルでは、ALTER TABLE によるテーブル定義の変更がサポートされていない
ので、CREATE TABLE のときに、すべての定義を設定しておく必要があります。
「顧客マスター」テーブルの場合は、次のように col7 列へ DEFAULT 制約を追加しました。
CREATE TABLE [dbo].[顧客マスター]
( [カードID] [nchar](16) COLLATE Japanese_BIN2 NOT NULL,
[カード種別] [nvarchar](2) COLLATE Japanese_BIN2 NOT NULL,
:
[col6] [datetime] NULL,
[col7] [nchar](1) NOT NULL CONSTRAINT def_col7_2 DEFAULT ('0'),
CONSTRAINT [PK_顧客マスター_2] PRIMARY KEY NONCLUSTERED
HASH ( [カードID], [カード種別]) WITH (BUCKET_COUNT = 20000000)
)
-- DEFAULT 制約
CONSTRAINT で指定している制約の名前は「_2」を付与して、既存の制約と名前が重複しない
ように変更しています。
追加のインデックスのスクリプト化
既存のディスク ベースのテーブルで、PRIMARY KEY 以外の列にインデックス(b-tree インデッ
クス)を作成している場合には、インデックス定義をスクリプト化します(CREATE INDEX を
自動生成)。スクリプト生成を行うには、次のように該当インデックスを右クリックして、
[インデ
148
SQL Server 2014 実践 No.1 インメモリ OLTP
ックスをスクリプト化]から[新規作成]の[新しいクエリ エディター ウィンドウ]をクリック
します。
これにより、インデックスのキー列(どの列に対して作成されているか)が分かります。このディ
スク ベースのインデックスは b-tree インデックスなので、ハッシュまたは bw-tree インデッ
クスへ変更して、CREATE TABLE でのテーブル定義へ含めるようにします。
インデックスをハッシュまたは bw-tree へ変更
スクリプト生成したインデックス定義(CREATE INDEX)をもとに、ハッシュまたは bw-tree イ
ンデックスへ変更して、CREATE TABLE でのテーブル定義へ含めるようにします。
トランザクションテーブルでは、次のように 2 つのインデックスを追加しています。
CREATE TABLE [トランザクションテーブル](
seq bigint IDENTITY(1,1) NOT NULL,
col2 データ型,
col3 データ型,
:
カードID nchar(16) COLLATE Japanese_BIN2 NOT NULL,
149
SQL Server 2014 実践 No.1 インメモリ OLTP
col29
col30
,INDEX
,INDEX
:
データ型 NOT NULL,
データ型 NOT NULL,
IDX2 HASH (カードID) WITH (BUCKET_COUNT = 20000000)
IDX3 HASH (col29, col30) WITH (BUCKET_COUNT = 20000000)
(カード ID)にハッシュ インデックス、
(col29, col30)にハッシュ インデックスを作成して
います。このように、列定義の後(col30 までのデータ型や NULL 設定をした後)に、INDEX キ
ーワードを付けて、インデックス名を指定し(上の例では IDX2 や IDX3)
、HASH キーワード
の後にインデックスのキー列をカッコで囲んで指定、WITH 句で BUCKET_COUNT(バケット
数)を指定することで、追加のハッシュ インデックスを作成することができます。
利用できないデータ型
メモリ最適化テーブルでは、以下のデータ型がサポートされていません。

行サイズは、8060 バイトが上限となり、varchar(max) や varbinary(max)、image、
text などの LOB(ラージ オブジェクト)がサポートされない

xml 、 sql_variant 、 datetimeoffset 、 hierarchyid 、 geography 、 geometry 、
rowversion、UDT(ユーザー定義データ型)がサポートされない
これらを利用している場合には、サポートされている別のデータ型へ置き換えられないかを検討す
る必要があります。例えば、varchar(max) を利用している場合には、データが 2000 バイト
以下のものしか格納されないと決まっているのであれば、varchar(2000) へ置き換えることがで
きます。どうしても 8060 バイト以上のデータを格納したい場合には、データを分割して格納す
るという方法もあります(第 4 章を参照)
。
利用できない制約を削除する
メモリ最適化テーブルでは、CHECK 制約と FOREIGN KEY 制約がサポートされていません。
したがって、これらの制約は、メモリ最適化テーブルからは削除しておく必要があります。
データの永続化の決定(SCHEMA_AND_DATA/SCHEMA_ONLY)
メモリ最適化テーブルでは、データを永続化するかどうかを設定する必要があります(既定では、
データが永続化されます)。データを永続化する場合は、SQL Server を再起動しても、データを
復旧することができます。
データの永続化は、CREATE TABLE ステートメントの 一番下の WITH 句で指定します。
「 MEMORY_OPTIMIZED = ON 」 で 、 メ モ リ 最 適 化 テ ー ブ ル で あ る こ と を 指 定 し て 、
「DURABILITY =」でデータを永続化するかどうか(SCHEMA_AND_DATA なら永続化する、
SCHEMA_ONLY なら永続化しない)を設定します。
150
SQL Server 2014 実践 No.1 インメモリ OLTP
今回のポイントカード システムでは、すべてのテーブルで SCHEMA_AND_DATA を指定して、
データを永続化しています。
「顧客マスター」テーブルでは、次のように作成しています。
CREATE TABLE [dbo].[顧客マスター]
( [カードID] [nchar](16) COLLATE Japanese_BIN2 NOT NULL,
[カード種別] [nvarchar](2) COLLATE Japanese_BIN2 NOT NULL,
[col3] [datetime] NULL,
[col4] [int] NOT NULL,
[col5] [datetime] NOT NULL,
[col6] [datetime] NULL,
[col7] [nchar](1) NOT NULL CONSTRAINT def_col7_2 DEFAULT ('0'),
CONSTRAINT [PK_顧客マスター_2] PRIMARY KEY NONCLUSTERED
HASH ( [カードID], [カード種別] ) WITH (BUCKET_COUNT = 20000000)
) WITH ( MEMORY_OPTIMIZED = ON
,DURABILITY = SCHEMA_AND_DATA ) -- データを永続化
CREATE TABLE ステートメントの実行して、メモリ最適化テーブルを作成する
スクリプトの修正が完了したら、CREATE TABLE ステートメントを実行して、メモリ最適化テー
ブルを作成します。
CREATE TABLE ステートメン
トを実行して、メモリ最適化
テーブルを作成
151
SQL Server 2014 実践 No.1 インメモリ OLTP
3.5
既存テーブルからデータのコピー
メモリ最適化テーブルの作成が完了したら、データを既存のテーブルからコピーします。コピーに
は、INSERT .. SELECT を利用することができます。
INSERT INTO 顧客マスター
SELECT * FROM 顧客マスター_OnDisk
* 結果件数は、実際の顧客マスター テーブルの件数から変更しています。
なお、IDENTITY を設定している列がある場合には、SELECT * ですべての列を指定すると、次
のようにエラーとなってしまいます(今回は、トランザクションテーブルと顧客利用履歴テーブル
で IDENTITY 列を利用しているので、このエラーが発生します)
。
この場合は、IDENTITY 列を除いた、すべての列を列挙して、実行する必要があります。
INSERT INTO トランザクションテーブル
SELECT col2, col3, col4, col5, col6, col7, …
FROM トランザクションテーブル_OnDisk
このときに、すべての列を手書きで列挙するのは面倒なので、私はこれを回避するために、[上位
1000 件の選択]を利用しました(オブジェクト エクスプローラーで、該当テーブルを右クリッ
クして、
[上位 1000 件の選択]をクリック)。
TOP 1000 付きの
すべての列名が列挙された
SELECT ステートメント
が生成される
これで、すべての列名が列挙された SELECT ステートメントを生成できるので、ここから
IDENTITY 列を除いた列をコピーすれば、簡単に INSERT .. SELECT を実行できます。
152
SQL Server 2014 実践 No.1 インメモリ OLTP
3.6
ネイティブ コンパイル SP の作成(オプション)
メモリ最適化テーブルを作成した後は、性能向上のために、ネイティブ コンパイル SP を作成し
ます。第 2 章で説明したように、ネイティブ コンパイル SP を作成することで、大きな性能向上
を実現できるので、作成しておくことをお勧めします。
ネイティブ コンパイル SP の基本構文
ネイティブ コンパイル SP を作成するときの基本構文は、次のとおりです。
CREATE PROC ストアドプロシージャ名
パラメーター定義
WITH
NATIVE_COMPILATION,
EXECUTE AS OWNER,
SCHEMABINDING
AS
BEGIN ATOMIC
WITH (
TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'japanese')
-- 実行したいステートメント
END
CREATE PROCEDURE ステートメントで、「WITH NATIVE_COMPILATION」を指定するこ
とで、ネイティブ コンパイル SP として、ストアド プロシージャを作成することができます。ま
た、ネイティブ コンパイル SP では、EXECUTE AS OWNER と SCHEMABINDING も指定
する必要があります。
BEGIN ATOMIC と END で囲んでいる部分も必須になりますが、これは 1 つのトランザクショ
ンとして、ステートメントを実行するという意味になります。また、TRANSACTION ISOLATION
LEVEL で分離レベルを指定し、SNAPSHOT を指定した場合はスナップショット分離レベルにな
ります。分離レベルは、SNAPSHOT のほかに、REPEATABLE READ と SERIALIZABLE が
サポートされます(READ COMMITTED はサポートされません)。
LANGUAGE は、日付フォーマットやシステムメッセージをどの言語にするかを指定するもので、
「japanese」とすることで、日本語の日付フォーマット(2014/06/15 形式)およびメッセージ
を表示できるようになります。
実行するステートメントやパラメーターには、主に以下の制約があります(2.10 も参照)。

dbo. など、オブジェクトの指定にはスキーマ名が必須

メモリ最適化テーブルにしかアクセスできない

SELECT * がサポートされないため、列名の指定が必須

比較や並べ替えは、BIN2 照合順序でのみサポートされる
153
SQL Server 2014 実践 No.1 インメモリ OLTP

データベースの照合順序に、Japanese_CI_AS などのコードページが 1252 ではな
いものを利用している場合は、文字列に N プレフィックスを付ける必要がある(N プレ
フィックスを付けない場合は、varchar データ型と判断されてエラーになる)

ALTER PROCEDURE がサポートされない(変更するには、DROP PROCEDURE で
削除してから、CREATE PROCEDURE で作成し直す必要がある)

SET オプションがサポートされない

ATOMIC のみで、明示的な BEGIN TRAN や ROLLBACK、COMMIT などのトラン
ザクション操作がサポートされない

RAISERROR がサポートされない。
TRY .. CATCH および THROW はサポートされる。ATOMIC(トランザクション)な
ので、THROW では、トランザクションのロールバック(取り消し)が行われる

WHERE 句で OR、IN、NOT がサポートされない。
OR/IN は、メモリ最適化テーブル変数で代替できることもある(第 2 章を参照)

その他の制限事項に関しては、第 4 章を参照
今回のポイントカード システムでは、「顧客マスター」テーブルを更新するネイティブ コンパイ
ル SP は、次のように作成しています(詳しくは 2.7 を参照)。
CREATE PROC p_顧客マスター更新
@p1 nchar(16), @p2 nvarchar(2),
@p3 datetime, @p4 int, @p5 datetime, @p6 datetime, @p7 nchar(1)
WITH
NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'japanese')
UPDATE dbo.顧客マスター
SET col3 = @p3, col4 = @p4, col5 = @p5, col6 = @p6, col7 = @p7
WHERE カードID = @p1 AND カード種別 = @p2
END
go
ポイントカード システムでは、全部で 11 個のネイティブ コンパイル SP を作成しています(第
2 章を参照)
。
以上が、実際の移行時に行った作業になります。
154
SQL Server 2014 実践 No.1 インメモリ OLTP
STEP 4. インメモリ OLTP の
その他の役立つ利用方法
第 4 章では、インメモリ OLTP 機能を利用する上で、その他の役立つ利用方法を
説明します。
この章の目次は、次のとおりです。

ELEVATE_TO_SNAPSHOT の有効化

スナップショット分離レベルでの考え方

更新競合が多発する場合の考え方(ナンバリング処理など)

メモリ使用量の制限を行いたい場合(リソース プールの変更)

bw-tree インデックスの選択基準、範囲スキャン

SELECT の取得データ件数が異なる場合や JOIN の場合の性能

フル スキャンを避ける(インメモリ OLTP の苦手な処理)

BUCKET_COUNT の違いによる性能差

性能検証をする上での注意点

インメモリ OLTP の制限事項

メモリ最適化アドバイザー、ネイティブ コンパイル アドバイザー

ASP.NET Session State Provider for SQL Server In-Memory
155
SQL Server 2014 実践 No.1 インメモリ OLTP
4.1
ELEVATE_TO_SNAPSHOT で自動的にスナップショット分離へ
インメモリ OLTP では、トランザクション内では、スナップショット分離レベルでステートメン
トを実行するのが基本になります。
例えば、トランザクション内で、次のように SELECT ステートメントを実行したとします。
BEGIN TRAN
SELECT * FROM t1_InMem WHERE col1 = 1
結果は、エラーになり、
「WITH (SNAPSHOT) などのテーブル ヒントを使用して、メモリ最適
化テーブルのサポートされている分離レベルを指定してください。」と返されます。
このエラーは、次のように UPDATE ステートメントを実行したとしても発生します。
BEGIN TRAN
UPDATE t1_InMem SET col2 = '1111' WHERE col1 = 1
インメモリ OLTP では、従来ながらのディスク ベースのテーブルの既定値である「READ
COMMITTED」がサポートされていないので、このエラーが発生しています。
インメモリ OLTP でサポートされている分離レベル
インメモリ OLTP(メモリ最適化テーブル)でサポートされている分離レベルは、次の3つです。

SNAPSHOT

REPEATABLE READ
156
SQL Server 2014 実践 No.1 インメモリ OLTP

SERIALIZABLE
「 READ COMMITTED 」( 既 定 値 ) が サ ポ ー ト さ れ て お ら ず 、「 READ UNCOMMITTED 」
(NOLOCK ヒント)や、UPDLOCK(更新ロック)もサポートされていません(これについては、
次の項で説明します)
。
WITH (SNAPSHOT) でスナップショット分離レベルを指定
インメモリ OLTP で、スナップショット分離レベルを指定するには、次のようにテーブル名の隣
に「WITH(SNAPSHOT)」を付与します。
BEGIN TRAN
SELECT * FROM t1_InMem WITH(SNAPSHOT)
WHERE col1 = 1
インメモリ OLTP(メモリ最適化テーブル)
でスナップショット分離レベルを指定
このように、スナップショット分離レベルを指定すれば、トランザクション内でステートメントを
実行できるようになります。
UPDATE ステートメントでも同様、ステートメントの隣に WITH(SNAPSHOT) を付与するこ
とで、実行できるようになります。
BEGIN TRAN
UPDATE t1_InMem WITH(SNAPSHOT) SET col2 = '1111' WHERE col1 = 1
ネ イ テ ィ ブ コ ン パ イ ル SP の 場 合 は 、 次 の よ う に BEGIN ATOMIC の WITH 句 で
「TRANSACTION ISOLATION LEVEL = SNAPSHOT」を記述することで、スナップショット
分離レベルを指定することできます。
CREATE PROC ストアドプロシージャ名
パラメーター定義
157
SQL Server 2014 実践 No.1 インメモリ OLTP
WITH
NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'japanese')
-- 実行するステートメント
END
ネイティブ コンパイル SP では、BEGIN .. ATOMIC で囲んだものが 1 トランザクションとし
て扱われます。
ELEVATE_TO_SNAPSHOT を有効化
データベースに対して、MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT を ON へ設定
すると、ステートメントの分離レベルを自動的にスナップショット分離レベルへ昇格(ELEVATE)
させることができます。これは次のように設定できます。
USE データベース名
ALTER DATABASE CURRENT
SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT = ON
このように、ELEVATE_TO_SNAPSHOT を有効化しておくと、トランザクション内の各ステー
トメントに WITH(SNAPSHOT) を付けなくても、スナップショット分離レベルで実行できるよ
うになります。前述のエラーになった記述も、次のように実行できます。
ELEVATE_TO_SNAPSHOT を有効化
WITH(SNAPSHOT) を付けなくても
自動的にスナップショット分離レベル
として実行できるようになる
こ の よ う に 、 ELEVATE_TO_SNAPSHOT を 有 効 化 し て お く と 、 各 ス テ ー ト メ ン ト に
WITH(SNAPSHOT) を付けなくて済むようになるので、アプリケーションの修正をしなくても
済むようになります。これは、移行の際に多いに役立ちます。
158
SQL Server 2014 実践 No.1 インメモリ OLTP
4.2
スナップショット分離レベルでの考え方
インメモリ OLTP での基本となる「スナップショット分離レベル」は、今までスナップショット
分離レベルや READ COMMITTED SNAPSHOT(どちらも SQL Server 2005 以降から提供)
を利用してきた方にとってはお馴染みのものですが、既定の分離レベル「READ COMMITTED」
を利用してきた方にとっては動作が異なるので、注意が必要です。
トランザクション開始時点での正しいデータを返す ~読み取り一貫性~
インメモリ OLTP でのスナップショット分離レベルは、次のように動作します。
トランザクション
X
トランザクション
Y
ロック待ちなし
で読み取り可能!
【 col1=1 を更新 】
UPDATE t1_InMem
SET col2='1111'
WHERE col1=1
:
処理中
:
COMMIT or
ROLLBACK
更新
1
t1_InMemテーブル
col1
col2
1
1111
2
AAAAA
3
AAAAA
:
:
更新中のデータ。
未コミット(まだ確定
していない)データ
【 col1=1 を検索 】
更新前データ
(スナップショット)
1
AAAAA
2
SELECT *
FROM t1_InMem
WHERE col1=1
トランザクション開始時点
での正しいデータが返る
= 読み取り一貫性
スナップショットの格納先
として tempdb は利用しない
読み取り操作は、トランザクション開始時点での正しいデータが返るという動作で、読み取り一貫
性を実現しています。この動作は、今まで Oracle データベースを利用してきた方にとってもお馴
染みのものですが、読み取ったデータ(更新中のデータ)がコミットされたすると、それは正しい
データではなくなってしまうことに注意しなければなりません。
これに対して、従来ながらの READ COMMITTED 分離レベルでは、次のように動作します。
従来ながらの READ COMMITTED 分離レベル(既定値)の場合の動作
トランザクション
X
ロック待ち
トランザクション
Y
【 col1=1 を更新 】
UPDATE t1
SET col2='1111'
WHERE col1=1
:
処理中
:
COMMIT or
ROLLBACK
更新
1
t1 テーブル
col1
col2
1
1111
2
AAAAA
3
AAAAA
:
:
更新中のデータ。
未コミット(まだ確定
していない)データ
【 col1=1 を検索 】
2
SELECT *
FROM t1
WHERE col1=1
コミットまたはロールバック
されるまで待つ。
未コミットのデータは読まない
*なお、未コミットのデータを読み取る
ことができる NOLOCK ヒントもある
READ COMMITTED 分離レベルでは、未コミット(UNCOMMITTED)のデータを読まないこと
159
SQL Server 2014 実践 No.1 インメモリ OLTP
で、読み取り一貫性を実現しています。コミット済み(COMMITTED)のデータしか読まない、と
いう意味で、READ COMMITTED と呼ばれています。なお、未コミットのデータを(強引に)
読んでしまう NOLOCK ヒント(READ UNCOMMITTED 分離レベル)というオプションも用
意されていますが、このオプションはインメモリ OLTP では利用することができません。
このように、スナップショット分離レベルと READ COMMITTED 分離レベルでは、動作が異な
るので、更新中のデータを SELECT で読みってしまう可能性(更新が確定すると正しくないデー
タになってしまうこと)があることを考慮して、アプリケーションを開発していく必要があります。
スナップショット分離レベルの動作は、次のような状況が分かりやすいと思います。
トランザクション開始時点での
正しいデータが返る
=トランザクション一貫性
1
2
3
コミット後
この時点での
正しいデータ
4
5
トランザクション開始時点から
先に更新したユーザーがいるので、
41302(更新競合)が発生。
= 楽観的同時実行制御
ロールバックされる
スナップショット分離レベルでは、トランザクションの開始時点での正しいデータが返るので、ト
ランザクション中は、一貫してそのデータが返ります(画面の 2、4、5。画面の 3 によって
COMMIT されたとしても、トランザクション開始時点での正しいデータが返ります)。
このときに、同じデータを更新しようとすると(画面の 5)、更新競合が発生して、エラー 41302
が通達されます(後から更新した方が負けて、ロールバックされます)
。
従来ながらのディスク ベースのテーブルでは、SELECT 後に更新(UPDATE)を行うような場合
には、SELECT 時に UPDLOCK(更新ロック)をかけて、読み取りデータを保護する、という方
法がありますが、インメモリ OLTP では、更新ロックがサポートされていないので、更新競合を
受け入れなければなりません。したがって、このような同時更新の可能性がある場合には、更新競
合を考慮して、再試行を行うロジックへ変更していく必要があります(ネイティブ コンパイル SP
での再試行ロジックについては、第 2 章で説明しましたが、次の項でも別の例で説明します)。
INSERT の場合の動作 ~スナップショット分離レベル~
上の例は、UPDATE での動作でしたが、INSERT の場合は、次のように動作します。
160
SQL Server 2014 実践 No.1 インメモリ OLTP
トランザクション開始時点での
正しいデータが返る
=トランザクション一貫性
1
2
3
コミット後
この時点での
正しいデータ
4
5
同じデータを追加
この時点では競合検出されない
前述したように、スナップショット分離レベルでは、トランザクションの開始時点での正しいデー
タが返るので、画面の 2、4 は、3 で COMMIT されても、トランザクション開始時点での正
しいデータが返ります。
画面の 5 では、同じデータ(PRIMARY KEY 列が同じ値のデータ)を追加しようとしていますが、
INSERT の場合は、この時点では更新競合は検出されません。INSERT の場合は、次のように
COMMIT のタイミングでエラーが通達されます(41325 エラーが返る)
。
コミットのタイミングで
41325 エラーが通達されて
ロールバックされる
このように、スナップショット分離レベルは、今までの分離レベル(READ COMMITTED)とは、
異なる動作になるので、アプリケーションを移行する際には、この動作で問題がないかどうかをチ
ェックして、問題がある場合は、再試行ロジックを追加したり、アプリケーション側にメッセージ
を追加したり(例えば、在庫を表示するようなシステムであれば、このデータは XX:XX 時点での
データで、実際の購入時には在庫がなくなっている可能性があります、といった注意書きを加える
など)、運用ルールを変更して対応したりするなどを考慮していく必要があります。
161
SQL Server 2014 実践 No.1 インメモリ OLTP
4.3
更新競合が多発する場合の考え方 ~ナンバリング処理など~
更新競合が発生した場合の再試行ロジックについては、第 2 章で、次のように説明しました。
ネイティブ コンパイル SP の
再試行を行うための
通常のストアド プロシージャ
再試行回数
10 なら 10回再試行
BEGIN TRY の中で
ネイティブ コンパイル SP を実行する
実行成功なら @retry=0 にして、
WHILE ループを抜ける
ネイティブ コンパイル SP でエラー(更新競合)が
発生すると CATCH へ飛ぶ。CATCH に飛んで来たら、
@retry(何回目の再試行なのか)を +1 する
更新競合が発生したときのエラーは
41302、41305、41325、41301 など
更新競合が発生したらロールバック
更新競合以外のエラーが発生した場合は
そのままエラーを通達(THROW)
コードは、オンライン ブックの「メモリ最適化テーブルでのトランザクションの再試行ロジックのガイドライン」より引用
http://msdn.microsoft.com/ja-jp/library/dn169141(v=sql.120).aspx
ネイティブ コンパイル SP を実行するための、通常のストアド プロシージャを作成して、BEGIN
TRY の中でネイティブ コンパイル SP を実行して、実行が成功したのか、失敗したのかを判断
し、成功したら WHILE ループを抜けて終了、失敗した場合は、CATCH ブロックでエラー番号
が更新競合かどうか(41302、41305、41325、41301、1205 かどうか)をチェックして、
これらの番号なら再試行を行う、そうでない場合は THROW で、該当エラーをそのまま通達する、
という流れになっています。
CATCH ブロックで処理しているエラー番号の意味は、次のとおりです。
エラー番号
エラーの内容
41302
更新競合
41305
REPEATABLE READ 分離レベル時の検証の失敗
41325
SERIALIZABLE 分離レベル時の検証の失敗
41301
依存トランザクションのエラー
1205
デッドロック エラー
このように、インメモリ OLTP では、更新競合に対しては、再試行ロジックを利用するのが基本
になりますが、第 2 章で説明したポイントカード システムのように、そもそも更新競合が発生し
ない(同じカード ID のデータが同時に更新されることがない)というケースもあります。この場
162
SQL Server 2014 実践 No.1 インメモリ OLTP
合は、再試行ロジックは不要で、アプリケーション側でエラー処理をするだけで十分です。
更新競合が多発するケース ~DELAY、再試行回数の調整~
オンライン ブックの「再試行ロジックのガイドライン」トピックにも記載されていますが、更新
競合(41302 エラー)が多発する場合には、WAITFOR DELAY を調整してタイミングを遅らせ
たり、再試行回数を増やしたりするといったことも考慮する必要があります。
更新競合が多発する場合は
WAITFOR DELAY で調整すると
良いと記載されている
このように「WAITFOR DELAY '00:00:00.001'」と記述した場合は、1 ミリ秒待機して、再試
行が行えるようになります。
第 1 章で説明した bwin 社では、次のような再試行ロジックを利用しています。
@IsDone = 0 の間
繰り返す。
成功するまで再試行を行う
成功なら @IsDone = 1
で WHILE ループを抜ける
41302(更新競合)が発生
した場合は 1ミリ秒待機して
再試行を行う
* 上のコードは、TechEd North America 2014 での「DBI-B313 Microsoft SQL Server 2014: In-Memory
OLTP Customer Deployments and Lessons Learned」セッションより引用。吹出しは筆者が追加。
1 ミリ秒待機して、再試行を行ってしますが、再試行回数は設定せずに、成功するまで再試行を行
うコードになっています。
ナンバリング処理 ~手動で連番生成をしている場合~
ナンバリング処理(採番処理)を手動で行っている(手動で連番を生成している)場合は、多重度
が上がった場合に(50 多重や 100 多重など同時実行数が増えた場合に)
、更新競合が多発してし
まいます。
これについて、次のような「連番管理」テーブルを例に説明します。
163
SQL Server 2014 実践 No.1 インメモリ OLTP
従来ながらのディスク ベースのテーブルでのナンバリング処理(更新ロックを利用)
連番管理テーブル
現在の値は
10
更新ロック
をかける
1
更新
ロック後
2
3
ロック待ち
更新ロックに
ブロックされる
11 が
採番される
このように、従来ながらのディスク ベースのテーブルでは、SELECT 後に UPDATE を行う場合
には、UPDLOCK(更新ロック)をかけて、読み取りデータを保護する方法をとります。
これに対して、インメモリ OLTP では、更新ロックがサポートされていないので、更新競合を受
け入れる必要があります。これは、次のような状況です。
1
2
3
4
後から更新した方には
41302(更新競合)が発生
先に更新
ロールバックされる
インメモリ OLTP では、後から更新した方が負けて、41302(更新競合)エラーが通達されます。
このエラーは、多重度が上がった場合に多発する可能性があります(この連番管理テーブルの例で
は、同じ管理番号への同時アクセスが集中する場合に発生し得ます)
。
これについて、次のようなネイティブ コンパイル SP を例に説明します。
164
SQL Server 2014 実践 No.1 インメモリ OLTP
CREATE PROC sp_numbering
@kanriNo int, @renban int OUTPUT
WITH NATIVE_COMPILATION, EXECUTE AS OWNER, SCHEMABINDING
AS
BEGIN ATOMIC
WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'japanese')
DECLARE @現在の番号 int
-- 現在の番号を取得
SELECT @現在の番号 = 開始番号
FROM dbo.連番管理_InMem
WHERE 管理番号 = @kanriNo
-- 現在の番号 + 1 へ UPDATE
UPDATE dbo.連番管理_InMem
SET 開始番号 = @現在の番号 + 1
WHERE 管理番号 = @kanriNo
-- 採番された値(現在の番号 + 1)を OUTPUT で返す
SELECT @renban = @現在の番号 + 1
END
「連番管理_InMem」テーブルから現在の番号(開始番号)を取得して、それに +1 をしたもの
を UPDATE し、OUTPUT パラメーターでこの値を返します。
このネイティブ コンパイル SP(sp_numbering)は、次のように実行することができます。
DECLARE @kanriNo int = 3, @renban int
EXEC sp_numbering @kanriNo, @renban OUTPUT
SELECT @renban
ostress ツールで多重実行 ~更新競合のシミュレート~
更新競合を確認するには、多重実行をする必要がありますが、これは ostress ツール(RML
Utilities に含まれる負荷テスト ツール)を利用して試すことができます。ostress ツールは、
次のように実行できます(詳しくは後述します)
。
ostress.exe -n20 -r1 -Sサーバー名 -E -dデータベース名 -q -Q"DECLARE @kanriNo int = 3,
@renban int; EXEC sp_numbering @kanriNo, @renban OUTPUT"
165
SQL Server 2014 実践 No.1 インメモリ OLTP
「-n20」と指定することで 20 多重で、
「-Q"~"」に記述したクエリを同時実行することができ
ます。なお、-r1 は 1 回実行、-S で接続先となる SQL Server の指定、-E で Windows 認証、
-d でデータベース、-q で quiet モードを指定するという意味です。
実行結果は、次のようになります。
…
更新競合が
複数発生
更新競合は 41302 エラーですが
ostress ツールでは 41301
(依存トランザクションのエ
ラー)として通達されます
このように、更新競合への対応を行っていない場合は、多重実行時にエラーが多発してしまいます。
更新競合への対応を行うには、次のように作成します(冒頭に記載した再試行ロジックとほぼ同じ)。
ネイティブ コンパイル SP のパラメーターと同じ
ものを定義した通常のストアド プロシージャ
再試行回数
10 なら 10回再試行
BEGIN TRY の中で
ネイティブ コンパイル SP を実行
WAITFOR DELAY で 1ミリ秒待機
更新競合が多発する場合は、
これを調整する
166
SQL Server 2014 実践 No.1 インメモリ OLTP
このストアド プロシージャ(sp_numbering_retry)は、次のように実行できます。
DECLARE @kanriNo int = 3, @renban int
EXEC sp_numbering_retry @kanriNo, @renban OUTPUT
SELECT @renban
ostress ツールでの多重実行では、次のようにストアド プロシージャの名前のところのみを変更
して実行してみます。
ostress.exe -n20 -r1 -Sサーバー名 -E -dデータベース名 -q -Q"DECLARE @kanriNo int = 3,
@renban int; EXEC sp_numbering_retry @kanriNo, @renban OUTPUT"
しかし、更新競合がまだ発生してしまいます(環境によっては発生しない場合もあります)
。
更新競合が
まだ発生
これは、
再試行回数が 10 回では少ないために発生しているので、
「DECLARE @retry INT = 10」
の 10 を 50 へ変更して、ストアド プロシージャを再作成し、もう一度 ostress を実行してみ
ます。
エラーなし
167
SQL Server 2014 実践 No.1 インメモリ OLTP
今度は、エラーが発生することなく、実行が成功します。
次に、ostress での多重度を 100 へ変更(-n20 を -n100 へ変更)して実行してみます。
ostress.exe -n100 -r1 -Sサーバー名 -E -dデータベース名 -q -Q"DECLARE @kanriNo int = 3,
@renban int; EXEC sp_numbering_retry @kanriNo, @renban OUTPUT"
結果は、次のように更新競合が発生してしまいます。
更新競合が
発生
この更新競合は、環境によっては発生しない場合があり、弊社環境では 5 回に 2 回ぐらいは更新
競合が発生しませんでした。しかし、更新競合が発生する場合があるということは、再試行回数が
50 回でも少ないということです。
したがって、実際のアプリケーションでの同時実行数や、後続の処理(採番した後の実際の処理)
などを含めて、どのくらいの再試行回数が妥当なのか、あるいは WAITFOR DELAY で待機する
時間を「'00:00:00.002'」のように変更して、2 ミリ秒待機するようにするなど、実際のアプリ
ケーションでテストしておくことが重要です。また、そもそもナンバリング処理(採番処理)を手
動で行うのを止めて、IDENTITY やシーケンスを利用するようにすれば、このような更新競合に
悩まされることはなくなります。
再試行回数を調べてみる
再試行がとれぐらい発生したかは、次のように OUTPUT パラメーターを利用すれば、確認する
ことができます。
再試行回数を返すための
OUTPUT パラメーターを追加
とりあえず
1万回再試行
成功時は再試行回数
を返す
168
SQL Server 2014 実践 No.1 インメモリ OLTP
この OUTPUT パラメーターは、次のように取得できます(ADO.NET の場合)。
OUTPUT パラメーター
の取得
再試行回数
採番された値と
再試行回数を別テーブル
(dummy)へ格納
@retry は 10000 に設定している
ので、再試行が 0回 なら 10000、
1回なら 9999、7回なら 9993 が
格納される
このように再試行回数を調べたところ、次のような結果になりました。
多重度
再試行回数
(合計)
最大再試行回数
100
12,573
1,976
200
83,426
3,007
300
89,265
1,082
400
89,841
1,553
結果は、実行のたびに大きく異なっていたのですが、そのうちのワースト ケースの部分を抜き出
してみました。ベスト ケース(良い結果)では、多重度 100 で再試行回数がわずか 3 回、多重
度 200 でわずか 6 回ということもあったのですが、表にあげたように多重度 200 以上では 8
万回以上の再試行回数となってしまうことが多々ありました(最大再試行回数は 1,000 を超える
ものも有り、多重度 200 では 3,007 回再試行するものもありました)。
WAITFOR DELAY で待機する時間を「'00:00:00.002'」
(2 ミリ秒)に変更した場合は、次の
ようになりました。
多重度
再試行回数
(合計)
最大再試行回数
100
1,244
30
200
5,742
111
300
11,900
146
400
12,163
143
この結果も、実行のたびに大きく異なっていたのですが、このうちのワースト ケースを表にしま
した。再試行回数は、大きく減らすことができましたが、待機時間が 1 ミリ秒長くなってしまう
ことが難点です。
169
SQL Server 2014 実践 No.1 インメモリ OLTP
しかし、再試行回数を減らせられるのは、大きな差で、次のように考えることができます。
再試行回数
待機時間
1ミリ秒
待機時間
2ミリ秒
100
100ミリ秒
200ミリ秒
1,000
1秒
2秒
待機時間(WAITFOR DELAY)が 1 ミリ秒のときは、1,000 回以上再試行するものがありまし
たが、これだと 1 秒以上の実行時間がかかってしまうことになります。これに対して、2 ミリ秒
に設定して、再試行回数を 100 回に減らせられるのであれば、200 ミリ秒程度の実行時間で済む
わけです。
ナンバリング処理はインメモリ化すべきか???
再試行回数が多いということは、待機時間だけでなく、その分の実行時間もかかっているというこ
とを考慮しなければなりません(以下の表)
。
1回の架空の
実行速度
実行回数
実行時間
On Disk
500マイクロ秒
1回
500マイクロ秒
In Memory
100マイクロ秒
100回
10ミリ秒
1 回の実行速度は、仮のものとしていますが、例えばディスク ベースで 500 マイクロ秒で完了す
る処理は、再試行をしなくて済む(UPDLOCK を利用して同時更新をブロックしている)ので、実
行時間は 500 マイクロ秒で済み、これに対してインメモリ OLTP で実行速度が 100 マイクロ秒
だと仮定して、再試行回数が 100 回であったとすると 10 ミリ秒も実行時間がかかってしまうわ
けです(もちろん、すべての実行が 100 回の再試行をするわけではなく、1 回の実行で終わるも
のもあれば、数回の再試行で済むものも多数あります)
。
このように、1 回の実行速度が速くなっても、再試行の回数が増えてしまえば、その差は逆転して
しまうことがあり得ます。
したがって、実際のアプリケーションでの同時実行数や、後続の処理(採番した後の実際の処理)
などを含めて、実際のアプリケーションで入念なテストを行って、どれぐらいの更新競合/再試行
回数が発生するのか、ナンバリング処理をディスク ベースのままにしておくのか、インメモリ化
をするのかをしっかりと検証しておくことが重要です。
そもそも、ナンバリング処理(採番処理)を手動で行うのを止めるというのがベストな選択で、
IDENTITY やシーケンスを利用するように変更するのがお勧めになります。また、更新競合が多
発するような状況を避けるようにアプリケーションやテーブルを再設計していくことも重要です。
170
SQL Server 2014 実践 No.1 インメモリ OLTP
4.4
メモリ使用量を制限したい場合 ~リソース プールの作成~
インメモリ OLTP のメモリ使用量を制限したい場合には、リソース プールを作成するようにしま
す。既定では、
「default」リソース プール内で動作するので、新しくリソース プールを作成して、
そのリソース プールの最大メモリ使用量(MAX_MEMORY_PERCENT)を設定し、そこへイン
メモリ OLTP のデータベースを割り当てることで、メモリ使用量を制限することができます。
新しいリソース プールを作成するには、次のように CREATE RESOURCE POOL ステートメン
トを実行します。
CREATE RESOURCE POOL pooltest1
WITH ( MAX_MEMORY_PERCENT = 70 )
-- pooltest1 は任意のリソース プール名
-- 70% に制限する場合
ALTER RESOURCE GOVERNOR RECONFIGURE
ステートメント内の「pooltest1」はリソース プール名、70 はメモリ使用量の最大を 70%に制
限する(メモリが 100GB なら 70GB に制限)という意味なので、ここを変更することで、指
定したメモリ使用量に制限することができます。
なお、ステートメントではなく、GUI 操作でリソース プールを作成する場合には、次のようにオ
ブジェクト エクスプローラーで[管理]フォルダーの[リソース ガバナー]から行います。
リソース プール名を入力
最大メモリ使用量を
% で指定
171
SQL Server 2014 実践 No.1 インメモリ OLTP
データベースをリソース プールへバインドする ~~
リソース プールを作成した後は、そのリソース プールへインメモリ OLTP のデータベースをバ
インドする(割り当てる)ことで、メモリ使用量を制限することができます。これを行うには、次
のように sp_xtp_bind_db_resource_pool システム ストアド プロシージャを利用します。
EXEC sp_xtp_bind_db_resource_pool 'testDB', 'pooltest1'
第1引数でバインドしたいデータベース名(画面は testDB)、第2引数でリソース プール名(画
面は pooltest1)を指定します。
実行後、SQL Server サービスを再起動すれば、バインドが有効になります。
これで、メモリ使用量を制限することができます。
なお、バインドを元に戻したい場合(default リソース プールにバインドされた状態へ戻したい
場合)は、次のように sp_xtp_unbind_db_resource_pool システム ストアド プロシージャ
を実行します。
EXEC sp_xtp_unbind_db_resource_pool 'testDB'
メモリ使用量を使い切った場合のエラー
メモリ使用量を使い切った場合には、次のようにエラーが表示されます。
リソース プールへのバインドを
設定していない場合は、default
リソース プールが利用されるので、
default と表示される
172
SQL Server 2014 実践 No.1 インメモリ OLTP
「リソース プール '~' のシステム メモリが不足しています。」と表示されて、リソース プール
へのバインドを設定していない場合は、default リソース プールが利用されるので、default と
表示されます。
バインドを設定している場合は、次のようにバインドしているリソース プールの名前が表示され
ます。
バインドしている
リソース プール名が表示される
173
SQL Server 2014 実践 No.1 インメモリ OLTP
4.5
SELECT の取得データ件数が異なる場合の性能差
キー列(PRIMARY KEY)を利用した検索(SELECT ステートメント)の性能効果に関しては、
第 2 章のポイントカード システムで確認しているので、ここでは単純なテーブルを利用して、取
得するデータ件数が異なる場合の SELECT ステートメントの性能効果を確認してみます。次のパ
ターンを確認してみました。

SELECT の取得件数が約 5 件の場合(シングル実行、100 万回実行時)

SELECT の取得件数が約 100 件の場合(シングル実行、50 万回実行時)

SELECT の取得件数が約 1 万件の場合(シングル実行、500 回実行時)
いずれも WHERE 句で「=」演算子で検索を行って、その結果件数が異なる場合の性能を確認し
てみました。結果は、次のとおりです。
SELECT の取得件数が約 5件の場合
テーブル構造(ディスク ベースの場合)
1.3倍の
性能向上
実行した SQL(ディスク ベースの場合)
テーブルには 1,000万件のデータを格納
col2 列には、0~200万の間の乱数が格納されている
@p1 には 0~10万の間の乱数を与える。
どの乱数値でも結果件数は約 5件になる。
100万回の実行を 3回行って、その平均値を取得。
Hash Native SP: col2 に HASH Index。PK も HASH
bw Native SP : col2 に bw-tree Index。PK は HASH
bw bw Native SP: col2 に bw-tree Index。PK も bw-tree
~ SQL: Native SP を利用せずに、直接 SQL を実行した場合
* ベンチマーク結果の公開は、使用許諾契約書で禁止されているので、
On Disk(ディスクベース)の結果を 100 とした場合の相対値で表示
テスト PC の構成(約15万円の普通のパソコン)
・Core i7 3770K 3.5GHz 4コア
・メモリ 32GB、HDD Western Digital WD30EZRX
SELECT の取得件数が約 100件の場合
テーブル構造(ディスク ベースの場合)
2.19倍の
性能向上
実行した SQL(ディスク ベースの場合)
テーブルには 1,000万件のデータを格納
col3 列には、0~10万の間の乱数が格納されている
@p1 には 0~1万の間の乱数を与える。
どの乱数値でも結果件数は約 100件になる。
50万回の実行を 3回行って、その平均値を取得。
Hash Native SP: col2 に HASH Index。PK も HASH
bw Native SP : col2 に bw-tree Index。PK は HASH
bw bw Native SP: col2 に bw-tree Index。PK も bw-tree
~ SQL: Native SP を利用せずに、直接 SQL を実行した場合
On Disk(ディスクベース)の結果を 100
とした場合の相対値で表示
174
SQL Server 2014 実践 No.1 インメモリ OLTP
SELECT の取得件数が約 1万件の場合
テーブル構造(ディスク ベースの場合)
2.17倍の
性能向上
実行した SQL(ディスク ベースの場合)
テーブルには 1,000万件のデータを格納
col4 列には、0~1000の間の乱数が格納されている
@p1 には 0~500の間の乱数を与える。
どの乱数値でも結果件数は約 1万件になる。
500回の実行を 3回行って、その平均値を取得。
Hash Native SP: col2 に HASH Index。PK も HASH
bw Native SP : col2 に bw-tree Index。PK は HASH
bw bw Native SP: col2 に bw-tree Index。PK も bw-tree
~ SQL: Native SP を利用せずに、直接 SQL を実行した場合
On Disk(ディスクベース)の結果を 100
とした場合の相対値で表示
結果は、約 5 件の結果が返る場合に 1.3 倍、約 100 件の結果が返る場合に 2.19 倍、約 1 万件
の結果が返る場合に 2.17 倍となり、いずれもディスク ベースよりも性能向上することを確認で
きました。このように、インメモリ OLTP は、
「=」演算子を利用した検索には強いことを確認す
ることができました。また、今回のテストはシングル実行でしたが、インメモリ OLTP では、
SELECT ステートメントがロック待ちおよびラッチ待ちになることがないので、多重度が上がっ
て、更新系のステートメントと同時実行される状況では、さらに SELECT ステートメントの性能
が向上することになります。
検証の詳細
この検証の詳細は、次のとおりです。
テストに使用したテーブル(1000万件のデータ、col1~col5 の 5列)
col5 datetime は
2009/01/01~ 5年間の間
の日時分(分単位)
データを 1,000万件作成するときに利用したスクリプト
col4 int は
0~1000の間の乱数
col2 用の乱数は
0~200万の間
col3 int は
0~10万の間の乱数
0~10万の乱数
0~1000の乱数
col2 int は
0~200万の間の乱数
col1 int は
1 からの連番
5年分のデータが分単位
で入るようにしたもの
テストで使用したテーブルには、1,000 万件のデータ(ディスク ベースもインメモリ OLTP も同
じデータ)を格納して、col1(int)には 1 からの連番(IDENTITY)
、col2(int)には 0~200
万の間の乱数、col3(int)には 0~10 万の間の乱数、col4(int)には 0~1,000 の間の乱数、
col5(datetime)には 2009/01/01 以降の 5 年分のデータを分単位(2,628,000 通り)で格
175
SQL Server 2014 実践 No.1 インメモリ OLTP
納(乱数で分ごとに 4~5 件のデータが格納)されるようにしています。
このように乱数を利用することで、col2 で検索した場合には約 5 件、col3 で検索した場合には
約 100 件、col4 で検索した場合には約 1 万件の結果が返るようになります。
col2 で検索した場合は、約5件の結果が返る
実行プラン
col2 で検索
Index Seek で検索で
きるようにインデック
スを作成している
約 5件の結果
が返る
また、col2 で検索する場合には、テーブル構造を次のように変更して、col2 列へインデックス
を作成し、Index Seek で検索できるようにしています。
col2 検索時の OnDisk のテーブル構造
col2 検索時の Hash のテーブル構造
col1(PK)も HASH インデックス
col2 に HASH インデックス
col2 に b-tree インデックス
col2 検索時の BW BW のテーブル構造
col2 検索時の BW のテーブル構造
col1(PK)は HASH
col2 に bw-tree
グラフ上の表記
ディスク ベース
On Disk
インメモリ OLTP
Hash
bw
bw bw
col1(Primary Key)
クラスター化インデックス
(b-tree)
HASH インデックス
HASH インデックス
bw-tree インデックス
col1(PK)も bw-tree
col2 に bw-tree
col2(検索列)
非クラスター化インデックス
(b-tree)
HASH インデックス
bw-tree インデックス
bw-tree インデックス
ディスク ベースのテーブルは、従来ながらの b-tree インデックス、インメモリ OLTP では、
HASH または bw-tree インデックスを作成して、性能に違いが出るのかも確認してみました。
ネイティブ コンパイル SP は、次のように作成しています。
パラメーターは int 型
全列を取得
col2 で検索
176
SQL Server 2014 実践 No.1 インメモリ OLTP
実際に、ネイティブ コンパイル SP や SQL ステートメントを実行する部分には、.NET(VB.NET
と ADO.NET)を利用して、次のようなコードを記述しています。
Stopwatch クラス
で実行時間を計測
100万回ループ処理(col2 の場合)
col2 に与える乱数
0~10万の間
ネイティブ コンパイル SP の場合
WHERE col2 =@p1
接続 Open
SqlParameter を利用
結果を受け取るための変数
SqlDataReader で結果を受け
取り、結果件数分ループ処理。
結果を変数へ格納
接続 Close
Stopwatch クラス
で実行時間を計測
col2 の検索では、For ループで 100 万回繰り返し実行を行って、col2 へ与える値(@p1)に
は Random クラスで 0~10 万の間の乱数を生成したものを利用することで、同じ値での検索に
ならないようにしています。また、接続(SqlConnection)の Open と Close を入れたり、検
索結果を変数へ格納したりすることで、より実際のアプリケーションに近い形の検索になるように
しています。
このコードを実行したときを SQL Server Profiler ツールでキャプチャすると、次のように
col2 へ与える値(@p1)に乱数がセットされていることを確認することができます。
@p1 = 44735
@p1 = 94001
@p1 = 40204
@p1 = 71126
ADO.NET では、SQL は
sp_executesql に変換
されて実行される
また、実行時間の測定には、Stopwatch クラスを利用してします(後述しますが、Stopwatch ク
ラスでは、ミリ秒未満の計測=マイクロ秒単位での計測をすることができません。今回のように何
177
SQL Server 2014 実践 No.1 インメモリ OLTP
万回と SQL ステートメントを実行する場合の計測であれば問題ありませんが、単一/1 回のステ
ートメントを実行する場合の計測には利用することができません。∵1 回のステートメントの実行
ではマイクロ秒レベルで完了してしまうため)。
col3 列の検索の場合(約 100 件の結果が返る)
col3 で検索した場合には約 100 件の結果が返るようにしています。
col3 で検索した場合は、約100件の結果が返る
実行プラン
col3 で検索
Index Seek で検索で
きるようにインデック
スを作成している
約 100件の
結果が返る
また、col3 で検索する場合には、テーブル構造を次のように変更して、col3 列へインデックス
を作成し、Index Seek で検索できるようにしています。
col3 検索時の OnDisk のテーブル構造
col3 検索時の Hash のテーブル構造
col1(PK)も HASH
col3 に HASH
col3 に b-tree インデックス
col3 検索時の BW BW のテーブル構造
col3 検索時の BW のテーブル構造
col1(PK)は HASH
col3 に bw-tree
グラフ上の表記
ディスク ベース
On Disk
インメモリ OLTP
Hash
bw
bw bw
col1(Primary Key)
クラスター化インデックス
(b-tree)
HASH インデックス
HASH インデックス
bw-tree インデックス
ネイティブ コンパイル SP は、次のように作成しています。
178
col1(PK)も bw-tree
col3 に bw-tree
col3(検索列)
非クラスター化インデックス
(b-tree)
HASH インデックス
bw-tree インデックス
bw-tree インデックス
SQL Server 2014 実践 No.1 インメモリ OLTP
パラメーターは int 型
全列を取得
col3 で検索
実際に、ネイティブ コンパイル SP や SQL ステートメントを実行する部分は、前述の .NET コ
ードと同じものを利用しています。
col4 列の検索の場合(約 1 万件の結果が返る)
col4 で検索した場合には約 1 万件の結果が返るようにしています。
col4 で検索した場合は、約1万件の結果が返る
実行プラン
col4 で検索
Index Seek で検索で
きるようにインデック
スを作成している
約 1万件の
結果が返る
1万件分のキー参照がコスト高になるので
不足しているインデックスが提示されている
また、col4 で検索する場合には、テーブル構造を次のように変更して、col4 列へインデックス
を作成し、Index Seek で検索できるようにしています。
col4 検索時の OnDisk のテーブル構造
col4 検索時の Hash のテーブル構造
col1(PK)も HASH
col4 に HASH
col4 に b-tree インデックス
col4 検索時の BW BW のテーブル構造
col4 検索時の BW のテーブル構造
col1(PK)は HASH
col4 に bw-tree
グラフ上の表記
ディスク ベース
On Disk
インメモリ OLTP
Hash
bw
bw bw
col1(Primary Key)
クラスター化インデックス
(b-tree)
HASH インデックス
HASH インデックス
bw-tree インデックス
ネイティブ コンパイル SP は、次のように作成しています。
179
col1(PK)も bw-tree
col4 に bw-tree
col4(検索列)
非クラスター化インデックス
(b-tree)
HASH インデックス
bw-tree インデックス
bw-tree インデックス
SQL Server 2014 実践 No.1 インメモリ OLTP
パラメーターは int 型
全列を取得
col4 で検索
実際に、ネイティブ コンパイル SP や SQL ステートメントを実行する部分は、前述の .NET コ
ードと同じものを利用しています。
検証のまとめ ~SELECT の取得データ件数の違い~
検証結果をまとめると、次のようになります。
ディスク ベース
5件
100件
1万件
100
100
100
76.9
1.3倍
45.6
2.19倍
46.1
2.17倍
bw
77.5
1.29倍
46.4
2.15倍
46.1
2.17倍
bw bw
77.6
1.29倍
46.4
2.15倍
46.1
2.17倍
Hash
87.0
1.15倍
51.4
1.94倍
46.3
2.16倍
bw
89.2
1.12倍
53.1
1.88倍
46.4
2.16倍
bw bw
88.5
1.13倍
52.9
1.89倍
46.4
2.16倍
On Disk
Hash
Native SP
インメモリ OLTP
SQL 直接
Native SP
を不利用
Native SP Hash では、約 5 件の結果が返る場合に 1.3 倍、約 100 件の結果が返る場合に 2.19
倍、約 1 万件の結果が返る場合に 2.17 倍となり、どれもディスク ベースよりも性能向上するこ
とを確認できました。このように、インメモリ OLTP は、
「=」演算子を利用した検索には強いこ
とを確認することができました。
検索列に HASH インデックスを利用するか、bw-tree インデックスを利用するかでは、HASH
インデックスのほうが若干速い程度で、ほとんど差が出ないことも確認することができました(後
述しますが、bw-tree インデックスは、範囲スキャンに強いという特性があります)。
また、PRIMARY KEY に bw-tree インデックスを利用した場合(表の bw bw)と HASH イ
ンデックスを利用した場合(表の bw)も、大きな差は出ないことを確認することができました。
また、今回のテストはシングル実行でしたが、インメモリ OLTP では、SELECT ステートメント
がロック待ちおよびラッチ待ちになることがないので、多重度が上がって、更新系のステートメン
トと同時実行される状況では、さらに SELECT ステートメントの性能が向上することになります。
180
SQL Server 2014 実践 No.1 インメモリ OLTP
ハッシュ インデックスのチェーンの長さ
BUCKET_COUNT(バケット数)については後述しますが、HASH インデックスでは、十分な
BUCKET_COUNT を設定している場合には、同じ値が、同じハッシュ値になって、チェーンが長
くなります。
ハッシュ インデックスのチェーン
(111)
(123)
(222)
(123)
(333)
(123)
0
1
2
3
4
5
6
:
ハッシュ値 1
ハッシュ値 3
ハッシュ値 5
ハッシュ値 3
ハッシュ値 6
ハッシュ値 3
●
●
●
チェーン
十分な BUCKET_COUNT(バケット数)がある場合は、
同じ値が、同じハッシュ値になるのでチェーンが伸びる
チェーンの長さは、次のように「dm_db_xtp_hash_index_stats」動的管理ビューを利用する
ことで確認することができます。
SELECT
-- object_name(hs.object_id) AS 'object name',
i.name as 'index name',
hs.total_bucket_count,
hs.empty_bucket_count,
floor((cast(empty_bucket_count as float)/total_bucket_count) * 100) AS
'empty_bucket_percent',
hs.avg_chain_length,
hs.max_chain_length
FROM sys.dm_db_xtp_hash_index_stats AS hs
JOIN sys.indexes AS i
ON hs.object_id=i.object_id AND hs.index_id=i.index_id
avg_chain_length
(平均のチェーンの長さ)
が5
max_chain_length
(最大チェーンの長さ)
が 35
idx2 は col2 に作成した HASH インデックス
上記クエリは、オンライン ブックの「ハッシュ インデックスの適切なバケット数の決定」より引用
http://msdn.microsoft.com/ja-jp/library/dn494956.aspx
上の結果は、col2 列に作成した HASH インデックスの場合の結果ですが、col2 列は同じ値が
181
SQL Server 2014 実践 No.1 インメモリ OLTP
約 5 件なので、avg_chain_length(平均のチェーンの長さ)も 5 になっていることを確認で
きます。
col3 列に作成した HASH インデックス(idx3)の場合は、次のような結果になります。
avg_chain_length
(平均のチェーンの長さ)
が 100
max_chain_length
(最大チェーンの長さ)
が 230
idx3 は col3 に作成した HASH インデックス
col3 列は同じ値が約 100 件なので、avg_chain_length(平均のチェーンの長さ)も 100 に
なっていることを確認できます。
182
SQL Server 2014 実践 No.1 インメモリ OLTP
4.6
JOIN がある場合の性能差
前項の 1,000 万件のテーブルを利用して、次のパターンの JOIN の性能を確認してみました。

col2 を JOIN キーとした場合(取得件数が約 5 件、シングル実行、50 万回実行時)

col3 を JOIN キーとした場合(取得件数が約 100 件、シングル実行、10 万回実行時)
col2 との JOIN のためのマスター テーブル(col2master_~)は、次のように 200 万件のデ
ータを格納しています(ディスク ベースとインメモリ OLTP の 2 種類を作成)
。
col2 列と JOIN するための
マスター テーブル(200万件)
c2 int は
0~9999の間の乱数
c4 nvarchar(200) は
0~99 バイトの ’B’
c3 nvarchar(200) は
0~99 バイトの ’A’
c1 int は
1 からの連番
インデックスを作成
JOIN は、次のように行っています(ディスク ベースの場合)
。
col2 が JOIN キー
すべての列を取得
特定の値で絞り込み
テストでは乱数を利用
約5件の結果
が返る
マスター側(col2master_~)の JOIN キー(c1 列)には、次のようにインデックスを作成し
て、Index Seek で検索できるようにしています。
col2 のマスター テーブル
インメモリ OLTP 用は
c1 列に HASH インデックス
ディスク ベースは
c1 列に b-tree インデックス
col3 との JOIN のためのマスター テーブル(col3master_~)は、次のように 10 万件のデ
ータを格納しています(ディスク ベースとインメモリ OLTP の 2 種類を作成)
。
183
SQL Server 2014 実践 No.1 インメモリ OLTP
col3 列と JOIN するための
マスター テーブル(10万件)
c2 int は
0~9999の間の乱数
c3 nvarchar(200) は
0~99 バイトの ’A’
c1 int は
1 からの連番
インデックスを作成
c4 nvarchar(200) は
0~99 バイトの ’B’
col3 での JOIN は、次のように行っています(ディスク ベースの場合)。
col3 が JOIN キー
特定の値で絞り込み
テストでは乱数を利用
すべての列を取得
約100件の
結果が返る
マスター側(col3master_~)の JOIN キー(c1 列)には、インデックスを作成して、Index
Seek で検索できるようにしています。
検証結果
検証結果は、次のようになりました。
1.27倍の
性能向上
col2 での JOIN
1,000万件のテーブルと
200万件のテーブルの JOIN。
WHERE 条件で特定の値(@p1)に絞り込み有り
@p1 には 0~10万の間の乱数を与える。
col2 列には 0~200万の間の乱数が格納されている
どの乱数値でも結果件数は約 5件になる。
50万回の実行を 3回行って、その平均値を取得。
Hash Native SP: col2 に HASH Index。PK も HASH
bw Native SP : col2 に bw-tree Index。PK は HASH
bw bw Native SP: col2 に bw-tree Index。PK も bw-tree
~ SQL: Native SP を利用せずに、直接 SQL を実行した場合
On Disk(ディスクベース)の結果を 100
とした場合の相対値で表示
テスト PC の構成
・Core i7 3770K 3.5GHz 4コア
・メモリ 32GB、HDD Western Digital WD30EZRX
184
SQL Server 2014 実践 No.1 インメモリ OLTP
1.45倍の
性能向上
col3 での JOIN
1,000万件のテーブルと
10万件のテーブルの JOIN。
WHERE 条件で特定の値(@p1)に絞り込み有り
@p1 には 0~1万の間の乱数を与える。
col3 列には 0~10万の間の乱数が格納されている
どの乱数値でも結果件数は約 100件になる。
10万回の実行を 3回行って、その平均値を取得。
Hash Native SP: col2 に HASH Index。PK も HASH
bw Native SP : col2 に bw-tree Index。PK は HASH
bw bw Native SP: col2 に bw-tree Index。PK も bw-tree
~ SQL: Native SP を利用せずに、直接 SQL を実行した場合
On Disk(ディスクベース)の結果を 100
とした場合の相対値で表示
col2 を JOIN キーとした場合(約 5 件の結果が返る場合)に 1.27 倍、col3 を JOIN キーと
した場合(約 100 件の結果が返る場合)に 1.45 倍となり、どちらもディスク ベースよりも性能
が向上することを確認できました。
このように、インメモリ OLTP は、
「=」演算子を利用して、データを絞り込んでいる場合の JOIN
には強いことを確認することができました(後述しますが、データを絞り込まずに、全スキャンを
するような JOIN の場合は、得意ではありません。範囲スキャンの場合は、次項で説明する
bw-tree インデックスを利用することで、性能を向上させることができます)。
185
SQL Server 2014 実践 No.1 インメモリ OLTP
4.7
bw-tree インデックスの選択基準、範囲スキャン
前々項(SELECT の取得件数の違い)と前項(JOIN の性能)では、HASH インデックスでも
bw-tree インデックスでも大きな性能差はないということを確認しましたが、大きな差が現れる
のが ”範囲スキャン”
(Range Scan)の場合です。範囲スキャンは、範囲検索(< や >、BETWEEN
などを利用した検索)の場合に内部的に行われるスキャンです。HASH インデックスは範囲スキ
ャンに弱く、bw-tree インデックスは範囲スキャンに強いという特性を持ちます。このため、
bw-tree インデックスは Range Index と呼ばれることもあります。
テストで使用したテーブルの構成
範囲スキャンについては、前項で利用した 1,000 万件のテーブルの col5 列(datetime 型)を
利用して性能を確認してみました。
テストに使用したテーブル(1000万件のデータ、col1~col5 の 5列)
データを 1000万件作成するときに利用したスクリプト
col2 用の乱数は
0~200万の間
0~10万の乱数
0~1000の乱数
col5 datetime は
2009/01/01~ 5年間の間
の日時分(分単位)
5年分のデータが分単位
で入るようにしたもの
col5 には、2009/01/01 00:00:00~2014/01/01 00:00:00 までの間の 5 年分のデータを
分単位(2,628,000 通り)で格納(乱数で分ごとに 4~5 件のデータが格納)されるようにして
います。
col5 で「=」演算子を利用して検索した場合(範囲検索ではない場合)には、次のように 4~5
件の結果が返ります。
col5 で「=」検索すると 4~5件の結果が返る
実行プラン
col5 で検索
= 演算子で日時分を
指定する場合は
Index Seek になる
4~5件の
結果が返る
このように、
「=」演算子で日時分を指定する場合には、Index Seek で検索できるように、col5
列にインデックスを作成しています(以下)
。
186
SQL Server 2014 実践 No.1 インメモリ OLTP
col5 検索のための OnDisk のテーブル構造
col5 検索のための Hash のテーブル構造
col1(PK)も HASH
col5 に HASH
col5 に b-tree インデックス
col5 検索のための BW BW のテーブル構造
col5 検索のための BW のテーブル構造
col1(PK)も bw-tree
col1(PK)は HASH
col5 に bw-tree
col5 に bw-tree
グラフ上の表記
ディスク ベース
On Disk
インメモリ OLTP
Hash
bw
bw bw
col1(Primary Key)
クラスター化インデックス
(b-tree)
HASH インデックス
HASH インデックス
bw-tree インデックス
col5(検索列)
非クラスター化インデックス
(b-tree)
HASH インデックス
bw-tree インデックス
bw-tree インデックス
範囲スキャン ~日付の検索~
このテーブルに対して、次のように範囲検索(< や >、BETWEEN などを利用した検索)を行
った場合は、ディスク ベースの b-tree インデックスでは、Index Seek(範囲スキャン)で完
了します。
col5 で範囲検索。1日分は 約 5,000件の結果が返る
実行プラン
col5 で範囲検索
1日分のデータを取得
ディスク ベースの
b-tree インデックスでは
範囲検索でも
Index Seek になる
約5,000件の
結果が返る
表記は Index Seek ですが、
内部的には範囲スキャン
(Range Scan)が行われている
これに対して、インメモリ OLTP の HASH インデックスを利用している場合は、次のように
Table Scan になってしまいます。
HASH インデックスだと Table Scan(全スキャン)
Table Scan
187
SQL Server 2014 実践 No.1 インメモリ OLTP
HASH インデックスは、範囲検索には弱く、全データをスキャンしなければなりません。
一方、bw-tree インデックスを利用している場合は、次のように Index Seek(範囲スキャン)
で取得することができます。
bw-tree インデックスであれば Index Seek
Index Seek
ディスク ベースの b-tree インデックスと同様、bw-tree インデックスは範囲検索に強いとい
う特性を持ちます。
検証結果
範囲スキャンを 5,000 回シングル実行したときの実行時間(5,000 回を 3 回実行して、その平
均を取得し、ディスク ベースの結果を 100 とした相対値に変換したもの)は、次のようになりま
した。
テーブル構造(ディスク ベースの場合)
172倍も
遅い
実行した SQL(ディスク ベースの場合)
82倍も
遅い
テーブルには 1,000万件のデータを格納
col5 列には、分単位で 5年分のデータ格納されている
@p1 には乱数をもとに任意の日付を与える
@p2 には、@p1+1日 を与えて、1日分のデータを
取得するようにし、結果件数は約 5,000件になる。
5,000回の実行を 3回行って、その平均値を取得。
2.7倍
性能向上
Hash Native SP: col2 に HASH Index。PK も HASH
bw Native SP : col2 に bw-tree Index。PK は HASH
bw bw Native SP: col2 に bw-tree Index。PK も bw-tree
~ SQL: Native SP を利用せずに、直接 SQL を実行した場合
On Disk(ディスクベース)の結果を 100
とした場合の相対値で表示
テスト PC の構成
・Core i7 3770K 3.5GHz 4コア
・メモリ 32GB、HDD Western Digital WD30EZRX
実行時間
(相対値)
ディスク ベース
100
On Disk
Hash
Native SP
bw
SQL 直接
Native SP
を不利用
8,178
82倍 遅い
37.0
2.7倍 速い
37.0
2.7倍 速い
17,241
172倍 遅い
bw
37.4
2.68倍 速い
bw bw
37.4
2.68倍 速い
bw bw
インメモリ OLTP
差
Hash
188
SQL Server 2014 実践 No.1 インメモリ OLTP
結果は、HASH インデックスを利用した場合は、桁違いに遅くなることを確認することができま
した(ネイティブ コンパイル SP 利用時は 82 倍、SQL 直接実行時は 172 倍も遅い)
。これに
対して、bw-tree インデックスを利用した場合は、ディスク ベースよりも約 2.7 倍も速い結果
になり、大きな性能効果を得られることが分かりました。
このように、範囲検索(< や >、BETWEEN などを利用した検索)を速くしたい場合には
bw-tree インデックスが非常にお勧めになります。これに対して、HASH インデックスを利用し
ている場合には、範囲検索(範囲スキャン)が桁違いに遅くなってしまうことを念頭に置いておく
必要があります(次項で説明しますが、HASH インデックスはフル スキャンにも弱いという特性
を持っていることに注意する必要があります)。
更新への影響 ~bw-tree は更新が遅くなる~
bw-tree インデックスは、範囲検索が速くなることが大きなメリットですが、更新(UPDATE、
INSERT、DELETE)処理が遅くなってしまうというデメリットもあります。
次のグラフは、第 1 章で説明した 5,000 万件分の INSERT(1 件ずつ INSERT を多重実行)を
行った場合の結果に、bw-tree の結果を加えたものです。
ディスク ベースと
比べれば断然速い
bw-tree のほうが
少し遅い
テーブル構造(bw-tree の場合)
bw-tree
インデックス
HASH インデックスよりも、bw-tree インデックスのほうが少し遅いことを確認できると思いま
す。しかし、ディスク ベースの結果に比べれば断然速いので、範囲スキャンが HASH インデッ
クスに比べて圧倒的に速いことを考慮すれば、利用価値が高いインデックスと言えます。
bw-tree インデックスが更新に弱いことは、一括 INSERT などで大量データを更新した場合に、
顕著な性能差を確認することができます。これは次のような状況です。
189
SQL Server 2014 実践 No.1 インメモリ OLTP
1,000万件を一括 INSERT したときの実行時間(ディスク ベース)
col1 に b-tree
クラスタ化
テーブル構造
SET STATISTICS TIME
コマンドで実行時間を計測
col2 に b-tree
非クラスタ化
1,000万件の
データを一括 Insert
このように、INSERT .. SELECT を利用して、1,000 万件のデータを丸ごと INSERT したとき
の実行時間を比較すると、次のグラフのようになります。
PK と col2
が bw-tree
ディスク
ベース
col2 が bw-tree
HASH のみ
On Disk: PK と col2 に b-tree
Hash Hash : PK と col2 に HASH
Hash bw
: PK に HASH、col2 に bw-tree
bw bw
: PK と col2 に bw-tree
On Disk(ディスクベース)の結果を 100
とした場合の相対値で表示
テスト PC の構成
・Core i7 3770K 3.5GHz 4コア
・メモリ 32GB、HDD Western Digital WD30EZRX
このテストで利用したテーブルは、col2 の検索(取得件数が約 5 件になる検証)で利用したとき
と同じもので、次のようになっています。
OnDisk のテーブル構造
Hash Hash のテーブル構造
col1(PK)
に b-tree
col1(PK)も HASH
col2 に HASH
col2 に b-tree
BW BW のテーブル構造
Hash BW のテーブル構造
col1(PK)は HASH
col2 に bw-tree
col1(PK)も bw-tree
col2 に bw-tree
このように、bw-tree インデックスは、HASH インデックスよりも更新(UPDATE、INSERT、
DELETE)処理が遅くなってしまうというデメリットがあるので、それを覚えておく必要がありま
す。もちろん、bw-tree インデックスには、範囲スキャン(範囲検索)に強い(ディスク ベース
よりも範囲スキャンが速い)という大きなメリットもあるので、それとのトレード オフで利用す
ることになります。
190
SQL Server 2014 実践 No.1 インメモリ OLTP
4.8
フル スキャンを避ける(インメモリ OLTP の苦手な処理)
ここまでは、インメモリ OLTP が得意な処理に関して見てきましたが、もちろん苦手な処理もあ
ります。それはフル スキャン(全スキャン)が発生するような場合です。これは、前項の範囲検
索のところでも少し出ていますが、HASH インデックスを利用している場合に、範囲検索を行っ
たとすると、Index Seek にはならず、Table Scan または Index Scan が行われてしまい、
桁違いに遅くなってしまう(前項ではネイティブ コンパイル SP 利用時に 82 倍、直接 SQL を
実行した場合に 172 倍も遅くなってしまう)というものです。このスピードは、ディスク ベー
スでのフル スキャンよりも遅いものなので、注意しなければなりません。
インメモリ OLTP のフル スキャンのスピード
ここでは、col2 の検索(取得件数が約 5 件になる検証)で利用したのと同じテーブル(1,000
万件のデータ)で説明します(以下)
。
OnDisk のテーブル構造
Hash Hash のテーブル構造
col1(PK)
に b-tree
col1(PK)も HASH
col2 に HASH
col2 に b-tree
BW BW のテーブル構造
Hash BW のテーブル構造
col1(PK)は HASH
col2 に bw-tree
col1(PK)も bw-tree
col2 に bw-tree
前述のテストでは、col2 列へインデックスを作成している場合は、「WHERE col2 = ~」のよ
うに「=」演算子を利用した検索は、次のように Index Seek になることを説明しました。
col2 で「=」検索なら Index Seek
col2 で検索
また、この検索は、ディスク ベースよりも、インメモリ OLTP のほうが速く結果を取得できるこ
とも確認しました(約 1.3 倍速い)
。
これに対して、
「WHERE col3 = ~」のように、インデックスを作成していない列(col3)を利
用した場合の検索は、次のようにフル スキャン(Table Scan または Index Scan)になります。
191
SQL Server 2014 実践 No.1 インメモリ OLTP
col3(インデックスがない列)での検索はフル スキャンになる(ディスク ベースの場合)
col3 で検索
インデックスを付けたほうが
良いよと提案されている
Clustered Index Scan
は全データのスキャン
インメモリ OLTP の場合
col3 で検索
インデックスを付けたほうが
良いよと提案されている
Table Scan
全データのスキャン
このように、フル スキャンになる検索を、次のように SET STATISTICS TIME コマンドを利用
して実行時間を計測してみます。
SET STATISTICS TIME
コマンドで実行時間を計測
後述しますが、初回実行時は、統計が自動作成
されるので、構文解析とコンパイルに時間がか
かります
実行時間がミリ秒単位で
表示される
結果は、次のようになりました。
PK HASH
col2 HASH
PK HASH
col2 bw-tree
PK bw-tree
col2 bw-tree
27倍も
遅い
On Disk: PK と col2 に b-tree
Hash Hash : PK と col2 に HASH
Hash bw
: PK に HASH、col2 に bw-tree
bw bw
: PK と col2 に bw-tree
ディスク
ベース
初回実行時は、自動統計が作成される負荷が
あるので、2回目以降の実行時の性能を比較
On Disk(ディスクベース)の結果を 100
とした場合の相対値で表示
テスト PC の構成
・Core i7 3770K 3.5GHz 4コア
・メモリ 32GB、HDD Western Digital WD30EZRX
192
SQL Server 2014 実践 No.1 インメモリ OLTP
実行時間
(相対値)
ディスク ベース
100
On Disk
インメモリ OLTP
差
Hash Hash
2,683
27倍 遅い
Hash bw
3,190
32倍 遅い
bw bw
2,078
21倍 遅い
結果は、ディスク ベースでの全スキャンに比べて、HASH インデックスでも bw-tree インデッ
クスでも桁違いに遅くなることが分かりました(強いて言えば、bw-tree インデックスが速いの
ですが....)
。Hash Hash の 27 倍遅いというのは、仮にディスク ベースが 100 ミリ秒で完了し
たとすると、Hash Hash では 2.7 秒もかかってしまうという意味で、この差は非常に大きいもの
です。
ネイティブ コンパイル SP を作成するとフル スキャンにも効果がある
次に、ネイティブ コンパイル SP を作成した場合の性能差を確認してみました。
col3 での検索のためのネイティブ コンパイル SP の作成
col3 で検索
この場合の結果は、次のようになりました。
ネイティブ コンパイル SP を
利用することでフル スキャン
が速くなる
193
SQL Server 2014 実践 No.1 インメモリ OLTP
実行時間
(相対値)
ディスク ベース
100
On Disk
Native SP
インメモリ OLTP
SQL 直接
Native SP
を不利用
差
Hash Hash
1,285
13倍 遅い
Hash bw
1,653
17倍 遅い
bw bw
1,316
13倍 遅い
Hash Hash
2,683
27倍 遅い
Hash bw
3,190
32倍 遅い
bw bw
2,078
21倍 遅い
結果は、どのパターンも、直接 SQL を実行するよりも、ネイティブ コンパイル SP を利用した
ほうが速く実行できることを確認できました(Hash Hash は 27 倍遅かったところが 13 倍、
bw bw は 21 倍遅かったところが 13 倍へ改善)
。
このように、ネイティブ コンパイル SP を作成すれば、フル スキャンの性能を上げることができ
ますが、これでもディスク ベースよりも 10 倍以上遅い(仮にディスク ベースが 100 ミリ秒な
ら、インメモリ OLTP では 1 秒かかってしまう)ことに気を付けなければいけません(インメモ
リ OLTP は、フル スキャンが苦手です)
。
したがって、インメモリ OLTP を利用する場合には、フル スキャン(全スキャン)にならないよ
うに、検索で利用する列に対して、インデックス(HASH または bw-tree)を確実に作成/付与
しておくことが非常に重要になります。また、範囲スキャンを避けるには、bw-tree インデック
スを活用することもポイントになります。
Tips: ディスク ベースではフル スキャンが Parallel 処理される ~並列実行~
ディスク ベースでのフル スキャンが速い理由の 1 つには、パラレル処理があります。これは、
次のような状況です。
フル スキャンが
パラレル処理
されている
Parallelism
パラレル処理されているかどうかは、実行プランの黄色いアイコン( ← が2つあるもの)で確
認することができます。これで、仮に CPU 時間が 400 ミリ秒かかるような処理の場合に、4
コアの CPU であれば 100~200 ミリ秒ぐらい(∵ 4 コア=4 倍の性能にはならないため)で
実行できるようになります。
インメモリ OLTP は全データを対象とした処理が苦手 ~集計処理など~
前述したように、インメモリ OLTP では、フル スキャン(全スキャン)が遅いので、全データを
対象とした集計処理も苦手です。これは、次のようなクエリです。
194
SQL Server 2014 実践 No.1 インメモリ OLTP
col2 列で
GROUP BY
Table Scan
全データのスキャン
このクエリは、WHERE 句での絞り込みを行わないで、全データ(1,000 万件)を対象としてい
ます。このような集計処理は、col2 列に HASH インデックスを作成していても、Table Scan
(全スキャン)になってしまいます。
この処理を性能比較すると、次のようになります。
PK HASH
col2 HASH
22.5倍も
遅い
PK HASH
col2 bw-tree
PK bw-tree
col2 bw-tree
On Disk: PK と col2 に b-tree
Hash Hash : PK と col2 に HASH
Hash bw
: PK に HASH、col2 に bw-tree
bw bw
: PK と col2 に bw-tree
3.7倍
遅い
ディスク
ベース
On Disk(ディスクベース)の結果を 100
とした場合の相対値で表示
テスト PC の構成
・Core i7 3770K 3.5GHz 4コア
・メモリ 32GB、HDD Western Digital WD30EZRX
実行時間
(相対値)
ディスク ベース
On Disk
100
2,252
22.5倍 遅い
Hash bw
365
3.7倍 遅い
bw bw
364
3.6倍 遅い
Hash Hash
インメモリ OLTP
差
col2 列に HASH インデックスを作成している場合は 22.5 倍も遅くなり、bw-tree インデッ
クスを作成している場合は 3.6~3.7 倍遅くなることを確認できました。これは、仮にディスク ベ
ースでの結果が 500 ミリ秒だったとすると、22.5 倍では 13.5 秒、3.6 倍では 1.8 秒もかかっ
てしまうということを意味しています。
bw-tree インデックスのほうが性能が良い理由は、Table Scan ではなく、次のように Index
Scan で行われているためです。
195
SQL Server 2014 実践 No.1 インメモリ OLTP
bw-tree インデックスの
Index Scan
インデックスの全スキャン
col2 列に bw-tree
インデックスがある場合
しかし、bw-tree インデックスのほうが性能が良いとは言っても(HASH インデックスの場合の
22.5 倍に比べれば断然速いですが)、ディスク ベースと比べると約 3.6 倍も遅いわけです。した
がって、このような全データを対象とした集計処理を頻繁に行っている場合には、注意してくださ
い。
col3 列(インデックスを付与していない列)で GROUP BY を行った場合
col2 列ではなく、col3 列(インデックスを作成していない列)で、GROUP BY 演算を行った
場合は、次のような性能結果になります。
col3 列には
インデックスがない場合
2.2倍
遅い
Table Scan
全データのスキャン
2.4倍
遅い
2.6倍
遅い
ディスク
ベース
On Disk: PK と col2 に b-tree
Hash Hash : PK と col2 に HASH
Hash bw
: PK に HASH、col2 に bw-tree
bw bw
: PK と col2 に bw-tree
初回実行時は、自動統計が作成される負荷が
あるので、2回目以降の実行時の性能を比較
col3 での GROUP BY の場合は、ディスク ベースでも、インメモリ OLTP でも、どのパターン
でも Table Scan になりますが(正確には、ディスク ベースでは Clustered Index Scan)
、
やはりディスク ベースよりも 2 倍以上も遅い結果となりました。差が小さくなったのは、col2 列
の GROUP BY の結果が 200 万件であったのに対して、col3 では 10 万件であったためです。
196
SQL Server 2014 実践 No.1 インメモリ OLTP
Note: インデックスを付与していない列を利用すると初回実行が遅い ~統計の自動作成~
col3 のようにインデックスを付与していない列を GROUP BY 句や WHERE 句の検索条件
に指定すると、初回実行時に自動的に統計が作成されるので(_WA_Sys_~という名前)
、初回
実行が遅くなります(統計の作成処理の負荷もインメモリ OLTP のほうが遅くなります)
。
初回実行時は、構文解析とコンパ
イルに 12.8秒もかかっている
∵統計が自動作成されているため
インデックスがない列には
自動的に統計が作成される
_WA_Sys_~ という名前
インデックスを作成している列
の場合は、インデックスの作成
時に統計が作成されている
col4 列(インデックスを付与していない列)で GROUP BY を行った場合
次に、col3 と同様、インデックスを作成していない col4 列で GROUP BY 演算を行ってみま
す。この場合は 1,000 件の結果が返ります。
実行プラン
col4 列には
インデックスがない場合
Table Scan
全データのスキャン
1,000件の結果
が返る
この場合の性能結果は、次のとおりです。
6倍
遅い
7倍
遅い
6.2倍
遅い
ディスク
ベース
On Disk: PK と col2 に b-tree
Hash Hash : PK と col2 に HASH
Hash bw
: PK に HASH、col2 に bw-tree
bw bw
: PK と col2 に bw-tree
初回実行時は、自動統計が作成される負荷が
あるので、2回目以降の実行時の性能を比較
インメモリ OLTP の結果は、いずれもディスク ベースよりも 6 倍以上遅い結果となりました。
197
SQL Server 2014 実践 No.1 インメモリ OLTP
col2、col3、col4 の結果を同じスケールにしてまとめると、次のようになります。
Table
Scan
Table
Scan
Table
Scan
Table
Scan
Index
Scan
Table
Scan
Index
Scan
Table
Scan
Table
Scan
Index
Scan
Table
Scan
col2 の結果件数は 200万件
col3 の結果件数は 10万件
col4 の結果件数は 1,000件
On Disk
PK と col2 に b-tree
Hash Hash
PK と col2 に HASH
Hash bw
PK に HASH、col2 に bw-tree
bw bw
PK と col2 に bw-tree
Table
Scan
col2 の OnDisk の結果を 100
とした場合の相対値で表示
Index Scan でも Table Scan でも、インメモリ OLTP のほうがディスク ベースよりも遅いこ
とを確認できると思います。なお、このテストでは、GROUP BY の結果を一時テーブルへ書き込
んでいますが、ディスク ベースの場合は、次のようにパラレル処理(Parallel Insert)が可能で、
その分の性能差も現れています。
ディスク ベース
の場合
Table Insert
がパラレル処理
フル スキャンにはクラスター化列ストア インデックス(CCSI)を利用
インメモリ OLTP は、フル スキャンが苦手ですが、SQL Server 2014 には、フル スキャンが
得意な機能として「クラスター化列ストア インデックス」
(CCSI:Clustered Column-store Index)
もあります。これは、カラム型データベース(列指向データベース)の SQL Server 実装で、SQL
Server 2014 からの新機能です(SQL Server 2012 でも、非クラスター化列ストア インデック
ス機能を利用することで、読み取り専用で利用することもできます)
。
クラスター化列ストア インデックスは、次のように作成することができます。
CREATE CLUSTERED COLUMNSTORE INDEX インデックス名
ON テーブル名
198
SQL Server 2014 実践 No.1 インメモリ OLTP
テーブル データ(1,000万件)を
丸ごと複製したテーブルを作成
クラスター化列ストア インデックス
(CCSI)を作成
CREATE CLUSTERED COLUMNSTORE INDEX ステートメントで、インデックス名(画面は
idx1)を指定し、ON 句でテーブル名(画面は CCSI_table)を指定すれば、作成が完了です。
このようにクラスター化列ストア インデックスを作成すると、集計処理のパフォーマンスが大幅
に向上します。
クラスター化列ストア インデックスを利用して、col2、col3、col4 の GROUP BY 演算(1,000
万件のデータ)を行った結果は、次のようになります。
2倍
速い
1.7倍
速い
36倍
速い
col2 の結果件数は 200万件
col3 の結果件数は 10万件
col4 の結果件数は 1,000件
col2 の OnDisk の結果を
100 とした場合の相対値
col2 列の集計処理では 2 倍、col3 列では 1.7 倍、col4 列では 36 倍も速い結果になっていま
す(col4 が桁違いに速いのは、結果件数が 1,000 件と少ないためです)
。このように、クラスタ
ー化列ストア インデックスを利用すれば、圧倒的な集計パフォーマンスを実現することができる
ので、集計処理が中心のシステムの場合には、検討してみることをお勧めします(インメモリ OLTP
とクラスター化列ストア インデックスは、同じテーブルに対して設定することはできないので、
集計処理を強くしたい場合にはクラスター化列ストア インデックスがお勧めになります)
。
クラスター化列ストア インデックスについては、本実践シリーズの「SQL Server 2014 への移
行/アップグレード」編でも詳しく説明するので、こちらもぜひご覧いただければと思います。
199
SQL Server 2014 実践 No.1 インメモリ OLTP
4.9
BUCKET_COUNT の違いによる性能差
HASH インデックスでは、BUCKET_COUNT(バケット数)を適切な値へ設定していないと、性
能低下の原因に繋がります。
これについて、col2 の検索(取得件数が約 5 件になる検証)で利用したのと同じテーブル(1,000
万件のデータ)で説明します(以下)
。
col2 の HASH インデックスの
BUCKET_COUNT を 400万に設定
BUCKET_COUNT を 100万に設定
BUCKET_COUNT を 10万に設定
…
BUCKET_COUNT を 1万に設定
…
col2 列の HASH インデックスの BUCKET_COUNT を 400 万、100 万、10 万、1 万に設定
したテーブルを 4 つ作成しました。
このテーブルに対して、次のように INSERT .. SELECT ステートメントを利用して、1,000 万
件分のデータを一括コピーしています。
1,000万件のデータが
格納されているテーブル
1,000万件のデータを
INSERT .. SELECT でコピー
BUCKET_COUNT の違いによる INSERT .. SELECT の性能差
上の INSERT .. SELECT で 1,000 万件のデータを一括コピーしたときの性能差は、次のように
なりました。
200
SQL Server 2014 実践 No.1 インメモリ OLTP
9.2倍
遅い
2.1倍
遅い
1.1倍
遅い
BUCKET_COUNT が 400万のときの結果
を 100 とした場合の相対値で表示
テスト PC の構成
・Core i7 3770K 3.5GHz 4コア
・メモリ 32GB、HDD Western Digital WD30EZRX
BUCKET_COUNT が 100 万のときは 1.1 倍、10 万のときは 2.1 倍、1 万のときは 9.2 倍も
遅くなっていることを確認できました。
今回の col2 列は、次のように一意な値が約 200 万件あります。
約 200万件
また、特定の値で検索すると、約 5 件の結果が返ります(約 200 万件 * 約 5 件=1,000 万件)
。
col2 で検索
約 5件の結果
BUCKET_COUNT の違いによる SELECT の性能差
BUCKET_COUNT が異なる場合の SELECT ステートメント(col2 列での検索)の性能差は、
次のようになりました。
201
SQL Server 2014 実践 No.1 インメモリ OLTP
1.66倍
遅い
1.01倍
遅い
1.1倍
遅い
100万回の実行を 3回行って、
その平均値を取得。
BUCKET_COUNT が 400万のとき
の結果を 100 とした場合の相対値
BUCKET_COUNT が 100 万のときは 1.01 倍(ほぼ同じ)
、10 万のときは 1.1 倍、1 万のと
きは 1.66 倍も遅くなっていることを確認できました。
このように、BUCKET_COUNT の設定は、INSERT や SELECT へ影響があることを確認でき
ました。特に BUCKET_COUNT を小さい値に設定した場合(col2 の例では 1万件)は、性能
が大きく低下するので注意が必要です。
BUCKET_COUNT の設定基準
BUCKET_COUNT の設定基準は、オンライン ブックの以下のトピックに記載されています。
ハッシュ インデックスの適切なバケット数の決定
http://msdn.microsoft.com/ja-jp/library/dn494956.aspx
このトピックでは、
「ほとんどの場合、バケット数はインデックス キーにおける別個の値の数の 1
倍から 2 倍の範囲に設定する必要があります」と記載されています。別個の数は、前述の
202
SQL Server 2014 実践 No.1 インメモリ OLTP
DISTINCT で検索した一意な値の数のことで、col2 では約 200 万件でした(以下に再掲)。
約 200万件
したがって、オンライン ブックの記述によれば、col2 列では、200 万(1 倍)~400 万(2 倍)
ぐらいが妥当な設定値であるということになります。実際、400 万に設定したときと、100 万に
設定したときでは、性能差が現れたので(100 万に設定したほうが若干遅くなったので)
、col2 に
関しては、400 万程度が妥当な設定値であることが分かります。また、BUCKET_COUNT は、
テーブルの作成時(CREATE TABLE 時)に設定して、後から変更することができないものなので、
将来増えるであろうデータ量も想定して、多めに設定しておく必要があります(その分、メモリを
消費することになりますが、どれぐらいのメモリを消費するのかについては後述します)
。
また、このトピックでは、
「バケット数は内部的に、最も近い 2 のべき乗に切り上げられます。た
とえば、バケット数に 300,000 を指定すると、実際のバケット数は 524,288 になります。」
と記載されていて、BUCKET_COUNT で設定した値に、最も近い 2 のべき乗に設定される(切
り上げられる)とあります。
したがって、代表的な 2 のべき乗を知っておいた方が設定がしやすくなるので、次の表が参考に
なると思います。
代表的な 2のべき乗
2 のべき乗
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
バケット数
131,072
262,144
524,288
1,048,576
2,097,152
4,194,304
8,388,608
16,777,216
33,554,432
67,108,864
134,217,728
268,435,456
536,870,912
1,073,741,824
2,147,483,648
4,294,967,296
備考
約 13万
約 26万
約 50万
約 100万
約 210万
約 420万
約 840万
約 1,680万
約 3,360万
約 6,700万
約 1.3億
約 2.7億
約 5.4億
約 10.7億
約 21.5億
約 43億
BUCKET_COUNT
を 100万に設定すると
この値になる
400万に設定すると
この値になる
1,000万に設定すると
この値になる
1億に設定すると
1.3億の値になる
203
SQL Server 2014 実践 No.1 インメモリ OLTP
空きバケット数、ハッシュ インデックスのチェーンの長さ
HASH インデックスでは、十分な BUCKET_COUNT を設定している場合(一意な数よりも大き
い値へ設定している場合)には、同じ値が、同じハッシュ値になって、チェーンが構成されます。
ハッシュ インデックスのチェーン
(111)
(123)
(222)
(123)
(333)
(123)
ハッシュ値 1
ハッシュ値 3
ハッシュ値 5
ハッシュ値 3
ハッシュ値 6
ハッシュ値 3
0
1
2
3
4
5
6
:
●
●
●
チェーン
十分な BUCKET_COUNT(バケット数)がある場合は、
同じ値が、同じハッシュ値になってチェーンが構成される
これに対して、BUCKET_COUNT の設定が小さい場合には、異なる値でも、同じハッシュ値にな
ってしまうことがあるので、次のようにチェーンが長くなってしまいます。これは、ハッシュ コ
リジョン(衝突)と呼ばれています。
ハッシュ インデックスのチェーン(コリジョンによってチェーンが長くなる)
(111)
(123)
(222)
(123)
ハッシュ値 1
ハッシュ値 3
ハッシュ値 3
ハッシュ値 3
ハッシュ値 3
(333)
(123)
ハッシュ値 3
0
1
2
3
4
BUCKET_COUNT
が小さい場合
●
●
●
●
●
ハッシュ コリジョン(衝突)によって
チェーンが長くなってしまう
BUCKET_COUNT(バケット数)が小さい場合には、
異なる値でも、同じハッシュ値になることがあり(これを
ハッシュ コリジョン:衝突と呼ぶ)、チェーンが伸びる
現在のバケット数や、空いているバケット数、チェーンの長さなどは、次のように
「dm_db_xtp_hash_index_stats」動的管理ビューを利用すると確認することができます。
SELECT
-- object_name(hs.object_id) AS 'object name',
i.name as 'index name',
hs.total_bucket_count,
hs.empty_bucket_count,
floor((cast(empty_bucket_count as float)/total_bucket_count) * 100) AS
'empty_bucket_percent',
hs.avg_chain_length,
hs.max_chain_length
FROM sys.dm_db_xtp_hash_index_stats AS hs
JOIN sys.indexes AS i
ON hs.object_id=i.object_id AND hs.index_id=i.index_id
204
SQL Server 2014 実践 No.1 インメモリ OLTP
チェーンが長くなっている
ハッシュ コリジョン
(衝突)の発生
空のバケットが 0
BUCKET_COUNT を
1万に設定したもの
10万
100万
400万
PK は
100,000,000
(1億)に設定
PK のチェーンの
長さは 1(平均)
上記クエリは、オンライン ブックの「ハッシュ インデックスの適切なバケット数の決定」より引用
http://msdn.microsoft.com/ja-jp/library/dn494956.aspx
この結果のうち、一番上のものが BUCKET_COUNT を 1 万に設定したときのもので、現在のバ
ケット数(total_bucket_count)が 16,384、空きバケット数(empty_bucket_count)が
0、avg_chain_length(平均のチェーンの長さ)が 610 にもなっていて、ハッシュ コリジョ
ンが多発していることが分かります。
これに対して、上から 4 つ目の結果が BUCKET_COUNT を 400 万に設定したときのもので、
現在のバケット数が 4,194,304、空きバケット数が 2,590,043(259 万空いている)
、平均の
チェーンの長さが 6 になっていて、妥当なチェーンの長さになっていることが分かります(col2
は、約 5 件の結果が返るので、チェーンの長さは 5 前後が妥当になります)
。
このように、設定した BUCKET_COUNT が妥当かどうかは、このクエリを実行することで確認
することができるので、確認しておくことをお勧めします。
バケット数の違いによるメモリ使用量の差
バケット数の違いによるメモリ使用量の差は、dm_db_xtp_table_memory_stats 動的管理
ビューを利用して確認することができます。
SELECT OBJECT_NAME(object_id) AS テーブル名, *
FROM sys.dm_db_xtp_table_memory_stats
1,000万件分の
データの使用メモリ
205
PK と col2 の
HASH インデックス
の使用メモリ
SQL Server 2014 実践 No.1 インメモリ OLTP
memory_allocated_for_indexes_kb が、インデックスに割り当てられたメモリ量で、この
値は、バケット数をもとに決定されます。インメモリ OLTP では、1 つのバケット数に 8 バイト
を消費するので、次の表のようになります。
バケット数の違いによるメモリ使用量の差
2 のべき乗
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
バケット数
131,072
262,144
524,288
1,048,576
2,097,152
4,194,304
8,388,608
16,777,216
33,554,432
67,108,864
134,217,728
268,435,456
536,870,912
1,073,741,824
2,147,483,648
4,294,967,296
備考
約 13万
約 26万
約 50万
約 100万
約 210万
約 420万
約 840万
約 1,680万
約 3,360万
約 6,700万
約 1.3億
約 2.7億
約 5.4億
約 10.7億
約 21.5億
約 43億
使用メモリ (KB)
1バケット 8 バイト
1,024
2,048
4,096
8,192
16,384
32,768
65,536
131,072
262,144
524,288
1,048,576
2,097,152
4,194,304
8,388,608
16,777,216
33,554,432
BUCKET_COUNT
を 100万に設定すると
8MB を消費
400万に設定すると
32MB を消費
1,000万に設定すると
130MB を消費
1億に設定すると
1GB を消費
今回のテーブルは、PK(col1)の BUCKET_COUNT を 1 億に設定しているので、1,048,576
KB(1GB)にプラスして、col2 の BUCKET_COUNT が 100 万なら +8,192(8MB)で、
1,056,768 KB、400 万なら +32,768(8MB)で、1,081,344 KB を消費することになりま
す。
なお、メモリ使用量の見積もり方法については、オンライン ブックの以下のトピックも参考にな
ります。
メモリ最適化テーブルのメモリ必要量の推定
http://msdn.microsoft.com/ja-jp/library/dn282389.aspx
メモリ最適化テーブルのテーブルと行のサイズ
http://msdn.microsoft.com/ja-jp/library/dn205318.aspx
206
SQL Server 2014 実践 No.1 インメモリ OLTP
4.10 性能検証をする上での注意点
インメモリ OLTP では、性能検証をする上での注意点が多くあるので、ここでまとめておきます。
また、性能検証結果(ベンチマーク結果)を第 3 者へ公開することは、SQL Server 2014 の使用
許諾契約書違反になるので、検証結果をブログやツイッター、Facebook などの SNS、雑誌記事、
技術カンファレンス/セミナーで公開することも規約違反になります。今回のドキュメントでは、
一部実測値を掲載していますが、これは許可をいただいています。
性能検証の注意点
性能検証をする上では、次の点に注意する必要があります。

SQL Server では、インメモリ OLTP だけでなく、従来ながらのディスク ベースでも、
単一ステートメントの実行速度はマイクロ秒レベルになる。
測定ツールや測定コマンドは、ミリ秒単位での測定しかできないものが多い(例え
ば、.NET の Stopwatch クラスや SET STATISTICS TIME コマンドでは、ミリ秒
単位での測定しかできず、マイクロ秒での計測ができない。詳しくは後述)

.NET アプリケーションで計測する場合は、デバッグ実行ではなく、Release ビルド
(.exe)で実行しないと本当の性能差を確認できない(詳しくは後述)

SELECT ステートメントの検証では、結果件数が 0 件になるような検索では、正しい性
能検証にはならない(乱数を利用する場合には、結果件数が 1 件以上になるように考慮
する必要がある)

Management Studio のクエリ エディターで SELECT ステートメントの検証を行う
場合には、結果件数が多い場合に、描画の負荷がかかることや、結果をファイルへ書き込
む負荷があることに注意する(描画やファイル書き込みがボトルネックになって、本来の
性能差を確認することができない)

Transact-SQL ステートメントで WHILE ループを利用する場合は、SET NOCOUNT
ON を付けて、件数カウントを無効にすることで、件数カウントのオーバーヘッドを軽
減しておく。また、SET STATISTICS TIME を ON していると、速度計測および計測
結果を描画するオーバーヘッドが発生するので、利用しないようにする(詳しくは後述)

SQL の直接実行(Ad hoc)と sp_executesql(パラメーター化)では性能差が異なる
ので注意する(詳しくは後述)

ostress ツールでは負荷のかけ方が弱いことがある(詳しくは後述)

市販/オープン ソースの負荷テスト ツールには、負荷のかけ方が弱いものがある(負荷
のかけ方が弱いと、本当の性能差を確認することができない)

BUCKET_COUNT(HASH インデックスのバケット数)を適切な値に設定しておかない
と、本来の性能が出ないことに注意する(前項を参照)

ネイティブ コンパイル SP は、SQL Server サービスの再起動(データベースの復旧)
を行った場合に、最初の実行時(ストアド プロシージャの呼出し時)に DLL の再作成
207
SQL Server 2014 実践 No.1 インメモリ OLTP
(再コンパイル)が行われる(=初回実行が遅くなる)
。なお、作成時(CREATE PROC
時)にも DLL は作成される。
詳しくは、オンライン ブックの「メモリ最適化テーブルのクエリ処理のガイド」を参照
http://msdn.microsoft.com/ja-jp/library/dn205319.aspx

インデックスを付与していない列を GROUP BY や WHERE 句の検索条件に指定する
と、最初の実行時に統計が自動作成される(=初回実行が遅くなる)
。
統計については、オンライン ブックの「メモリ最適化テーブルの統計」も参照
http://msdn.microsoft.com/ja-jp/library/dn232522.aspx

SQL Server への接続の Open/Close をしているかどうか、接続プール(Connection
Pool)を利用しているかどうかで性能が変わってくる

アプリケーションからの実行方法の違いによって、性能差が現れる(文字列連結での実行
だと遅くなる/SqlParameter を利用しないと遅くなるなど。第 2 章を参照)

データ型の違いによって、性能差が現れる(暗黙の型変換が発生すると、桁違いに遅くな
るなど。第 2 章を参照)

ラッチ待ちやロック待ちは、多重度を上げたテストでないと確認することができない

ファイルの自動拡張が発生すると、性能が大きく低下するので、事前にサイズを大きくし
ておくようにする
(特に、
トランザクション ログ ファイルの自動拡張が発生した場合は、
その間のトランザクションが完全にブロックされてしまうので、
自動拡張が発生しないよ
うに、事前に大きくしておくことが重要)

瞬時初期化を有効にしておかないと、データ ファイルへの書き込みが遅くなるので、有
効化しておくことが重要(第 3 章を参照)

データ ファイルとログ ファイルを異なる RAID セットに配置するかどうかで性能が
変わってくる(特に、チェックポイント処理で性能差が現れる)

HDD と SSD では、性能結果が変わってくる(特に、チェックポイント処理で性能差
が現れる。∵ディスク ベースでのチェックポイント処理はランダム書き込みのため)

新しいデータベース(RESTORE DATABASE ではなく、CREATE DATABASE で作成
した新規 DB、復旧モデルが完全)で検証する場合は、一度もバックアップを行っていな
い場合には、ログの切り捨て(チェックポイント時のログ切り捨て)が行われるので、実
際のアプリケーションおよびログの肥大化を正確にシミュレートするには、一度フル バ
ックアップを取得しておく必要がある

数年前のノート PC を利用している場合には、Intel SpeedStep テクノロジーが有効
になっていると、
正しい実行時間を計測できない場合があるので、正確な計測を行うには、
この機能を無効化しておく必要がある
このように、インメモリ OLTP を検証する上では、多くの注意点があります。インメモリ OLTP や
ディスク ベースにおける SQL ステートメントの実行は、本来の性能とは関係ないものがボトル
ネックになっている場合があり、それに足を引っ張られて、性能差を確認できないことがあります。
特に SQL Server をインストールしただけのデフォルト構成や、何も指定せずにデータベースを
作成した場合にはこの状態になります。例えば、ログ ファイルの自動拡張が発生してしまえば、
208
SQL Server 2014 実践 No.1 インメモリ OLTP
それがボトルネックになって、インメモリ OLTP でもディスク ベースでも同じような性能結果に
なってしまいます。また、.NET アプリケーションでデバッグ実行を利用していると、デバッグ実
行のオーバーヘッドがボトルネックになり、本当の性能差を確認することができません。
人間の目で見た感じでは同じような実行速度に見えても(例えば、目視だと 100 ミリ秒も 10 マ
イクロ秒も同じように見えます)、インメモリ OLTP とディスク ベースの性能差は、マイクロ秒
レベルでの差になるので、非常に大きな性能差になっているという場合があります。例えば、100
ミリ秒と 10 マイクロ秒では 1 万倍もの差があるわけです。この性能差は、同時実行数が増えれ
ば増えるほど、大きな差になるので、クリティカルなアプリケーションであれば見逃すことができ
なくなります。
マイクロ秒の測定ができないツールやコマンドに注意
SQL Server では、OLTP 系の小さいトランザクションの単一ステートメントは、マイクロ秒レベ
ル(ミリ秒以下)で実行が完了します。測定ツールや測定コマンドによっては、ミリ秒単位での測
定しかできないものが多いので注意が必要です。
例えば、SET STATISTICS TIME コマンドではマイクロ秒の計測ができないので、マイクロ秒で
完了したステートメントは、次のように 0 ミリ秒と表示されます。
SQL Server では従来ながらの
ディスク ベースの SQL でも
マイクロ秒レベルで結果が返る
実行速度がマイクロ秒だと
0ミリ秒と表示される
また、.NET の Stopwatch クラスも、マイクロ秒の計測ができません。
インメモリ OLTP ではなく
ディスク ベースの SQL
Stopwatch クラス
実行速度がマイクロ秒だと
0ミリ秒と表示される
ミリ秒単位でしか
計測できない
209
SQL Server 2014 実践 No.1 インメモリ OLTP
マイクロ秒で完了したステートメントは、0 ミリ秒と表示されます。
このように、単体のステートメントの性能を測定する場合には、マイクロ秒での計測ができるもの
を利用する必要があることに注意してください。
SQL Server の標準のツールとしては、SQL Server Profiler ツールを利用することで、マイク
ロ秒の測定をすることが可能です(既定ではミリ秒単位)。SQL Server Profiler ツールでは、次
のように[ツール]メニューの[オプション]から「実行時間列の値をマイクロ秒で表示する」を
チェックすることで、マイクロ秒の測定ができるようになります。
なお、数年前のノート PC などで検証する場合には、Intel SpeedStep テクノロジーが有効にな
っていると、SQL Server Profiler を利用しても、正しい実行時間を計測できない場合があるので、
正確な計測を行うには、この機能を無効にしておく必要があります。
.NET アプリはデバッグ実行ではなく Release ビルドにすること
.NET アプリケーションで、性能を測定する場合は、デバッグ実行ではなく、Release ビルド
(.exe)で実行しないと、本当の性能差を確認できないことに注意する必要があります。例えば、
次のように、[デバッグ]メニューから[デバッグ開始]をクリックすると、マイクロ秒で完了す
る処理も、ミリ秒以上かかってしまいます。
Release ビルドであれば
0ミリ秒(マイクロ秒)で
完了するものも、デバッグ実
行だとミリ秒かかってしまう
Release ビルド(.exe)を作成するには、次のように[ビルド]メニューから[構成マネージャ
210
SQL Server 2014 実践 No.1 インメモリ OLTP
ー]をクリックして、
[アクティブ ソリューション構成]を「Release」へ変更します。
Transact-SQL の WHILE ループでの注意点
Transact-SQL ステートメントで WHILE ループを利用して、性能を計測する場合は、既定では
1 つの SQL ステートメントの実行ごとに「~件処理されました」メッセージが表示されるので、
これによって実行速度が遅くなります。
SET NOCOUNT ON を付ければ
メッセージが表示されなくなる
1件ごとに
「~件処理されました」
メッセージが表示される
ことで実行速度が遅くなる
これを回避するには、SET NOCOUNT ON を実行して、件数カウントを無効にするようにします。
また、SET STATISTICS TIME を ON している場合も、1 つの SQL ステートメントごとに、
速度計測および計測結果を描画するオーバーヘッドが発生するので、WHILE ループを利用してい
る場合は、SET STATISTICS TIME は利用しないようにしましょう。
1つの SQL ステートメントごとに
速度計測および実行時間を描画する
ことで実行速度が遅くなる
211
SQL Server 2014 実践 No.1 インメモリ OLTP
SQL の直接実行と sp_executesql では性能差が異なることに注意する
SQL の直接実行(Ad hoc)と sp_executesql(SQL のパラメーター化実行)では、Ad hoc の
ほうが良い性能になるので、注意が必要です。これは次のような状況です。
SQL の直接実行
sp_executesql でパラメーター化
INSERT を
パラメーター化
sp_executesql
のほうが遅い
INSERT を直接実行
このように、sp_executesql を利用した場合は、直接実行した場合よりも遅くなります。しか
し、.NET や Java アプリケーションから SQL を実行する場合には、内部的に sp_executesql
に変換されて実行されているので(第 2 章を参照)、アプリケーションの性能検証が目的である場
合には、sp_executesql を利用するのが正確な結果になります。
.NET アプリケーションから SQL を実行
(VB.NET、ADO.NET の場合)
SqlParameter オブジェクトを利用
することでパラメーター化される
アプリケーションから実行される SQL は
sp_executesql に変換される
ostress ツールによる負荷テスト実行時の注意点
ostress ツールは、RML Utilities に含まれる負荷テスト ツールで、次の URL からダウンロー
ドすることができます。
212
SQL Server 2014 実践 No.1 インメモリ OLTP
Cumulative Update 2 to the RML Utilities for Microsoft SQL Server Released
http://blogs.msdn.com/b/psssql/archive/2013/10/29/cumulative-update-2-to-the-rml-u
tilities-for-microsoft-sql-server-released.aspx
64-bit 版のダウンロードは
ココをクリック
ostress ツールは、次のように実行することができます(ナンバリングの処理のところでも少し
紹介しました)
。
ostress.exe -n多重度 -r繰返し回数 -Sサーバー名 -E -dデータベース名 -q -Q"クエリ"
-n で多重度、-r で繰り返し実行する回数、-S で接続先となる SQL Server、-E で Windows
認証、-d でデータベース、-q で quiet モード、-Q へ負荷テストをしたいクエリを記述します。
例えば、次のように実行できます(RML Cmd Prompt から実行)
。
…
実行完了後に
実行時間が表示される
213
SQL Server 2014 実践 No.1 インメモリ OLTP
ostress.exe -n400 -r125 -S. -E -dHekatonTestDB_HDD2 -q -Q"INSERT INTO OnDisk DEFAULT
VALUES"
これは、400 多重で、それぞれ 125 回繰り返し実行、ローカル SQL Server へ接続(. と指定)
、
HekatonTestDB_HDD2 データベースへ接続、OnDisk テーブルへ INSERT を実行するもの
です。これにより、400*125=50,000 回の INSERT が OnDisk テーブルに対して実行され
ます(以下)。
50,000件の
データが格納されている
このように、ostress ツールを利用すると、簡単に多重実行を試すことができるのですが、次の
注意点があります。

SQL の直接実行と sp_executesql(パラメーター化)では、実行速度が異なる

日本語のオブジェクト名は [ ] で囲む、日本語の文字列には N を付ける必要がある

多重度が低い場合(1~100 ぐらいまで)は、負荷のかけ方が弱いことがある(実行する
クエリが軽い場合には、リソースを持て余してしまう)

クエリの実行ごとに接続の Open と Close を行わない(-r の多重度に応じて、最初
に接続を作り、それをキープして、-n の回数分それぞれの接続からクエリを実行する)
SQL の直接実行と sp_executesql の違いは、次のとおりです。
クエリを直接実行
クエリを sp_executesql
で実行する
214
SQL Server 2014 実践 No.1 インメモリ OLTP
-Q へ指定するクエリに sp_executesql を利用するかどうかで、実行結果が変わってくる(SQL
の直接実行のほうが性能が良い)ので、アプリケーションをシミュレーションする場合には、
sp_executesql を利用するようにしないと正確な性能測定ができません。
ostress ツールでは、日本語のオブジェクト名は [ ] で囲み、日本語の文字列には N プレフィ
ックスを付けて実行する必要があります。これを行わない場合は、次のようにエラーになります。
日本語のテーブル名を [ ] で囲ん
でいない場合や、文字リテラルに
N を付けていない場合
…
日本語が解釈できない
場合のエラーは ’?’ と
表示される
ostress ツールの負荷のかけ方が弱いことについては、第 1 章で説明した 5,000 万件の Insert
を例を説明します。これと同じ Insert を ostress ツールで実行すると次のようになります。
* ベンチマークの結果の公表は、使用許諾契約書で
禁止されていますが、その効果をわかりやすく表現
するため、日本マイクロソフト株式会社の監修のも
と、数値を掲載しております。
テスト PC の構成(約15万円の普通のパソコン)
・Core i7 3770K 3.5GHz 4コア
・メモリ 32GB、HDD Western Digital WD30EZRX
このテストでは、多重度 40~100 ぐらいまでは、負荷のかけ方が弱いことが分かります(200 で
は、NonDurable への負荷のかけ方が弱い)
。
弊社の負荷テスト ツール(接続キープ版)での結果と比較すると、次のようになります。
215
SQL Server 2014 実践 No.1 インメモリ OLTP
40多重での
負荷のかけ方
が弱い
200多重では
一部の結果は
近い結果になる
400多重では
近い結果になる
多重度
弊社の負荷テスト ツールのほうが、すべてのパターンで処理時間が速いことを確認できると思い
ます。400 多重の結果は、近い実行時間になっているので、単純なクエリ(このテストは 10 列の
テーブルに対する乱数を利用した INSERT。詳細は第 1 章を参照)では、多重度を上げたほうが
良い結果となります。
いずれにしても、負荷テストに関しては、各システム/実際のアプリケーションで利用している状
況をシミュレートできるものを利用することが一番です。市販/オープン ソースの負荷テスト ツ
ールでは、負荷のかけ方が弱いものが多くあるので、いかに実際の利用状況をシミュレートできる
かどうかが重要になります。
216
SQL Server 2014 実践 No.1 インメモリ OLTP
4.11 メモリ最適化テーブルを作成するときの制限事項
ここでは、メモリ最適化テーブルを作成するときの制限事項をまとめます(これまでに説明してき
たもののまとめになります)
。

メモリ最適化テーブルでは、後からテーブル定義を変更することができない
(ALTER TABLE ステートメントがサポートされていない)

メモリ最適化テーブルでは、後からインデックスを追加することができない(CREATE
INDEX ステートメントがサポートされていないので、CREATE TABLE でテーブルを
作成するときに、インデックスを一緒に作成する必要がある)

メモリ最適化テーブルでは、1 つ以上の非クラスター化(NONCLUSTERED)のインデ
ックス(HASH または bw-tree)を作成するのが必須になる。
SCHEMA_AND_DATA の場合は、PRIMARY KEY 制約が必須になり、PRIMARY
KEY 制約にはインデックスが必須になる。
SCHEMA_ONLY の場合は、インデックスが必須になる(PK は必須ではない)

メモリ最適化テーブルでは、非クラスター化(NONCLUSTERED)のインデックス
(HASH または bw-tree)のみがサポートされる(1 つのテーブルに最大 8 個まで)

インデックスを作成する列には、NOT NULL が必須になる

HASH インデックスを利用する場合は、適切な BUCKET_COUNT(バケット数)を設
定しておくことが重要(後からバケット数を変更することができない)

イ ン デ ッ ク ス を 作 成 す る 列 が char 系 の デ ー タ 型 の 場 合 に は 、 BIN2 照 合 順 序
(Japanese_BIN2 や Latin1_General_BIN2 など)が必須になる

char/varchar データ型は、1252 コードページの照合順序のみがサポートされる。
他の照合順序を利用するには nchar/nvarchar データ型が必要(n 付きのデータ型)

データベースの照合順序に Japanese_CI_AS(日本語版 SQL Server の場合の既定値)
を利用している場合は、char/varchar を nchar/nvarchar データ型へ変換するの
がお勧め(∵より良い性能を出すため。詳しくは第 2 章を参照)

IDENTITY は、(1, 1) のみがサポートされる(SEQUENCE を利用することも可能)

行サイズは、8060 バイトが上限となり、varchar(max) や varbinary(max)、image、
text などの LOB(ラージ オブジェクト)がサポートされない(LOB への対応につい
ては第 1 章の bwin 社の事例を参照)

利用できないデータ型には、xml、sql_variant、datetimeoffset、hierarchyid、
geography、geometry、rowversion、UDT(ユーザー定義データ型)がある

CHECK 制約や FOREIGN KEY 制約がサポートされない
また、インメモリ OLTP を利用するための要件に関しては、オンライン ブックの以下のトピック
も一読しておくことをお勧めします。
217
SQL Server 2014 実践 No.1 インメモリ OLTP
メモリ最適化テーブルを使用するための要件
http://msdn.microsoft.com/ja-jp/library/dn170449.aspx
既存のテーブル(ディスク ベースのテーブル)を、メモリ最適化テーブルへ移行するにあたって
は、次項で説明する「メモリ最適化アドバイザー」機能が役立ちます。この機能を利用すれば、既
存のテーブルがメモリ最適化テーブルに変換(移行)できるかどうかをチェックしてくれたり、変
換ができる場合には実際の変換を行ったりすることができます。
218
SQL Server 2014 実践 No.1 インメモリ OLTP
4.12 メモリ最適化アドバイザーによる移行チェック/変換
メモリ最適化アドバイザーは、既存のテーブルがメモリ最適化テーブルに変換できるかどうかをチ
ェックしてくれる、大変便利な機能です。また、メモリ最適化テーブルへ変換できる場合には、実
際の変換を行うこともできます(char と nchar の自動変換も行えます)
。
メモリ最適化アドバイザーを起動するには、次のようにオブジェクト エクスプローラーで既存の
テーブル(ディスク ベースのテーブル)を右クリックして、
「メモリ最適化アドバイザー」をクリ
ックします。
次の[メモリ最適化チェックリスト]ページでは、該当テーブルが、メモリ最適化テーブルへ移行
できるかどうかがチェックされます。
方法を表示をクリックする
とオンライン ブックが表示
される
移行に問題がある場合は
赤い × で表示される
219
SQL Server 2014 実践 No.1 インメモリ OLTP
移行に問題がある場合は、赤い×マークが表示されて、どう変更すれば良いのかが提示されます。
この画面では、「インデックス idx_c3 の NULL 値が許可されるインデックス列 c3」と表示さ
れていて、既存のインデックスに NULL 値が許可されているので、移行ができないという主旨に
なっています。インメモリ OLTP では、インデックスを付与する列には NOT NULL が必須にな
るので、事前に変更しておくことが必要であると提示されています。
なお、既存の列を NOT NULL へ変更するには、次のように ALTER TABLE ステートメントで
ALTER COLUMN を利用して行うことができます。
-- 列を変更するには、いったんインデックスを削除
DROP INDEX ind_c3 ON table1
-- 該当列を NOT NULL へ変更
ALTER TABLE table1
ALTER COLUMN c3 int NOT NULL
-- もう一度インデックスを作成
CREATE NONCLUSTERED INDEX ind_c3
ON table1(c3)
メモリ最適化アドバイザーでは、移行に問題がない場合は、次のようにすべてが緑のチェックマー
クで表示されます。
移行に問題がない場合は
すべてが緑のチェックマーク
で表示される
次の[メモリ最適化警告]ページでは、メモリ最適化テーブルを移行するにあたっての警告がある
場合は、それが表示されます。
220
SQL Server 2014 実践 No.1 インメモリ OLTP
画面の黄色の警告マークでは、「c2」列のデータ型が varchar から nvarchar へ自動変換され
る主旨の内容が表示されています。メモリ最適化テーブルでは、varchar データ型は 1252 コー
ドページの照合順序でのみサポートされるので、Japanese_CI_AS を利用している場合には、
nvarchar へ自動的に変換されます。
次の[最適化オプションの確認]ページでは。元のテーブルの名前をどうするか(既定では _old
が付く)や、データのコピーをするかどうか、メモリ最適化テーブルの DURABILITY(永続化)
をどうするかなどを設定できます。
元のテーブルの名前を
どうするか。既定は _old
データをコピーする場合は
これをチェックする
Durability の設定。
チェックをオフにしていると
SCHEMA_AND_DATA、
オンにすると SCHEMA_ONLY
次の[主キー変換の確認]ページでは。PRIMARY KEY をどのように変換するか(HASH イン
221
SQL Server 2014 実践 No.1 インメモリ OLTP
デックスにするのか、bw-tree インデックスにするのかなど)を設定できます。
インデックスの名前
HASH インデックスにする
場合はこちらをチェック
BUCKET_COUNT の設定
bw-tree インデックスにす
る場合はこちらをチェック
次の[インデックス変換の確認]ページでは。PRIMARY KEY のほかにインデックスがある場合
に、それをどのように変換するか(HASH インデックスにするのか、bw-tree インデックスにす
るのかなど)を設定できます。
PK のほかに
インデックスがある場合は、
それを HASH にするか
bw-tree にするかを決定する
HASH インデックスにする
場合はこちらをチェック
BUCKET_COUNT の設定
bw-tree インデックスにす
る場合はこちらをチェック
次の[移行アクションの検証]ページでは、設定内容を確認して、[スクリプト]ボタンをクリッ
クすると、移行のためのスクリプトを生成することができます。
222
SQL Server 2014 実践 No.1 インメモリ OLTP
[スクリプト]をクリック
すると移行のためのスクリ
プトを生成できる
[移行]をクリックした場合は、
すぐに移行を実行できる
生成されたスクリプトは、次のようになっています。
メモリ最適化テーブル用の
ファイル グループの作成
sp_rename で
移行元のテーブルの名前
を _old へ変更
メモリ最適化テーブル
の作成
Japanese 照合順序の場合、
varchar 列は、自動的に
nvarchar に変換される
c3 列への HASH インデックス
c1 列は、PRIMARY KEY
で HASH インデックス
DURABILITY(永続化)
の設定
INSERT .. SELECT で
既存のデータをコピー
あとは、このスクリプトを実行すれば、移行が完了です(あるいはウィザードの[移行]ボタンを
クリックしても移行を行えます)。
このように、メモリ最適化アドバイザーを利用すれば、既存のテーブルがメモリ最適化テーブルに
移行できるかどうかをチェックしたり、実際の移行のためのスクリプトを生成したりすることがで
きるので、大変便利です。
223
SQL Server 2014 実践 No.1 インメモリ OLTP
4.13 メモリ最適化テーブルの機能的な制限事項
インメモリ OLTP は、1~3 章で説明したような典型的な OLTP システムであれば(単純な
SELECT/INSERT/UPDATE/DELETE の実行のみのシステムであれば)
、多くの場合は何の問
題もなく利用することができます。
もちろん、利用にあたっては、いくつか機能的な制限事項もあるので、ここではそれをまとめます。

TRUNCATE TABLE ステートメントがサポートされない

PRIMARY KEY 列に対する更新がサポートされない

SELECT INTO ステートメントでのターゲット テーブルには指定することができない。
代替案としては、メモリ最適化テーブル変数を利用したデータのコピーがある(後述のオ
ンライン ブックのトピック参照)

MERGE ステートメントでのターゲット テーブルには指定することができない。
MERGE は IF で代替可能(後述のオンライン ブックのトピック参照)

データベースをまたがったクエリ/トランザクションがサポートされない。
「DB 名.スキーマ名.オブジェクト名」で指定したアクセスがサポートされない

CLR アクセスがサポートされない

リンク サーバーがサポートされない

READ COMMITTED 分離レベル(SQL Server の既定の分離レベル)は、自動コミッ
ト トランザクションのみでサポートされる

ト ラ ン ザ ク シ ョ ン 内 で サ ポ ー ト さ れ る 分 離 レ ベ ル は 、 SNAPSHOT ま た は
REPEATABLE READ、SERIALIZABLE のみで、WITH 句でのテーブル ヒントとし
て指定する必要がある。または ELEVATE_TO_SNAPSHOT 機能を有効にする(設定
方法は 4.1 を参照)。ネイティブ コンパイル SP を利用している場合は、BEGIN
ATOMIC 句の ISOLATION LEVEL で分離レベルを指定する

ロックヒント(NOLOCK や UPDLOCK、ROWLOCK、NOWAIT など)がサポートさ
れない(4.2 を参照)
メモリ最適化テーブルの制限事項については、オンライン ブックの以下のトピックに詳しく記載
されているので、こちらも一読しておくことをお勧めします。
解釈された Transact-SQL を使用したメモリ最適化テーブルへのアクセス
http://msdn.microsoft.com/ja-jp/library/dn133177.aspx
インメモリ OLTP でサポートされていない Transact-SQL の構造
http://msdn.microsoft.com/ja-jp/library/dn246937.aspx
224
SQL Server 2014 実践 No.1 インメモリ OLTP
制限事項の回避策
オンライン ブックの以下のトピックでは、未サポートの機能(LOB や データベースをまたがっ
たクエリなど)の代替案や、どうやって移行するかなどが記載されているので、参考になります。
インメモリ OLTP への移行
http://msdn.microsoft.com/ja-jp/library/dn247639.aspx
複数データベースにまたがるクエリ
http://msdn.microsoft.com/ja-jp/library/dn584627.aspx
メモリ最適化テーブル変数
http://msdn.microsoft.com/ja-jp/library/dn535766.aspx
MERGE 機能の実装
http://msdn.microsoft.com/ja-jp/library/dn579376.aspx
メモリ最適化テーブルへの LOB 列の実装
http://msdn.microsoft.com/ja-jp/library/dn296676.aspx
CHECK 制約と FOREIGN KEY 制約の移行
http://msdn.microsoft.com/ja-jp/library/dn626021.aspx
トリガーの移行
http://msdn.microsoft.com/ja-jp/library/dn582041.aspx
計算列の移行
http://msdn.microsoft.com/ja-jp/library/dn387050.aspx
225
SQL Server 2014 実践 No.1 インメモリ OLTP
4.14 ネイティブ コンパイル SP の制限事項
ネイティブ コンパイル SP は、大きな性能向上を実現できる反面、その分多くの制限事項があり
ます。その主なものは、次のとおりです。

SCHEMABINDING と EXECUTE AS OWNER が必須

SCHEMABINDING では、オブジェクトの指定にスキーマ名が必須(dbo. などを付ける)

メモリ最適化テーブルにしかアクセスできない

SELECT * がサポートされないため、列名の指定が必須

比較や並べ替えは、BIN2 照合順序でのみサポートされる

WHERE 句で OR、IN、NOT がサポートされない。
OR/IN は、メモリ最適化テーブル変数で代替できることもある(第 2 章を参照)

LIKE や CASE 式がサポートされない

LEFT/RIGHT OUTER JOIN やサブクエリ、カーソルがサポートされない。
INNER JOIN はサポートされる

SEQUENCE がサポートされない(NEXT VALUE FOR が利用できない)

利用できる関数が制限される(後述)

MERGE がサポートされない(IF で代替可能)

OFFSET .. FETCH がサポートされない

データベースの照合順序に、Japanese_CI_AS などのコードページが 1252 ではな
いものを利用している場合は、文字列に N プレフィックスを付ける必要がある(N プレ
フィックスを付けない場合は、varchar データ型と判断されてエラーになる)

ALTER PROCEDURE がサポートされない

SET オプションがサポートされない

LANGUAGE は 、 日 付 フ ォ ー マ ッ ト や シ ス テ ム メ ッ セ ー ジ に 関 係 す る 。 例 え ば 、
us_english を指定した場合は、日付は「月/日/年」と解釈されるので、
「12/05/28」
というデータは、2028 年 12 月 05 日 と解釈される

ATOMIC のみで、明示的な BEGIN TRAN や ROLLBACK、COMMIT などのトラン
ザクション操作がサポートされない

トランザクションの分離レベルは、
SNAPSHOT、REPEATABLE READ、SERIALIZABLE のみがサポートされる

RAISERROR がサポートされない
TRY .. CATCH および THROW はサポートされる。ATOMIC(トランザクション)な
ので、THROW では、トランザクションのロールバック(取り消し)が行われる
226
SQL Server 2014 実践 No.1 インメモリ OLTP
ネイティブ コンパイル SP 内でサポートされている関数
ネイティブ コンパイル SP 内でサポートされている関数は、次のとおりです。
種類
関数名
数学関数
ACOS、ASIN、ATAN、ATN2、COS、COT、DEGREES、EXP、LOG、LOG10、PI、
POWER、RAND、SIN、SQRT、SQUARE、TAN
日付関数
CURRENT_TIMESTAMP、DATEADD、DATEDIFF、DATEFROMPARTS、
DATEPART、DATETIME2FROMPARTS、DATETIMEFROMPARTS、DAY、
EOMONTH、GETDATE、GETUTCDATE、MONTH、
SMALLDATETIMEFROMPARTS、SYSDATETIME、SYSUTCDATETIME、YEAR
文字列関数
LEN、LTRIM、RTRIM、SUBSTRING
エラー関数
ERROR_LINE、ERROR_MESSAGE、ERROR_NUMBER、ERROR_PROCEDURE、
ERROR_SEVERITY、ERROR_STATE
変換関数、
その他
CAST と CONVERT(ただし (var)char と n(var)char 間の変換はサポートされない)
ISNULL, NEWID, NEWSEQUENTIALID
SCOPE_IDENTITY、@@rowcount
詳しくは、オンライン ブックの「ネイティブ コンパイル ストアド プロシージャ内でサポートされる構造」を参照
http://msdn.microsoft.com/ja-jp/library/dn452279.aspx
そのほかのネイティブ コンパイル ストアド プロシージャの制限事項や回避策については、オン
ライン ブックの以下のトピックが参考になります。
ネイティブ コンパイル ストアド プロシージャ内でサポートされる構造
http://msdn.microsoft.com/ja-jp/library/dn452279.aspx
ネイティブ コンパイル ストアド プロシージャ内の EXISTS 句のシミュレーション
http://msdn.microsoft.com/ja-jp/library/dn579375.aspx
ネイティブ コンパイル ストアド プロシージャでの OR 演算子の実装
http://msdn.microsoft.com/ja-jp/library/dn741216.aspx
ネイティブ コンパイル ストアド プロシージャからの TempDB 内のテーブルの作成
およびアクセス
http://msdn.microsoft.com/ja-jp/library/dn579373.aspx
メモリ最適化テーブル変数
http://msdn.microsoft.com/ja-jp/library/dn535766.aspx
CASE ステートメントの実装
http://msdn.microsoft.com/ja-jp/library/dn629453.aspx
外部結合の実装
http://msdn.microsoft.com/ja-jp/library/dn629454.aspx
227
SQL Server 2014 実践 No.1 インメモリ OLTP
4.15 ネイティブ コンパイル アドバイザー
ネイティブ コンパイル アドバイザーは、既存のストアド プロシージャがネイティブ コンパイル
SP に変換できるかどうかをチェックしてくれる、大変便利なツールです。
ネイティブ コンパイル アドバイザーを利用するには、次のように既存のストアド プロシージャ
を右クリックして、「ネイティブ コンパイル アドバイザー」をクリックします。
次の[ストアド プロシージャの検証]ページでは、該当ストアド プロシージャが、ネイティブ コ
ンパイル SP へ移行可能かどうかがチェックされます。
移行に問題がある場合は
赤い × で表示される
次の[ストアド プロシージャの検証結果]ページでは、何が理由で移行できないのかが詳しく表
示されます。
228
SQL Server 2014 実践 No.1 インメモリ OLTP
SELECT * が
利用できないこと
スキーマ名が
省略されていること
この画面は、既存のストアド プロシージャで「SELECT *」を利用している場合で、ネイティブ コ
ンパイル SP では「*」が利用できないこと、FROM 句で指定している「t1_InMem」テーブル
がスキーマ名を省略していることが原因で、移行できないという主旨の結果を表示してくれていま
す。
移行可能なストアド プロシージャである場合は、次のように緑のチェックマークが表示されます。
移行に問題がない場合は
緑のチェックマーク
が表示される
このように、ネイティブ コンパイル アドバイザーを利用すれば、既存のストアド プロシージャ
の中で、ネイティブ コンパイル SP ではサポートされない Transact-SQL を使っているかどうか
をチェックできるので、大変便利です。
229
SQL Server 2014 実践 No.1 インメモリ OLTP
4.16 AMR ツール(データ コレクションによる分析/提案機能)
データ コレクションの「トランザクション パフォーマンス コレクション セット」を設定/有効
化すると、テーブルやストアド プロシージャの使用状況を分析して、メモリ最適化テーブルやネ
イティブ コンパイル SP へ移行したほうが良いかどうかを提案してくれるレポートを参照できる
ようになります。この機能は、AMR(Analysis, Migrate and Report)ツールと呼ばれています。
分析レポートは、次のように参照することができます。
テーブルの利用状況
を分析した結果
競合(ロック待ちな
ど)を分析した結果
ストアド プロシージャの
利用状況を分析した結果
総 CPU 時間の長いストアド プロシージャ
(ネイティブ コンパイル ストアド プロシージャ
へ移行するのがお勧めのもの)をリストアップ
「トランザクション パフォーマンス コレクション セット」は、[データ コレクション構成ウィ
ザード]で、次のように設定/有効化することができます。
230
SQL Server 2014 実践 No.1 インメモリ OLTP
1
2
3
231
SQL Server 2014 実践 No.1 インメモリ OLTP
4.17 ASP.NET Session State provider for SQL Sever In-Memory
bwin 社が実装したような ASP.NET のセッション状態データベースのインメモリ化は、NuGet
(.NET 用のパッケージ マネージャー)から取得できる「Microsoft ASP.NET Session State
provider for SQL Sever In-Memory 1.0.1」を利用すると、簡単に実装することができます。
http://www.nuget.org/packages/Microsoft.Web.SessionState.SqlInMemory/
利用手順
このプロバイダーを利用する手順は、次のとおりです。
まず、Visual Studio で、「ASP.NET Web アプリケーション」プロジェクトを作成します。
232
SQL Server 2014 実践 No.1 インメモリ OLTP
次に、
[ツール]メニューから[ライブラリ パッケージ マネージャー]の[パッケージ マネージ
ャー コンソール]をクリックします。
パッケージ マネージャー コンソール
が表示される
パッケージ マネージャー コンソールが開いたら、次のように入力します。
Install-Package Microsoft.Web.SessionState.SqlInMemory
Install-Package Microsoft.Web.SessionState.SqlInMemory と入力
これらが表示されれば
インストール完了
これでインストールが完了です。インストール完了後は、ソリューション エクスプローラーに
「ASPStateInMemory..sql」ファイルが追加されているので、これを開きます。
データベースやテーブルを作成
するスクリプトになっている
233
ASPStateInMemory.sql
が追加されている
SQL Server 2014 実践 No.1 インメモリ OLTP
次に、「ASPStateInMemory..sql」ファイルの内容を、SQL Server 2014 の Management
Studio のクエリ エディターへ丸ごと貼り付けて、実行します。
ASPStateInMemory
データベースの作成
Sessions と
SessionItems
テーブルが
作成される
データベースの作成先の
フォルダーは任意で変更
Sessions テーブル
の作成
Item 列が
varbinary(7000)
BUCKET_COUNT は同時接続数に合
わせて変更する。既定は 100万
作成されたネイティブ
コンパイル ストアド
プロシージャ
既定は SCHEMA_ONLY
「コマンドは正常に完了しました」
と表示されれば作成が完了
CREATE DATABASE では、「ASPStateInMemory」という名前のデータベースを作成します
が、既定では D:\SQL\data フォルダーにデータベースを作成するようになっているので、任意
の場所へ変更します。
スクリプトでは、2 つのテーブル(Sessions と SessionItems)が作成されますが、Sessions
は 7000 バイト未満のセッション変数を格納するためのテーブル(varbinary(7000) に設定さ
れた Item 列に値を格納)、SessionItems は 7000 バイト以上(BLOB)のセッション変数が
あった場合に、これを分割して格納するためのテーブルです。どちらのテーブルも、既定では
BUCKET_COUNT が 100 万、DURABILITY が SCHEMA_ONLY に設定されています。
BUCKET_COUNT は、同時接続数が多い環境では、さらに大きい値へ変更しておくようにします
(Session 変数は、既定では 20 分間保持されるので、20 分間にアクセスされる同時接続数をも
とに BUCKET_COUNT を調整します)
。
スクリプトでは、いくつかのネイティブ コンパイル SP が作成されますが、主な役割は、次のと
おりです。
ネイティブ コンパイル SP
役割
GetStateItemExclusive
セッション変数の取得時に利用される
InsertOrUpdateStateItem
セッション変数の格納、変更時に利用される
DeleteExpiredSessions
古いセッション(変数)を削除
既定では 20分間がセッション変数の有効期間。
ディスク ベースの「InstallSqlState.sql」スクリプトで登録さ
れる SQL Server Agent ジョブの「ASPState_Job_Delete
ExpiredSessions」へ登録することで、定期削除が可能になる
234
SQL Server 2014 実践 No.1 インメモリ OLTP
最後に、Visual Studio のソリューション エクスプローラーで「Web.config」ファイルを開い
て、connectionString を変更すれば、設定が完了です。
Web.config を開く
SQL Server 名へ変更
SQL Server へ接続するユーザー名、
パスワードへ変更
これで、ASP.NET のセッション状態データベースのインメモリ化が完了です。
ASP.NET Session State provider for SQL Sever In-Memory の動作
このプロバイダーの動作について、次のような ASP.NET Web フォームでセッション変数を格納
した場合を例に説明します。
セッション変数「test1」
に AAA を格納
この Web フォームへアクセスしたときを SQL Server Profiler ツールでキャプチャすると、
次のように InsertOrUpdateStateItem ネイティブ コンパイル SP が実行されていることが
分かります。
InsertOrUpdateStateItem
でセッション変数が格納される
セッション変数は
バイナリ データで登録される
235
SQL Server 2014 実践 No.1 インメモリ OLTP
このネイティブ コンパイル SP では、7000 バイト未満のデータであれば、Sessions テーブル
へ値を格納するようになっています(Item 列に値が格納されます)
。
Sessions テーブルの
中身を確認
既定では 20分が
有効期限
Item 列に値が格納
される。414141
は AAA
次に、セッション変数の値を取得する場合を見てみます(別の Web フォームから取得)
。
セッション変数「test1」
の値を取得
このようにセッション変数の値を取得した場合を SQL Server Profiler ツールでキャプチャす
ると、次のようになります。
GetStateItemExclusive
でセッション変数を取得する
セッション変数の取得時は、GetStateItemExclusive ネイティブ コンパイル SP が実行され
ていることが分かります。
以上のように、Microsoft ASP.NET Session State provider for SQL Sever In-Memory
1.0.1 を利用すると、セッション状態データベースを簡単にインメモリ化することができます。
236
SQL Server 2014 実践 No.1 インメモリ OLTP
4.18 1 秒間に 100 万件のデータ挿入も可能 ~Codeplex サンプル~
Codeplex サイトで提供されている「インメモリ OLTP サンプル」では、1 秒間に 100 万件も
のデータ挿入を行うことができるサンプルが提供されています。
インメモリ OLTP サンプル(Codeplex)
https://msftdbprodsamples.codeplex.com/releases/view/114491
このサンプルを利用する手順は、オンライン ブックの以下のトピックに記載されています。
インメモリ OLTP を実証する AdventureWorks の拡張
http://msdn.microsoft.com/ja-jp/library/dn511655(v=sql.120).aspx
237
SQL Server 2014 実践 No.1 インメモリ OLTP
負荷テストの結果
弊社環境で、このサンプルの負荷テストを実行したときの結果が次のグラフです。
インメモリ OLTP サンプルの実行結果
57:36
約52倍の
性能向上
実行時間(分:秒)
50:24
43:12
約20倍の
性能向上
12.8倍の性能向上
実行時間が
約 1/13 に短縮
36:00
28:48
21:36
14:24
07:12
00:00
MS社 24コア
MS社 8コア
弊社 4コア
On Disk
52:16
41:25
31:05
インメモリ OLTP
01:00
02:05
02:26
On Disk
インメモリ OLTP
差%
性能向上(n倍)
MS社 24コア
52:16
01:00
98.1%
52.3
MS社 8コア
41:25
02:05
95.0%
19.9
弊社 4コア
31:05
02:26
92.2%
12.8
Microsoft 社での実行結果(グラフ内の 24コアと 8コアの結果)はオンライン ブック内に記載されているものです。
弊社環境のハードウェア(4コア)は、Core i7-2600K、32GBメモリ、SSD(PLEXTOR M3P 512GB)。
ベンチマークの結果の公表は、使用許諾契約書で禁止されていますが、その効果をわかりやすく表現するため、
日本マイクロソフト株式会社の監修のもと、数値を掲載しております。
Microsoft 社の環境(グラフの左側2つ)では、24 コアで 約 52 倍の性能向上、8 コアで 約 20
倍の性能向上、弊社環境(4 コア)では、約 12.8 倍の性能向上を確認することができました。
従来ながらのディスクベース(On Disk)の結果が、コア数が少ないほど良い結果になっているの
は、(Microsoft 社でのテスト環境の詳細は記載されていないので想像になりますが)おそらくラ
ッチ待ちが原因と考えられます。このサンプルの負荷テストを実行すると、ディスク ベースでは、
ラッチ待ちが多発することによって、性能が出ないのですが、このラッチ待ちは、コア数が多いほ
どオーバーヘッドがあるという面があります(ラッチ待ちが多発すると、コア数が多いほどスルー
プットが低下します)
。
これに対して、インメモリ OLTP ではラッチ待ちが発生しないので大幅な性能向上を実現するこ
とができ、コア数が増えれば、その分の性能メリットを享受できる形になっています。
サンプルの概要
このサンプルの概要は、次のとおりです。

AdventureWorks2012 データベースを利用(SQL Server 2012 用のサンプル DB)

このデータベース内に、インメモリ OLTP のテーブルと、従来ながらのディスク ベース
のテーブルを作成(同じデータベース内に混在)
238
SQL Server 2014 実践 No.1 インメモリ OLTP
ostress ツールで負荷テストを実施(100 接続から 5,000 回ずつ繰り返し処理を実施。

乱数を利用して、ランダム値での繰り返し処理)

SalesOrderHeader と SalesOrderDetail テーブルへデータの INSERT

上記の INSERT 時に、SpecialOffer テーブルからディスカウント率を取得(SELECT)

インメモリ OLTP では、一連の処理にネイティブ コンパイル SP を利用
テーブル構造は、次のとおりです。
負荷テストを実行すると、SalesOrderHeader には約 1,000 万件、SalesOrderDetail には
約 5,000 万件のデータが INSERT されるので、合計 6,000 万件のデータが追加されることに
なります(Microsoft 社の 24 コア環境では、1 分で処理が完了します)。
したがって、1 秒あたりの INSERT 件数を割り出してみると、以下のようになります。
1秒あたりの Insert 件数
1,000,000
1秒あたり
約100万件の Insert
900,000
1秒あたりの Insert 件数
800,000
700,000
600,000
500,000
400,000
300,000
200,000
100,000
0
On Disk
インメモリ OLTP
弊社 4コア
MS社 8コア
32,257
24,209
MS社 24コア
19,183
412,046
481,270
1,002,646
239
SQL Server 2014 実践 No.1 インメモリ OLTP
弊社環境(4 コア)では、1 秒あたり約 41 万件の INSERT ですが、Microsoft 社の 24 コア環
境では、
1 秒あたり約 100 万件もの INSERT が完了(ディスク ベースよりも 50 倍の性能向上)
しています。
On Disk
インメモリ OLTP
差
弊社 4コア
32,257
412,046
12.8
MS社 8コア MS社 24コア
24,209
19,183
481,270
1,002,646
19.9
52.3
ディスク ベース
では 約1.9万件
24コアでは
1秒 100万件
このように、ラッチ待ちが多発するような環境では、インメモリ OLTP の性能メリットが最大限
に発揮されます。
おわりに
最後までこのドキュメントを読まれた皆さん、いかがでしたでしょうか? インメモリ OLTP をう
まく活用することで、大きな性能向上の可能性があることがお分かりいただけたのではないでしょ
うか? もちろん、インメモリ OLTP は、魔法の杖ではないので、苦手な処理があります(フル ス
キャンや全データを対象とした集計処理が遅いなど)。しかし、フル スキャンや集計処理を速くし
たいのであれば、クラスター化列ストア インデックス(カラム型データベースの SQL Server 実
装)を利用すれば良いのです。
どんな機能にも、メリットとデメリットがあり、利用にはトレード オフがあります。インメモリ
OLTP が得意な処理は、典型的な OLTP システムであり、クラスター化列ストア インデックスの
得意な処理は、大量データにおける集計処理です。SQL Server 2014 は、インメモリ OLTP も
クラスター化列ストア インデックスも、従来ながらのディスク ベースのテーブルも、同じデータ
ベース内に混在させることができるので、ハイブリッドに利用することができます。適材適所で、
使い分けができるのは、SQL Server の大きなメリットです。
240
SQL Server 2014 実践 No.1 インメモリ OLTP
執筆者プロフィール
有限会社エスキューエル・クオリティ(http://www.sqlquality.com/)
SQLQuality(エスキューエル・クオリティ)は、日本で唯一の SQL Server 専門の独立系コンサルティン
グ会社です。過去のバージョンから最新バージョンまでの SQL Server を知りつくし、多数の実績と豊富な
経験を持つ、OS や .NET にも詳しい SQL Server の専門家(キャリア 17 年以上)がすべての案件に対応
します。人気メニューの「パフォーマンス チューニング サービス」は、100%の成果を上げ、過去すべての
お客様環境で驚異的な性能向上を実現。チューニング スキルは世界トップレベルを自負、検索エンジンでは
(英語情報を含めて)ヒットしないノウハウを多数保持。ここ数年は BI/DWH システム構築支援のご依頼
が多く、支援だけでなく実際の構築も行う。
主なコンサルティング実績
大手製造業の「CAD 端末の利用状況の見える化」システム構築
Oracle や CSV(Notes)、TSV ファイル、Excel からデータを抽出し、SQL Server 2012 上に DWH を構築
見える化レポートには Reporting Services を利用
大手映像制作会社の BI システム構築(会計/業務システムにおける予実管理/原価管理など)
従来 Excel で管理していたシートを Reporting Services のレポートへ完全移行。
Oracle や勘定奉行からデータを抽出して、SQL Server 上に DWH を構築
大手流通系の DWH/BI システム構築支援(POS データ/在庫データ分析/ABC 分析/ポイントカード分析)
大手アミューズメント企業の BI システム構築支援(人事システムにおける人材パフォーマンス管理)
Reporting Services による勤怠状況の見える化レポートの作成、PostgreSQL/人事システムからのデータ抽出
外資系医療メーカーの BI システム構築支援(Analysis Services と Excel による販売分析システム)
OLAP キューブによる売上および顧客データの多次元分析/自由分析(ユーザーによる自由操作が可能)
大手流通系の DWH システムのパフォーマンス チューニング
データ量 100 億件の DWH、総ステップ数2万越えのストアド プロシージャのパフォーマンス チューニング
ミッション クリティカルな金融システムでのトラブル シューティング/定期メンテナンス支援
SQL Server の下位バージョンからの移行/アップグレード支援(32 ビットから x64 への対応も含む)
複数台の SQL Server の Hyper-V 仮想環境への移行支援(サーバー統合支援)
ハードウェア リプレイス時のハードウェア選定(最適なサーバー、ストレージの選定)、高可用性環境の構築
2 時間かかっていた日中バッチ実行時間を、わずか 5 分へ短縮(95.8% の性能向上)
Java 環境(Tomcat、Seasar2、S2Dao)の SQL Server パフォーマンス チューニング etc
コンサルティング時の作業例(パフォーマンス チューニングの場合)
アプリケーション コード(VB、C#、Java、ASP、VBScript、VBA)の解析/改修支援
ストアド プロシージャ/ユーザー定義関数/トリガー(Transact-SQL)の解析/改修支援
インデックス チューニング/SQL チューニング/ロック処理の見直し
現状のハードウェアで将来のアクセス増にどこまで耐えられるかを測定する高負荷テストの実施
IIS ログの解析/アプリケーション ログ(log4net/log4j)の解析
ボトルネック ハードウェアの発見/ボトルネック SQL の発見/ボトルネック アプリケーションの発見
SQL Server の構成オプション/データベース設定の分析/使用状況(CPU, メモリ, ディスク, Wait)解析
定期メンテナンス支援(インデックスの再構築/断片化解消のタイミングや断片化の事前防止策など)etc
松本美穂(まつもと・みほ)
有限会社エスキューエル・クオリティ 代表取締役 Microsoft MVP for SQL Server(2004 年 4 月~)
経産省認定データベース スペシャリスト/MCDBA/MCSD for .NET/MCITP Database Administrator
SQL Server の日本における最初のバージョンである「SQL Server 4.21a」から SQL Server に携わり、現在、SQL Server
を中心とするコンサルティングを行っている。得意分野はパフォーマンス チューニングと Reporting Services。著書の
『SQL Server 2000 でいってみよう』と『ASP.NET でいってみよう』
(いずれも翔泳社刊)
は、トップ セラー(前者は 28,500
部、後者は 16,500 部発行)。近刊に『SQL Server 2012 の教科書』(ソシム刊)がある。
松本崇博(まつもと・たかひろ)
有限会社エスキューエル・クオリティ 取締役 Microsoft MVP for SQL Server(2004 年 4 月~)
経産省認定データベース スペシャリスト/MCDBA/MCSD for .NET/MCITP Database Administrator
SQL Server の BI システムとパフォーマンス チューニングを得意とするコンサルタント。アプリケーション開発(ASP
/ASP.NET、C#、VB 6.0、Java、Access VBA など)やシステム管理者(IT Pro)経験もあり、SQL Server だけでなく、
アプリケーションや OS、Web サーバーを絡めた、総合的なコンサルティングが行えるのが強み。Analysis Services と
Excel による BI システムも得意とする。マイクロソフト認定トレーナー時代の 1998 年度には、Microsoft CPLS トレーナ
ー アワード(Trainer of the Year)を受賞。
241
Fly UP