Máy chủ SQL không thể đoán trước kết quả chọn (lỗi dbms?)


37

Dưới đây là ví dụ đơn giản, trả về kết quả lạ, không thể đoán trước và chúng tôi không thể giải thích điều đó trong nhóm của mình. Có phải chúng tôi đang làm gì đó sai hoặc là lỗi SQL Server?

Sau một số điều tra, chúng tôi đã giảm khu vực tìm kiếm thành mệnh đề hợp nhất trong truy vấn con , trong đó chọn một bản ghi từ bảng "men"

Nó hoạt động như mong đợi trong SQL Server 2000 (trả về 12 hàng), nhưng trong năm 2008 và 2012, nó chỉ trả về một hàng.

create table dual (dummy int)

insert into dual values (0)

create table men (
man_id int,
wife_id int )

-- there are 12 men, 6 married 
insert into men values (1, 1)
insert into men values (2, 2)
insert into men values (3, null)
insert into men values (4, null)
insert into men values (5, null)
insert into men values (6, 3)
insert into men values (7, 5)
insert into men values (8, 7)
insert into men values (9, null)
insert into men values (10, null)
insert into men values (11, null)
insert into men values (12, 9)

Điều này chỉ trả về một hàng: 1 1 2

select 
man_id,
wife_id,
(select count( * ) from 
    (select dummy from dual
     union select men.wife_id  ) family_members
) as family_size
from men
--where wife_id = 2 -- uncomment me and try again

Uncomment dòng cuối cùng và nó cung cấp: 2 2 2

Có rất nhiều hành vi kỳ quặc:

  • Sau một loạt các giọt, tạo, cắt và chèn vào bảng "men" , đôi khi nó hoạt động (trả về 12 hàng)
  • Khi bạn thay đổi "union select men.wife_id" thành "union all select men.wife_id" hoặc "union select isnull (men.wife_id, null)" (!!!), nó sẽ trả về 12 hàng (như mong đợi).
  • Hành vi kỳ lạ dường như không liên quan đến kiểu dữ liệu của cột "vợ_id". Chúng tôi quan sát nó trên hệ thống phát triển với các tập dữ liệu lớn hơn nhiều.
  • "nơi vợ_id> 0" trả về 6 hàng
  • chúng tôi cũng quan sát hành vi kỳ lạ của quan điểm với loại tuyên bố này. CHỌN * trả về tập hợp con của các hàng, CHỌN 1000 trả về tất cả

Câu trả lời:


35

Có phải chúng tôi đang làm gì đó sai hoặc là lỗi SQL Server?

Đây là một lỗi kết quả sai, mà bạn nên báo cáo qua kênh hỗ trợ thông thường của mình. Nếu bạn không có thỏa thuận hỗ trợ, có thể giúp biết rằng các sự cố đã thanh toán thường được hoàn lại nếu Microsoft xác nhận hành vi là một lỗi.

Các lỗi đòi hỏi ba thành phần:

  1. Các vòng lặp lồng nhau với một tham chiếu bên ngoài (áp dụng)
  2. Một bộ chỉ mục lười biếng bên trong tìm kiếm trên tham chiếu bên ngoài
  3. Một toán tử nối bên trong

Ví dụ: truy vấn trong câu hỏi tạo ra một kế hoạch như sau:

Kế hoạch chú thích

Có nhiều cách để loại bỏ một trong các yếu tố này, vì vậy lỗi không còn sinh sản nữa.

Ví dụ: người ta có thể tạo các chỉ mục hoặc số liệu thống kê xảy ra có nghĩa là trình tối ưu hóa chọn không sử dụng Bộ chỉ số lười biếng. Hoặc, người ta có thể sử dụng các gợi ý để buộc băm hoặc hợp nhất liên kết thay vì sử dụng Ghép. Người ta cũng có thể viết lại truy vấn để diễn đạt cùng một ngữ nghĩa, nhưng điều đó dẫn đến một hình dạng kế hoạch khác trong đó thiếu một hoặc nhiều yếu tố cần thiết.

Thêm chi tiết

Một chỉ số lười biếng Bộ đệm lười biếng lưu trữ các hàng kết quả bên trong, trong một bảng làm việc được lập chỉ mục bởi các giá trị tham chiếu bên ngoài (tham số tương quan). Nếu một chỉ số Lazy Index Spool được yêu cầu tham chiếu bên ngoài mà nó đã thấy trước đó, nó sẽ tìm nạp hàng kết quả được lưu trong bộ nhớ cache từ bảng làm việc của nó ("tua lại"). Nếu bộ đệm được yêu cầu một giá trị tham chiếu bên ngoài mà nó chưa từng thấy trước đó, nó sẽ chạy cây con của nó với giá trị tham chiếu bên ngoài hiện tại và lưu trữ kết quả (một "rebind"). Vị từ tìm kiếm trên Lazy Index Spool chỉ ra (các) khóa cho bảng làm việc của nó.

Vấn đề xảy ra trong hình dạng kế hoạch cụ thể này khi ống chỉ kiểm tra xem liệu một tham chiếu bên ngoài mới có giống với tham chiếu bên ngoài không. Các vòng lặp lồng nhau tham gia cập nhật chính xác các tham chiếu bên ngoài của nó và thông báo cho các toán tử về đầu vào bên trong của nó thông qua các PrepRecomputephương thức giao diện của chúng . Khi bắt đầu kiểm tra này, các nhà khai thác bên trong đọc thuộc CParamBounds:FNeedToReloadtính để xem liệu tham chiếu bên ngoài đã thay đổi so với lần trước. Một dấu vết ngăn xếp ví dụ được hiển thị dưới đây:

CParamBound: FNeedToReload

Khi cây con được hiển thị ở trên tồn tại, cụ thể là nơi sử dụng Phép nối, có vấn đề xảy ra (có lẽ là sự cố ByVal / ByRef / Sao chép) với các ràng buộc CParamBounds:FNeedToReloadluôn trả về sai, bất kể tham chiếu bên ngoài có thực sự thay đổi hay không.

Khi cùng một cây con tồn tại, nhưng Merge Union hoặc Hash Union được sử dụng, thuộc tính thiết yếu này được đặt chính xác trên mỗi lần lặp và Bộ đệm chỉ số lười biếng tua lại hoặc tua lại mỗi lần khi thích hợp. Nhân tiện, sắp xếp phân biệt và luồng tổng hợp là đáng trách. Sự nghi ngờ của tôi là Hợp nhất và Hash Union tạo một bản sao của giá trị trước đó, trong khi Kết hợp sử dụng một tham chiếu. Thật không may là không thể xác minh điều này mà không có quyền truy cập vào mã nguồn SQL Server.

Kết quả cuối cùng là Lazy Index Spool trong hình dạng kế hoạch có vấn đề luôn nghĩ rằng nó đã thấy tham chiếu bên ngoài hiện tại, tua lại bằng cách tìm kiếm trong bảng làm việc của nó, thường không tìm thấy gì, vì vậy không có hàng nào được trả về cho tham chiếu bên ngoài đó. Bước qua thực thi trong trình gỡ lỗi, bộ đệm chỉ thực hiện RewindHelperphương thức của nó và không bao giờ ReloadHelperphương thức của nó (reload = rebind trong ngữ cảnh này). Điều này thể hiện rõ trong kế hoạch thực hiện bởi vì tất cả các toán tử trong bộ đệm đều có 'Số lần thực hiện = 1'.

Tua lại

Tất nhiên, ngoại lệ là dành cho tham chiếu bên ngoài đầu tiên, Lazy Index Spool được đưa ra. Điều này luôn luôn thực thi cây con và lưu trữ một hàng kết quả trong bảng làm việc. Tất cả các lần lặp lại tiếp theo dẫn đến tua lại, sẽ chỉ tạo ra một hàng (hàng được lưu trong bộ nhớ cache) khi lần lặp hiện tại có cùng giá trị cho tham chiếu bên ngoài như lần đầu tiên.

Vì vậy, đối với bất kỳ đầu vào đã cho nào được đặt ở phía bên ngoài của Tham gia các vòng lặp lồng nhau, truy vấn sẽ trả về nhiều hàng như có các bản sao của hàng đầu tiên được xử lý (dĩ nhiên cộng với một hàng cho hàng đầu tiên).

Bản giới thiệu

Bảng và dữ liệu mẫu:

CREATE TABLE #T1 
(
    pk integer IDENTITY NOT NULL,
    c1 integer NOT NULL,

    CONSTRAINT PK_T1
    PRIMARY KEY CLUSTERED (pk)
);
GO
INSERT #T1 (c1)
VALUES
    (1), (2), (3), (4), (5), (6),
    (1), (2), (3), (4), (5), (6),
    (1), (2), (3), (4), (5), (6);

Truy vấn (tầm thường) sau đây tạo ra số đếm chính xác là hai cho mỗi hàng (tổng cộng 18) bằng cách sử dụng Liên minh Hợp nhất:

SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY 
(
    SELECT COUNT_BIG(*) AS c1
    FROM
    (
        SELECT T1.c1
        UNION
        SELECT NULL
    ) AS U
) AS C;

Hợp nhất kế hoạch liên minh

Nếu bây giờ chúng ta thêm một gợi ý truy vấn để buộc Kết nối:

SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY 
(
    SELECT COUNT_BIG(*) AS c1
    FROM
    (
        SELECT T1.c1
        UNION
        SELECT NULL
    ) AS U
) AS C
OPTION (CONCAT UNION);

Kế hoạch thực hiện có hình dạng có vấn đề:

Kế hoạch kết nối

Và kết quả bây giờ không chính xác, chỉ ba hàng:

Kết quả ba hàng

Mặc dù hành vi này không được đảm bảo, hàng đầu tiên từ Quét chỉ mục cụm có c1giá trị là 1. Có hai hàng khác có giá trị này, do đó tổng cộng ba hàng được tạo ra.

Bây giờ cắt bớt bảng dữ liệu và tải nó với nhiều bản sao của hàng 'đầu tiên':

TRUNCATE TABLE #T1;

INSERT #T1 (c1)
VALUES
    (1), (2), (3), (4), (5), (6),
    (1), (2), (3), (4), (5), (6),
    (1), (1), (1), (1), (1), (1);

Bây giờ kế hoạch kết nối là:

Kế hoạch kết nối 8 hàng

Và, như đã chỉ ra, 8 hàng được sản xuất, tất c1 = 1nhiên đều có:

Kết quả 8 hàng

Tôi nhận thấy bạn đã mở một mục Kết nối cho lỗi này nhưng thực sự đó không phải là nơi để báo cáo các vấn đề đang có tác động sản xuất. Nếu đó là trường hợp, bạn thực sự nên liên hệ với bộ phận Hỗ trợ của Microsoft.


Lỗi kết quả sai này đã được sửa ở một số giai đoạn. Nó không còn sao chép cho tôi trên bất kỳ phiên bản SQL Server nào từ năm 2012 trở đi. Nó không repro trên SQL Server 2008 R2 SP3-GDR bản dựng 10.50,6560.0 (X64).


-3

Tại sao bạn sử dụng truy vấn con mà không có câu lệnh from? Tôi nghĩ rằng điều đó có thể gây ra sự khác biệt trong các máy chủ năm 2005 và 2008. Có lẽ bạn có thể đi với tham gia rõ ràng?

select 
m1.man_id,
m1.wife_id,
(select count( * ) from 
    (select dummy from dual
     union
     select m2.wife_id
     from men m2
     where m2.man_id = m1.man_id) family_members
) as family_size
from men m1

3
Vâng, điều này hoạt động, nhưng phiên bản của tôi cũng nên làm việc. Trên ví dụ trừu tượng là phiên bản đơn giản hơn nhiều của truy vấn sản xuất của chúng tôi, điều này có ý nghĩa hơn nhiều.
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.