Sự hiện diện của trường XML khiến hầu hết dữ liệu bảng được đặt trên các trang LOB_DATA (thực tế ~ 90% các trang của bảng là LOB_DATA).
Chỉ có cột XML trong bảng không có hiệu ứng đó. Đó là sự hiện diện của dữ liệu XML , trong một số điều kiện nhất định , khiến một phần dữ liệu của một hàng được lưu trữ ngoài hàng, trên các trang LOB_DATA. Và trong khi một (hoặc có thể một số ;-) có thể lập luận rằng duh, thì XML
cột ngụ ý rằng thực sự sẽ có dữ liệu XML, không đảm bảo rằng dữ liệu XML sẽ cần được lưu trữ ngoài hàng: trừ khi hàng đã được điền khá nhiều bên ngoài bất kỳ dữ liệu XML nào, các tài liệu nhỏ (tối đa 8000 byte) có thể phù hợp với nhau và không bao giờ truy cập trang LOB_DATA.
Tôi có đúng không khi nghĩ rằng các trang LOB_DATA có thể gây ra các bản quét chậm không chỉ vì kích thước của chúng mà còn bởi vì SQL Server không thể quét chỉ mục được nhóm một cách hiệu quả khi có nhiều trang LOB_DATA trong bảng?
Quét liên quan đến việc nhìn vào tất cả các hàng. Tất nhiên, khi một trang dữ liệu được đọc, tất cả dữ liệu liên tiếp sẽ được đọc, ngay cả khi bạn đã chọn một tập hợp con của các cột. Sự khác biệt với dữ liệu LOB là nếu bạn không chọn cột đó, thì dữ liệu ngoài hàng sẽ không được đọc. Do đó, thật không công bằng khi đưa ra kết luận về việc SQL Server có thể quét Chỉ mục cụm này hiệu quả như thế nào vì bạn đã không kiểm tra chính xác điều đó (hoặc bạn đã kiểm tra một nửa số đó). Bạn đã chọn tất cả các cột, bao gồm cột XML và như bạn đã đề cập, đó là nơi chứa hầu hết dữ liệu.
Vì vậy, chúng tôi đã biết rằng SELECT TOP 1000 *
bài kiểm tra không chỉ đơn thuần là đọc một loạt các trang dữ liệu 8k, tất cả trong một hàng, mà thay vào đó nhảy đến các vị trí khác trên mỗi hàng . Cấu trúc chính xác của dữ liệu LOB đó có thể thay đổi dựa trên mức độ lớn của nó. Dựa trên nghiên cứu được hiển thị ở đây ( Kích thước của Con trỏ LOB cho các loại (MAX) như Varchar, Varbinary, Etc? ), Có hai loại phân bổ LOB ngoài hàng:
- Root nội tuyến - đối với dữ liệu từ 8001 đến 40.000 (thực sự là 42.000) byte, cho phép không gian, sẽ có 1 đến 5 con trỏ (24 - 72 byte) IN ROW trỏ trực tiếp vào (các) trang LOB.
- TEXT_TREE - đối với dữ liệu trên 42.000 byte hoặc nếu 1 đến 5 con trỏ không khớp với nhau, thì sẽ chỉ có một con trỏ 24 byte đến trang bắt đầu của danh sách các con trỏ tới các trang LOB (tức là " text_tree "trang).
Một trong hai tình huống này xảy ra mỗi khi bạn truy xuất dữ liệu LOB lớn hơn 8000 byte hoặc không phù hợp. Tôi đã đăng một tập lệnh thử nghiệm trên PasteBin.com (tập lệnh T-SQL để kiểm tra phân bổ và đọc LOB ) cho thấy 3 loại phân bổ LOB (dựa trên kích thước của dữ liệu) cũng như hiệu ứng của từng loại đó đối với logic và đọc vật lý. Trong trường hợp của bạn, nếu dữ liệu XML thực sự ít hơn 42.000 byte mỗi hàng, thì không có dữ liệu nào trong số đó (hoặc rất ít trong số đó) phải ở trong cấu trúc TEXT_TREE kém hiệu quả nhất.
Nếu bạn muốn kiểm tra SQL Server có thể quét Chỉ mục cụm đó nhanh như thế nào, hãy thực hiện SELECT TOP 1000
nhưng chỉ định một hoặc nhiều cột không bao gồm cột XML đó. Điều đó ảnh hưởng đến kết quả của bạn như thế nào? Nó sẽ khá nhanh hơn một chút.
nó có được coi là hợp lý để có cấu trúc bảng / mẫu dữ liệu như vậy không?
Cho rằng chúng ta có một mô tả không đầy đủ về cấu trúc bảng dữ liệu và mẫu dữ liệu thực tế, bất kỳ câu trả lời nào có thể không tối ưu tùy thuộc vào những chi tiết bị thiếu đó là gì. Với ý nghĩ đó, tôi sẽ nói rằng rõ ràng không có gì bất hợp lý về cấu trúc bảng hoặc mẫu dữ liệu của bạn.
Tôi có thể (trong ứng dụng ac #) nén XML từ 20KB xuống ~ 2,5KB và lưu trữ nó trong cột VARBINARY, ngăn chặn việc sử dụng các trang dữ liệu LOB. Tốc độ này CHỌN 20 lần trong các thử nghiệm của tôi.
Điều đó làm cho việc chọn tất cả các cột hoặc thậm chí chỉ là dữ liệu XML (hiện tại VARBINARY
) nhanh hơn, nhưng thực sự làm tổn thương các truy vấn không chọn dữ liệu "XML". Giả sử bạn có khoảng 50 byte trong các cột khác và có FILLFACTOR
100, thì:
Không nén: 15k XML
dữ liệu cần 2 trang LOB_DATA, sau đó yêu cầu 2 con trỏ cho Root nội tuyến. Con trỏ đầu tiên là 24 byte và thứ hai là 12, với tổng số 36 byte được lưu liên tiếp cho dữ liệu XML. Tổng kích thước hàng là 86 byte và bạn có thể điều chỉnh khoảng 93 hàng trong số đó trên trang dữ liệu 8060 byte. Do đó, 1 triệu hàng yêu cầu 10,753 trang dữ liệu.
Nén tùy chỉnh: 2,5k VARBINARY
dữ liệu sẽ phù hợp với hàng. Tổng kích thước hàng là 2610 (2,5 * 1024 = 2560) byte và bạn chỉ có thể vừa 3 hàng trong số đó trên trang dữ liệu 8060 byte. Do đó, 1 triệu hàng yêu cầu 333.334 trang dữ liệu.
Ergo, thực hiện các kết quả nén tùy chỉnh trong việc tăng 30 lần trong các trang dữ liệu cho Chỉ mục cụm. Có nghĩa là, tất cả các truy vấn sử dụng một Clustered Index quét tại có khoảng 322.500 hơn các trang dữ liệu để đọc. Vui lòng xem phần chi tiết bên dưới để biết thêm các phân nhánh thực hiện kiểu nén này.
Tôi sẽ thận trọng chống lại bất kỳ tái cấu trúc dựa trên hiệu suất của SELECT TOP 1000 *
. Đó không có khả năng là một truy vấn mà ứng dụng thậm chí sẽ đưa ra và không nên được sử dụng làm cơ sở duy nhất cho (các) tối ưu hóa không cần thiết.
Để biết thêm thông tin chi tiết và thử nghiệm nhiều hơn để thử, vui lòng xem phần bên dưới.
Câu hỏi này không thể được đưa ra một câu trả lời dứt khoát, nhưng ít nhất chúng ta có thể đạt được một số tiến bộ và đề xuất nghiên cứu bổ sung để giúp chúng ta tiến gần hơn để tìm ra vấn đề chính xác (lý tưởng dựa trên bằng chứng).
Những gì chúng ta biết:
- Bảng có khoảng 1 triệu hàng
- Kích thước bảng xấp xỉ 15 GB
- Bảng chứa một
XML
cột và một vài cột khác của các loại: INT
, BIGINT
, UNIQUEIDENTIFIER
, "vv"
XML
cột "kích thước" là, trung bình khoảng 15k
- Sau khi chạy
DBCC DROPCLEANBUFFERS
, phải mất 20 - 25 giây để hoàn thành truy vấn sau:SELECT TOP 1000 * FROM TABLE
- Chỉ số cụm đang được quét
- Phân mảnh trên Chỉ số cụm là gần 0%
Những gì chúng tôi nghĩ rằng chúng tôi biết:
- Không có hoạt động đĩa khác ngoài các truy vấn này. Bạn có chắc không? Ngay cả khi không có truy vấn người dùng khác, có hoạt động nền diễn ra không? Có các quy trình bên ngoài để SQL Server chạy trên cùng một máy có thể chiếm một số IO không? Có thể không có, nhưng nó không rõ ràng chỉ dựa trên thông tin được cung cấp.
- 15 MB dữ liệu XML đang được trả lại. Con số này dựa trên cái gì? Một ước tính xuất phát từ 1000 hàng nhân trung bình 15k dữ liệu XML mỗi hàng? Hoặc tổng hợp theo chương trình những gì đã nhận được cho truy vấn đó? Nếu nó chỉ là một ước tính, tôi sẽ không dựa vào nó vì việc phân phối dữ liệu XML có thể thậm chí không theo cách được ngụ ý bởi một mức trung bình đơn giản.
Nén XML có thể giúp. Làm thế nào chính xác bạn sẽ thực hiện nén trong .NET? Thông qua các lớp GZipStream hoặc DeflateStream ? Đây không phải là một lựa chọn chi phí bằng không. Nó chắc chắn sẽ nén một số dữ liệu theo một tỷ lệ lớn, nhưng nó cũng sẽ cần nhiều CPU hơn vì bạn sẽ cần một quy trình bổ sung để nén / giải nén dữ liệu mỗi lần. Kế hoạch này cũng sẽ loại bỏ hoàn toàn khả năng của bạn:
- truy vấn dữ liệu XML thông qua
.nodes
, .value
, .query
, và .modify
chức năng XML.
lập chỉ mục dữ liệu XML.
Xin lưu ý (vì bạn đã đề cập rằng XML là "rất dư thừa") rằng XML
kiểu dữ liệu đã được tối ưu hóa ở chỗ nó lưu trữ các tên thành phần và thuộc tính trong từ điển, gán ID chỉ mục số nguyên cho từng mục và sau đó sử dụng ID số nguyên đó trong toàn bộ tài liệu (do đó nó không lặp lại tên đầy đủ cho mỗi lần sử dụng và cũng không lặp lại nó dưới dạng thẻ đóng cho các thành phần). Các dữ liệu thực tế cũng có không gian trắng bên ngoài được loại bỏ. Đây là lý do tại sao các tài liệu XML được trích xuất không giữ lại cấu trúc ban đầu của chúng và tại sao các phần tử trống trích xuất <element />
ngay cả khi chúng đi vào như<element></element>
. Vì vậy, bất kỳ lợi ích nào từ việc nén qua GZip (hoặc bất cứ thứ gì khác) sẽ chỉ được tìm thấy bằng cách nén các giá trị phần tử và / hoặc thuộc tính, có diện tích bề mặt nhỏ hơn nhiều có thể được cải thiện hơn hầu hết mong đợi và rất có thể không đáng để mất khả năng như đã lưu ý trực tiếp ở trên.
Cũng xin lưu ý rằng việc nén dữ liệu XML và lưu trữ VARBINARY(MAX)
kết quả sẽ không loại bỏ quyền truy cập LOB, nó sẽ làm giảm dữ liệu. Tùy thuộc vào kích thước của phần còn lại của dữ liệu trên hàng, giá trị nén có thể vừa với hàng hoặc nó vẫn có thể yêu cầu các trang LOB.
Thông tin đó, trong khi hữu ích, gần như không đủ. Có rất nhiều yếu tố ảnh hưởng đến hiệu suất truy vấn, vì vậy chúng tôi cần một bức tranh chi tiết hơn nhiều về những gì đang diễn ra.
Những gì chúng ta không biết, nhưng cần phải:
- Tại sao hiệu suất của
SELECT *
vật chất? Đây có phải là một mẫu mà bạn sử dụng trong mã. Nếu vậy, tại sao?
- Hiệu suất của việc chỉ chọn cột XML là gì? Số liệu thống kê và thời gian nếu bạn làm chỉ :
SELECT TOP 1000 XmlColumn FROM TABLE;
?
Mất bao nhiêu trong 20 - 25 giây để trả lại 1000 hàng này có liên quan đến các yếu tố mạng (lấy dữ liệu qua dây) và mức độ liên quan đến các yếu tố máy khách (hiển thị khoảng 15 MB cộng với phần còn lại của không Dữ liệu XML vào lưới trong SSMS, hoặc có thể lưu vào đĩa)?
Việc bao gồm hai khía cạnh của hoạt động đôi khi có thể được thực hiện bằng cách đơn giản là không trả lại dữ liệu. Bây giờ, người ta có thể nghĩ rằng chọn vào Bảng tạm thời hoặc Biến bảng, nhưng điều này sẽ chỉ giới thiệu một vài biến mới (ví dụ: I / O đĩa tempdb
, ghi nhật ký giao dịch, có thể tự động tăng trưởng dữ liệu tempdb và / hoặc tệp nhật ký không gian trong vùng đệm, v.v.). Tất cả những yếu tố mới thực sự có thể làm tăng thời gian truy vấn. Thay vào đó, tôi thường lưu trữ các cột thành các biến (của kiểu dữ liệu thích hợp; không SQL_VARIANT
) được ghi đè lên với mỗi hàng mới (nghĩa là SELECT @Column1 = tab.Column1,...
).
TUY NHIÊN , như đã được @PaulWhite chỉ ra trong DBA.StackExchange Q & A này, Logical đọc khác nhau khi truy cập cùng một dữ liệu LOB , với nghiên cứu bổ sung của riêng tôi được đăng trên PasteBin ( tập lệnh T-SQL để kiểm tra các kịch bản khác nhau cho các lần đọc LOB ) , LOB không được truy cập một cách nhất quán giữa SELECT
, SELECT INTO
, SELECT @XmlVariable = XmlColumn
, SELECT @XmlVariable = XmlColumn.query(N'/')
, và SELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Vì vậy, các tùy chọn của chúng tôi bị giới hạn hơn một chút ở đây, nhưng đây là những gì có thể được thực hiện:
- Loại trừ các sự cố mạng bằng cách thực hiện truy vấn trên máy chủ đang chạy SQL Server, trong SSMS hoặc SQLCMD.EXE.
- Loại trừ các sự cố của máy khách trong SSMS bằng cách đi tới Tùy chọn truy vấn -> Kết quả -> Lưới và kiểm tra tùy chọn cho "Hủy kết quả sau khi thực hiện". Xin lưu ý rằng tùy chọn này sẽ ngăn TẤT CẢ đầu ra, bao gồm các thông báo, nhưng vẫn có thể hữu ích để loại trừ thời gian SSMS cần phân bổ bộ nhớ cho mỗi hàng và sau đó vẽ nó vào lưới.
Ngoài ra, bạn có thể thực hiện truy vấn thông qua SQLCMD.EXE và hướng đầu ra đi đến đâu thông qua : -o NUL:
.
- Có Loại Chờ liên quan đến truy vấn này không? Nếu có, Loại Chờ đó là gì?
Có gì là thực tế kích thước dữ liệu cho các XML
cột được trả lại ? Kích thước trung bình của cột đó trên toàn bộ bảng không thực sự quan trọng nếu các hàng "TOP 1000" chứa một phần lớn không tương xứng trong tổng số XML
dữ liệu. Nếu bạn muốn biết về TOP 1000 hàng, hãy nhìn vào những hàng đó. Vui lòng chạy như sau:
SELECT TOP 1000 tab.*,
SUM(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [TotalXmlKBytes],
AVG(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [AverageXmlKBytes]
STDEV(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [StandardDeviationForXmlKBytes]
FROM SchemaName.TableName tab;
- Các chính xác schema bảng. Vui lòng cung cấp báo cáo đầy đủ
CREATE TABLE
, bao gồm tất cả các chỉ mục.
- Kế hoạch truy vấn? Đó có phải là một cái gì đó mà bạn có thể đăng? Thông tin đó có thể sẽ không thay đổi bất cứ điều gì, nhưng tốt hơn là nên biết rằng nó sẽ không thay vì đoán rằng nó sẽ không sai và;
- Có sự phân mảnh vật lý / bên ngoài trên tệp dữ liệu? Mặc dù điều này có thể không phải là một yếu tố lớn ở đây, vì bạn đang sử dụng "SATA cấp độ người tiêu dùng" và không phải SSD hoặc thậm chí là siêu đắt tiền, hiệu ứng của các lĩnh vực được đặt hàng tối ưu sẽ đáng chú ý hơn, đặc biệt là số lượng các lĩnh vực đó cần phải đọc tăng lên.
Các kết quả chính xác của truy vấn sau đây là gì:
SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(),
OBJECT_ID(N'dbo.SchemaName.TableName'), 1, 0, N'LIMITED');
CẬP NHẬT
Nó xảy ra với tôi rằng tôi nên thử tái tạo kịch bản này để xem liệu tôi có trải nghiệm hành vi tương tự không. Vì vậy, tôi đã tạo một bảng có nhiều cột (tương tự như mô tả mơ hồ trong Câu hỏi) và sau đó điền vào đó 1 triệu hàng và cột XML có khoảng 15k dữ liệu mỗi hàng (xem mã bên dưới).
Những gì tôi tìm thấy là thực hiện SELECT TOP 1000 * FROM TABLE
hoàn thành trong 8 giây lần đầu tiên và 2 - 4 giây mỗi lần sau đó (vâng, thực hiện DBCC DROPCLEANBUFFERS
trước mỗi lần chạy SELECT *
truy vấn). Và máy tính xách tay vài năm tuổi của tôi không nhanh: SQL Server 2012 SP2 Developer Edition, 64 bit, RAM 6 GB, lõi kép 2,5 Ghz Core i5 và ổ đĩa SATA 5400 RPM. Tôi cũng đang chạy SSMS 2014, SQL Server Express 2014, Chrome và một số thứ khác.
Dựa trên thời gian phản hồi của hệ thống của tôi, tôi sẽ nhắc lại rằng chúng tôi cần thêm thông tin (ví dụ cụ thể về bảng và dữ liệu, kết quả của các thử nghiệm được đề xuất, v.v.) để giúp thu hẹp nguyên nhân của thời gian phản hồi 20 - 25 giây mà bạn đang thấy
SET ANSI_NULLS, NOCOUNT ON;
GO
IF (OBJECT_ID(N'dbo.XmlReadTest') IS NOT NULL)
BEGIN
PRINT N'Dropping table...';
DROP TABLE dbo.XmlReadTest;
END;
PRINT N'Creating table...';
CREATE TABLE dbo.XmlReadTest
(
ID INT NOT NULL IDENTITY(1, 1),
Col2 BIGINT,
Col3 UNIQUEIDENTIFIER,
Col4 DATETIME,
Col5 XML,
CONSTRAINT [PK_XmlReadTest] PRIMARY KEY CLUSTERED ([ID])
);
GO
DECLARE @MaxSets INT = 1000,
@CurrentSet INT = 1;
WHILE (@CurrentSet <= @MaxSets)
BEGIN
RAISERROR(N'Populating data (1000 sets of 1000 rows); Set # %d ...',
10, 1, @CurrentSet) WITH NOWAIT;
INSERT INTO dbo.XmlReadTest (Col2, Col3, Col4, Col5)
SELECT TOP 1000
CONVERT(BIGINT, CRYPT_GEN_RANDOM(8)),
NEWID(),
GETDATE(),
N'<test>'
+ REPLICATE(CONVERT(NVARCHAR(MAX), CRYPT_GEN_RANDOM(1), 2), 3750)
+ N'</test>'
FROM [master].[sys].all_columns sac1;
IF ((@CurrentSet % 100) = 0)
BEGIN
RAISERROR(N'Executing CHECKPOINT ...', 10, 1) WITH NOWAIT;
CHECKPOINT;
END;
SET @CurrentSet += 1;
END;
--
SELECT COUNT(*) FROM dbo.XmlReadTest; -- Verify that we have 1 million rows
-- O.P. states that the "clustered index fragmentation is close to 0%"
ALTER INDEX [PK_XmlReadTest] ON dbo.XmlReadTest REBUILD WITH (FILLFACTOR = 90);
CHECKPOINT;
--
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS IO, TIME ON;
SELECT TOP 1000 * FROM dbo.XmlReadTest;
SET STATISTICS IO, TIME OFF;
/*
Scan count 1, logical reads 21, physical reads 1, read-ahead reads 4436,
lob logical reads 5676, lob physical reads 1, lob read-ahead reads 3967.
SQL Server Execution Times:
CPU time = 171 ms, elapsed time = 8329 ms.
*/
Và, vì chúng tôi muốn tính thời gian đọc các trang không phải LOB, tôi đã chạy truy vấn sau để chọn tất cả trừ cột XML (một trong những thử nghiệm tôi đã đề xuất ở trên). Điều này trở lại trong 1,5 giây khá nhất quán.
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
SET STATISTICS IO, TIME ON;
SELECT TOP 1000 ID, Col2, Col3, Col4 FROM dbo.XmlReadTest;
SET STATISTICS IO, TIME OFF;
/*
Scan count 1, logical reads 21, physical reads 1, read-ahead reads 4436,
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 1666 ms.
*/
Kết luận (hiện tại)
Dựa trên nỗ lực tái tạo kịch bản của bạn, tôi không nghĩ rằng chúng ta có thể chỉ ra ổ đĩa SATA hoặc I / O không tuần tự là nguyên nhân chính của 20 - 25 giây, đặc biệt là vì chúng ta vẫn không biết truy vấn trả về nhanh như thế nào khi không bao gồm cột XML. Và tôi đã không thể sao chép số lượng lớn các lần đọc logic (không phải LOB) mà bạn đang hiển thị, nhưng tôi có cảm giác rằng tôi cần thêm nhiều dữ liệu vào mỗi hàng theo điều đó và tuyên bố về:
~ 90% trang bảng là LOB_DATA
Bảng của tôi có 1 triệu hàng, mỗi hàng chỉ có hơn 15k dữ liệu XML và sys.dm_db_index_physical_stats
cho thấy có 2 triệu trang LOB_DATA. 10% còn lại sau đó sẽ là 222k trang dữ liệu IN_law, nhưng tôi chỉ có 11.630 trang. Vì vậy, một lần nữa, chúng ta cần thêm thông tin về lược đồ bảng thực tế và dữ liệu thực tế.