Loại và số lượng khóa có được và phát hành trong quá trình thực thi truy vấn có thể có tác động đáng ngạc nhiên đến hiệu suất (khi sử dụng mức cách ly khóa như cam kết đọc mặc định) ngay cả khi không có chờ đợi hoặc chặn xảy ra. Không có thông tin nào trong kế hoạch thực thi để chỉ ra số lượng hoạt động khóa trong khi thực thi, điều này khiến việc phát hiện khó hơn khi khóa quá nhiều gây ra sự cố hiệu suất.
Để khám phá một số hành vi khóa ít được biết đến trong SQL Server, tôi sẽ sử dụng lại các truy vấn và dữ liệu mẫu từ bài đăng cuối cùng của tôi về tính toán trung bình. Trong bài đăng đó, tôi đã đề cập rằng OFFSET
giải pháp trung bình được nhóm lại cần một PAGLOCK
rõ ràng khóa gợi ý để tránh bị mất nặng vào con trỏ lồng nhau giải pháp, vì vậy chúng ta hãy bắt đầu bằng cách xem xét chi tiết lý do cho điều đó.
Giải pháp trung bình được nhóm OFFSET
Thử nghiệm trung vị được nhóm đã sử dụng lại dữ liệu mẫu từ bài báo trước đó của Aaron Bertrand. Tập lệnh dưới đây tạo lại thiết lập hàng triệu hàng này, bao gồm mười nghìn bản ghi cho mỗi trăm người bán hàng tưởng tượng:
CREATE TABLE dbo.Sales ( SalesPerson integer NOT NULL, Amount integer NOT NULL ); WITH X AS ( SELECT TOP (100) V.number FROM master.dbo.spt_values AS V GROUP BY V.number ) INSERT dbo.Sales WITH (TABLOCKX) ( SalesPerson, Amount ) SELECT X.number, ABS(CHECKSUM(NEWID())) % 99 FROM X CROSS JOIN X AS X2 CROSS JOIN X AS X3; CREATE CLUSTERED INDEX cx ON dbo.Sales (SalesPerson, Amount);
SQL Server 2012 (và mới hơn) OFFSET
giải pháp do Peter Larsson tạo ra như sau (không có bất kỳ gợi ý khóa nào):
DECLARE @s datetime2 = SYSUTCDATETIME(); DECLARE @Result AS table ( SalesPerson integer PRIMARY KEY, Median float NOT NULL ); INSERT @Result (SalesPerson, Median) SELECT d.SalesPerson, w.Median FROM ( SELECT SalesPerson, COUNT(*) AS y FROM dbo.Sales GROUP BY SalesPerson ) AS d CROSS APPLY ( SELECT AVG(0E + Amount) FROM ( SELECT z.Amount FROM dbo.Sales AS z WHERE z.SalesPerson = d.SalesPerson ORDER BY z.Amount OFFSET (d.y - 1) / 2 ROWS FETCH NEXT 2 - d.y % 2 ROWS ONLY ) AS f ) AS w (Median); SELECT Peso = DATEDIFF(MILLISECOND, @s, SYSUTCDATETIME());
Các phần quan trọng của kế hoạch sau khi thực hiện được trình bày dưới đây:
Với tất cả dữ liệu bắt buộc trong bộ nhớ, truy vấn này thực thi trong 580 mili giây trung bình trên máy tính xách tay của tôi (chạy SQL Server 2014 Gói Dịch vụ 1). Hiệu suất của truy vấn này có thể được cải thiện thành 320 mili giây chỉ bằng cách thêm gợi ý khóa mức độ chi tiết của trang vào bảng Bán hàng trong truy vấn con áp dụng:
DECLARE @s datetime2 = SYSUTCDATETIME(); DECLARE @Result AS table ( SalesPerson integer PRIMARY KEY, Median float NOT NULL ); INSERT @Result (SalesPerson, Median) SELECT d.SalesPerson, w.Median FROM ( SELECT SalesPerson, COUNT(*) AS y FROM dbo.Sales GROUP BY SalesPerson ) AS d CROSS APPLY ( SELECT AVG(0E + Amount) FROM ( SELECT z.Amount FROM dbo.Sales AS z WITH (PAGLOCK) -- NEW! WHERE z.SalesPerson = d.SalesPerson ORDER BY z.Amount OFFSET (d.y - 1) / 2 ROWS FETCH NEXT 2 - d.y % 2 ROWS ONLY ) AS f ) AS w (Median); SELECT Peso = DATEDIFF(MILLISECOND, @s, SYSUTCDATETIME());
Kế hoạch thực thi không thay đổi (tất nhiên là ngoài văn bản gợi ý khóa trong showplan XML):
Phân tích khóa trung vị được nhóm
Lời giải thích cho sự cải thiện đáng kể về hiệu suất do PAGLOCK
gợi ý là khá đơn giản, ít nhất là ban đầu.
Nếu chúng tôi theo dõi hoạt động khóa theo cách thủ công trong khi truy vấn này thực thi, chúng tôi thấy rằng không có gợi ý về mức độ chi tiết khóa trang, SQL Server thu thập và phát hành hơn nửa triệu khóa cấp hàng trong khi tìm kiếm chỉ mục được phân nhóm. Không có sự ngăn chặn để đổ lỗi; chỉ cần có được và giải phóng nhiều khóa này sẽ thêm chi phí đáng kể cho việc thực thi truy vấn này. Yêu cầu khóa cấp độ trang làm giảm đáng kể hoạt động khóa, dẫn đến hiệu suất được cải thiện nhiều.
Vấn đề hiệu suất khóa của kế hoạch cụ thể này được giới hạn trong tìm kiếm chỉ mục theo nhóm trong kế hoạch ở trên. Quá trình quét toàn bộ chỉ mục theo nhóm (được sử dụng để tính toán số lượng hàng hiện có cho mỗi người bán hàng) sử dụng tự động khóa cấp độ trang. Đây là một thông tin thú vị. Hành vi khóa chi tiết của công cụ SQL Server không được ghi lại trong Books Online ở bất kỳ mức độ nào, nhưng các thành viên khác nhau của nhóm SQL Server đã đưa ra một số nhận xét chung trong nhiều năm, bao gồm thực tế là các bản quét không hạn chế có xu hướng bắt đầu lấy trang khóa, trong khi các hoạt động nhỏ hơn có xu hướng bắt đầu bằng khóa hàng.
Trình tối ưu hóa truy vấn cung cấp một số thông tin cho công cụ lưu trữ, bao gồm ước tính số lượng, gợi ý nội bộ cho mức độ cô lập và mức độ chi tiết khóa, mà các tối ưu hóa nội bộ có thể được áp dụng một cách an toàn, v.v. Một lần nữa, những chi tiết này không được ghi lại trong Sách Trực tuyến. Cuối cùng, bộ máy lưu trữ sử dụng nhiều thông tin khác nhau để quyết định khóa nào được yêu cầu tại thời điểm chạy và mức độ chi tiết của chúng.
Một lưu ý nhỏ và hãy nhớ rằng chúng ta đang nói về một truy vấn thực thi ở mức cách ly giao dịch đã cam kết đã đọc khóa mặc định, hãy lưu ý rằng các khóa hàng được thực hiện mà không có gợi ý về mức độ chi tiết sẽ không chuyển sang khóa bảng trong trường hợp này. Điều này là do hành vi bình thường trong đã cam kết đọc là giải phóng khóa trước đó ngay trước khi có được khóa tiếp theo, có nghĩa là chỉ một khóa hàng chia sẻ duy nhất (với các khóa chia sẻ mục đích cấp cao hơn được liên kết) sẽ được giữ tại bất kỳ thời điểm cụ thể nào. Vì số lượng khóa hàng được giữ đồng thời không bao giờ đạt đến ngưỡng, không có khóa nào được cố gắng leo thang.
Giải pháp trung bình đơn OFFSET
Kiểm tra hiệu suất cho một phép tính trung bình duy nhất sử dụng một bộ dữ liệu mẫu khác, được sao chép lại từ bài báo trước đó của Aaron. Tập lệnh bên dưới tạo một bảng với mười triệu hàng dữ liệu giả ngẫu nhiên:
CREATE TABLE dbo.obj ( id integer NOT NULL IDENTITY(1,1), val integer NOT NULL ); INSERT dbo.obj WITH (TABLOCKX) (val) SELECT TOP (10000000) AO.[object_id] FROM sys.all_columns AS AC CROSS JOIN sys.all_objects AS AO CROSS JOIN sys.all_objects AS AO2 WHERE AO.[object_id] > 0 ORDER BY AC.[object_id]; CREATE UNIQUE CLUSTERED INDEX cx ON dbo.obj(val, id);
OFFSET
giải pháp là:
DECLARE @Start datetime2 = SYSUTCDATETIME(); DECLARE @Count bigint = 10000000 --( -- SELECT COUNT_BIG(*) -- FROM dbo.obj AS O --); SELECT Median = AVG(1.0 * SQ1.val) FROM ( SELECT O.val FROM dbo.obj AS O ORDER BY O.val OFFSET (@Count - 1) / 2 ROWS FETCH NEXT 1 + (1 - @Count % 2) ROWS ONLY ) AS SQ1; SELECT Peso = DATEDIFF(MILLISECOND, @Start, SYSUTCDATETIME());
Kế hoạch sau khi thực hiện là:
Truy vấn này thực thi trong 910 mili giây trung bình trên máy thử nghiệm của tôi. Hiệu suất không thay đổi nếu một PAGLOCK
gợi ý được thêm vào, nhưng lý do cho điều đó không phải như những gì bạn có thể nghĩ…
Phân tích khóa trung vị đơn
Bạn có thể mong đợi công cụ lưu trữ vẫn chọn các khóa chia sẻ ở cấp độ trang, do quá trình quét chỉ mục theo nhóm, giải thích tại sao lại có PAGLOCK
gợi ý không có hiệu lực. Trên thực tế, việc theo dõi các khóa được thực hiện trong khi truy vấn này thực thi cho thấy rằng không có khóa dùng chung (S) nào được thực hiện, ở bất kỳ mức độ chi tiết nào . Các khóa duy nhất được thực hiện là chia sẻ mục đích (IS) ở cấp đối tượng và cấp trang.
Lời giải thích cho hành vi này có hai phần. Điều đầu tiên cần chú ý là Quét chỉ mục theo cụm nằm bên dưới một toán tử Hàng đầu trong kế hoạch thực thi. Điều này có ảnh hưởng quan trọng đến các ước tính về số lượng, như được thể hiện trong kế hoạch trước khi thực hiện (ước tính):
OFFSET
và FETCH
các mệnh đề trong truy vấn tham chiếu một biểu thức và một biến, do đó, trình tối ưu hóa truy vấn đoán số hàng sẽ cần trong thời gian chạy. Dự đoán tiêu chuẩn cho Top là một trăm hàng. Tất nhiên, đây là một dự đoán khủng khiếp, nhưng nó đủ để thuyết phục công cụ lưu trữ khóa ở mức độ chi tiết của hàng thay vì ở cấp độ trang.
Nếu chúng tôi vô hiệu hóa hiệu ứng "mục tiêu hàng" của toán tử Hàng đầu bằng cách sử dụng cờ theo dõi được lập thành văn bản 4138, thì số hàng ước tính khi quét sẽ thay đổi thành mười triệu (vẫn sai, nhưng theo hướng khác). Điều này đủ để thay đổi quyết định về mức độ chi tiết khóa của công cụ lưu trữ, do đó, các khóa được chia sẻ ở cấp độ trang (lưu ý, không phải khóa được chia sẻ có mục đích) được thực hiện:
DECLARE @Start datetime2 = SYSUTCDATETIME(); DECLARE @Count bigint = 10000000 --( -- SELECT COUNT_BIG(*) -- FROM dbo.obj AS O --); SELECT Median = AVG(1.0 * SQ1.val) FROM ( SELECT O.val FROM dbo.obj AS O ORDER BY O.val OFFSET (@Count - 1) / 2 ROWS FETCH NEXT 1 + (1 - @Count % 2) ROWS ONLY ) AS SQ1 OPTION (QUERYTRACEON 4138); -- NEW! SELECT Peso = DATEDIFF(MILLISECOND, @Start, SYSUTCDATETIME());
Kế hoạch thực hiện ước tính được đưa ra dưới cờ theo dõi 4138 là:
Quay trở lại ví dụ chính, ước tính hàng trăm do mục tiêu hàng được đoán có nghĩa là công cụ lưu trữ chọn khóa ở cấp hàng. Tuy nhiên, chúng tôi chỉ quan sát các khóa chia sẻ mục đích (IS) ở cấp bảng và trang. Các khóa cấp cao hơn này sẽ khá bình thường nếu chúng ta thấy các khóa chia sẻ (S) cấp hàng, vậy chúng đã đi đâu?
Câu trả lời là công cụ lưu trữ chứa một tối ưu hóa khác có thể bỏ qua các khóa chia sẻ cấp hàng trong một số trường hợp nhất định. Khi tối ưu hóa này được áp dụng, các khóa chia sẻ ý định cấp cao hơn vẫn được nhận.
Tóm lại, đối với truy vấn trung bình đơn:
- Việc sử dụng một biến và biểu thức trong
OFFSET
mệnh đề có nghĩa là trình tối ưu hóa đoán số lượng. - Ước tính thấp có nghĩa là bộ máy lưu trữ quyết định chiến lược khóa ở cấp độ hàng.
- Tối ưu hóa nội bộ có nghĩa là các khóa S cấp hàng được bỏ qua trong thời gian chạy, chỉ để lại các khóa IS ở cấp trang và đối tượng.
Truy vấn trung bình duy nhất sẽ có cùng một vấn đề về hiệu suất khóa hàng như trung bình được nhóm (do ước tính không chính xác của trình tối ưu hóa truy vấn) nhưng nó được lưu bằng một tối ưu hóa công cụ lưu trữ riêng biệt dẫn đến chỉ thực hiện các khóa trang và bảng được chia sẻ ý định trong thời gian chạy.
Kiểm tra trung vị được nhóm được xem lại
Bạn có thể tự hỏi tại sao Tìm kiếm chỉ mục theo cụm trong thử nghiệm trung vị được nhóm lại không tận dụng tối ưu hóa công cụ lưu trữ giống nhau để bỏ qua các khóa chia sẻ cấp hàng. Tại sao nhiều khóa hàng chia sẻ được sử dụng, làm cho PAGLOCK
gợi ý cần thiết?
Câu trả lời ngắn gọn là tối ưu hóa này không khả dụng cho INSERT...SELECT
truy vấn. Nếu chúng ta chạy SELECT
của riêng nó (tức là không ghi kết quả vào bảng) và không có PAGLOCK
gợi ý, tối ưu hóa bỏ qua khóa hàng là đã áp dụng:
DECLARE @s datetime2 = SYSUTCDATETIME(); --DECLARE @Result AS table --( -- SalesPerson integer PRIMARY KEY, -- Median float NOT NULL --); --INSERT @Result -- (SalesPerson, Median) SELECT d.SalesPerson, w.Median FROM ( SELECT SalesPerson, COUNT(*) AS y FROM dbo.Sales GROUP BY SalesPerson ) AS d CROSS APPLY ( SELECT AVG(0E + Amount) FROM ( SELECT z.Amount FROM dbo.Sales AS z WHERE z.SalesPerson = d.SalesPerson ORDER BY z.Amount OFFSET (d.y - 1) / 2 ROWS FETCH NEXT 2 - d.y % 2 ROWS ONLY ) AS f ) AS w (Median); SELECT Peso = DATEDIFF(MILLISECOND, @s, SYSUTCDATETIME());
Chỉ các khóa chia sẻ mục đích (IS) cấp bảng và cấp trang được sử dụng và hiệu suất tăng lên cùng mức như khi chúng tôi sử dụng PAGLOCK
gợi ý. Tất nhiên, bạn sẽ không tìm thấy hành vi này trong tài liệu và nó có thể thay đổi bất cứ lúc nào. Tuy nhiên, điều tốt là nên biết.
Ngoài ra, trong trường hợp bạn đang thắc mắc, cờ theo dõi 4138 không ảnh hưởng đến lựa chọn mức độ chi tiết khóa của công cụ lưu trữ trong trường hợp này vì số lượng hàng ước tính khi tìm kiếm quá thấp (mỗi lần lặp áp dụng) ngay cả khi mục tiêu hàng bị vô hiệu hóa.
Trước khi đưa ra kết luận về hiệu suất của một truy vấn, hãy đảm bảo kiểm tra số lượng và loại khóa mà nó đang sử dụng trong quá trình thực thi. Mặc dù SQL Server thường chọn độ chi tiết 'đúng', nhưng đôi khi nó có thể làm sai, đôi khi có những ảnh hưởng đáng kể đến hiệu suất.