Có thể tăng hiệu suất truy vấn trên một bảng hẹp với hàng triệu hàng không?


14

Tôi có một truy vấn hiện đang mất trung bình 2500ms để hoàn thành. Bàn của tôi rất hẹp, nhưng có 44 triệu hàng. Tôi có những lựa chọn nào để cải thiện hiệu suất, hay nó có tốt như vậy không?

Truy vấn

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'; 

Cái bàn

CREATE TABLE [dbo].[Heartbeats](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [DeviceID] [int] NOT NULL,
    [IsPUp] [bit] NOT NULL,
    [IsWebUp] [bit] NOT NULL,
    [IsPingUp] [bit] NOT NULL,
    [DateEntered] [datetime] NOT NULL,
 CONSTRAINT [PK_Heartbeats] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Chỉ số

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Sẽ thêm chỉ số bổ sung giúp? Nếu vậy, họ sẽ trông như thế nào? Hiệu suất hiện tại có thể chấp nhận được, bởi vì truy vấn chỉ thỉnh thoảng chạy, nhưng tôi tự hỏi là một bài tập học tập, tôi có thể làm gì để làm điều này nhanh hơn không?

CẬP NHẬT

Khi tôi thay đổi truy vấn để sử dụng gợi ý chỉ số lực lượng, truy vấn sẽ thực hiện trong 50ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats] WITH(INDEX(CommonQueryIndex))
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 

Thêm một mệnh đề DeviceID chọn lọc chính xác cũng đạt phạm vi 50ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' AND DeviceID = 4;

Nếu tôi thêm ORDER BY [DateEntered], [DeviceID]vào truy vấn ban đầu, tôi ở trong phạm vi 50ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

Tất cả đều sử dụng chỉ mục mà tôi mong đợi (CommonQuery Index) vì vậy, tôi cho rằng câu hỏi của tôi là bây giờ, có cách nào để buộc chỉ mục này được sử dụng cho các truy vấn như thế này không? Hoặc là kích thước của bảng của tôi ném ra trình tối ưu hóa quá nhiều và tôi chỉ cần sử dụng một ORDER BYhoặc một gợi ý?


Tôi đoán bạn có thể thêm một chỉ mục không được nhóm vào "Date Entryed" sẽ tăng hiệu suất lên một mức độ nào đó
Praveen

@Praveen Về cơ bản nó có giống với chỉ mục hiện tại của tôi không? Tôi có cần phải làm gì đặc biệt vì sẽ có hai chỉ mục trên cùng một lĩnh vực không?
Nate

@Nate, vì bảng được gọi là heartbeat và có 44 triệu bản ghi liên quan Tôi giả sử bạn có chèn nặng trên bảng này? Với lập chỉ mục, bạn chỉ có thể thêm một chỉ số bao phủ để tăng tốc. Nhưng như bạn đã đề cập, thỉnh thoảng bạn chỉ sử dụng truy vấn này, tôi sẽ khuyên bạn nên chống lại điều đó nếu bạn thực hiện các thao tác chèn nặng. Về cơ bản nó tăng gấp đôi tải chèn của bạn. Bạn đang chạy trên phiên bản doanh nghiệp?
Edward tổng hợp

Tôi nhận thấy rằng bạn có deviceID trong chỉ mục NC của bạn. Có thể bao gồm điều đó trong mệnh đề where của bạn? Và điều đó sẽ làm giảm kết quả được đặt dưới ngưỡng? <35k hồ sơ (không có điều khoản 1000 đầu).
Edward tổng hợp

1
câu hỏi cuối cùng, bạn có luôn chèn theo thứ tự ngày Nhập không? Hoặc những thứ này có thể bị lỗi do các thiết bị có thể chèn không đồng bộ với nhau. Bạn có thể thử thay đổi chỉ mục được nhóm thành cột Date Entryed. Các trang còn lại của bạn trong chỉ mục Clustered của bạn hiện là 445 trang. Điều đó sẽ tăng gấp đôi, nếu bạn đi từ int đến datetime. Nhưng trong trường hợp này, điều đó có thể không tệ.
Edward tổng hợp

Câu trả lời:


13

Tại sao trình tối ưu hóa không đi theo chỉ số đầu tiên của bạn:

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Là vấn đề chọn lọc của Cột [Ngày nhập].

Bạn nói với chúng tôi rằng bảng của bạn có 44 triệu hàng. kích thước hàng là:

4 byte, cho ID, 4 byte cho ID thiết bị, 8 byte cho ngày và 1 byte cho các cột 4 bit. đó là 17 byte + 7 byte chi phí cho (thẻ, bit Null, var col offset ,, tổng số col) tổng cộng là 24 byte mỗi hàng.

Điều đó sẽ khó khăn để dịch đến 140k trang. Để lưu trữ 44 triệu hàng đó.

Bây giờ trình tối ưu hóa có thể làm hai việc:

  1. Nó có thể quét bảng (quét chỉ mục cụm)
  2. Hoặc nó có thể sử dụng chỉ mục của bạn. Đối với mỗi hàng trong chỉ mục của bạn, sau đó sẽ cần thực hiện tra cứu dấu trang trong chỉ mục được nhóm.

Bây giờ tại một thời điểm nhất định, nó trở nên đắt hơn khi thực hiện tất cả các tra cứu đơn lẻ này trong chỉ mục được nhóm cho mỗi mục nhập chỉ mục được tìm thấy trong chỉ mục không được nhóm của bạn. Ngưỡng cho điều đó nói chung là tổng số lần tra cứu sẽ vượt quá 25% tổng 33% tổng số trang của bảng.

Vì vậy, trong trường hợp này: 140k / 25% = 35000 hàng 140k / 33% = 46666 hàng.

(@RBarryYoung, 35k là 0,08% tổng số hàng và 46666 là 0,10%, vì vậy tôi nghĩ đó là nơi gây nhầm lẫn)

Vì vậy, nếu mệnh đề where của bạn sẽ dẫn đến một nơi nào đó trong khoảng từ 35000 đến 46666 hàng (đây là bên dưới mệnh đề trên cùng!) Rất có khả năng là cụm không được phân cụm của bạn sẽ không được sử dụng và quét chỉ mục cụm sẽ được sử dụng.

Hai cách duy nhất để thay đổi điều này là:

  1. Làm cho mệnh đề where của bạn có chọn lọc hơn. (nếu có thể)
  2. Thả dấu * và chỉ chọn một vài cột để bạn có thể sử dụng chỉ mục che phủ.

bây giờ chắc chắn rằng bạn có thể tạo một chỉ mục bao phủ ngay cả khi bạn sử dụng chọn *. Hoever chỉ tạo ra một chi phí lớn cho chèn / cập nhật / xóa của bạn. Chúng tôi sẽ phải biết thêm về khối lượng công việc của bạn (đọc so với viết) để đảm bảo nếu đó là giải pháp tốt nhất.

Thay đổi từ datetime sang smalldatetime là giảm 16% kích thước trên chỉ mục được nhóm và giảm 24% kích thước trên chỉ mục không được nhóm của bạn.


ngưỡng quét thường thấp hơn nhiều so với mức đó (10% hoặc thậm chí thấp hơn), tuy nhiên vì phạm vi là một ngày so với hơn một năm trước nên nó không tạo ra ngưỡng đó. Và Quét chỉ mục cụm không phải là một định sẵn, vì một chỉ số bao phủ đã được thêm vào. Vì chỉ mục đó làm cho mệnh đề WHERE SARG có thể, nên nó được ưu tiên.
RBarryYoung

@RBarryYoung Tôi đã cố gắng giải thích tại sao chỉ mục không được nhóm trên [EnteredDate], [DeviceID] không được sử dụng ở vị trí đầu tiên. Về Ngưỡng tôi nghĩ cả hai chúng tôi đều đồng ý, tôi chỉ nói từ góc độ trang. Tôi sẽ thay đổi câu trả lời của tôi để làm cho nó rõ ràng hơn.
Edward Pa-ri

Thay đổi câu trả lời để làm rõ hơn những gì tôi đang trả lời. Tôi không thể giải thích lý do tại sao chỉ số bao phủ mà @RBarryYoung đề xuất không được sử dụng. Tôi đã thử nghiệm nó trên một triệu hàng ngay tại đây và trình tối ưu hóa sử dụng chỉ số bao phủ.
Edward tổng hợp

Cảm ơn cho một phản ứng rất toàn diện, có rất nhiều ý nghĩa. Đối với khối lượng công việc, bảng có 150-300 chèn mỗi khoảng thời gian 5 phút và một vài lần đọc mỗi ngày cho mục đích báo cáo.
Nate

Đầu trên cho chỉ số che phủ không thực sự có ý nghĩa vì đó là một bảng hẹp và "bao phủ" chỉ là một bổ sung cho chỉ mục có sẵn đã bao gồm hầu hết các hàng.
RBarryYoung

8

Có một lý do cụ thể mà PK của bạn được nhóm lại? Nhiều người làm điều này bởi vì nó mặc định theo cách đó, hoặc họ nghĩ rằng PK phải được nhóm lại. Không như vậy. Các chỉ mục được nhóm thường là tốt nhất cho các truy vấn phạm vi (như truy vấn này) hoặc trên khóa ngoại của bảng con.

Một ảnh hưởng của một chỉ mục phân cụm là nó tập hợp tất cả các dữ liệu lại với nhau vì dữ liệu được lưu trữ trên các nút lá của cây cụm b. Vì vậy, giả sử rằng bạn không yêu cầu 'quá rộng' của phạm vi, trình tối ưu hóa sẽ biết chính xác phần nào của cây b chứa dữ liệu và nó sẽ không phải tìm một định danh hàng và sau đó chuyển đến nơi chứa dữ liệu là (giống như khi làm việc với chỉ số NC). "Quá rộng" của một phạm vi là gì? Một ví dụ lố bịch sẽ yêu cầu 11 tháng dữ liệu từ một bảng chỉ có hồ sơ trị giá một năm. Kéo một ngày dữ liệu không phải là một vấn đề, giả sử rằng số liệu thống kê của bạn được cập nhật. (Mặc dù, trình tối ưu hóa có thể gặp rắc rối nếu bạn đang tìm kiếm dữ liệu của ngày hôm qua và bạn chưa cập nhật số liệu thống kê trong ba ngày.)

Vì bạn đang chạy truy vấn "CHỌN *", công cụ sẽ cần trả về tất cả các cột trong bảng (ngay cả khi ai đó thêm một cột mới mà ứng dụng của bạn không cần tại thời điểm đó), vì vậy chỉ mục hoặc chỉ mục bao phủ với các cột được bao gồm sẽ không giúp được gì nhiều, nếu có. (Nếu bạn bao gồm tất cả các cột từ bảng trong một chỉ mục, bạn đang làm gì đó sai.) Trình tối ưu hóa có thể sẽ bỏ qua các chỉ mục NC đó.

Vậy lam gi?

Đề xuất của tôi sẽ là bỏ chỉ mục NC, thay đổi PK được phân cụm thành không được tách riêng và tạo một chỉ mục được nhóm trên [Date Entryed]. Đơn giản hơn là tốt hơn, cho đến khi nó được chứng minh khác.


Giả sử các hàng được chèn theo thứ tự tăng dần thì đây là câu trả lời đơn giản nhất - nhưng chèn theo thứ tự phi tuyến tính sẽ gây ra sự phân mảnh.
Kirk Broadhurst

Thêm dữ liệu vào bất kỳ cấu trúc cây b nào sẽ khiến nó mất thăng bằng. Ngay cả khi bạn đang thêm hàng theo thứ tự cụm, các chỉ mục sẽ mất cân bằng. Các bảng lập chỉ mục lại sẽ loại bỏ phân mảnh và bất kỳ DBA nào cũng sẽ cho bạn biết rằng các bảng cần được lập chỉ mục lại sau khi dữ liệu "đủ" đã được thêm vào bảng. (Định nghĩa về "đủ" có thể được tranh luận hoặc "khi nào" có thể là một cuộc thảo luận.) Tôi không thấy bất cứ điều gì trong câu hỏi nói rằng việc lập chỉ mục lại không thể được thực hiện vì một số lý do.
eo biển darin

4

Chừng nào bạn còn có chữ "*" trong đó, thì điều duy nhất tôi có thể tưởng tượng sẽ tạo ra nhiều khác biệt là thay đổi định nghĩa chỉ mục của bạn thành điều này:

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)INCLUDE (ID, IsWebUp, IsPingUp, IsPUp)
 WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Như tôi đã lưu ý trong các bình luận, nó nên sử dụng chỉ mục đó, nhưng nếu không, bạn có thể thuyết phục nó bằng lệnh ORDER BY hoặc gợi ý chỉ mục.


Tôi mới thử cái này và tôi vẫn ở cùng một chỗ, 2500ms chờ phản hồi của máy chủ và thời gian xử lý máy khách 10ms.
Nate

Đăng kế hoạch truy vấn.
RBarryYoung

Có vẻ như nó đang sử dụng Chỉ số cụm. (Chi phí CHỌN: 0% <- Chi phí hàng đầu: 20% <- Quét chỉ mục cụm PK_Heartbeats Chi phí: 80%)
Nate

Vâng, điều đó không đúng, đôi khi làm mất các số liệu thống kê / tối ưu hóa. Thêm một gợi ý để buộc nó sử dụng chỉ mục mới.
RBarryYoung

@Max Vernon: Có thể, nhưng điều đó nên được gắn cờ trong kế hoạch truy vấn.
RBarryYoung

3

Tôi sẽ xem xét điều này một chút khác nhau.

  • Vâng, tôi biết đó là một chủ đề cũ nhưng tôi tò mò.

Tôi sẽ đổ cột datetime - thay đổi nó thành int. Có một bảng tra cứu hoặc thực hiện chuyển đổi cho ngày của bạn.

Kết xuất chỉ mục được nhóm - để lại dưới dạng một đống và tạo một chỉ mục không được nhóm trên cột INT mới đại diện cho ngày. tức là hôm nay sẽ là 20121015. Thứ tự đó rất quan trọng. Tùy thuộc vào tần suất bạn tải bảng, hãy xem việc tạo chỉ mục đó theo thứ tự DESC. Duy trì chi phí sẽ cao hơn và bạn sẽ muốn giới thiệu một yếu tố điền hoặc phân vùng. Phân vùng cũng sẽ giúp giảm thời gian chạy của bạn.

Cuối cùng, nếu bạn có thể sử dụng SQL 2012, hãy thử sử dụng SEQUENCE - nó sẽ vượt trội hơn danh tính () cho các phần chèn.


Giải pháp thú vị. Mặc dù không rõ ràng từ câu hỏi của tôi, phần thời gian của DateTime là rất quan trọng. Nói chung tôi truy vấn dựa trên ngày, để xem xét thời gian cụ thể trong khoảng thời gian đó. Làm thế nào bạn sẽ điều chỉnh giải pháp này để giải thích cho điều đó?
Nate

Trong trường hợp đó, hãy giữ cột datetime, thêm cột int cho ngày (vì phạm vi của bạn dựa trên phần tử ngày chứ không phải phần tử thời gian). Bạn cũng có thể xem xét sử dụng kiểu dữ liệu TIME và sau đó, phân chia thời gian một cách hiệu quả khỏi ngày. Theo cách đó, dấu chân dữ liệu của bạn nhỏ hơn và bạn vẫn có yếu tố Thời gian của cột.
Jeremy Lowell

1
Tôi không chắc tại sao tôi lại bỏ lỡ điều này sớm hơn nhưng cũng sử dụng nén hàng trên chỉ mục được phân cụm và chỉ mục không được phân cụm. Tôi vừa thực hiện một thử nghiệm nhanh với bảng của bạn và đây là những gì tôi tìm thấy: Tôi đã tạo một bộ dữ liệu (5,8 triệu hàng) trong bảng được xác định ở trên. Tôi đã nén (hàng) chỉ mục được nhóm và không bao gồm. đọc logic, dựa trên truy vấn chính xác của bạn, đã giảm từ 2.074 xuống 1.433. Đó là một sự giảm đáng kể và tôi tin rằng một mình sẽ giúp bạn thoát khỏi - và đó là rủi ro rất thấp.
Jeremy Lowell
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.