Tại sao SQL Server ước tính ít hàng hơn sẽ được phát ra từ một liên kết sau khi chèn một số hàng?


7

Dưới đây là phiên bản đơn giản hóa của một thứ tôi đã sản xuất (trong đó kế hoạch trở nên tồi tệ hơn vào một ngày mà số lượng lô cao bất thường được xử lý).

Bản repro đã được thử nghiệm so với năm 2014 và 2016 với công cụ ước tính cardinality mới.

CREATE TABLE T1 (FromDate  DATE, ToDate DATE, SomeId INT, BatchNumber INT);

INSERT INTO T1
SELECT TOP 1000 FromDate = '2017-01-01',
                ToDate = '2017-01-01',
                SomeId = ROW_NUMBER() OVER (ORDER BY @@SPID) -1,
                BatchNumber = 1
FROM   master..spt_values v1

CREATE TABLE T2 (SomeDateTime DATETIME, SomeId INT, INDEX IX(SomeDateTime));

INSERT INTO T2
SELECT TOP 1000000 '2017-01-01',
                   ROW_NUMBER() OVER (ORDER BY @@SPID) %1000
FROM   master..spt_values v1,
       master..spt_values v2

T1 chứa 1.000 hàng.

Các FromDate, ToDateBatchNumberlà giống hệt nhau trong tất cả chúng. Giá trị duy nhất khác SomeIdvới giá trị giữa 0999

+------------+------------+--------+-----------+
|  FromDate  |   ToDate   | SomeId | BatchNumber |
+------------+------------+--------+-----------+
| 2017-01-01 | 2017-01-01 |      0 |         1 |
| 2017-01-01 | 2017-01-01 |      1 |         1 |
....
| 2017-01-01 | 2017-01-01 |    998 |         1 |
| 2017-01-01 | 2017-01-01 |    999 |         1 |
+------------+------------+--------+-----------+

T2 chứa 1 triệu hàng

nhưng chỉ có 1.000 cái riêng biệt. Mỗi lần lặp lại 1.000 lần như dưới đây.

+-------------------------+--------+-------+
|      SomeDateTime       | SomeId | Count |
+-------------------------+--------+-------+
| 2017-01-01 00:00:00.000 |      0 |  1000 |
| 2017-01-01 00:00:00.000 |      1 |  1000 |
...
| 2017-01-01 00:00:00.000 |    998 |  1000 |
| 2017-01-01 00:00:00.000 |    999 |  1000 |
+-------------------------+--------+-------+

Thực hiện như sau

SELECT *
FROM   T1
       INNER JOIN T2
               ON CAST(t2.SomeDateTime AS DATE) BETWEEN T1.FromDate AND T1.ToDate
                  AND T1.SomeId = T2.SomeId
WHERE  T1.BatchNumber = 1

Mất khoảng 7 giây trên máy của tôi. Các hàng thực tế và ước tính là hoàn hảo cho tất cả các nhà khai thác trong kế hoạch.

nhập mô tả hình ảnh ở đây

Bây giờ thêm 3.000 lô bổ sung vào T1 (với số lô từ 2 đến 3001). Mỗi cái này nhân bản hàng nghìn hàng hiện có cho lô số 1

INSERT INTO T1
SELECT T1.FromDate,
       T1.ToDate,
       T1.SomeId,
       Nums.NewBatchNumber
FROM   T1
       CROSS JOIN (SELECT TOP (3000) 1 + ROW_NUMBER() OVER (ORDER BY @@SPID) AS NewBatchNumber
                   FROM   master..spt_values v1, master..spt_values v2) Nums 

và cập nhật số liệu thống kê cho may mắn

 UPDATE STATISTICS T1 WITH FULLSCAN

Và chạy lại truy vấn ban đầu.

SELECT *
FROM   T1
       INNER JOIN T2
               ON CAST(t2.SomeDateTime AS DATE) BETWEEN T1.FromDate AND T1.ToDate
                  AND T1.SomeId = T2.SomeId
WHERE  T1.BatchNumber = 1

Tôi để nó chạy trong một phút trước khi giết nó. Vào thời điểm đó, nó đã xuất ra 40.380 hàng nên tôi đoán sẽ mất 25 phút để xuất ra cả triệu.

Điều duy nhất đã thay đổi là tôi đã thêm một số hàng bổ sung không khớp với T1.BatchNumber = 1vị ngữ.

Tuy nhiên, kế hoạch đã thay đổi. Nó sử dụng các vòng lặp lồng nhau thay vào đó và trong khi số lượng hàng xuất phát t1vẫn được ước tính chính xác là 1.000 (), ước tính số lượng hàng đã tham gia hiện đã giảm từ 1 triệu xuống còn một nghìn ().

nhập mô tả hình ảnh ở đây

Vì vậy, câu hỏi là ...

Tại sao việc thêm các hàng bổ sung BatchNumber <> 1bằng cách nào đó ảnh hưởng đến ước tính cho các hàng được tham gia khi BatchNumber = 1nào?

Có vẻ như trực quan phản đối rằng việc thêm các hàng vào một bảng sẽ kết thúc việc giảm số lượng hàng ước tính từ toàn bộ truy vấn.


Kế hoạch đầu tiên của bạn cũng lạ bởi vì đó là kế hoạch nối tiếp ngay cả khi kết quả ước tính đếm được nhiều hàng hơn so với kế hoạch thứ hai song song, bạn có chắc hai kế hoạch này đến từ cùng một máy chủ không? Người đầu tiên dường như có maxdop = 1
sepupic

@sepupic - vâng. cả hai trên cùng một ví dụ - có lẽ SQL Server không nghĩ rằng chi phí song song tôi đáng giá trong kế hoạch đầu tiên.
Martin Smith

Buộc tham gia băm với T1 lớn hơn sẽ đưa ra một kế hoạch song song. Toán tử băm tham gia hiện có giá 9.10751tại MAXDOP 1 so với ban đầu 7.6675465- tôi không chắc những gì dựa trên các đầu vào là như nhau.
Martin Smith

Câu trả lời:


6

Điều quan trọng cần nhớ là bạn không đảm bảo tính nhất quán khi bạn thay đổi truy vấn hoặc dữ liệu trong các bảng. Trình tối ưu hóa truy vấn có thể chuyển sang sử dụng một phương pháp ước tính cardinality khác nhau (chẳng hạn như sử dụng mật độ trái ngược với biểu đồ) có thể làm cho hai truy vấn dường như không nhất quán với nhau. Như đã nói, có vẻ như trình tối ưu hóa truy vấn đang đưa ra lựa chọn không hợp lý trong trường hợp của bạn, vì vậy hãy tìm hiểu kỹ.

Bản demo của bạn quá phức tạp nên tôi sẽ xử lý một ví dụ đơn giản hơn mà tôi tin là cho thấy hành vi tương tự. Bắt đầu chuẩn bị dữ liệu và định nghĩa bảng:

DROP TABLE dbo.T1 IF EXISTS;
CREATE TABLE dbo.T1 (FromDate DATE, ToDate DATE, SomeId INT);

INSERT INTO dbo.T1 WITH (TABLOCK)
SELECT TOP 1000 NULL, NULL, 1
FROM master..spt_values v1;

DROP TABLE dbo.T2 IF EXISTS;
CREATE TABLE dbo.T2 (SomeDateTime DATETIME, INDEX IX(SomeDateTime));

INSERT INTO dbo.T2 WITH (TABLOCK)
SELECT TOP 2 NULL
FROM master..spt_values v1
CROSS JOIN master..spt_values v2;

Đây là SELECTtruy vấn để điều tra:

SELECT *
FROM T1
INNER JOIN T2 ON t2.SomeDateTime BETWEEN T1.FromDate AND T1.ToDate
WHERE T1.SomeId = 1;

Truy vấn này đủ đơn giản để chúng tôi có thể tìm ra công thức cho ước tính cardinality mà không có bất kỳ cờ theo dõi nào. Tuy nhiên, tôi sẽ cố gắng sử dụng TF 2363 khi tôi minh họa rõ hơn những gì đang diễn ra trong trình tối ưu hóa. Không rõ liệu tôi có thành công không.

Xác định các biến sau:

C1 = số lượng hàng trong bảng T1

C2 = số lượng hàng trong bảng T2

S1= độ chọn lọc của T1.SomeIdbộ lọc

Yêu cầu của tôi là ước tính cardinality cho truy vấn trên như sau:

  1. Khi > = * :C2S1C1

C2* với giới hạn dưới của * S1S1C1

  1. Khi < * :C2S1C1

164.317* * với giới hạn trên của *C2S1S1C1

Chúng ta hãy đi qua một số ví dụ, mặc dù tôi sẽ không đi qua từng ví dụ mà tôi đã thử nghiệm. Đối với chuẩn bị dữ liệu ban đầu, chúng tôi có:

C1 = 1000

C2 = 2

S1 = 1,0

Do đó, ước tính cardinality nên:

2 * 164.317 = 328.634

Ảnh chụp màn hình không thể giả mạo dưới đây chứng minh điều này:

ví dụ 1

Sử dụng cờ theo dõi không có giấy tờ 2363, chúng ta có thể có được một vài manh mối về những gì đang diễn ra:

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB2].[dbo].[T1].SomeId

Loaded histogram for column QCOL: [SE_DB2].[dbo].[T1].SomeId from stats with id 2

Selectivity: 1

Stats collection generated: 

  CStCollFilter(ID=3, CARD=1000)

      CStCollBaseTable(ID=1, CARD=1000 TBL: T1)

End selectivity computation

Begin selectivity computation

Input tree:

...

Plan for computation:

  CSelCalcSimpleJoinWithUpperBound (Using base cardinality)

      CSelCalcOneSided (RIGHT)

          CSelCalcCombineFilters_ExponentialBackoff (AND)

              CSelCalcFixedFilter (0.3)

              CSelCalcFixedFilter (0.3)

Selectivity: 0.164317

Stats collection generated: 

  CStCollJoin(ID=4, CARD=328.634 x_jtInner)

      CStCollFilter(ID=3, CARD=1000)

          CStCollBaseTable(ID=1, CARD=1000 TBL: T1)

      CStCollBaseTable(ID=2, CARD=2 TBL: T2)

End selectivity computation

Với CE mới, chúng tôi có được ước tính 16% thông thường cho a BETWEEN. Điều này là do sự thụt lùi theo cấp số nhân với CE 2014 mới. Mỗi bất đẳng thức có ước tính cardinality là 0,3 nên BETWEENđược tính là 0,3 * sqrt (0,3) = 0,164317. Nhân tỷ lệ chọn lọc 16% với số lượng hàng trong T2 và T1 và chúng tôi có được ước tính của chúng tôi. Có vẻ đủ hợp lý. Hãy tăng số lượng hàng T2lên 7. Bây giờ chúng ta có các mục sau:

C1 = 1000

C2 = 7

S1 = 1,0

Do đó, ước tính cardinality phải là 1000 vì:

7 * 164.317 = 1150> 1000

Kế hoạch truy vấn xác nhận nó:

ví dụ 1

Chúng ta có thể có một cái nhìn khác với TF 2363 nhưng có vẻ như sự chọn lọc đã được điều chỉnh đằng sau hậu trường để tôn trọng giới hạn trên. Tôi nghi ngờ rằng điều đó CSelCalcSimpleJoinWithUpperBoundngăn cản ước tính cardinality vượt quá 1000.

Loaded histogram for column QCOL: [SE_DB2].[dbo].[T1].SomeId from stats with id 2

Selectivity: 1

Stats collection generated: 

  CStCollFilter(ID=3, CARD=1000)

      CStCollBaseTable(ID=1, CARD=1000 TBL: T1)

End selectivity computation

Begin selectivity computation

Input tree:

...

Plan for computation:

  CSelCalcSimpleJoinWithUpperBound (Using base cardinality)

      CSelCalcOneSided (RIGHT)

          CSelCalcCombineFilters_ExponentialBackoff (AND)

              CSelCalcFixedFilter (0.3)

              CSelCalcFixedFilter (0.3)

Selectivity: 0.142857

Stats collection generated: 

  CStCollJoin(ID=4, CARD=1000 x_jtInner)

      CStCollFilter(ID=3, CARD=1000)

          CStCollBaseTable(ID=1, CARD=1000 TBL: T1)

      CStCollBaseTable(ID=2, CARD=7 TBL: T2)

Hãy gập T2xuống 50000 hàng. Bây giờ chúng tôi có:

C1 = 1000

C2 = 50000

S1 = 1,0

Do đó, ước tính cardinality nên:

50000 * 1.0 = 50000

Kế hoạch truy vấn một lần nữa xác nhận nó. Việc ước tính sẽ dễ dàng hơn nhiều sau khi bạn đã tìm ra công thức:

ví dụ 2

Đầu ra TF:

Loaded histogram for column QCOL: [SE_DB2].[dbo].[T1].SomeId from stats with id 2

Selectivity: 1

Stats collection generated: 

  CStCollFilter(ID=3, CARD=1000)

      CStCollBaseTable(ID=1, CARD=1000 TBL: T1)

...

Plan for computation:

  CSelCalcSimpleJoinWithUpperBound (Using base cardinality)

      CSelCalcOneSided (RIGHT)

          CSelCalcCombineFilters_ExponentialBackoff (AND)

              CSelCalcFixedFilter (0.3)

              CSelCalcFixedFilter (0.3)

Selectivity: 0.001

Stats collection generated: 

  CStCollJoin(ID=4, CARD=50000 x_jtInner)

      CStCollFilter(ID=3, CARD=1000)

          CStCollBaseTable(ID=1, CARD=1000 TBL: T1)

      CStCollBaseTable(ID=2, CARD=50000 TBL: T2)

Trong ví dụ này, backoff theo cấp số nhân dường như không liên quan:

5000 * 1000 * 0,001 = 50000.

Bây giờ, hãy thêm 3k hàng vào T1 với SomeIdgiá trị 0. Mã để làm như vậy:

INSERT INTO T1 WITH (TABLOCK)
SELECT TOP 3000 NULL, NULL, 0
FROM   master..spt_values v1,
       master..spt_values v2;

UPDATE STATISTICS T1 WITH FULLSCAN;

Bây giờ chúng tôi có:

C1 = 4000

C2 = 50000

S1 = 0,25

Do đó, ước tính cardinality nên:

50000 * 0,25 = 12500

Kế hoạch truy vấn xác nhận nó:

ví dụ 3

Đây là hành vi tương tự mà bạn gọi ra trong câu hỏi. Tôi đã thêm các hàng không liên quan vào một bảng và ước tính cardinality giảm. Tại sao điều đó xảy ra? Hãy chú ý đến các đường in đậm:

Loaded histogram for column QCOL: [SE_DB2].[dbo].[T1].SomeId from stats with id 2

Độ chọn lọc: 0,25

Stats collection generated: 

  CStCollFilter(ID=3, CARD=1000)

      CStCollBaseTable(ID=1, CARD=4000 TBL: T1)

End selectivity computation

Begin selectivity computation

Input tree:

...

Plan for computation:

  CSelCalcSimpleJoinWithUpperBound (Using base cardinality)

      CSelCalcOneSided (RIGHT)

          CSelCalcCombineFilters_ExponentialBackoff (AND)

              CSelCalcFixedFilter (0.3)

              CSelCalcFixedFilter (0.3)

Độ chọn lọc: 0,00025

Stats collection generated: 

  CStCollJoin(ID=4, CARD=12500 x_jtInner)

      CStCollFilter(ID=3, CARD=1000)

          CStCollBaseTable(ID=1, CARD=4000 TBL: T1)

      CStCollBaseTable(ID=2, CARD=50000 TBL: T2)

End selectivity computation

Có vẻ như ước tính cardinality cho trường hợp này được tính như sau:

C1* * * / ( * )S1C2S1S1C1

Hoặc cho ví dụ cụ thể này:

4000 * 0,25 * 50000 * 0,25 / (0,25 * 4000) = 12500

Tất nhiên, công thức chung có thể được đơn giản hóa thành:

C2 * S1

Đó là công thức mà tôi tuyên bố ở trên. Có vẻ như có một số hủy bỏ đang diễn ra mà không nên. Tôi hy vọng tổng số hàng T1có liên quan đến ước tính.

Nếu chúng ta chèn thêm hàng vào, T1chúng ta có thể thấy giới hạn dưới trong hành động:

INSERT INTO T1 WITH (TABLOCK)
SELECT TOP 997000 NULL, NULL, 0
FROM   master..spt_values v1,
       master..spt_values v2;

UPDATE STATISTICS T1 WITH FULLSCAN;

Ước tính cardinality trong trường hợp này là 1000 hàng. Tôi sẽ bỏ qua kế hoạch truy vấn và đầu ra TF 2363.

Cuối cùng, hành vi này khá đáng ngờ nhưng tôi không biết đủ để tuyên bố nếu đó là lỗi hay không. Ví dụ của tôi không khớp chính xác với lời trách móc của bạn nhưng tôi tin rằng tôi đã quan sát hành vi chung đó. Ngoài ra tôi sẽ nói rằng bạn có một chút may mắn với cách bạn chọn dữ liệu ban đầu của mình. Dường như có rất nhiều phỏng đoán đang diễn ra bởi trình tối ưu hóa vì vậy tôi sẽ không quá bận tâm về thực tế rằng truy vấn ban đầu đã trả về 1 triệu hàng khớp chính xác với ước tính.


Trong ví dụ trong câu hỏi của tôi, tôi đã thử chèn số lượng lô khác nhau và điều đó thực sự phù hợp với mô hình ở đây rextester.com/WUJJJ48346 với mức giảm đều đặn cho mỗi đợt cho đến khi sàn 1.000 chỉ gặp phải C2 * S1> S1 * C1, C2 * S1, S1 * C1trường hợp này.
Martin Smith
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.