Nếu bạn sử dụng phân vùng bảng với một hoặc nhiều phân vùng được lưu trữ trên nhóm tệp chỉ đọc, câu lệnh cập nhật và xóa SQL có thể không thành công với lỗi. Tất nhiên, đây là hành vi được mong đợi nếu bất kỳ sửa đổi nào yêu cầu ghi vào nhóm tệp chỉ đọc; tuy nhiên, cũng có thể gặp phải tình trạng lỗi này khi các thay đổi bị hạn chế đối với các nhóm tệp được đánh dấu là đọc-ghi.
Cơ sở dữ liệu mẫu
Để giải thích vấn đề này, chúng tôi sẽ tạo một cơ sở dữ liệu đơn giản với một nhóm tệp tùy chỉnh duy nhất mà sau này chúng tôi sẽ đánh dấu là chỉ đọc. Lưu ý rằng bạn sẽ cần thêm đường dẫn tên tệp cho phù hợp với phiên bản thử nghiệm của mình.
USE master; GO CREATE DATABASE Test; GO -- This filegroup will be marked read-only later ALTER DATABASE Test ADD FILEGROUP ReadOnlyFileGroup; GO -- Add a file to the new filegroup ALTER DATABASE Test ADD FILE ( NAME = 'Test_RO', FILENAME = '<...your path...>\MSSQL\DATA\Test_ReadOnly.ndf' ) TO FILEGROUP ReadOnlyFileGroup;
Chức năng và lược đồ phân vùng
Bây giờ chúng tôi sẽ tạo một hàm và lược đồ phân vùng cơ bản sẽ định hướng các hàng có dữ liệu trước ngày 1 tháng 1 năm 2000 vào phân vùng chỉ đọc. Dữ liệu sau này sẽ được giữ trong nhóm tệp chính đọc-ghi:
USE Test; GO CREATE PARTITION FUNCTION PF (datetime) AS RANGE RIGHT FOR VALUES ({D '2000-01-01'}); GO CREATE PARTITION SCHEME PS AS PARTITION PF TO (ReadOnlyFileGroup, [PRIMARY]);
Đặc tả bên phải phạm vi có nghĩa là các hàng có giá trị ranh giới từ ngày 1 tháng 1 năm 2000 sẽ nằm trong phân vùng đọc-ghi.
Bảng và chỉ mục được phân vùng
Bây giờ chúng ta có thể tạo bảng thử nghiệm của mình:
CREATE TABLE dbo.Test ( dt datetime NOT NULL, c1 integer NOT NULL, c2 integer NOT NULL, CONSTRAINT PK_dbo_Test__c1_dt PRIMARY KEY CLUSTERED (dt) ON PS (dt) ) ON PS (dt); GO CREATE NONCLUSTERED INDEX IX_dbo_Test_c1 ON dbo.Test (c1) ON PS (dt); GO CREATE NONCLUSTERED INDEX IX_dbo_Test_c2 ON dbo.Test (c2) ON PS (dt);
Bảng có khóa chính được phân nhóm trên cột ngày giờ và cũng được phân vùng trên cột đó. Không có chỉ mục nào được phân biệt trên hai cột số nguyên khác, được phân chia theo cùng một cách (các chỉ mục được căn chỉnh với bảng cơ sở).
Dữ liệu mẫu
Cuối cùng, chúng tôi thêm một vài hàng dữ liệu mẫu và làm cho phân vùng dữ liệu trước năm 2000 chỉ đọc:
INSERT dbo.Test WITH (TABLOCKX) (dt, c1, c2) VALUES ({D '1999-12-31'}, 1, 1), -- Read only ({D '2000-01-01'}, 2, 2); -- Writable GO ALTER DATABASE Test MODIFY FILEGROUP ReadOnlyFileGroup READ_ONLY;
Bạn có thể sử dụng các câu lệnh cập nhật thử nghiệm sau để xác nhận rằng không thể sửa đổi dữ liệu trong phân vùng chỉ đọc, trong khi dữ liệu có dt
giá trị vào hoặc sau ngày 1 tháng 1 năm 2000 có thể được ghi vào:
-- Will fail, as expected UPDATE dbo.Test SET c2 = 1 WHERE dt = {D '1999-12-31'}; -- Will succeed, as expected UPDATE dbo.Test SET c2 = 999 WHERE dt = {D '2000-01-01'}; -- Reset the value of c2 UPDATE dbo.Test SET c2 = 2 WHERE dt = {D '2000-01-01'};
Một sự cố không mong muốn
Chúng tôi có hai hàng:một hàng chỉ đọc (1999-12-31); và một lần đọc-ghi (2000-01-01):
Bây giờ hãy thử truy vấn sau. Nó xác định cùng một hàng "2000-01-01" có thể ghi mà chúng tôi vừa cập nhật thành công, nhưng sử dụng một vị từ mệnh đề where khác:
UPDATE dbo.Test SET c2 = 2 WHERE c1 = 2;
Kế hoạch ước tính (trước khi thực hiện) là:
Bốn (!) Tính vô hướng không quan trọng đối với cuộc thảo luận này. Chúng được sử dụng để xác định xem chỉ mục không phân nhóm có cần được duy trì cho mỗi hàng đến với toán tử Cập nhật chỉ mục được phân cụm hay không.
Điều thú vị hơn là tuyên bố cập nhật này không thành công với một lỗi tương tự như:
Msg 652, Mức 16, Trạng thái 1Chỉ mục "PK_dbo_Test__c1_dt" cho bảng "dbo.Test" (RowsetId 72057594039042048) nằm trên nhóm tệp chỉ đọc ("ReadOnlyFileGroup"), không thể sửa đổi.
Không phải loại bỏ phân vùng
Nếu bạn đã làm việc với phân vùng trước đây, bạn có thể nghĩ rằng 'loại bỏ phân vùng' có thể là lý do. Logic sẽ diễn ra như sau:
Trong các câu lệnh trước, một giá trị theo nghĩa đen cho cột phân vùng đã được cung cấp trong mệnh đề where, vì vậy SQL Server sẽ có thể xác định ngay lập tức (các) phân vùng nào cần truy cập. Bằng cách thay đổi mệnh đề where để không còn tham chiếu đến cột phân vùng, chúng tôi đã buộc SQL Server truy cập vào mọi phân vùng bằng cách sử dụng Quét chỉ mục theo cụm.
Nói chung, điều đó là đúng, nhưng nó không phải là lý do khiến câu lệnh cập nhật không thành công ở đây.
Hành vi mong đợi là SQL Server sẽ có thể đọc từ bất kỳ và tất cả các phân vùng trong quá trình thực thi truy vấn. Thao tác sửa đổi dữ liệu chỉ nên thất bại nếu công cụ thực thi thực sự cố gắng sửa đổi một hàng được lưu trữ trên nhóm tệp chỉ đọc.
Để minh họa, chúng ta hãy thực hiện một thay đổi nhỏ đối với truy vấn trước đó:
UPDATE dbo.Test SET c2 = 2, dt = dt WHERE c1 = 2;
Mệnh đề where giống hệt như trước. Sự khác biệt duy nhất là bây giờ chúng tôi (cố ý) đặt cột phân vùng bằng với chính nó. Điều này sẽ không thay đổi giá trị được lưu trữ trong cột đó, nhưng nó ảnh hưởng đến kết quả. Cập nhật bây giờ thành công (mặc dù với một kế hoạch thực hiện phức tạp hơn):
Trình tối ưu hóa đã giới thiệu các toán tử Tách, Sắp xếp và Thu gọn mới, đồng thời thêm công cụ cần thiết để duy trì từng chỉ mục không gộp có thể bị ảnh hưởng một cách riêng biệt (sử dụng chiến lược rộng hoặc theo mỗi chỉ mục).
Thuộc tính Quét chỉ mục theo cụm cho thấy rằng cả hai phân vùng của bảng đã được truy cập khi đọc:
Ngược lại, Bản cập nhật chỉ mục theo cụm cho thấy rằng chỉ phân vùng đọc-ghi mới được truy cập để ghi:
Mỗi toán tử Cập nhật chỉ mục không bị trộn lẫn hiển thị thông tin tương tự:chỉ phân vùng có thể ghi (# 2) được sửa đổi tại thời điểm chạy, do đó không xảy ra lỗi.
Lý do được tiết lộ
Kế hoạch mới thành công không bởi vì các chỉ mục không phân tán được duy trì riêng biệt; cũng không có phải (trực tiếp) do sự kết hợp Phân tách-Sắp xếp-Thu gọn cần thiết để tránh các lỗi chính trùng lặp tạm thời trong chỉ mục duy nhất hay không.
Lý do thực sự là điều tôi đã đề cập ngắn gọn trong bài viết trước của mình, "Tối ưu hóa các truy vấn cập nhật" - một tối ưu hóa nội bộ được gọi là Chia sẻ tập hợp dòng . Khi điều này được sử dụng, Bản cập nhật chỉ mục theo cụm sẽ chia sẻ cùng một bộ hàng của công cụ lưu trữ cơ bản như một Bản quét chỉ mục theo cụm, Tìm kiếm hoặc Tra cứu khóa ở phía đọc của kế hoạch.
Với tính năng tối ưu hóa Rowset Sharing, SQL Server kiểm tra nhóm tệp ngoại tuyến hoặc chỉ đọc khi đọc. Trong các kế hoạch mà Cập nhật chỉ mục theo cụm sử dụng một bộ hàng riêng biệt, kiểm tra ngoại tuyến / chỉ đọc chỉ được thực hiện cho mỗi hàng tại trình lặp cập nhật (hoặc xóa).
Cách giải quyết không có giấy tờ
Trước tiên, hãy tìm hiểu những thứ thú vị, lập dị nhưng không thực tế.
Tối ưu hóa tập hợp hàng chia sẻ chỉ có thể được áp dụng khi tuyến đường từ tìm kiếm chỉ mục được phân nhóm, quét hoặc tra cứu khóa là một đường dẫn . Không có toán tử chặn hoặc bán chặn nào được phép. Nói một cách khác, mỗi hàng phải có thể lấy từ nguồn đọc để ghi đích trước khi hàng tiếp theo được đọc.
Xin nhắc lại, đây là dữ liệu mẫu, câu lệnh và kế hoạch thực thi cho trường hợp không thành công cập nhật lại:
--Change the read-write row UPDATE dbo.Test SET c2 = 2 WHERE c1 = 2;
Bảo vệ Halloween
Một cách để giới thiệu một nhà điều hành chặn vào kế hoạch là yêu cầu Bảo vệ Halloween (HP) rõ ràng cho bản cập nhật này. Tách phần đọc khỏi phần ghi bằng toán tử chặn sẽ ngăn không cho sử dụng tối ưu hóa chia sẻ bộ hàng (không có đường ống dẫn). Cờ theo dõi không có giấy tờ và không được hỗ trợ (chỉ dành cho hệ thống thử nghiệm!) 8692 thêm một Bộ đệm Bảng Eager cho HP rõ ràng:
-- Works (explicit HP) UPDATE dbo.Test SET c2 = 2 WHERE c1 = 2 OPTION (QUERYTRACEON 8692);
Kế hoạch thực thi thực tế (khả dụng vì lỗi không còn được đưa ra) là:
Sắp xếp trong kết hợp Phân tách-Sắp xếp-Thu gọn được thấy trong bản cập nhật thành công trước đó cung cấp tính năng chặn cần thiết để tắt tính năng chia sẻ bộ hàng trong trường hợp đó.
Cờ chia sẻ dấu vết chống dòng chảy
Có một cờ theo dõi không có tài liệu khác vô hiệu hóa tối ưu hóa chia sẻ bộ hàng. Điều này có lợi thế là không giới thiệu một nhà khai thác chặn có khả năng tốn kém. Tất nhiên, nó không thể được sử dụng trong thực tế (trừ khi bạn liên hệ với Bộ phận hỗ trợ của Microsoft và nhận được thông tin nào đó bằng văn bản khuyên bạn nên bật nó, tôi cho là vậy). Tuy nhiên, vì mục đích giải trí, đây là cờ theo dõi 8746 đang hoạt động:
-- Works (no rowset sharing) UPDATE dbo.Test SET c2 = 2 WHERE c1 = 2 OPTION (QUERYTRACEON 8746);
Kế hoạch thực hiện thực tế cho câu lệnh đó là:
Hãy thử nghiệm với các giá trị khác nhau (những giá trị thực sự thay đổi các giá trị được lưu trữ nếu bạn muốn) để thuyết phục bản thân về sự khác biệt ở đây. Như đã đề cập trong bài viết trước của tôi, bạn cũng có thể sử dụng cờ theo dõi không có giấy tờ 8666 để hiển thị thuộc tính chia sẻ bộ row trong kế hoạch thực thi.
Nếu bạn muốn thấy lỗi chia sẻ tập hợp hàng bằng một câu lệnh xóa, chỉ cần thay thế bản cập nhật và đặt các mệnh đề bằng một lệnh xóa, trong khi sử dụng cùng một mệnh đề where.
Các giải pháp được hỗ trợ
Có bất kỳ cách tiềm năng nào để đảm bảo rằng chia sẻ tập hợp hàng không được áp dụng trong các truy vấn trong thế giới thực mà không sử dụng cờ theo dõi. Bây giờ bạn đã biết vấn đề cốt lõi yêu cầu một kế hoạch đọc và ghi chỉ mục được chia sẻ và liên kết với nhau, bạn có thể đưa ra kế hoạch của riêng mình. Mặc dù vậy, có một vài ví dụ đặc biệt đáng xem ở đây.
Chỉ mục bắt buộc / Chỉ mục bao gồm
Một ý tưởng tự nhiên là buộc bên đọc của kế hoạch sử dụng chỉ mục không phân tán thay vì chỉ mục theo nhóm. Chúng tôi không thể thêm gợi ý chỉ mục trực tiếp vào truy vấn thử nghiệm như đã viết, nhưng việc tạo bí danh cho bảng cho phép điều này:
UPDATE T SET c2 = 2 FROM dbo.Test AS T WITH (INDEX(IX_dbo_Test_c1)) WHERE c1 = 2;
Điều này có vẻ giống như giải pháp mà trình tối ưu hóa truy vấn nên chọn ngay từ đầu, vì chúng ta có một chỉ mục không phân biệt trên cột vị từ mệnh đề where c1. Kế hoạch thực thi cho thấy lý do tại sao trình tối ưu hóa đã chọn như nó đã làm:
Chi phí của Tra cứu Khóa đủ để thuyết phục trình tối ưu hóa sử dụng chỉ mục được phân cụm để đọc. Việc tra cứu là cần thiết để tìm nạp giá trị hiện tại của cột c2, do đó, Compute Scalars có thể quyết định xem chỉ mục nonclustered có cần được duy trì hay không.
Thêm cột c2 vào chỉ mục không phân biệt (khóa hoặc bao gồm) sẽ tránh được vấn đề. Trình tối ưu hóa sẽ chọn chỉ mục hiện đang bao phủ thay vì chỉ mục theo nhóm.
Điều đó nói rằng, không phải lúc nào bạn cũng có thể đoán trước được cột nào sẽ cần thiết hoặc bao gồm tất cả chúng ngay cả khi đã biết tập hợp. Hãy nhớ rằng cột này là cần thiết vì c2 nằm trong mệnh đề đặt của tuyên bố cập nhật. Nếu các truy vấn là đặc biệt (ví dụ:do người dùng gửi hoặc được tạo bởi một công cụ), thì mọi chỉ mục không phân bổ sẽ cần phải bao gồm tất cả các cột để biến đây trở thành một tùy chọn mạnh mẽ.
Một điều thú vị về kế hoạch với Key Lookup ở trên là nó không tạo ra một lỗi. Điều này xảy ra bất chấp việc Cập nhật chỉ mục theo cụm và Tra cứu khóa bằng cách sử dụng Tập hợp được chia sẻ. Lý do là Tìm kiếm chỉ mục không phân biệt định vị hàng có c1 =2 trước Key Lookup chạm vào chỉ mục nhóm. Kiểm tra tập hợp hàng được chia sẻ cho nhóm tệp ngoại tuyến / chỉ đọc vẫn được thực hiện khi tra cứu, nhưng nó không chạm vào phân vùng chỉ đọc, do đó không có lỗi nào được đưa ra. Là điểm quan tâm cuối cùng (có liên quan), hãy lưu ý rằng Tìm kiếm chỉ mục chạm vào cả hai phân vùng, nhưng Tìm kiếm chìa khóa chỉ chạm đến một phân vùng.
Loại trừ phân vùng chỉ đọc
Một giải pháp nhỏ là dựa vào loại bỏ phân vùng để phía đọc của kế hoạch không bao giờ chạm vào phân vùng chỉ đọc. Điều này có thể được thực hiện với một vị từ rõ ràng, chẳng hạn như một trong hai điều này:
UPDATE dbo.Test SET c2 = 2 WHERE c1 = 2 AND dt >= {D '2000-01-01'}; UPDATE dbo.Test SET c2 = 2 WHERE c1 = 2 AND $PARTITION.PF(dt) > 1; -- Not partition #1
Trong trường hợp không thể hoặc không thuận tiện, thay đổi mọi truy vấn để thêm vị từ loại bỏ phân vùng, các giải pháp khác như cập nhật thông qua chế độ xem có thể phù hợp. Ví dụ:
CREATE VIEW dbo.TestWritablePartitions WITH SCHEMABINDING AS -- Only the writable portion of the table SELECT T.dt, T.c1, T.c2 FROM dbo.Test AS T WHERE $PARTITION.PF(dt) > 1; GO -- Succeeds UPDATE dbo.TestWritablePartitions SET c2 = 2 WHERE c1 = 2;
Một bất lợi của việc sử dụng dạng xem là cập nhật hoặc xóa nhắm mục tiêu đến phần chỉ đọc của bảng cơ sở sẽ thành công mà không có hàng nào bị ảnh hưởng, thay vì không thành công do lỗi. Thay vì kích hoạt trên bảng hoặc chế độ xem có thể là một giải pháp thay thế cho điều đó trong một số trường hợp, nhưng cũng có thể gây ra nhiều vấn đề hơn… nhưng tôi lạc đề.
Như đã đề cập trước đây, có rất nhiều giải pháp được hỗ trợ tiềm năng. Mục đích của bài viết này là chỉ ra cách chia sẻ tập hợp hàng đã gây ra lỗi cập nhật không mong muốn.