#はじめに
前回データの格納具合を確認したが、それは1行が1ページ(8KB)に収まる場合だった。
今回は1ページに収まらない場合の様子を確認していく。
なお「1行が1ページに収まらない」にも2パターンある。
- 各フィールド単体の長さは8KBに収まるが、行全体として合算すると8KBを超える場合
- そもそもフィールド長が8KBを超えている場合
前者は「Row-Overflow Storage」という方法で格納される。
後者は「LOB Storage」という方法で格納される。
今回はまず前者「Row-Overflow Storage」を攻略しようではないか。
-- 実験用テーブル作成
Use DORAYAKI;
GO
if Exists(
Select *
From INFORMATION_SCHEMA.TABLES
Where TABLE_SCHEMA = 'dbo' and TABLE_NAME = 'futsukame'
)
Drop Table dbo.futsukame;
GO
-- nvarcharは1文字で2バイト占領するので列c3はページに収まらないはず。
CREATE TABLE [dbo].[futsukame](
[c1] [int] NOT NULL,
[c2] [nvarchar](3600) NULL,
[c3] [nvarchar](2800) NULL
);
GO
-- 実験用データ投入
Insert into dbo.futsukame Values(1, replicate('0123456789',360), replicate('0123456789', 280));
GO
データは1行しか投入しないのでSelect文の確認は掲載省略。
ついでdbcc indで使用ページの情報を確認する。
In-row dataに加えて、Row-overflow dataを確認。列c1とc2はIn-row data(PagePID:432)に格納されていて、はみ出した列c3がRow-overflow data(pagePID:424)に格納されているはず。
まずはIn-row dataのページを前回同様の方法で確認してみる。
とここまで書いて、クエリの結果を見て早くも間違いに気づいてしまった。
Row-overflow dataに追いやられる列は、「そのレコードの中で最長の列」なのだ。
だから今回の場合だと列c2が追い出されていた。
スキーマ定義に関係なく、実際に格納されているデータで最長のものが追い出される。
だから行によって追い出される列は全然違うということもあるだろう。
さて、dbcc pageの結果は次のとおり。
まずは元の(In-row data)ページから。
0000000000000000: 30000800 01000000 0300c802 00298009 16020000 0.........È..). ....
0000000000000014: 00010000 003c1500 00201c00 00a80100 00010000 .....<... ...¨......
0000000000000028: 00300031 00320033 00340035 00360037 00380039 .0.1.2.3.4.5.6.7.8.9
000000000000003C: 00300031 00320033 00340035 00360037 00380039 .0.1.2.3.4.5.6.7.8.9
0000000000000050: 00300031 00320033 00340035 00360037 00380039 .0.1.2.3.4.5.6.7.8.9
--中略--
00000000000015B8: 00300031 00320033 00340035 00360037 00380039 .0.1.2.3.4.5.6.7.8.9
00000000000015CC: 00300031 00320033 00340035 00360037 00380039 .0.1.2.3.4.5.6.7.8.9
00000000000015E0: 00300031 00320033 00340035 00360037 00380039 .0.1.2.3.4.5.6.7.8.9
00000000000015F4: 00300031 00320033 00340035 00360037 00380039 .0.1.2.3.4.5.6.7.8.9
0000000000001608: 00 .
先頭部分を紐解いていきます。
リトルエンディアンなので混乱しないように...自分はようやく慣れてきましたよ。
3000 : status bit A and B
0800 : id列(4byte) + offset(4byte) = 8
01000000 : id列の値
0300 : 列数(3)
c8 : null bitmap 今回nullの列はないので00になると思っていたが...(課題)
0200 : nullではない可変長フィールド数(2)
2980 : 列c2の終端までのオフセットバイト数だが...(課題)
※2900になると思っていたが「80」が予想外。
0916 : 列c3の終端までのオフセットバイト数
そのあと16bytes : 列c2のメタデータ(今は追求しません)
a8010000 : 列c2のデータ格納先(Row-overflow data page)のPagePID
※hexで「1a8」なので、decだと「424」となる。(dbcc indの画像参照)
0100 : File Number
0000 : Slot Number
次にRow-overflow data pageを見てみる。
dbcc traceon(3604);
dbcc page('DORAYAKI', 1, 424, 3);
--以下結果を編集したもの
Blob row at: Page (1:424) Slot 0 Length: 7214 Type: 3 (DATA)
Blob Id:356253696
0000009D779FA06E: 00310030 00330032 00350034 00370036 0.1.2.3.4.5.6.7.
0000009D779FA07E: 00390038 00310030 00330032 00350034 8.9.0.1.2.3.4.5.
0000009D779FA08E: 00370036 00390038 00310030 00330032 6.7.8.9.0.1.2.3.
0000009D779FA09E: 00350034 00370036 00390038 00310030 4.5.6.7.8.9.0.1.
0000009D779FA0AE: 00330032 00350034 00370036 00390038 2.3.4.5.6.7.8.9.
(以下省略)
Row-overflow data pageは、データだけが延々と続いていた。
#追加情報
MS公式サイトにて、Row-Overflow列が頻繁に参照される場合は別テーブルの利用(垂直分割)の検討がお勧めされている。
理由はOverflowしない(普通の)テーブルであれば、非同期で結合(join)処理が行われるからだということだ。Overflowの管理が同期処理で重いということなのだろう。
ということで、次は垂直分割した場合の効果を実験してみたいと思う。
#残課題とか謎とか
- dbcc pageの結果表示が、In-rowとRow-overflowでフォーマットが違う。左端のオフセット表示もOverflowのほうは意味の分からない値だ。
- null bitmapと、Overflow対象となった可変長フィールド(=24bytes)の終端までのオフセット値が謎のまま解明できなかった。