SQL Server nvarchar (max) vs nvarchar (n) memengaruhi kinerja

16

Ini adalah SQL Server 2008 R2 SP2. Saya punya 2 tabel. Keduanya identik (data dan pengindeksan), kecuali tabel pertama memiliki kolom VALUE nvarchar(max)dan yang kedua memiliki kolom yang sama dengan nvarchar(800). Kolom ini termasuk dalam indeks non-cluster. Saya juga membuat indeks berkerumun di kedua tabel. Saya juga telah membangun kembali indeks. Panjang string maksimal dalam kolom ini adalah 650.

Jika saya menjalankan kueri yang sama terhadap kedua nvarchar(800)tabel secara konsisten lebih cepat, beberapa kali lebih cepat. Yakin sepertinya mengalahkan tujuan "varchar". Tabel berisi 800.000+ baris. Kueri harus melihat sekitar 110.000 baris (yang merupakan perkiraan rencana).

Menurut statistik io, tidak ada lob yang dibaca, jadi semuanya tampak berurutan. Rencana eksekusi adalah sama, kecuali ada sedikit perbedaan dalam persentase biaya antara dua tabel dan ukuran baris diperkirakan lebih besar dengan nvarchar(max)(91 byte vs 63 byte). Jumlah bacaan juga hampir sama.

Kenapa bedanya?

===== Skema ======

 CREATE TABLE [dbo].[table1](
        [ID] [bigint] IDENTITY(1,1) NOT NULL,
        [ProductID] [bigint] NOT NULL,
        [ProductSkeletonID] [bigint] NOT NULL,
        [Value] [nvarchar](max) NOT NULL,
        [IsKeywordSearchable] [bit] NULL,
        [ValueInteger] [bigint] NULL,
        [ValueDecimal] [decimal](18, 2) NULL,
        [ValueDate] [datetime] NULL,
        [TypeOfData] [nvarchar](20) NOT NULL,
     CONSTRAINT [PK_table1] PRIMARY KEY CLUSTERED 
    (
        [ID] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

    CREATE NONCLUSTERED INDEX [IX_table1_productskeletonid] ON [dbo].[table1] 
    (
        [ProductSkeletonID] ASC
    )
    INCLUDE ( [ProductID],
    [Value]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

    CREATE TABLE [dbo].[table2](
        [ID] [bigint] IDENTITY(1,1) NOT NULL,
        [ProductID] [bigint] NOT NULL,
        [ProductSkeletonID] [bigint] NOT NULL,
        [Value] [nvarchar](800) NOT NULL,
        [IsKeywordSearchable] [bit] NULL,
        [ValueInteger] [bigint] NULL,
        [ValueDecimal] [decimal](18, 2) NULL,
        [ValueDate] [datetime] NULL,
        [TypeOfData] [nvarchar](20) NOT NULL,
     CONSTRAINT [PK_table2] PRIMARY KEY CLUSTERED 
    (
        [ID] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
    ) ON [PRIMARY]

    CREATE NONCLUSTERED INDEX [IX_table2_productskeletonid] ON [dbo].[table2] 
    (
        [ProductSkeletonID] ASC
    )
    INCLUDE ( [ProductID],
    [Value]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]


CREATE TABLE [dbo].[table_results](
    [SearchID] [bigint] NOT NULL,
    [RowNbr] [int] NOT NULL,
    [ProductID] [bigint] NOT NULL,
    [PermissionList] [varchar](250) NULL,
    [SearchWeight] [int] NULL,
 CONSTRAINT [PK_table_results] PRIMARY KEY NONCLUSTERED 
(
    [SearchID] ASC,
    [RowNbr] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_table_results_SearchID] ON [dbo].[cart_product_searches_results] 
(
    [SearchID] ASC
)
INCLUDE ( [ProductID]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

===== Permintaan Table1 ======

    SELECT cppev.ProductSkeletonID, cppev.Value, COUNT(*) AS Value FROM table1 cppev
    JOIN search_results cpsr ON cppev.ProductID = cpsr.ProductID AND cpsr.SearchID = 227568 
    WHERE cppev.ProductSkeletonID in (3191, 3160, 3158, 3201)
    GROUP BY cppev.ProductSkeletonID, cppev.Value

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'table1'. Scan count 4, logical reads 582, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'table_results'. Scan count 1, logical reads 82, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    SQL Server Execution Times:
       CPU time = 1373 ms,  elapsed time = 1576 ms.

 |--Compute Scalar(DEFINE:([Expr1005]=CONVERT_IMPLICIT(int,[Expr1008],0)))
       |--Stream Aggregate(GROUP BY:([cppev].[Value], [cppev].[ProductSkeletonID]) DEFINE:([Expr1008]=Count(*)))
            |--Sort(ORDER BY:([cppev].[Value] ASC, [cppev].[ProductSkeletonID] ASC))
                 |--Hash Match(Inner Join, HASH:([cpsr].[ProductID])=([cppev].[ProductID]), RESIDUAL:([dbo].[table1].[ProductID] as [cppev].[ProductID]=[dbo].[table_results].[ProductID] as [cpsr].[ProductID]))
                      |--Index Seek(OBJECT:([dbo].[table_results].[IX_table_results_SearchID] AS [cpsr]), SEEK:([cpsr].[SearchID]=(227568)) ORDERED FORWARD)
                      |--Index Seek(OBJECT:([dbo].[table1].[IX_table1_productskeletonid] AS [cppev]), SEEK:([cppev].[ProductSkeletonID]=(3158) OR [cppev].[ProductSkeletonID]=(3160) OR [cppev].[ProductSkeletonID]=(3191) OR [cppev].[ProductSkeletonID]=(3201)) ORDERED FORWARD)

===== Permintaan Table2 ======

    SELECT cppev.ProductSkeletonID, cppev.Value, COUNT(*) AS Value FROM table2 cppev
    JOIN table_results cpsr ON cppev.ProductID = cpsr.ProductID AND cpsr.SearchID = 227568 
    WHERE cppev.ProductSkeletonID in (3191, 3160, 3158, 3201)
    GROUP BY cppev.ProductSkeletonID, cppev.Value

    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'table2'. Scan count 4, logical reads 584, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'table_results'. Scan count 1, logical reads 82, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    SQL Server Execution Times:
       CPU time = 484 ms,  elapsed time = 796 ms.

  |--Compute Scalar(DEFINE:([Expr1005]=CONVERT_IMPLICIT(int,[Expr1008],0)))
       |--Stream Aggregate(GROUP BY:([cppev].[Value], [cppev].[ProductSkeletonID]) DEFINE:([Expr1008]=Count(*)))
            |--Sort(ORDER BY:([cppev].[Value] ASC, [cppev].[ProductSkeletonID] ASC))
                 |--Hash Match(Inner Join, HASH:([cpsr].[ProductID])=([cppev].[ProductID]), RESIDUAL:([auctori_core_v40_D].[dbo].[table2].[ProductID] as [cppev].[ProductID]= [dbo].[table2].[ProductID] as [cpsr].[ProductID]))
                      |--Index Seek(OBJECT:([dbo].[table_results].[IX_table_results_SearchID] AS [cpsr]), SEEK:([cpsr].[SearchID]=(227568)) ORDERED FORWARD)
                      |--Index Seek(OBJECT:([dbo].[table2].[IX_table2_productskeletonid] AS [cppev]), SEEK:([cppev].[ProductSkeletonID]=(3158) OR [cppev].[ProductSkeletonID]=(3160) OR [cppev].[ProductSkeletonID]=(3191) OR [cppev].[ProductSkeletonID]=(3201)) ORDERED FORWARD)
Brian Bohl
sumber
4
Tolong, skema tabel, sampel atau data indikatif dan rencana eksekusi untuk setiap permintaan. "Saya tidak berpikir ..." tidak sama dengan "Pasti tidak ada ...".
Mark Storey-Smith
Versi SQL Server apa yang Anda miliki?
Max Vernon
Lihat technet.microsoft.com/en-us/library/ms189087(v=SQL.105).aspx untuk detail tentang penyimpanan dalam baris untuk bidang nvarchar (maks). Seberapa besar data aktual di bidang tersebut?
Max Vernon
Saya memperbarui pos untuk mengatasi umpan balik di atas.
Brian Bohl

Jawaban:

14

Anda melihat biaya overhead penggunaan MAXjenis.

Meskipun NVARCHAR(MAX)identik dengan NVARCHAR(n)TSQL dan dapat disimpan dalam baris, ini ditangani secara terpisah oleh mesin penyimpanan karena dapat didorong keluar baris. Ketika off-row itu adalah LOB_DATAunit alokasi, bukan ROW_OVERFLOW_DATAunit alokasi dan kami dapat mengasumsikan dari pengamatan Anda bahwa ini membawa overhead.

Anda dapat melihat dua jenis disimpan secara internal berbeda dengan spelunking DBCC PAGE kecil . Mark Rasmussen memposting contoh halaman dump yang menunjukkan perbedaan dalam Apa Ukuran LOB Pointer untuk (MAX) Jenis Seperti Varchar, Varbinary, Etc?

Kami mungkin dapat berasumsi bahwa itu adalah GROUP BYpada MAXkolom yang menyebabkan perbedaan kinerja dalam kasus Anda. Saya belum menguji operasi lain pada suatu MAXtipe tetapi mungkin menarik untuk melakukannya dan melihat apakah hasil yang serupa terlihat.

Mark Storey-Smith
sumber
Jadi Anda mengatakan ada pemrosesan ekstra membaca [BLOB Inline Data] vs a plain 'ol varchar? Saya mengharapkan overhead yang signifikan jika berbunyi baris, tetapi semua data ini sebaris (digunakan dbcc ind). Dan mengapa menurut Anda kelompok dengan mengeluarkan ini?
Brian Bohl
Sedikit overhead untuk membacanya, banyak untuk perhitungan misalnya, mis GROUP BY. @RemusRusanu mungkin bisa menawarkan beberapa wawasan (semoga dia akan melihat ping).
Mark Storey-Smith
Saya menemukan artikel lain yang mendokumentasikan perilaku yang sama, bahkan pada persamaan dan suka. Saya ingin tahu apakah nvarchar (max) menggunakan algoritma yang kurang efisien.
Brian Bohl