NEWID () Trong bảng ảo đã tham gia gây ra hành vi áp dụng chéo ngoài ý muốn


9

Truy vấn công việc thực tế của tôi là một phép nối bên trong, nhưng ví dụ đơn giản này với phép nối chéo dường như luôn luôn tái tạo vấn đề.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT NEWID() TEST_ID
) BB ( B )

Với phép nối bên trong của tôi, tôi đã có nhiều hàng mà tôi đã thêm vào mỗi GUID bằng hàm NEWID () và trong khoảng 9 trên 10 hàng như vậy, phép nhân với bảng ảo 2 hàng tạo ra kết quả như mong đợi, chỉ 2 bản sao cùng một GUID, trong khi 1 trên 10 sẽ tạo ra kết quả khác nhau. Điều này thật bất ngờ để nói ít nhất và đã cho tôi một thời gian thực sự khó khăn khi cố gắng tìm ra lỗi này trong kịch bản tạo dữ liệu thử nghiệm của tôi.

Nếu bạn xem các truy vấn sau bằng cách sử dụng các hàm getdate và sysdatetime không xác định, bạn sẽ không thấy điều này, dù sao tôi cũng không thấy - tôi luôn thấy cùng một giá trị thời gian trong cả hai hàng kết quả cuối cùng.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT GETDATE() TEST_ID
) BB ( B )

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT SYSDATETIME() TEST_ID
) BB ( B )

Tôi hiện đang sử dụng SQL Server 2008 và công việc của tôi hiện tại là tải các hàng của tôi với GUID vào một biến bảng trước khi hoàn thành tập lệnh tạo dữ liệu ngẫu nhiên của tôi. Khi tôi có chúng dưới dạng các giá trị trong một bảng trái ngược với bảng ảo, vấn đề sẽ biến mất.

Tôi có một cách giải quyết, nhưng tôi đang tìm cách để giải quyết mà không có các bảng hoặc biến bảng thực tế.

Trong khi viết điều này, tôi đã thử nhưng không thành công những khả năng này: 1) đặt newid () vào một bảng ảo lồng nhau:

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT TEST_ID
    FROM (
        SELECT NEWID() TEST_ID
    ) TT
) BB ( B )

2) gói newid () trong một biểu thức cast, chẳng hạn như:

SELECT CAST(NEWID() AS VARCHAR(100)) TEST_ID

3) đảo ngược thứ tự xuất hiện của các bảng ảo trong biểu thức nối

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS JOIN (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

4) sử dụng chéo không tương thích

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

Ngay trước khi cuối cùng đăng câu hỏi này, bây giờ tôi đã thử nó với thành công, có vẻ như một ứng dụng chéo tương quan:

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT A
    FROM (
        SELECT 1 UNION ALL
        SELECT 2
    ) TT ( A )
    WHERE BB.B IS NOT NULL
) AA ( A )

Bất cứ ai có cách giải quyết khác đơn giản hơn, thanh lịch hơn? Tôi thực sự không muốn sử dụng áp dụng chéo hoặc tương quan cho phép nhân hàng đơn giản nếu tôi không phải làm vậy.

Câu trả lời:


20

Hành vi này là do thiết kế, như được giải thích chi tiết trên báo cáo lỗi Connect này . Câu trả lời thích hợp nhất của Microsoft được sao chép dưới đây để thuận tiện (và trong trường hợp liên kết bị chết tại một số điểm):

Được đăng bởi Microsoft vào ngày 7/7/2008 lúc 9:27 sáng

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ễ dàng 'sửa chữa' (kết quả là 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à:

  1. 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.
  2. Tránh sử dụng NEWID () sâu trong biểu thức bảng.
  3. 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".

Các hàm GETDATESYSDATETIMEhàm thực sự không xác định, nhưng chúng được coi là hằng số thời gian chạy cho một truy vấn cụ thể. Nói chung, điều này có nghĩa là giá trị của hàm được lưu trong bộ đệm khi bắt đầu thực hiện truy vấn và kết quả được sử dụng lại cho tất cả các tham chiếu trong truy vấn.

Không có "cách giải quyết" nào trong câu hỏi là an toàn; không có gì đảm bảo hành vi sẽ không thay đổi vào lần kế hoạch tiếp theo được biên soạn, khi bạn áp dụng gói dịch vụ hoặc cập nhật tích lũy tiếp theo ... hoặc vì những lý do khác.

Giải pháp an toàn duy nhất là sử dụng một đối tượng tạm thời thuộc loại nào đó - ví dụ, một biến, bảng hoặc hàm đa câu lệnh. Sử dụng một cách giải quyết có vẻ như để làm việc ngày hôm nay dựa trên quan sát là một cách tuyệt vời để trải nghiệm các hành vi bất ngờ trong tương lai, thường là dưới dạng cảnh báo phân trang vào lúc 3 giờ sáng Chủ nhật.


"Không có 'cách giải quyết' nào trong câu hỏi là an toàn;" ditto đó. Khi tôi cố gắng áp dụng một trong số chúng cho truy vấn thực tế của mình, nó không giúp ích gì cả.
JM Hicks
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.