Nhận một kế hoạch truy vấn thực tế khi phân vùng với khối lượng dữ liệu thấp


9

Chúng tôi đang sử dụng phân vùng để giảm số lượng chặn trải nghiệm hệ thống OLTP của chúng tôi do khóa, với sơ đồ phân vùng chia các bảng làm việc thành 100 phân vùng dựa trên id khách hàng. Chúng tôi đang tìm kiếm trong quá trình thử nghiệm tuy nhiên các kế hoạch thực hiện không được chọn theo cách chúng tôi mong đợi.

Kịch bản thử nghiệm là một khách hàng với 300.000 bản ghi liên hệ (mỗi dữ liệu của liên hệ được chia thành hai bảng), tất cả nằm trong một phân vùng duy nhất, với truy vấn tìm 500 hàng cụ thể trong phân vùng của khách hàng. Bạn sẽ mong đợi một cái gì đó giống như một trận đấu băm để loại bỏ 299.500 không mong muốn sẽ khởi động khá sớm trong kế hoạch, nhưng có vẻ như SQL Server đang chọn lấy số lượng bản ghi cho toàn bộ bảng và tính trung bình trên tất cả các phân vùng trước khi xem xét cách nhiều bản ghi sẽ được xử lý, điều này khiến nó chọn một vòng lặp lồng nhau và loại bỏ các bản ghi không mong muốn sau này trong quá trình. Thông thường, việc này mất 9 lần miễn là cùng một truy vấn đối với các bảng không được phân vùng.

Điều kỳ lạ là việc thêm một tùy chọn (biên dịch lại) vào phần chọn sẽ đưa ra một kế hoạch hợp lý, nhưng tôi không biết tại sao điều này lại tạo ra sự khác biệt. Đây không phải là một thủ tục được lưu trữ và trong các thử nghiệm, chúng tôi xóa bộ đệm thủ tục trước khi thực hiện mỗi lần chạy thử.

Hành vi này không được nhìn thấy khi các bảng liên quan không được phân vùng, tức là một kế hoạch phù hợp được chọn mỗi lần vì số lượng hàng ước tính khớp với số thực tế

Bất kỳ hiểu biết về hành vi này sẽ được đánh giá cao.

Thiết lập lược đồ:

USE [Scratch]
GO
CREATE SCHEMA part
GO
CREATE PARTITION FUNCTION [ContactPartition](smallint) AS RANGE LEFT FOR VALUES (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98)
GO
CREATE PARTITION SCHEME [ContactPartitionScheme] AS PARTITION [ContactPartition] ALL TO ([PRIMARY])
GO
CREATE TABLE [part].[Contact](
    [ContactId] [int] IDENTITY(1,1) NOT NULL,
    [CustomerPartitionKey] [smallint] NOT NULL,
    [CustomerId] [int] NOT NULL,
    [OptOut] [bit] NOT NULL,
 CONSTRAINT [cn_pk_cluContact_CustomerPartitionKey_ContactId] PRIMARY KEY CLUSTERED 
(
    [CustomerPartitionKey] ASC,
    [ContactId] ASC
) ON [ContactPartitionScheme]([CustomerPartitionKey])
) ON [ContactPartitionScheme]([CustomerPartitionKey])
GO
CREATE TABLE [part].[ContactIdentifier](
    [ContactIdentifierId] [int] IDENTITY(1,1) NOT NULL,
    [CustomerPartitionKey] [smallint] NOT NULL,
    [ContactId] [int] NOT NULL,
    [Identifier] [nvarchar](256) NOT NULL,
    [CustomerId] [int] NOT NULL,
 CONSTRAINT [cn_pk_cluContactIdentifier_CustomerPartitionKey_ContactId] PRIMARY KEY CLUSTERED 
(
    [CustomerPartitionKey] ASC,
    [ContactId] ASC
) ON [ContactPartitionScheme]([CustomerPartitionKey])
) ON [ContactPartitionScheme]([CustomerPartitionKey])
GO
CREATE NONCLUSTERED INDEX [idx_ncContactIdentifier_CustomerPartitionKey_ContactId] ON [part].[ContactIdentifier]
(
    [CustomerPartitionKey] ASC,
    [ContactId] ASC
)
 ON [ContactPartitionScheme]([CustomerPartitionKey])
GO
CREATE NONCLUSTERED INDEX [idx_ncContactIdentifier_CustomerPartitionKey_IdentifierType_Identifier] ON [part].[ContactIdentifier]
(
    [CustomerPartitionKey] ASC,
    [Identifier] ASC
) ON [ContactPartitionScheme]([CustomerPartitionKey])
GO
CREATE UNIQUE NONCLUSTERED INDEX [idx_ncuContactIdentifier_CustomerId_CustomerPartitionKey_Identifier] ON [part].[ContactIdentifier]
(
    [CustomerId] ASC,
    [CustomerPartitionKey] ASC,
    [Identifier] ASC
) ON [ContactPartitionScheme]([CustomerPartitionKey])
GO
ALTER TABLE [part].[ContactIdentifier]  WITH NOCHECK ADD  CONSTRAINT [cn_ContactIdentifier_CustomerPartitionKey_ContactId_fk_Contact_CustomerPartitionKey_ContactId] FOREIGN KEY([CustomerPartitionKey], [ContactId])
REFERENCES [part].[Contact] ([CustomerPartitionKey], [ContactId])
GO
ALTER TABLE [part].[ContactIdentifier] NOCHECK CONSTRAINT [cn_ContactIdentifier_CustomerPartitionKey_ContactId_fk_Contact_CustomerPartitionKey_ContactId]
GO
WITH TestData AS
(
    SELECT 1 As Ordinal
    UNION ALL
    SELECT td.Ordinal + 1
    FROM TestData td
    WHERE td.Ordinal < 30000
)
INSERT INTO part.Contact (CustomerPartitionKey, CustomerId, OptOut)
SELECT 3, 3, 0
FROM TestData OPTION (MAXRECURSION 30000);
GO 10
WITH TestData AS
(
    SELECT 1 AS Ordinal, ISNULL(MAX(ContactId) + 1, 1) AS ContactId FROM part.ContactIdentifier
    UNION ALL
    SELECT td.Ordinal + 1, td.ContactId + 1 AS ContactId
    FROM TestData td
    WHERE td.Ordinal < 30000
)
INSERT INTO part.ContactIdentifier (CustomerPartitionKey, CustomerId, ContactId, Identifier)
SELECT 3, 3, ContactId, CONCAT('User ', ContactId)
FROM TestData OPTION (MAXRECURSION 30000);
GO 10

Truy vấn

USE Scratch
GO
DBCC FREEPROCCACHE
SET STATISTICS IO ON
SET STATISTICS TIME ON
GO
DECLARE @CustomerId int = 3, @CustomerPartitionKey smallint = 3

SET NOCOUNT ON

CREATE TABLE #identifiers (Ordinal int NOT NULL, Identifier nvarchar(256) NOT NULL)
INSERT INTO #identifiers
VALUES(0,N'User 0')
,(1,N'User 1')
,(2,N'User 2')
,(3,N'User 3')
,(4,N'User 4')
,(5,N'User 5')
,(6,N'User 6')
,(7,N'User 7')
,(8,N'User 8')
,(9,N'User 9')
,(10,N'User 10')
,(11,N'User 11')
,(12,N'User 12')
,(13,N'User 13')
,(14,N'User 14')
,(15,N'User 15')
,(16,N'User 16')
,(17,N'User 17')
,(18,N'User 18')
,(19,N'User 19')
,(20,N'User 20')
,(21,N'User 21')
,(22,N'User 22')
,(23,N'User 23')
,(24,N'User 24')
,(25,N'User 25')
,(26,N'User 26')
,(27,N'User 27')
,(28,N'User 28')
,(29,N'User 29')
,(30,N'User 30')
,(31,N'User 31')
,(32,N'User 32')
,(33,N'User 33')
,(34,N'User 34')
,(35,N'User 35')
,(36,N'User 36')
,(37,N'User 37')
,(38,N'User 38')
,(39,N'User 39')
,(40,N'User 40')
,(41,N'User 41')
,(42,N'User 42')
,(43,N'User 43')
,(44,N'User 44')
,(45,N'User 45')
,(46,N'User 46')
,(47,N'User 47')
,(48,N'User 48')
,(49,N'User 49')
,(50,N'User 50')
,(51,N'User 51')
,(52,N'User 52')
,(53,N'User 53')
,(54,N'User 54')
,(55,N'User 55')
,(56,N'User 56')
,(57,N'User 57')
,(58,N'User 58')
,(59,N'User 59')
,(60,N'User 60')
,(61,N'User 61')
,(62,N'User 62')
,(63,N'User 63')
,(64,N'User 64')
,(65,N'User 65')
,(66,N'User 66')
,(67,N'User 67')
,(68,N'User 68')
,(69,N'User 69')
,(70,N'User 70')
,(71,N'User 71')
,(72,N'User 72')
,(73,N'User 73')
,(74,N'User 74')
,(75,N'User 75')
,(76,N'User 76')
,(77,N'User 77')
,(78,N'User 78')
,(79,N'User 79')
,(80,N'User 80')
,(81,N'User 81')
,(82,N'User 82')
,(83,N'User 83')
,(84,N'User 84')
,(85,N'User 85')
,(86,N'User 86')
,(87,N'User 87')
,(88,N'User 88')
,(89,N'User 89')
,(90,N'User 90')
,(91,N'User 91')
,(92,N'User 92')
,(93,N'User 93')
,(94,N'User 94')
,(95,N'User 95')
,(96,N'User 96')
,(97,N'User 97')
,(98,N'User 98')
,(99,N'User 99')
,(100,N'User 100')
,(101,N'User 101')
,(102,N'User 102')
,(103,N'User 103')
,(104,N'User 104')
,(105,N'User 105')
,(106,N'User 106')
,(107,N'User 107')
,(108,N'User 108')
,(109,N'User 109')
,(110,N'User 110')
,(111,N'User 111')
,(112,N'User 112')
,(113,N'User 113')
,(114,N'User 114')
,(115,N'User 115')
,(116,N'User 116')
,(117,N'User 117')
,(118,N'User 118')
,(119,N'User 119')
,(120,N'User 120')
,(121,N'User 121')
,(122,N'User 122')
,(123,N'User 123')
,(124,N'User 124')
,(125,N'User 125')
,(126,N'User 126')
,(127,N'User 127')
,(128,N'User 128')
,(129,N'User 129')
,(130,N'User 130')
,(131,N'User 131')
,(132,N'User 132')
,(133,N'User 133')
,(134,N'User 134')
,(135,N'User 135')
,(136,N'User 136')
,(137,N'User 137')
,(138,N'User 138')
,(139,N'User 139')
,(140,N'User 140')
,(141,N'User 141')
,(142,N'User 142')
,(143,N'User 143')
,(144,N'User 144')
,(145,N'User 145')
,(146,N'User 146')
,(147,N'User 147')
,(148,N'User 148')
,(149,N'User 149')
,(150,N'User 150')
,(151,N'User 151')
,(152,N'User 152')
,(153,N'User 153')
,(154,N'User 154')
,(155,N'User 155')
,(156,N'User 156')
,(157,N'User 157')
,(158,N'User 158')
,(159,N'User 159')
,(160,N'User 160')
,(161,N'User 161')
,(162,N'User 162')
,(163,N'User 163')
,(164,N'User 164')
,(165,N'User 165')
,(166,N'User 166')
,(167,N'User 167')
,(168,N'User 168')
,(169,N'User 169')
,(170,N'User 170')
,(171,N'User 171')
,(172,N'User 172')
,(173,N'User 173')
,(174,N'User 174')
,(175,N'User 175')
,(176,N'User 176')
,(177,N'User 177')
,(178,N'User 178')
,(179,N'User 179')
,(180,N'User 180')
,(181,N'User 181')
,(182,N'User 182')
,(183,N'User 183')
,(184,N'User 184')
,(185,N'User 185')
,(186,N'User 186')
,(187,N'User 187')
,(188,N'User 188')
,(189,N'User 189')
,(190,N'User 190')
,(191,N'User 191')
,(192,N'User 192')
,(193,N'User 193')
,(194,N'User 194')
,(195,N'User 195')
,(196,N'User 196')
,(197,N'User 197')
,(198,N'User 198')
,(199,N'User 199')
,(200,N'User 200')
,(201,N'User 201')
,(202,N'User 202')
,(203,N'User 203')
,(204,N'User 204')
,(205,N'User 205')
,(206,N'User 206')
,(207,N'User 207')
,(208,N'User 208')
,(209,N'User 209')
,(210,N'User 210')
,(211,N'User 211')
,(212,N'User 212')
,(213,N'User 213')
,(214,N'User 214')
,(215,N'User 215')
,(216,N'User 216')
,(217,N'User 217')
,(218,N'User 218')
,(219,N'User 219')
,(220,N'User 220')
,(221,N'User 221')
,(222,N'User 222')
,(223,N'User 223')
,(224,N'User 224')
,(225,N'User 225')
,(226,N'User 226')
,(227,N'User 227')
,(228,N'User 228')
,(229,N'User 229')
,(230,N'User 230')
,(231,N'User 231')
,(232,N'User 232')
,(233,N'User 233')
,(234,N'User 234')
,(235,N'User 235')
,(236,N'User 236')
,(237,N'User 237')
,(238,N'User 238')
,(239,N'User 239')
,(240,N'User 240')
,(241,N'User 241')
,(242,N'User 242')
,(243,N'User 243')
,(244,N'User 244')
,(245,N'User 245')
,(246,N'User 246')
,(247,N'User 247')
,(248,N'User 248')
,(249,N'User 249')
,(250,N'User 250')
,(251,N'User 251')
,(252,N'User 252')
,(253,N'User 253')
,(254,N'User 254')
,(255,N'User 255')
,(256,N'User 256')
,(257,N'User 257')
,(258,N'User 258')
,(259,N'User 259')
,(260,N'User 260')
,(261,N'User 261')
,(262,N'User 262')
,(263,N'User 263')
,(264,N'User 264')
,(265,N'User 265')
,(266,N'User 266')
,(267,N'User 267')
,(268,N'User 268')
,(269,N'User 269')
,(270,N'User 270')
,(271,N'User 271')
,(272,N'User 272')
,(273,N'User 273')
,(274,N'User 274')
,(275,N'User 275')
,(276,N'User 276')
,(277,N'User 277')
,(278,N'User 278')
,(279,N'User 279')
,(280,N'User 280')
,(281,N'User 281')
,(282,N'User 282')
,(283,N'User 283')
,(284,N'User 284')
,(285,N'User 285')
,(286,N'User 286')
,(287,N'User 287')
,(288,N'User 288')
,(289,N'User 289')
,(290,N'User 290')
,(291,N'User 291')
,(292,N'User 292')
,(293,N'User 293')
,(294,N'User 294')
,(295,N'User 295')
,(296,N'User 296')
,(297,N'User 297')
,(298,N'User 298')
,(299,N'User 299')

SELECT 
    CI.ContactId,
    I.Ordinal,
    I.Identifier
FROM    #identifiers I
JOIN    part.ContactIdentifier AS CI ON CI.CustomerId = @CustomerId AND CI.CustomerPartitionKey = @CustomerPartitionKey AND 
                                        CI.Identifier = I.Identifier
JOIN    part.Contact AS C ON C.CustomerPartitionKey = @CustomerPartitionKey AND C.ContactId = CI.ContactId
WHERE   C.OptOut = 0

DROP TABLE #identifiers

Kế hoạch thực hiện không tốt: http://pastebin.com/Us7HY4KF


Tôi chạy cái này Tôi không nhận được vấn đề. Chúng tôi có một điểm tra cứu cho mỗi trong số 300 khách hàng được truy vấn. Điều này là cực kỳ hiệu quả. Chính xác những gì bạn muốn có thay thế? Tôi chỉ xóa phân vùng và tôi nhận được cùng một kế hoạch (như tôi mong đợi). Đây là SQL 2014 EE. Đăng kế hoạch không mong muốn.
usr

Có, sự cố dường như được khắc phục bởi Công cụ ước tính Cardinality mới trong SQL 2014. Tuy nhiên, nếu bạn chạy với TÙY CHỌN (QUERYTRACEON 9481) để sử dụng Công cụ ước tính Cardinality cũ, bạn sẽ có thể tái tạo vấn đề trong SQL 2014 EE.
Geoff Patterson

Nếu bạn có thể tạo một kế hoạch đủ tốt để trong thử nghiệm của bạn cho thấy hiệu suất truy vấn chấp nhận được, thì bạn có thể sử dụng Hướng dẫn kế hoạch như một giải pháp khả thi khác.
Kin Shah

Câu trả lời:


4

Có vẻ như SQL Server đang tạo một gói truy vấn được tham số hóa có thể hoạt động với mọi giá trị của @CustomerPartitionKey. Để làm như vậy, có vẻ như coi @CustomerPartitionKey là cả phân vùng và cột mà bạn đang tìm kiếm. Nếu chúng ta xem xét kế hoạch truy vấn nơi chúng ta có ước tính xấu (ước tính 3000 hàng, 300000 thực tế), chúng ta sẽ thấy rằng thực sự có hai vị từ tìm kiếm riêng biệt part.Contactliên quan đến @CustomerPartitionKey:

Seek Keys[1]: Prefix: PtnId1004, [Test].[part].[Contact].CustomerPartitionKey = Scalar Operator([Expr1008]), Scalar Operator([@CustomerPartitionKey])

Tôi nghĩ rằng cái sau ( [Test].[part].[Contact].CustomerPartitionKey = Scalar Operator([@CustomerPartitionKey])có thể có được ước tính đúng dựa trên việc đánh hơi tham số cho giá trị của @CustomerPartitionKey. Tuy nhiên, cái trước ( Prefix: PtnId1004 = Scalar Operator([Expr1008])) có thể không làm được, có lẽ vì Expr1008biểu thức phức tạp xử lý loại bỏ phân vùng : [Expr1008]=RangePartitionNew([@CustomerPartitionKey],(0),(0),(1),(2),...,(97),(98)).

Trong trường hợp này, có 100 phân vùng và ước tính hàng chính xác quá thấp 100 lần vì SQL Server không thể xử lý loại bỏ phân vùng theo cách thông minh mà nó xử lý tìm kiếm thực tế trên cột và sử dụng ước tính cho giá trị tham số thời gian chạy là 3. Lý thuyết này được hỗ trợ theo cách các hàng ước tính thay đổi nếu bạn loại bỏ phân vùng; nếu bạn sử dụng 90 phân vùng thay thế, ước tính sẽ là 3333,33 (300000/90).

Trong các truy vấn của riêng chúng tôi, chúng tôi thường sử dụng một nghĩa đen (ví dụ: 3trong trường hợp này) hoặc sử dụng TÙY CHỌN TÙY CHỌN bất cứ khi nào chúng tôi viết một truy vấn sẽ tận dụng việc loại bỏ phân vùng. Thực tiễn này đã hoạt động khá tốt đối với chúng tôi vì số lượng truy vấn trên hệ thống rất khiêm tốn và chi phí biên dịch truy vấn cho các truy vấn đối với các bảng được phân vùng lớn không phải là vấn đề đáng lo ngại đối với chúng tôi. Không nhất thiết là một câu trả lời thỏa mãn, nhưng nó có thể làm việc cho bạn.


Có, đáng chú ý là việc sử dụng ID phân vùng bằng chữ buộc phải có kế hoạch phù hợp. Trong trường hợp của chúng tôi, nó không thực sự khả thi do bản chất của hệ thống, tuy nhiên biết rằng biên dịch lại tùy chọn là một cách đáng tin cậy để buộc kế hoạch thực hiện thành dạng chính xác của nó là rất hữu ích. Tôi không quá lo lắng về chi phí biên dịch lại ở giai đoạn này. Tốc độ phản ứng là mối quan tâm chính.
Simon Capewell

3

Tôi có thể tái tạo kế hoạch xấu. Tôi tìm thấy ba cách giải quyết:

  1. OPTION (RECOMPILE)
  2. INNER LOOP JOIN gợi ý
  3. Một viết lại khó chịu, điên rồ:

.

SELECT y.*
FROM (VALUES (@CustomerPartitionKey)) x(CustomerPartitionKey)
CROSS APPLY (
    SELECT --TOP 300
        CI.ContactId,
        I.Ordinal,
        I.Identifier
    FROM    #identifiers I
    INNER  JOIN    part.ContactIdentifier AS CI ON CI.CustomerId = @CustomerId AND CI.CustomerPartitionKey = x.CustomerPartitionKey AND 
                                            CI.Identifier = I.Identifier
    JOIN    part.Contact AS C ON C.CustomerPartitionKey = @CustomerPartitionKey AND C.ContactId = CI.ContactId
    WHERE   C.OptOut = 0
) y
WHERE x.CustomerPartitionKey <> 0
OPTION (QUERYTRACEON 9481 /*2012 estimator*/)

Việc viết lại được lấy cảm hứng từ bài nói chuyện "Mạnh mẽ song song" của Adam Machanic. Toàn bộ truy vấn được gói trong một "vòng lặp trình điều khiển" (the CROSS APPLY). WHEREĐiều khoản vô dụng về ngữ nghĩa là bắt buộc. Tôi nghi ngờ nó làm gián đoạn một số đơn giản hóa mà SQL Server sẽ làm.

Không có cái nhìn sâu sắc thực sự, than ôi. Chỉ là những điều ngẫu nhiên tôi đã thử.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.