Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Thói quen xấu:Đếm hàng một cách khó khăn

[Xem chỉ mục của tất cả các bài đăng về thói quen xấu / phương pháp hay nhất]

Một trong những trang trình bày trong bản trình bày Định kỳ về Thói quen xấu &Các phương pháp hay nhất của tôi có tựa đề "Lạm dụng COUNT(*) . "Tôi thấy việc lạm dụng này diễn ra khá phổ biến và nó có nhiều hình thức.

Có bao nhiêu hàng trong bảng?

Tôi thường thấy điều này:

SELECT @count = COUNT(*) FROM dbo.tablename;

SQL Server phải chạy quét chặn đối với toàn bộ bảng để tính số lượng này. Nó rất đắt tiền. Thông tin này được lưu trữ trong các chế độ xem danh mục và DMV, và bạn có thể lấy thông tin đó mà không cần tất cả I / O hoặc chặn:

SELECT @count = SUM(p.rows)
  FROM sys.partitions AS p
  INNER JOIN sys.tables AS t
  ON p.[object_id] = t.[object_id]
  INNER JOIN sys.schemas AS s
  ON t.[schema_id] = s.[schema_id]
  WHERE p.index_id IN (0,1) -- heap or clustered index
  AND t.name = N'tablename'
  AND s.name = N'dbo';

(Bạn có thể lấy thông tin tương tự từ sys.dm_db_partition_stats , nhưng trong trường hợp đó, hãy thay đổi p.rows tới p.row_count (yay nhất quán!). Trên thực tế, đây là cùng một chế độ xem mà sp_spaceused sử dụng để lấy ra số lượng - và mặc dù nó dễ nhập hơn nhiều so với truy vấn ở trên, tôi khuyên bạn không nên sử dụng nó chỉ để tính số lượng vì nó thực hiện tất cả các phép tính bổ sung - trừ khi bạn cũng muốn có thông tin đó. Cũng xin lưu ý rằng nó sử dụng các chức năng siêu dữ liệu không tuân theo mức cách ly bên ngoài của bạn, vì vậy bạn có thể phải chờ chặn khi bạn gọi quy trình này.)

Hiện tại, đúng là những lượt xem này không chính xác 100%, chính xác đến từng micro giây. Trừ khi bạn đang sử dụng heap, bạn có thể nhận được kết quả đáng tin cậy hơn từ sys.dm_db_index_physical_stats() cột record_count (chắc chắn lại nhất quán!), tuy nhiên, hàm này có thể ảnh hưởng đến hiệu suất, vẫn có thể chặn và thậm chí có thể đắt hơn SELECT COUNT(*) - nó phải thực hiện các hoạt động vật lý tương tự, nhưng phải tính toán thêm thông tin tùy thuộc vào chế độ mode (chẳng hạn như phân mảnh, mà bạn không quan tâm trong trường hợp này). Cảnh báo trong tài liệu cho biết một phần của câu chuyện, có liên quan nếu bạn đang sử dụng Nhóm khả dụng (và có thể ảnh hưởng đến phản chiếu cơ sở dữ liệu theo cách tương tự):

Nếu bạn truy vấn sys.dm_db_index_physical_stats trên một phiên bản máy chủ đang lưu trữ bản sao phụ có thể đọc được AlwaysOn, bạn có thể gặp phải sự cố chặn REDO. Điều này là do chế độ xem quản lý động này có được một khóa IS trên bảng hoặc chế độ xem người dùng được chỉ định có thể chặn các yêu cầu bởi một chuỗi REDO cho một khóa X trên bảng hoặc chế độ xem người dùng đó.

Tài liệu cũng giải thích lý do tại sao con số này có thể không đáng tin cậy đối với một đống (và cũng cung cấp cho chúng một chuẩn xác định cho sự không nhất quán của các hàng so với bản ghi):

Đối với một heap, số lượng bản ghi được trả về từ hàm này có thể không khớp với số lượng hàng được trả về bằng cách chạy SELECT COUNT (*) đối với heap. Điều này là do một hàng có thể chứa nhiều bản ghi. Ví dụ:trong một số tình huống cập nhật, một hàng heap có thể có bản ghi chuyển tiếp và bản ghi được chuyển tiếp do kết quả của thao tác cập nhật. Ngoài ra, hầu hết các hàng LOB lớn được chia thành nhiều bản ghi trong bộ lưu trữ LOB_DATA.

Vì vậy, tôi sẽ nghiêng về sys.partitions như một cách để tối ưu hóa điều này, hy sinh một chút độ chính xác.

    "Nhưng tôi không thể sử dụng DMV; số lượng của tôi cần phải siêu chính xác!"

    Một số đếm "siêu chính xác" thực sự khá vô nghĩa. Hãy xem xét rằng tùy chọn duy nhất của bạn để đếm "siêu chính xác" là khóa toàn bộ bảng và cấm bất kỳ ai thêm hoặc xóa bất kỳ hàng nào (nhưng không ngăn các lần đọc được chia sẻ), ví dụ:

    SELECT @count = COUNT(*) FROM dbo.table_name WITH (TABLOCK); -- not TABLOCKX!

    Vì vậy, truy vấn của bạn đang lặp lại, quét tất cả dữ liệu, hướng tới số lượng "hoàn hảo" đó. Trong khi đó, các yêu cầu viết đang bị chặn và đang chờ đợi. Đột nhiên, khi số lượng chính xác của bạn được trả lại, các ổ khóa trên bàn của bạn được giải phóng và tất cả những yêu cầu viết đó đã được xếp hàng và chờ đợi, bắt đầu kích hoạt tất cả các loại chèn, cập nhật và xóa đối với bảng của bạn. Làm thế nào "siêu chính xác" là số đếm của bạn bây giờ? Nó có đáng để nhận được một số lượng "chính xác" đã lỗi thời khủng khiếp không? Nếu hệ thống không bận, thì đây không phải là vấn đề quá lớn - nhưng nếu hệ thống không bận, tôi sẽ tranh luận khá gay gắt rằng các DMV sẽ khá chính xác.

    Bạn có thể đã sử dụng NOLOCK thay vào đó, nhưng điều đó chỉ có nghĩa là người viết có thể thay đổi dữ liệu trong khi bạn đang đọc nó và dẫn đến các vấn đề khác (tôi đã nói về điều này gần đây). Không sao cho nhiều pha tắc bóng, nhưng không phải nếu mục tiêu của bạn là độ chính xác. Các DMV sẽ ở đúng (hoặc ít nhất là gần hơn nhiều) trong nhiều tình huống và xa hơn trong rất ít (thực tế là tôi không thể nghĩ đến).

    Cuối cùng, bạn có thể sử dụng cách ly ảnh chụp nhanh đã đọc. Kendra Little có một bài đăng tuyệt vời về các cấp độ cô lập của ảnh chụp nhanh, nhưng tôi sẽ lặp lại danh sách các lưu ý mà tôi đã đề cập trong NOLOCK của mình bài viết:

    • Các khóa Sch-S vẫn cần được sử dụng ngay cả trong RCSI.
    • Các cấp độ cô lập của ảnh chụp nhanh sử dụng lập phiên bản hàng trong tempdb, vì vậy bạn thực sự cần kiểm tra tác động ở đó.
    • RCSI không thể sử dụng quét thứ tự phân bổ hiệu quả; thay vào đó bạn sẽ thấy các bản quét phạm vi.
    • Paul White (@SQL_Kiwi) có một số bài đăng tuyệt vời mà bạn nên đọc về các mức cô lập này:
      • Đọc Cách ly Ảnh chụp nhanh Đã cam kết
      • Sửa đổi dữ liệu trong phần tách biệt ảnh chụp nhanh đã cam kết đọc
      • Mức cô lập SNAPSHOT

    Ngoài ra, ngay cả với RCSI, việc đếm "chính xác" cần có thời gian (và tài nguyên bổ sung trong tempdb). Đến khi hoạt động kết thúc, số đếm còn chính xác không? Chỉ khi chưa có ai chạm vào bàn trong thời gian chờ đợi. Vì vậy, một trong những lợi ích của RCSI (người đọc không chặn người viết) bị lãng phí.

Có bao nhiêu hàng phù hợp với mệnh đề WHERE?

Đây là một kịch bản hơi khác - bạn cần biết có bao nhiêu hàng tồn tại cho một tập hợp con nhất định của bảng. Bạn không thể sử dụng DMV cho việc này, trừ khi WHERE mệnh đề khớp với một chỉ mục đã lọc hoặc bao phủ hoàn toàn một phân vùng chính xác (hoặc nhiều).

Nếu WHERE của bạn mệnh đề là động, bạn có thể sử dụng RCSI, như được mô tả ở trên.

Nếu WHERE của bạn mệnh đề không động, bạn cũng có thể sử dụng RCSI, nhưng bạn cũng có thể xem xét một trong các tùy chọn sau:

  • Chỉ mục đã lọc - ví dụ:nếu bạn có một bộ lọc đơn giản như is_active = 1 hoặc status < 5 , thì bạn có thể tạo chỉ mục như sau:
    CREATE INDEX ix_f ON dbo.table_name(leading_pk_column) WHERE is_active = 1;

    Bây giờ, bạn có thể nhận được số lượng khá chính xác từ các DMV, vì sẽ có các mục đại diện cho chỉ mục này (bạn chỉ cần xác định index_id thay vì dựa vào chỉ số heap (0) / clustered (1)). Tuy nhiên, bạn cần phải xem xét một số điểm yếu của các chỉ mục đã lọc.

  • Chế độ xem được lập chỉ mục - ví dụ:nếu bạn thường đếm đơn đặt hàng của khách hàng, chế độ xem được lập chỉ mục có thể hữu ích (mặc dù vui lòng không coi đây là sự chứng thực chung chung rằng "chế độ xem được lập chỉ mục cải thiện tất cả các truy vấn!"):
    CREATE VIEW dbo.view_name
    WITH SCHEMABINDING
    AS
      SELECT 
        customer_id, 
        customer_count = COUNT_BIG(*)
      FROM dbo.table_name
      GROUP BY customer_id;
    GO
     
    CREATE UNIQUE CLUSTERED INDEX ix_v ON dbo.view_name(customer_id);

    Bây giờ, dữ liệu trong chế độ xem sẽ được hiện thực hóa và số lượng được đảm bảo được đồng bộ hóa với dữ liệu bảng (có một số lỗi khó hiểu khi điều này không đúng, chẳng hạn như lỗi này với MERGE , nhưng nói chung điều này là đáng tin cậy). Vì vậy, bây giờ bạn có thể nhận được số lượng của mình cho mỗi khách hàng (hoặc cho một nhóm khách hàng) bằng cách truy vấn chế độ xem, với chi phí truy vấn thấp hơn nhiều (1 hoặc 2 lần đọc):

    SELECT customer_count FROM dbo.view_name WHERE customer_id = <x>;

    Tuy nhiên, không có bữa trưa miễn phí . Bạn cần xem xét chi phí duy trì chế độ xem được lập chỉ mục và tác động của nó đối với phần ghi của khối lượng công việc của bạn. Nếu bạn không thường xuyên chạy loại truy vấn này, bạn sẽ khó gặp phải rắc rối này.

Có ít nhất một hàng khớp với mệnh đề WHERE không?

Đây cũng là một câu hỏi hơi khác. Nhưng tôi thường thấy điều này:

IF (SELECT COUNT(*) FROM dbo.table_name WHERE <some clause>) > 0 -- or = 0 for not exists

Vì bạn rõ ràng không quan tâm đến số lượng thực tế, bạn chỉ quan tâm nếu có ít nhất một hàng tồn tại, tôi thực sự nghĩ bạn nên thay đổi nó thành như sau:

IF EXISTS (SELECT 1 FROM dbo.table_name WHERE <some clause>)

Điều này ít nhất có khả năng xảy ra đoản mạch trước khi đến cuối bảng và hầu như sẽ luôn hoạt động sai COUNT biến thể (mặc dù có một số trường hợp SQL Server đủ thông minh để chuyển đổi IF (SELECT COUNT...) > 0 thành IF EXISTS() đơn giản hơn ). Trong trường hợp xấu nhất tuyệt đối, không tìm thấy hàng nào (hoặc hàng đầu tiên được tìm thấy ở trang cuối cùng trong quá trình quét), hiệu suất sẽ giống nhau.

[Xem chỉ mục của tất cả các bài đăng về thói quen xấu / phương pháp hay nhất]


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tổng quan về chức năng CheckDB của DBCC

  2. Hekaton with a twist:In-memory TVPs - Part 2

  3. Dỡ bỏ cơ sở dữ liệu rất lớn

  4. Hướng dẫn DBMS:Toàn bộ Khóa học về sự cố trên DBMS

  5. Giới thiệu về Cơ sở dữ liệu Chuỗi thời gian