sql >> データベース >  >> RDS >> Sqlserver

SQLServerの「datetime2」ストレージサイズを理解する

    この記事では、 datetime2に関して私が経験したいくつかの所見を共有します。 SQLServerでのデータ型のストレージサイズ。おそらく、データベースに格納されるときにこのデータ型によって使用される実際のストレージサイズに関するいくつかのポイントを明確にします。

    特に、私は以下を見ています:

    • Microsoftのドキュメント
    • 変数に格納されているデータ
      • DATALENGTH()を使用したバイト単位の長さ
      • DATALENGTH()を使用したバイト単位の長さ varbinaryに変換した後
    • データベースに保存されているデータ
      • COL_LENGTH()を使用したバイト単位の長さ
      • DBCC PAGE()を使用したバイト単位の長さ

    それらのいくつかは互いに矛盾しているように見え、どこを見ているかに応じて、同じ値に対して2つの異なるストレージサイズの量が表示されます。

    datetime2 値は、データベースに保存されているかどうかに応じて、 datetime2として異なるストレージサイズを表示できます。 変数、または varbinaryに変換 。

    しかし、これにはもっともらしい説明があります–それは精度がどこにあるかに依存します 保存されています。

    この問題に関する調査中に、 datetime2に関するRonenArielyの詳細な記事を見つけました。 は非常に有益なデータファイルに保存されており、自分の開発環境で同様のテストを実行して、ここに表示するように促されました。

    Microsoftのドキュメント

    まず、公式ドキュメントの内容を見てみましょう。

    datetime2に関するMicrosoftのドキュメント データ型は、そのストレージサイズが次のとおりであることを示しています:

    3未満の精度の場合は6バイト。
    精度3または4の場合は7バイト。
    他のすべての精度には8バイトが必要です。

    ただし、上記の表は次のステートメントで修飾されます。

    datetime2の最初のバイト valueは、値の精度を格納します。これは、 datetime2に必要な実際のストレージを意味します valueは、上記の表に示されているストレージサイズに、精度を格納するための1バイトを加えたものです。これにより、 datetime2の最大サイズになります 値9バイト– 1バイトは精度を格納し、さらに最大精度でデータを保存するために8バイトを格納します。

    したがって、上記の情報を考慮すると、表を次のように書くことができる/(すべきでしょうか?)という明らかな結論を導き出すことができます。

    3未満の精度の場合は7バイト。
    精度3または4の場合は8バイト。
    他のすべての精度には9バイトが必要です。

    そうすれば、精度に関する追加情報で修飾する必要がなくなります。

    しかし、それはそれほど単純ではありません。

    変数に格納されたデータ

    まず、 datetime2を保存しましょう 変数の値を入力し、そのストレージサイズを確認します。次に、その値を varbinaryに変換します もう一度確認してください。

    DATALENGTHを使用したバイト単位の長さ

    DATALENGTH()を使用するとどうなりますか datetime2(7)に使用されたバイト数を返す関数 値:

    DECLARE @d datetime2(7);
    SET @d = '2025-05-21 10:15:30.1234567';
    SELECT 
      @d AS 'Value',
      DATALENGTH(@d) AS 'Length in Bytes';
    

    結果

    +-----------------------------+-------------------+
    | Value                       | Length in Bytes   |
    |-----------------------------+-------------------|
    | 2025-05-21 10:15:30.1234567 | 8                 |
    +-----------------------------+-------------------+
    

    この例の値の最大スケールは7です(変数を datetime2(7)として宣言しているためです。 )、8バイトの長さを返します。

    これは、精度を格納するために追加のバイトが必要であるというMicrosoftの状態と矛盾しているようです。 Microsoftを引用すると、これにより datetime2の最大サイズになります 値9バイト– 1バイトは、精度に加えて、最大精度でのデータストレージ用の8バイトを格納します。

    データストレージ用に8バイトを取得しているように見えるのは事実ですが 、精度を格納するために使用される1バイトが欠落しているようです。

    ただし、値を varbinaryに変換すると 別の話があります。

    「varbinary」に変換した後のバイト単位の長さ

    datetime2を変換するとどうなりますか varbinaryの値 :

    DECLARE @d datetime2(7);
    SET @d = '2025-05-21 10:15:30.1234567';
    SELECT 
      CONVERT(VARBINARY(10), @d) AS 'Value',
      DATALENGTH(CONVERT(VARBINARY(10), @d)) AS 'Length in Bytes';
    

    結果

    +----------------------+-------------------+
    | Value                | Length in Bytes   |
    |----------------------+-------------------|
    | 0x0787A311FC553F480B | 9                 |
    +----------------------+-------------------+
    

    この場合、9バイトになります。

    これは、 datetime2の16進表現です。 価値。実際の日時の値(およびその精度)は、0x以降のすべてです。 。 16進文字の各ペアは1バイトです。 9つのペアがあるため、9バイトです。これは、DATALENGTH()を使用するときに確認されます 長さをバイト単位で返します。

    この例では、最初のバイトが07であることがわかります。 。これは精度を表します(私は7のスケールを使用したので、ここに表示されます)。

    スケールを変更すると、最初のバイトがスケールに一致するように変更されていることがわかります。

    DECLARE @d datetime2(3);
    SET @d = '2025-05-21 10:15:30.1234567';
    SELECT 
      CONVERT(VARBINARY(10), @d) AS 'Value',
      DATALENGTH(CONVERT(VARBINARY(10), @d)) AS 'Length in Bytes';
    

    結果

    +--------------------+-------------------+
    | Value              | Length in Bytes   |
    |--------------------+-------------------|
    | 0x034B8233023F480B | 8                 |
    +--------------------+-------------------+
    

    それに応じて長さが短くなっていることもわかります。

    したがって、この場合、結果はMicrosoftのドキュメントと完全に一致します。精度を高めるために1バイトが追加されています。

    多くの開発者は、これがSQLServerがdatetime2を格納する方法であると想定しています。 データベース内の値。ただし、その仮定は正しくないようです。

    データベースに保存されているデータ

    この例では、さまざまな datetime2(n)を持つテーブルを含むデータベースを作成します。 列。次に、COL_LENGTH()を使用します 各列の長さをバイト単位で返します。その後、DBCC PAGEを使用する前に、値を挿入します 各datetime2のストレージサイズを確認します 値はページファイルで使用されます。

    データベースを作成します:

    CREATE DATABASE Test;
    

    テーブルを作成します:

    USE Test;
    
    CREATE TABLE Datetime2Test (
        d0 datetime2(0),
        d1 datetime2(1),
        d2 datetime2(2),
        d3 datetime2(3),
        d4 datetime2(4),
        d5 datetime2(5),
        d6 datetime2(6),
        d7 datetime2(7)
        );
    

    この場合、8つの列を作成します。 datetime2(n)で使用できるユーザー定義のスケールごとに1つです。 。

    これで、各列のストレージサイズを確認できます。

    COL_LENGTH()を使用したバイト単位の長さ

    COL_LENGTH()を使用します 各列の長さ(バイト単位)を確認するには:

    SELECT 
      COL_LENGTH ( 'Datetime2Test' , 'd0' ) AS 'd0',
      COL_LENGTH ( 'Datetime2Test' , 'd1' ) AS 'd1',
      COL_LENGTH ( 'Datetime2Test' , 'd2' ) AS 'd2',
      COL_LENGTH ( 'Datetime2Test' , 'd3' ) AS 'd3',
      COL_LENGTH ( 'Datetime2Test' , 'd4' ) AS 'd4',
      COL_LENGTH ( 'Datetime2Test' , 'd5' ) AS 'd5',
      COL_LENGTH ( 'Datetime2Test' , 'd6' ) AS 'd6',
      COL_LENGTH ( 'Datetime2Test' , 'd7' ) AS 'd7';  
    

    結果:

    +------+------+------+------+------+------+------+------+
    | d0   | d1   | d2   | d3   | d4   | d5   | d6   | d7   |
    |------+------+------+------+------+------+------+------|
    | 6    | 6    | 6    | 7    | 7    | 8    | 8    | 8    |
    +------+------+------+------+------+------+------+------+
    

    したがって、繰り返しになりますが、精度を格納するために使用される余分なバイトは取得されていないようです。

    DBCCPAGEを使用して保存データを確認する

    それでは、DBCC PAGEを使用しましょう このテーブルに保存するデータの実際のストレージサイズを確認します。

    まず、いくつかのデータを挿入しましょう:

    DECLARE @d datetime2(7) = '2025-05-21 10:15:30.1234567';
    INSERT INTO Datetime2Test ( d0, d1, d2, d3, d4, d5, d6, d7 )
    SELECT @d, @d, @d, @d, @d, @d, @d, @d;
    

    次に、データを選択します(確認するためだけに):

    SELECT * FROM Datetime2Test;
    

    結果(垂直出力を使用):

    d0 | 2025-05-21 10:15:30
    d1 | 2025-05-21 10:15:30.1
    d2 | 2025-05-21 10:15:30.12
    d3 | 2025-05-21 10:15:30.123
    d4 | 2025-05-21 10:15:30.1235
    d5 | 2025-05-21 10:15:30.12346
    d6 | 2025-05-21 10:15:30.123457
    d7 | 2025-05-21 10:15:30.1234567
    

    予想どおり、値は以前に列レベルで指定された精度を使用します。

    ここで、DBCC PAGE()を使用する前に 、どのPagePIDを渡すかを知る必要があります。 DBCC IND()を使用できます それを見つけるために。

    PagePIDを見つける:

    DBCC IND('Test', 'dbo.Datetime2Test', 0);
    

    結果(垂直出力を使用):

    -[ RECORD 1 ]-------------------------
    PageFID         | 1
    PagePID         | 306
    IAMFID          | NULL
    IAMPID          | NULL
    ObjectID        | 1205579333
    IndexID         | 0
    PartitionNumber | 1
    PartitionID     | 72057594043039744
    iam_chain_type  | In-row data
    PageType        | 10
    IndexLevel      | NULL
    NextPageFID     | 0
    NextPagePID     | 0
    PrevPageFID     | 0
    PrevPagePID     | 0
    -[ RECORD 2 ]-------------------------
    PageFID         | 1
    PagePID         | 360
    IAMFID          | 1
    IAMPID          | 306
    ObjectID        | 1205579333
    IndexID         | 0
    PartitionNumber | 1
    PartitionID     | 72057594043039744
    iam_chain_type  | In-row data
    PageType        | 1
    IndexLevel      | 0
    NextPageFID     | 0
    NextPagePID     | 0
    PrevPageFID     | 0
    PrevPagePID     | 0
    

    これにより、2つのレコードが返されます。 PageType of 1(2番目のレコード)に関心があります。そのレコードからPagePIDが必要です。この場合、PagePIDは 360 です。 。

    これで、そのPagePIDを取得して、次の場所で使用できます。

    DBCC TRACEON(3604, -1);
    DBCC PAGE(Test, 1, 360, 3);
    

    これにより大量のデータが生成されますが、主に次の部分に関心があります。

    Slot 0 Column 1 Offset 0x4 Length 6 Length (physical) 6
    
    d0 = 2025-05-21 10:15:30            
    
    Slot 0 Column 2 Offset 0xa Length 6 Length (physical) 6
    
    d1 = 2025-05-21 10:15:30.1          
    
    Slot 0 Column 3 Offset 0x10 Length 6 Length (physical) 6
    
    d2 = 2025-05-21 10:15:30.12         
    
    Slot 0 Column 4 Offset 0x16 Length 7 Length (physical) 7
    
    d3 = 2025-05-21 10:15:30.123   
    
    Slot 0 Column 5 Offset 0x1d Length 7 Length (physical) 7
    
    d4 = 2025-05-21 10:15:30.1235       
    
    Slot 0 Column 6 Offset 0x24 Length 8 Length (physical) 8
    
    d5 = 2025-05-21 10:15:30.12346      
    
    Slot 0 Column 7 Offset 0x2c Length 8 Length (physical) 8
    
    d6 = 2025-05-21 10:15:30.123457     
    
    Slot 0 Column 8 Offset 0x34 Length 8 Length (physical) 8
    
    d7 = 2025-05-21 10:15:30.1234567                                   
    

    したがって、精度のために余分なバイトを使用していないようです。

    ただし、結論に達する前に、実際のデータを調べてみましょう。

    実際のデータは、ページファイルのこの部分に保存されます:

    Memory Dump @0x000000041883A060
    
    0000000000000000:   10003c00 4290003f 480b95a2 053f480b d459383f  ..<.B..?H.•¢.?H.ÔY8?
    0000000000000014:   480b4b82 33023f48 0bf31603 163f480b 7ae51edc  H.K‚3.?H.ó...?H.zå.Ü
    0000000000000028:   003f480b c1f63499 083f480b 87a311fc 553f480b  .?H.Áö4..?H.‡£.üU?H.
    000000000000003C:   080000                                        ...                                           ...   
    

    ご覧のとおり、 datetime2を変換した場合の結果とは異なります。 varbinaryの値 。しかし、それはかなり近いです。

    いくつか削除すると、次のようになります。

    4290003f 480b95a2 053f480b d459383f
    480b4b82 33023f48 0bf31603 163f480b 7ae51edc
    003f480b c1f63499 083f480b 87a311fc 553f480b
    

    残りの16進数には、すべての日付と時刻のデータが含まれますが、精度は含まれません 。ただし、各行の実際の値を取得するには、スペースを再配置する必要があります。

    これが最終結果です。読みやすくするために、各日付/時刻の値を新しい行に配置しました。

    4290003f480b 
    95a2053f480b 
    d459383f480b 
    4b8233023f480b
    f31603163f480b 
    7ae51edc003f480b 
    c1f63499083f480b 
    87a311fc553f480b
    

    これらは実際の16進値です(精度を差し引いたもの datetime2を変換した場合に得られるもの varbinaryの値 。確かに、先に進んでそれを実行しましょう:

    SELECT 
      CONVERT(VARBINARY(10), d0) AS 'd0',
      CONVERT(VARBINARY(10), d1) AS 'd1',
      CONVERT(VARBINARY(10), d2) AS 'd2',
      CONVERT(VARBINARY(10), d3) AS 'd3',
      CONVERT(VARBINARY(10), d4) AS 'd4',
      CONVERT(VARBINARY(10), d5) AS 'd5',
      CONVERT(VARBINARY(10), d6) AS 'd6',
      CONVERT(VARBINARY(10), d7) AS 'd7'
    FROM Datetime2Test;
    

    結果(垂直出力を使用):

    d0 | 0x004290003F480B
    d1 | 0x0195A2053F480B
    d2 | 0x02D459383F480B
    d3 | 0x034B8233023F480B
    d4 | 0x04F31603163F480B
    d5 | 0x057AE51EDC003F480B
    d6 | 0x06C1F63499083F480B
    d7 | 0x0787A311FC553F480B
    

    したがって、同じ結果が得られますが、精度が付加されている点が異なります。

    ただし、わかりやすくするために、実際のページファイルデータをCONVERT()の結果と比較する表を次に示します。 操作。

    ページファイルデータ CONVERT()データ
    4290003f480b 004290003F480B
    95a2053f480b 0195A2053F480B
    d459383f480b 02D459383F480B
    4b8233023f480b 034B8233023F480B
    f31603163f480b 04F31603163F480B
    7ae51edc003f480b 057AE51EDC003F480B
    c1f63499083f480b 06C1F63499083F480B
    87a311fc553f480b 0787A311FC553F480B

    したがって、ページファイルには精度が保存されていないことがはっきりとわかりますが、変換された結果には保存されています。

    実際の日付と時刻の部分を赤で強調表示しました。 0xも削除しました 変換された結果のプレフィックス。実際の日付/時刻データのみが(精度とともに)表示されます。

    また、16進数は大文字と小文字を区別しないため、一方が小文字を使用し、もう一方が大文字を使用するという事実は問題ではないことに注意してください。

    結論

    datetime2を変換する場合 varbinaryの値 、精度を格納するために追加のバイトが必要です。時間部分を解釈するには精度が必要です(これは時間間隔として保存されるため、正確な値は精度によって異なります)。

    データベースに格納する場合、精度は列レベルで1回指定されます。列レベルで指定できる場合は、各行に余分なバイトを格納する必要がないため、これは論理的に思えます。したがって、sayを指定すると、 datetime2(7) 列レベルでは、すべての行が datetime2(7)になります 。すべての行でこれを繰り返す必要はありません。

    Ronen Arielyは、上記の彼の記事で同じ結論に達しました。

    datetime2(7)の行が100万行ある場合 値、各行で精度を保存するには9,000,000バイトが必要ですが、列全体で精度を1回保存すると8,000,001バイトになります。

    これにより、 datetime2も強化されます 日時と比較した場合 。 日時と同じ小数点以下の桁数を使用する場合でも (つまり3)、 datetime2 データ型はより少ないストレージを使用します(少なくとも複数の行を持つテーブルに格納されている場合)。そして、これをより高い精度で行います。 日時 値は8バイトを使用しますが、 datetime2(3) 7バイトを使用します(さらに、すべての行で共有される1つの「精度」バイト)。


    1. Postgresで列を複数の行に分割する

    2. データベースシャーディングとは何ですか?

    3. Oracleのカーソルを使用してレコードを挿入および更新します

    4. エンティティフレームワークにID列を挿入させるにはどうすればよいですか?