NHÓM THEO với MAX so với chỉ MAX


8

Tôi là một lập trình viên, làm việc với một bảng lớn theo sơ đồ sau:

UpdateTime, PK, datetime, notnull
Name, PK, char(14), notnull
TheData, float

Có một chỉ số nhóm trên Name, UpdateTime

Tôi đã tự hỏi những gì nên được nhanh hơn:

SELECT MAX(UpdateTime)
FROM [MyTable]

hoặc là

SELECT MAX([UpdateTime]) AS value
from
   (
    SELECT [UpdateTime]
    FROM [MyTable]
    group by [UpdateTime]
   ) as t

Các phần chèn vào bảng này nằm trong khối 50.000 hàng có cùng ngày . Vì vậy, tôi nghĩ rằng nhóm bằng cách có thể dễ dàng MAXtính toán.

Thay vì cố gắng tìm tối đa 150.000 hàng, việc nhóm thành 3 hàng và sau đó tính toán MAXsẽ nhanh hơn? Là giả định của tôi đúng hay nhóm bởi cũng tốn kém?

Câu trả lời:


12

Tôi đã tạo bảng big_table theo lược đồ của bạn

create table big_table
(
    updatetime datetime not null,
    name char(14) not null,
    TheData float,
    primary key(Name,updatetime)
)

Sau đó tôi điền vào bảng với 50.000 hàng với mã này:

DECLARE @ROWNUM as bigint = 1
WHILE(1=1)
BEGIN
    set @rownum  = @ROWNUM + 1
    insert into big_table values(getdate(),'name' + cast(@rownum as CHAR), cast(@rownum as float))
    if @ROWNUM > 50000
        BREAK;  
END

Sau đó, sử dụng SSMS, tôi đã kiểm tra cả hai truy vấn và nhận ra rằng trong truy vấn đầu tiên, bạn đang tìm kiếm MAX của TheData và trong lần thứ hai, MAX của thời gian cập nhật

Do đó, tôi đã sửa đổi truy vấn đầu tiên để nhận MAX của thời gian cập nhật

set statistics time on -- execution time
set statistics io on -- io stats (how many pages read, temp tables)

-- query 1
SELECT MAX([UpdateTime])
FROM big_table

-- query 2
SELECT MAX([UpdateTime]) AS value
from
   (
    SELECT [UpdateTime]
    FROM big_table
    group by [UpdateTime]
   ) as t


set statistics time off
set statistics io off

Sử dụng Thời gian Thống kê Tôi lấy lại số mili giây cần thiết để phân tích, biên dịch và thực thi mỗi câu lệnh

Sử dụng Thống kê IO Tôi lấy lại thông tin về hoạt động của đĩa

THỐNG KÊ THỜI GIAN và THỐNG KÊ IO cung cấp thông tin hữu ích. Chẳng hạn như các bảng tạm thời được sử dụng (được biểu thị bằng bàn làm việc). Ngoài ra có bao nhiêu trang logic đã đọc được cho biết số lượng trang cơ sở dữ liệu được đọc từ bộ đệm.

Sau đó tôi kích hoạt kế hoạch Thực thi với CTRL + M (kích hoạt hiển thị kế hoạch thực hiện thực tế) và sau đó thực hiện với F5.

Điều này sẽ cung cấp một so sánh của cả hai truy vấn.

Đây là đầu ra của Tab Tin nhắn

- Truy vấn 1

Bảng 'big_table'. Quét số 1, đọc logic 543 , đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý lob 0, đọc trước đọc 0, đọc trước 0.

Thời gian thực thi máy chủ SQL: Thời gian CPU = 16 ms, thời gian trôi qua = 6 ms .

- Truy vấn 2

Bảng ' bàn làm việc '. Quét số 0, đọc logic 0, đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý lob 0, đọc trước đọc 0, đọc trước 0.

Bảng 'big_table'. Quét số 1, đọc logic 543 , đọc vật lý 0, đọc trước đọc 0, đọc logic 0, đọc vật lý lob 0, đọc trước đọc 0, đọc trước 0.

Thời gian thực thi máy chủ SQL: Thời gian CPU = 0 ms, thời gian trôi qua = 35 ms .

Cả hai truy vấn đều cho kết quả là 543 lần đọc logic, nhưng truy vấn thứ hai có thời gian trôi qua là 35ms trong khi truy vấn đầu tiên chỉ có 6ms. Bạn cũng sẽ nhận thấy rằng truy vấn thứ hai dẫn đến việc sử dụng các bảng tạm thời trong tempdb, được biểu thị bằng từ bàn làm việc . Mặc dù tất cả các giá trị cho bàn làm việc đều ở mức 0, công việc vẫn được thực hiện trong tempdb.

Sau đó, có đầu ra từ tab Kế hoạch thực thi thực tế bên cạnh tab Tin nhắn

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

Theo kế hoạch thực hiện được cung cấp bởi MSSQL, truy vấn thứ hai bạn cung cấp có tổng chi phí lô là 64% trong khi truy vấn đầu tiên chỉ tốn 36% tổng số lô, vì vậy truy vấn đầu tiên yêu cầu ít công việc hơn.

Sử dụng SSMS, bạn có thể kiểm tra và so sánh các truy vấn của mình và tìm hiểu chính xác cách MSSQL phân tích cú pháp các truy vấn của bạn và các đối tượng: bảng, chỉ mục và / hoặc số liệu thống kê nếu có đang được sử dụng để đáp ứng các truy vấn đó.

Một lưu ý phụ cần lưu ý khi kiểm tra là dọn sạch bộ đệm trước khi kiểm tra, nếu có thể. Điều này giúp đảm bảo rằng các so sánh là chính xác và điều này rất quan trọng khi suy nghĩ về hoạt động của đĩa. Tôi bắt đầu với DBCC DROPCLEANBUFFERSDBCC FREEPROCCACHE để xóa tất cả bộ nhớ cache. Hãy cẩn thận mặc dù không sử dụng các lệnh này trên máy chủ sản xuất thực sự đang sử dụng vì bạn sẽ buộc máy chủ phải đọc mọi thứ từ đĩa vào bộ nhớ một cách hiệu quả.

Đây là tài liệu liên quan.

  1. Xóa bộ nhớ cache của gói với DBCC FREEPROCCACHE
  2. Xóa tất cả mọi thứ khỏi vùng đệm với DBCC DROPCLEANBUFFERS

Sử dụng các lệnh này có thể không thực hiện được tùy thuộc vào cách môi trường của bạn được sử dụng.

Cập nhật 10/11 12:46 chiều

Thực hiện chỉnh sửa cho hình ảnh kế hoạch thực hiện và thống kê đầu ra.


Cảm ơn câu trả lời sâu sắc, xin lưu ý dòng hói của tôi trong mã, mỗi nhóm 50.000 hàng có cùng một ngày khác với các khối khác. Vì vậy, tôi nên chuyển getdate()ra khỏi vòng lặp
Ofiris

1
Xin chào @Ofiris. Câu trả lời tôi đưa ra thực ra chỉ là để giúp bạn tự mình so sánh. Tôi đã tạo dữ liệu rác ngẫu nhiên chỉ để minh họa việc sử dụng các lệnh và công cụ khác nhau mà bạn có thể sử dụng để đưa ra kết luận của riêng mình.
Craig Efrein

1
Không có công việc nào được thực hiện trong tempdb. Bàn làm việc là để quản lý các phân vùng trong trường hợp tổng hợp băm phải tràn sang tempdb vì không đủ bộ nhớ cho nó. Vui lòng nhấn mạnh rằng chi phí luônước tính ngay cả trong kế hoạch 'thực tế'. Chúng là các ước tính của trình tối ưu hóa, có thể không liên quan nhiều đến hiệu suất thực tế. Không sử dụng% của lô làm số liệu điều chỉnh chính. Xóa bộ đệm chỉ quan trọng nếu bạn muốn kiểm tra hiệu năng bộ đệm lạnh.
Paul White 9

1
Xin chào @PaulWhite. Cảm ơn bạn đã cung cấp thêm thông tin, tôi chân thành đánh giá cao bất kỳ đề xuất nào về cách chính xác hơn. Khi bạn nói câu của bạn mặc dù: "Không sử dụng", điều đó có thể không bị hiểu sai là đưa ra một mệnh lệnh hơn là đưa ra lời khuyên chuyên nghiệp? Trân trọng.
Craig Efrein

@CraigEfrein Có lẽ. Tôi đã được ngắn gọn để phù hợp với không gian bình luận cho phép.
Paul White 9

6

Các phần chèn vào bảng này nằm trong khối 50.000 hàng có cùng ngày. Vì vậy, tôi nghĩ rằng việc nhóm bằng cách có thể dễ dàng tính toán MAX.

Việc viết lại có thể đã giúp nếu SQL Server triển khai quét chỉ mục, nhưng không được.

Quét bỏ qua chỉ mục cho phép một công cụ cơ sở dữ liệu tìm kiếm giá trị chỉ mục khác nhau tiếp theo thay vì quét tất cả các bản sao (hoặc các khóa phụ không liên quan) ở giữa. Trong trường hợp của bạn, bỏ qua quét sẽ cho phép động cơ tìm thấy MAX(UpdateTime)cái đầu tiên Name, bỏ qua MAX(UpdateTime)cái thứ hai Name... và cứ thế. Bước cuối cùng sẽ là tìm ra MAX(UpdateTime)từ các ứng cử viên một tên.

Bạn có thể mô phỏng điều này ở một mức độ nào đó bằng cách sử dụng CTE đệ quy, nhưng nó hơi lộn xộn và không hiệu quả như quét bỏ qua tích hợp sẽ là:

WITH RecursiveCTE
AS
(
    -- Anchor: MAX UpdateTime for
    -- highest-sorting Name
    SELECT TOP (1)
        BT.Name,
        BT.UpdateTime
    FROM dbo.BigTable AS BT
    ORDER BY
        BT.Name DESC,
        BT.UpdateTime DESC

    UNION ALL

    -- Recursive part
    -- MAX UpdateTime for Name
    -- that sorts immediately lower
    SELECT
        SubQuery.Name,
        SubQuery.UpdateTime
    FROM 
    (
        SELECT
            BT.Name,
            BT.UpdateTime,
            rn = ROW_NUMBER() OVER (
                ORDER BY BT.Name DESC, BT.UpdateTime DESC)
        FROM RecursiveCTE AS R
        JOIN dbo.BigTable AS BT
            ON BT.Name < R.Name
    ) AS SubQuery
    WHERE
        SubQuery.rn = 1
)
-- Final MAX aggregate over
-- MAX(UpdateTime) per Name
SELECT MAX(UpdateTime) 
FROM RecursiveCTE
OPTION (MAXRECURSION 0);

Kế hoạch CTE đệ quy

Kế hoạch đó thực hiện một tìm kiếm đơn lẻ cho mỗi khác biệt Name, sau đó tìm thấy cao nhất UpdateTimetừ các ứng cử viên. Hiệu suất của nó liên quan đến quá trình quét toàn bộ bảng đơn giản phụ thuộc vào số lượng bản sao có trên mỗi trang Namevà liệu các trang được chạm bởi đơn tìm kiếm có trong bộ nhớ hay không.

Các giải pháp thay thế

Nếu bạn có thể tạo một chỉ mục mới trên bảng này, một lựa chọn tốt cho truy vấn này sẽ là một chỉ mục UpdateTimemột mình:

CREATE INDEX IX__BigTable_UpdateTime 
ON dbo.BigTable (UpdateTime);

Chỉ mục này sẽ cho phép công cụ thực thi tìm mức cao nhất UpdateTimevới một tìm kiếm đơn lẻ đến cuối cây b-index:

Kế hoạch chỉ số mới

Kế hoạch này chỉ tiêu thụ một vài IO logic (để điều hướng các cấp độ b-cây) và hoàn thành ngay lập tức. Lưu ý rằng Quét chỉ mục trong kế hoạch không phải là quét toàn bộ chỉ mục mới - nó chỉ trả về một hàng từ một 'đầu' của chỉ mục.

Nếu bạn không muốn tạo một chỉ mục hoàn toàn mới trên bảng, bạn có thể xem xét một chế độ xem được lập chỉ mục chỉ chứa các UpdateTimegiá trị duy nhất :

CREATE VIEW dbo.BigTableUpdateTimes
WITH SCHEMABINDING AS
SELECT 
    UpdateTime, 
    NumRows = COUNT_BIG(*)
FROM dbo.BigTable AS BT
GROUP BY
    UpdateTime;
GO
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.BigTableUpdateTimes (UpdateTime);

Điều này có lợi thế là chỉ tạo một cấu trúc có nhiều hàng như có UpdateTimecác giá trị duy nhất , mặc dù mọi truy vấn thay đổi dữ liệu trong bảng cơ sở sẽ có các toán tử bổ sung được thêm vào kế hoạch thực hiện của nó để duy trì chế độ xem được lập chỉ mục. Truy vấn để tìm UpdateTimegiá trị tối đa sẽ là:

SELECT MAX(BTUT.UpdateTime)
FROM dbo.BigTableUpdateTimes AS BTUT
    WITH (NOEXPAND);

Kế hoạch xem chỉ mục

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.