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

FORMAT()は素晴らしいですが、…

    SQL Server 2012がまだベータ版だった頃、私は新しいFORMAT()についてブログを書きました。 関数:SQL Server v.Next(Denali):CTP3 T-SQLの機能強化:FORMAT()。

    当時、私は新機能にとても興奮していたので、パフォーマンステストを行うことすら考えていませんでした。私は最近のブログ投稿でこれに対処しましたが、それは単に日時から時間を取り除くという文脈でのみです:日時からの時間のトリミング–フォローアップ。

    先週、私の親友のJason Horner(ブログ| @jasonhorner)がこれらのツイートで私を騙しました:

    これに関する私の問題は、そのFORMAT()だけです。 便利に見えますが、他のアプローチと比較すると非常に非効率的です(ああ、そのAS VARCHAR 物事も悪いです)。あなたがこのonesy-twosyをやっていて、小さな結果セットの場合、私はそれについてあまり心配しません。しかし、大規模な場合、かなり高価になる可能性があります。例を挙げて説明しましょう。まず、1000個の疑似ランダム日付を持つ小さなテーブルを作成しましょう:

    SELECT TOP (1000) d = DATEADD(DAY, CHECKSUM(NEWID())%1000, o.create_date)
      INTO dbo.dtTest
      FROM sys.all_objects AS o
      ORDER BY NEWID();
    GO
    CREATE CLUSTERED INDEX d ON dbo.dtTest(d);

    それでは、このテーブルのデータを使用してキャッシュを準備し、人々がその時間だけを提示する傾向がある3つの一般的な方法を説明しましょう。

    SELECT d, 
      CONVERT(DATE, d), 
      CONVERT(CHAR(10), d, 120),
      FORMAT(d, 'yyyy-MM-dd')
    FROM dbo.dtTest;

    それでは、これらのさまざまな手法を使用して個々のクエリを実行してみましょう。それらを5回ごとに実行し、次のバリエーションを実行します。

    1. 1,000行すべてを選択
    2. クラスター化されたインデックスキーで並べ替えられたTOP(1)を選択する
    3. 変数への割り当て(フルスキャンを強制しますが、SSMSレンダリングがパフォーマンスに干渉するのを防ぎます)

    スクリプトは次のとおりです。

    -- select all 1,000 rows
    GO
    SELECT d FROM dbo.dtTest;
    GO 5
    SELECT d = CONVERT(DATE, d) FROM dbo.dtTest;
    GO 5
    SELECT d = CONVERT(CHAR(10), d, 120) FROM dbo.dtTest;
    GO 5
    SELECT d = FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest;
    GO 5
     
    -- select top 1
    GO
    SELECT TOP (1) d FROM dbo.dtTest ORDER BY d;
    GO 5
    SELECT TOP (1) CONVERT(DATE, d) FROM dbo.dtTest ORDER BY d;
    GO 5
    SELECT TOP (1) CONVERT(CHAR(10), d, 120) FROM dbo.dtTest ORDER BY d;
    GO 5
    SELECT TOP (1) FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest ORDER BY d;
    GO 5
     
    -- force scan but leave SSMS mostly out of it
    GO
    DECLARE @d DATE;
    SELECT @d = d FROM dbo.dtTest;
    GO 5
    DECLARE @d DATE;
    SELECT @d = CONVERT(DATE, d) FROM dbo.dtTest;
    GO 5
    DECLARE @d CHAR(10);
    SELECT @d = CONVERT(CHAR(10), d, 120) FROM dbo.dtTest;
    GO 5
    DECLARE @d CHAR(10);
    SELECT @d = FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest;
    GO 5

    これで、次のクエリでパフォーマンスを測定できます(私のシステムはかなり静かです。あなたのシステムでは、execution_countよりも高度なフィルタリングを実行する必要があるかもしれません。 ):

    SELECT 
      [t] = CONVERT(CHAR(255), t.[text]), 
      s.total_elapsed_time, 
      avg_elapsed_time = CONVERT(DECIMAL(12,2),s.total_elapsed_time / 5.0),
      s.total_worker_time, 
      avg_worker_time = CONVERT(DECIMAL(12,2),s.total_worker_time / 5.0),
      s.total_clr_time
    FROM sys.dm_exec_query_stats AS s 
    CROSS APPLY sys.dm_exec_sql_text(s.[sql_handle]) AS t
    WHERE s.execution_count = 5
      AND t.[text] LIKE N'%dbo.dtTest%'
    ORDER BY s.last_execution_time;

    私の場合の結果はかなり一貫していました:

    クエリ(切り捨て) 期間(マイクロ秒)
    total_elapsed avg_elapsed total_clr
    1,000行を選択 SELECT d FROM dbo.dtTest ORDER BY d; 1,170 234.00 0
    SELECT d = CONVERT(DATE, d) FROM dbo.dtTest ORDER BY d; 2,437 487.40 0
    SELECT d = CONVERT(CHAR(10), d, 120) FROM dbo.dtTest ORD ... 151,521 30,304.20 0
    SELECT d = FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest ORDER ... 240,152 48,030.40 107,258
    SELECT TOP (1) SELECT TOP (1) d FROM dbo.dtTest ORDER BY d; 251 50.20 0
    SELECT TOP (1) CONVERT(DATE, d) FROM dbo.dtTest ORDER BY ... 440 88.00 0
    SELECT TOP (1) CONVERT(CHAR(10), d, 120) FROM dbo.dtTest ... 301 60.20 0
    SELECT TOP (1) FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest O ... 1,094 218.80 589
    Assign variable DECLARE @d DATE; SELECT @d = d FROM dbo.dtTest; 639 127.80 0
    DECLARE @d DATE; SELECT @d = CONVERT(DATE, d) FROM dbo.d ... 644 128.80 0
    DECLARE @d CHAR(10); SELECT @d = CONVERT(CHAR(10), d, 12 ... 1,972 394.40 0
    DECLARE @d CHAR(10); SELECT @d = FORMAT(d, 'yyyy-MM-dd') ... 118,062 23,612.40 98,556

     

    And to visualize the avg_elapsed_timeを視覚化する 出力(クリックして拡大):

    FORMAT()は明らかに敗者です:avg_elapsed_time結果(マイクロ秒)

    これらの結果から(再び)学ぶことができること:

    1. 何よりもまず、 FORMAT() 高い
    2. FORMAT() 確かに、C#のような他の言語のメソッドと一貫性のある、より柔軟性があり、より直感的なメソッドを提供できます。ただし、そのオーバーヘッドに加えて、CONVERT() スタイル番号はわかりにくく、網羅的ではありません。FORMAT()なので、とにかく古いアプローチを使用する必要があるかもしれません。 SQLServer2012以降でのみ有効です。
    3. スタンバイのCONVERT()でも メソッドは大幅にコストがかかる可能性があります(ただし、SSMSが結果をレンダリングする必要がある場合は、非常に深刻です。日付値とは異なる方法で文字列を処理することは明らかです)。
    4. データベースからdatetime値を直接取得するだけが、常に最も効率的でした。アプリケーションがプレゼンテーション層で希望どおりに日付をフォーマットするのにかかる追加の時間をプロファイリングする必要があります-SQLServerがかなりのフォーマットに関与することを望まない可能性が高いです(実際、多くの人が主張します)これがそのロジックが常に属する場所です。

    ここではマイクロ秒しか話していませんが、1,000行しか話していません。それを実際のテーブルサイズにスケールアウトすると、間違ったフォーマットアプローチを選択した場合の影響は壊滅的なものになる可能性があります。

    自分のマシンでこの実験を試してみたい場合は、サンプルスクリプトをアップロードしました:FormatIsNiceAndAllBut.sql_.zip


    1. MySQLおよびMariaDBのデータベースセキュリティ監視

    2. 各行の列値に基づいて行を繰り返す

    3. Postgresql COPY TO STDIN with CSVは、競合時にどのように更新しますか?

    4. デフォルトでOracleデータベースを使用する場合、Hibernateはブールデータ型を何にマップしますか?