Máy chủ SQL chia A <> B thành A <B HOẶC A> B, mang lại kết quả lạ nếu B không xác định


26

Chúng tôi đã gặp một vấn đề thú vị với SQL Server. Hãy xem xét ví dụ repro sau:

CREATE TABLE #test (s_guid uniqueidentifier PRIMARY KEY);
INSERT INTO #test (s_guid) VALUES ('7E28EFF8-A80A-45E4-BFE0-C13989D69618');

SELECT s_guid FROM #test
WHERE s_guid = '7E28EFF8-A80A-45E4-BFE0-C13989D69618'
  AND s_guid <> NEWID();

DROP TABLE #test;

vĩ cầm

Xin vui lòng quên một lúc rằng s_guid <> NEWID()điều kiện dường như hoàn toàn vô dụng - đây chỉ là một ví dụ repro tối thiểu. Vì xác suất NEWID()khớp với một số giá trị không đổi đã cho là cực kỳ nhỏ, nên nó sẽ đánh giá thành TRUE mỗi lần.

Nhưng nó không. Chạy truy vấn này thường trả về 1 hàng, nhưng đôi khi (khá thường xuyên, hơn 1 lần trong số 10) trả về 0 hàng. Tôi đã sao chép nó với SQL Server 2008 trên hệ thống của mình và bạn có thể sao chép nó trực tuyến với fiddle được liên kết ở trên (SQL Server 2014).

Nhìn vào kế hoạch thực hiện cho thấy bộ phân tích truy vấn rõ ràng chia điều kiện thành s_guid < NEWID() OR s_guid > NEWID():

ảnh chụp màn hình kế hoạch truy vấn

... Điều này hoàn toàn giải thích tại sao đôi khi nó thất bại (nếu ID được tạo đầu tiên nhỏ hơn và ID thứ hai lớn hơn ID đã cho).

SQL Server có được phép đánh giá A <> BA < B OR A > B, ngay cả khi một trong các biểu thức là không xác định không? Nếu có, nó được ghi nhận ở đâu? Hay chúng ta đã tìm thấy một lỗi?

Thật thú vị, AND NOT (s_guid = NEWID())mang lại cùng một kế hoạch thực hiện (và cùng một kết quả ngẫu nhiên).

Chúng tôi đã tìm thấy sự cố này khi nhà phát triển muốn tùy chọn loại trừ một hàng cụ thể và được sử dụng:

s_guid <> ISNULL(@someParameter, NEWID())

như một "phím tắt" cho:

(@someParameter IS NULL OR s_guid <> @someParameter)

Tôi đang tìm tài liệu và / hoặc xác nhận lỗi. Mã này không phải là tất cả những gì có liên quan nên không cần phải có cách giải quyết.


Câu trả lời:


22

SQL Server có được phép đánh giá A <> BA < B OR A > B, ngay cả khi một trong các biểu thức là không xác định không?

Đây là một điểm gây tranh cãi và câu trả lời là "có" đủ điều kiện.

Cuộc thảo luận tốt nhất mà tôi biết đã được đưa ra để trả lời cho báo cáo lỗi Kết nối lỗi của Itzik Ben-Gan với NEWID và Biểu thức bảng , đã bị đóng vì sẽ không sửa. Kết nối đã bị loại bỏ, vì vậy liên kết có đến một kho lưu trữ web. Đáng buồn thay, rất nhiều tài liệu hữu ích đã bị mất (hoặc khó tìm hơn) bởi sự sụp đổ của Connect. Dù sao, những trích dẫn hữu ích nhất từ ​​Jim Hogg của Microsoft là:

Điều này đánh vào cốt lõi của vấn đề - tối ưu hóa có được phép thay đổi ngữ nghĩa của chương trình không? Tức là: nếu một chương trình mang lại một số câu trả lời nhất định, nhưng chạy chậm, liệu Trình tối ưu hóa truy vấn có hợp pháp làm cho chương trình đó chạy nhanh hơn mà còn thay đổi kết quả đã cho không?

Trước khi hét lên "KHÔNG!" (thiên hướng cá nhân của tôi cũng vậy :-), hãy xem xét: tin tốt là, trong 99% trường hợp, câu trả lời là như nhau. Vì vậy, Tối ưu hóa truy vấn là một chiến thắng rõ ràng. Tin xấu là, nếu truy vấn chứa mã hiệu ứng phụ, thì các gói khác nhau thực sự có thể mang lại kết quả khác nhau. Và NEWID () là một "chức năng" có tác dụng phụ (không xác định) như vậy phơi bày sự khác biệt. [Trên thực tế, nếu bạn thử nghiệm, bạn có thể nghĩ ra những người khác - ví dụ: đánh giá ngắn hạn các mệnh đề AND: làm cho mệnh đề thứ hai ném một số chia cho số học - các tối ưu hóa khác nhau có thể thực hiện mệnh đề thứ hai TRƯỚC mệnh đề thứ nhất] Điều này phản ánh Lời giải thích của Craig, ở những nơi khác trong luồng này, SqlServer không đảm bảo khi các toán tử vô hướng được thực thi.

Vì vậy, chúng tôi có một lựa chọn: nếu chúng tôi muốn đảm bảo một hành vi nhất định với sự hiện diện của mã không xác định (tác dụng phụ) - để kết quả của THAM GIA, ví dụ, tuân theo ngữ nghĩa của việc thực hiện vòng lặp lồng nhau - thì chúng tôi có thể sử dụng TÙY CHỌN thích hợp để ép buộc hành vi đó - như UC chỉ ra. Nhưng mã kết quả sẽ chạy chậm - thực tế, đó là chi phí, gây khó khăn cho Trình tối ưu hóa truy vấn.

Tất cả những gì đã nói, chúng tôi đang di chuyển Trình tối ưu hóa truy vấn theo hướng hành vi "như mong đợi" cho NEWID () - đánh đổi hiệu suất cho "kết quả như mong đợi".

Một ví dụ về sự thay đổi hành vi trong vấn đề này theo thời gian là NULLIF hoạt động không chính xác với các hàm không xác định như RAND () . Ngoài ra còn có các trường hợp tương tự khác sử dụng ví dụ COALESCEvới một truy vấn con có thể tạo ra kết quả không mong muốn và cũng đang được giải quyết dần dần.

Jim tiếp tục:

Kết thúc vòng lặp . . . Tôi đã thảo luận câu hỏi này với nhóm Dev. Và cuối cùng chúng tôi đã quyết định không thay đổi hành vi hiện tại, vì những lý do sau:

1) Trình tối ưu hóa không đảm bảo thời gian hoặc số lần thực hiện của các hàm vô hướng. Đây là một nguyên lý lâu dài. Đó là 'sự chậm trễ' cơ bản cho phép trình tối ưu hóa đủ tự do để đạt được những cải tiến đáng kể trong việc thực hiện kế hoạch truy vấn.

2) "Hành vi một lần mỗi hàng" này không phải là vấn đề mới, mặc dù nó không được thảo luận rộng rãi. Chúng tôi bắt đầu điều chỉnh hành vi của nó trở lại trong bản phát hành Yukon. Nhưng thật khó để xác định chính xác, trong mọi trường hợp, chính xác ý nghĩa của nó! Ví dụ: nó có áp dụng cho các hàng tạm thời được tính 'trên đường' cho kết quả cuối cùng không? - trong trường hợp đó rõ ràng phụ thuộc vào kế hoạch đã chọn. Hoặc nó chỉ áp dụng cho các hàng cuối cùng sẽ xuất hiện trong kết quả hoàn thành? - có một sự đệ quy khó chịu đang diễn ra ở đây, vì tôi chắc chắn bạn sẽ đồng ý!

3) Như tôi đã đề cập trước đó, chúng tôi mặc định là "tối ưu hóa hiệu suất" - tốt cho 99% trường hợp. 1% các trường hợp có thể thay đổi kết quả khá dễ dàng để phát hiện - các 'chức năng' có hiệu lực phụ như NEWID - và dễ 'sửa chữa' (do đó, giao dịch hoàn hảo). Mặc định này để "tối ưu hóa hiệu suất" một lần nữa, được thiết lập từ lâu và được chấp nhận. (Vâng, đó không phải là lập trường được các trình biên dịch chọn cho các ngôn ngữ lập trình thông thường, nhưng cũng vậy).

Vì vậy, các khuyến nghị của chúng tôi là:

a) Tránh phụ thuộc vào ngữ nghĩa không đảm bảo về thời gian và số lần thực hiện. b) Tránh sử dụng NEWID () sâu trong các biểu thức bảng. c) Sử dụng TÙY CHỌN để buộc một hành vi cụ thể (giao dịch hoàn hảo)

Hy vọng giải thích này giúp làm rõ lý do của chúng tôi để đóng lỗi này là "sẽ không sửa".


Thật thú vị, AND NOT (s_guid = NEWID())mang lại kế hoạch thực hiện tương tự

Đây là hậu quả của việc chuẩn hóa, xảy ra rất sớm trong quá trình biên dịch truy vấn. Cả hai biểu thức biên dịch thành chính xác cùng một dạng chuẩn hóa, do đó cùng một kế hoạch thực hiện được tạo ra.


Trong trường hợp này, nếu chúng tôi muốn buộc một kế hoạch cụ thể có vẻ như tránh được vấn đề, chúng tôi có thể sử dụng VỚI (FORCESCAN). Để chắc chắn, chúng ta nên sử dụng một biến để lưu trữ kết quả của NEWID () trước khi thực hiện truy vấn.
Razvan Socol

11

Đây là tài liệu (loại) ở đây:

Số lần mà một hàm được chỉ định trong truy vấn thực sự được thực thi có thể khác nhau giữa các kế hoạch thực hiện được xây dựng bởi trình tối ưu hóa. Một ví dụ là một hàm được gọi bởi một truy vấn con trong mệnh đề WHERE. Số lần truy vấn con và chức năng của nó được thực thi có thể thay đổi theo các đường dẫn truy cập khác nhau được chọn bởi trình tối ưu hóa.

Hàm do người dùng xác định

Đây không phải là hình thức truy vấn duy nhất mà kế hoạch truy vấn sẽ thực thi NEWID () nhiều lần và thay đổi kết quả. Điều này gây nhầm lẫn, nhưng thực sự quan trọng đối với NEWID () là hữu ích cho việc tạo khóa và sắp xếp ngẫu nhiên.

Điều khó hiểu nhất là không phải tất cả các hàm không xác định thực sự hoạt động như thế này. Chẳng hạn, RAND () và GETDATE () sẽ chỉ thực hiện một lần cho mỗi truy vấn.


Có bài đăng blog nào hoặc tương tự giải thích tại sao / khi động cơ sẽ chuyển đổi "không bằng" thành một phạm vi không?
Magoo

3
Không phải là tôi biết. Có thể là thông lệ bởi vì =, <>có thể được đánh giá hiệu quả so với BTree.
David Browne - Microsoft

5

Để biết giá trị của nó, nếu bạn xem tài liệu tiêu chuẩn SQL 92 cũ này , các yêu cầu xung quanh bất bình đẳng được mô tả trong phần " 8.2 <comparison predicate>" như sau:

1) Đặt X và Y là hai phần tử xây dựng giá trị hàng tương ứng> s. Đặt XV và YV lần lượt là các giá trị được biểu thị bằng X và Y.

[...]

ii) "X <> Y" là đúng khi và chỉ khi XV và YV không bằng nhau.

[...]

7) Đặt Rx và Ry là hai <constructor giá trị hàng> của <vị từ so sánh> và đặt RXi và RYi lần lượt là phần tử xây dựng giá trị hàng thứ i> của Rx và Ry. "Rx <comp op> Ry" là đúng, sai hoặc không xác định như sau:

[...]

b) "x <> Ry" là đúng khi và chỉ khi RXi <> RYi cho một số i.

[...]

h) "x <> Ry" là sai khi và chỉ khi "Rx = Ry" là đúng.

Lưu ý: Tôi đã bao gồm 7b và 7h cho tính đầy đủ vì chúng nói về <>so sánh - Tôi không nghĩ so sánh các hàm tạo giá trị hàng với nhiều giá trị được triển khai trong T-SQL, trừ khi tôi chỉ hiểu nhầm những gì nó nói - điều này hoàn toàn có thể xảy ra

Đây là một đống rác khó hiểu. Nhưng nếu bạn muốn tiếp tục lặn ...

Tôi nghĩ rằng 1.ii là mục áp dụng trong kịch bản này, vì chúng tôi đang so sánh các giá trị của "các phần tử hàm tạo giá trị hàng".

ii) "X <> Y" là đúng khi và chỉ khi XV và YV không bằng nhau.

Về cơ bản, điều đó X <> Ylà đúng nếu các giá trị được đại diện bởi X và Y không bằng nhau. Vì X < Y OR X > Ylà một bản viết lại tương đương logic của vị từ đó, nên trình tối ưu hóa hoàn toàn sử dụng nó.

Tiêu chuẩn không đặt bất kỳ ràng buộc nào cho định nghĩa này liên quan đến tính xác định (hoặc bất cứ điều gì, bạn có được nó) của các phần tử xây dựng giá trị hàng ở hai bên của <>toán tử so sánh. Trách nhiệm của mã người dùng là đối phó với thực tế là một biểu thức giá trị ở một bên có thể không mang tính quyết định.


1
Tôi sẽ refarin từ bỏ phiếu (lên hoặc xuống) nhưng tôi không bị thuyết phục. Các trích dẫn bạn cung cấp đề cập đến "giá trị" . Hiểu biết của tôi là so sánh là giữa hai giá trị, một giá trị ở mỗi bên. Không phải giữa hai (hoặc nhiều hơn) tức thời của một giá trị ở mỗi bên. Thêm vào đó, tiêu chuẩn (ít nhất là 92 bạn trích dẫn) không đề cập đến ở tất cả các chức năng không xác định. Bằng một lý do tương tự như của bạn, chúng tôi có thể giả sử rằng một sản phẩm SQL tuân thủ tiêu chuẩn không cung cấp bất kỳ chức năng không xác định nào mà chỉ có những sản phẩm được đề cập trong tiêu chuẩn.
ypercubeᵀᴹ

@yper cảm ơn đã phản hồi! Tôi nghĩ rằng giải thích của bạn là chắc chắn hợp lệ. Đây là lần đầu tiên tôi đọc tài liệu đó. Nó đề cập đến các giá trị trong ngữ cảnh của giá trị được biểu thị bằng "hàm tạo giá trị hàng", mà ở nơi khác trong tài liệu mà nó nói có thể là một truy vấn vô hướng (trong số nhiều thứ khác). Các truy vấn vô hướng nói riêng có vẻ như nó có thể không xác định. Nhưng tôi thực sự không biết tôi đang nói về cái gì =)
Josh Darnell
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.