Lọc dữ liệu theo thứ tự hàng


8

Tôi có một bảng dữ liệu SQL với cấu trúc sau:

CREATE TABLE Data(
    Id uniqueidentifier NOT NULL,
    Date datetime NOT NULL,
    Value decimal(20, 10) NULL,
    RV timestamp NOT NULL,
 CONSTRAINT PK_Data PRIMARY KEY CLUSTERED (Id, Date)
)

Số lượng Id khác nhau dao động từ 3000 đến 50000.
Kích thước của bảng thay đổi lên đến hơn một tỷ hàng.
Một Id có thể nằm giữa một vài hàng lên tới 5% của bảng.

Truy vấn được thực hiện nhiều nhất trên bảng này là:

SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate

Bây giờ tôi phải thực hiện truy xuất dữ liệu gia tăng trên một tập hợp con của Id, bao gồm các bản cập nhật.
Sau đó, tôi đã sử dụng một sơ đồ yêu cầu trong đó người gọi cung cấp một chuyển đổi hàng cụ thể, truy xuất một khối dữ liệu và sử dụng giá trị chuyển đổi tối đa của dữ liệu được trả về cho cuộc gọi tiếp theo.

Tôi đã viết thủ tục này:

CREATE TYPE guid_list_tbltype AS TABLE (Id uniqueidentifier not null primary key)
CREATE PROCEDURE GetData
    @Ids guid_list_tbltype READONLY,
    @Cursor rowversion,
    @MaxRows int
AS
BEGIN
    SELECT A.* 
    FROM (
        SELECT 
            Data.Id,
            Date,
            Value,
            RV,
            ROW_NUMBER() OVER (ORDER BY RV) AS RN
        FROM Data
             inner join (SELECT Id FROM @Ids) Ids ON Ids.Id = Data.Id
        WHERE RV > @Cursor
    ) A 
    WHERE RN <= @MaxRows
END

Trường hợp @MaxRowssẽ nằm trong khoảng từ 500.000 đến 2.000.000 tùy thuộc vào mức độ khách hàng sẽ muốn dữ liệu của mình.


Tôi đã thử các cách tiếp cận khác nhau:

  1. Lập chỉ mục trên (Id, RV):
    CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);

Sử dụng các chỉ số, truy vấn tìm kiếm các hàng nơi RV = @Cursorcho mỗi Idtrong @Ids, đọc các hàng sau sau đó hợp nhất kết quả và phân loại.
Hiệu quả sau đó phụ thuộc vào vị trí tương đối của @Cursorgiá trị.
Nếu nó ở gần cuối dữ liệu (được sắp xếp bởi RV) thì truy vấn là tức thời và nếu không truy vấn có thể mất đến vài phút (không bao giờ để nó chạy đến cuối).

vấn đề với cách tiếp cận này @Cursorlà ở gần cuối dữ liệu và việc sắp xếp không gây đau đớn (thậm chí không cần thiết nếu truy vấn trả về ít hàng hơn @MaxRows) hoặc là phía sau và truy vấn phải sắp xếp @MaxRows * LEN(@Ids)các hàng.

  1. Lập chỉ mục trên RV:
    CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);

Sử dụng chỉ mục, truy vấn tìm kiếm hàng trong RV = @Cursorđó sau đó đọc mọi hàng loại bỏ Id không được yêu cầu cho đến khi đạt được @MaxRows.
Hiệu quả sau đó phụ thuộc vào% Id được yêu cầu ( LEN(@Ids) / COUNT(DISTINCT Id)) và phân phối của chúng.
Id được yêu cầu nhiều hơn có nghĩa là các hàng bị loại bỏ ít hơn có nghĩa là các lần đọc hiệu quả hơn, Id được yêu cầu ít hơn có nghĩa là các hàng bị loại bỏ nhiều hơn có nghĩa là nhiều lượt đọc hơn cho cùng một lượng hàng kết quả.

Vấn đề với cách tiếp cận này là nếu Id được yêu cầu chỉ chứa một vài thành phần, thì có thể phải đọc toàn bộ chỉ mục để có được các hàng mong muốn.

  1. Sử dụng chỉ mục được lọc hoặc chế độ xem được lập chỉ mục
    CREATE NONCLUSTERED INDEX IDX_RVClient1 ON Data(Id, RV) INCLUDE(Date, Value)
    WHERE Id IN (/* list of Ids for specific client*/);

Hoặc là

    CREATE VIEW vDataClient1 WITH SCHEMABINDING
    AS
    SELECT
        Id,
        Date,
        Value,
        RV
    FROM dbo.Data
    WHERE Id IN (/* list of Ids for specific client*/)
    CREATE UNIQUE CLUSTERED INDEX IDX_IDRV ON vDataClient1(Id, Rv);

Phương pháp này cho phép lập kế hoạch thực hiện truy vấn và lập chỉ mục hiệu quả hoàn hảo nhưng có nhược điểm: 1. Thực tế, tôi sẽ phải triển khai SQL động để tạo chỉ mục hoặc dạng xem và sửa đổi quy trình yêu cầu để sử dụng chỉ mục hoặc dạng xem đúng. 2. Tôi sẽ phải duy trì một chỉ mục hoặc chế độ xem của khách hàng hiện tại, bao gồm cả lưu trữ. 3. Mỗi khi khách hàng phải sửa đổi danh sách Id được yêu cầu của mình, tôi sẽ phải bỏ chỉ mục hoặc xem và tạo lại nó.


Tôi dường như không thể tìm thấy một phương pháp phù hợp với nhu cầu của mình.
Tôi đang tìm kiếm ý tưởng tốt hơn để thực hiện truy xuất dữ liệu gia tăng. Những ý tưởng đó có thể ngụ ý làm lại lược đồ yêu cầu hoặc lược đồ cơ sở dữ liệu mặc dù tôi thích cách tiếp cận lập chỉ mục tốt hơn nếu có.


Crosspost với stackoverflow.com/questions/11586004/ . Hiện tại tôi đã xóa phiên bản Oracle vì tôi phát hiện ra rằng ORA_lawSCN không thể lập chỉ mục (và hầu như không thông qua các chế độ xem được lập chỉ mục).
Paciv

Làm thế nào để trường ngày phù hợp? Một hàng với một ID và ngày cụ thể có thể được cập nhật trong bảng không? Và nếu vậy, ngày cũng được cập nhật (như dấu thời gian bổ sung phải không?)
8kb

Có vẻ như đối với nỗ lực GetData (), thứ tự nên bao gồm Id (thứ tự theo RV, Id). Bạn có thể nhận xét về việc sử dụng chỉ mục của (Rv, Id) không? Ngoài ra, sử dụng chuyển đổi tối đa ">" từ cuộc gọi trước có vẻ như nó sẽ bỏ lỡ các bản ghi giữa các khối nếu các hàng có cùng một hàng (không thể thực hiện được?).
crokusek

@ 8kb: các câu lệnh cập nhật chạy trên bảng chỉ sửa đổi Valuecột. @crokusek: Không đặt hàng bằng RV, ID thay vì RV chỉ tăng khối lượng công việc sắp xếp mà không có bất kỳ lợi ích nào, tôi không hiểu lý do đằng sau nhận xét của bạn. Từ những gì tôi đã đọc, RV phải là duy nhất trừ khi chèn dữ liệu cụ thể vào cột đó, ứng dụng không có.
Paciv

Khách hàng có thể chấp nhận kết quả theo thứ tự (Id, Rv) và cung cấp đối số LastId ngoài đối số LastRowVersion để loại bỏ sắp xếp RV trên các id không? Những bình luận trước đây của tôi đều dựa trên giả định rằng RV có bản sao. Chỉ số được lọc trên mỗi khách hàng trông thú vị.
crokusek

Câu trả lời:


5

Một giải pháp là để ứng dụng khách ghi nhớ tối đa rowversiontrên mỗi ID. Loại bảng do người dùng định nghĩa sẽ thay đổi thành:

CREATE TYPE
    dbo.guid_list_tbltype
AS TABLE 
    (
    Id      uniqueidentifier PRIMARY KEY, 
    LastRV  rowversion NOT NULL
    );

Truy vấn trong thủ tục sau đó có thể được viết lại để sử dụng APPLYmẫu (xem các bài viết SQLServerCentral của tôi phần 1phần 2 - yêu cầu đăng nhập miễn phí). Chìa khóa để thực hiện tốt ở đây là ORDER BY- nó tránh được việc tìm nạp trước không được sắp xếp trên các vòng lặp lồng nhau. Điều RECOMPILEcần thiết là cho phép trình tối ưu hóa nhìn thấy mức độ chính của biến bảng trong thời gian biên dịch (có thể dẫn đến một kế hoạch song song mong muốn).

ALTER PROCEDURE dbo.GetData

    @IDs        guid_list_tbltype READONLY,
    @MaxRows    bigint

AS
BEGIN

    SELECT TOP (@MaxRows)
        d.Id,
        d.[Date],
        d.Value,
        d.RV
    FROM @Ids AS i
    CROSS APPLY
    (
        SELECT
            d.*
        FROM dbo.Data AS d
        WHERE
            d.Id = i.Id
            AND d.RV > i.LastRV
    ) AS d
    ORDER BY
        i.Id,
        d.RV
    OPTION (RECOMPILE);

END;

Bạn sẽ nhận được một kế hoạch truy vấn sau khi thực hiện như thế này (kế hoạch ước tính sẽ là nối tiếp):

kế hoạch truy vấn


Đúng, một trong những giải pháp thay đổi thiết kế là để khách hàng nhớ MAX(RV)mỗi Id (hoặc hệ thống đăng ký trong đó ứng dụng nội bộ ghi nhớ tất cả các cặp Id / RV) và tôi sử dụng máy khách này cho một khách hàng khác. Một giải pháp khác là buộc khách hàng luôn lấy tất cả các Id (điều này làm cho vấn đề lập chỉ mục trở nên tầm thường). Nó vẫn không đáp ứng được nhu cầu cụ thể của câu hỏi: Truy xuất tăng dần một tập hợp con Id chỉ với một bộ đếm toàn cầu do khách hàng cung cấp.
Paciv

2

Nếu có thể, tôi sẽ thiết kế lại bảng. Nếu chúng ta có thể có VersionNumber như một số nguyên gia tăng không có khoảng trống, thì nhiệm vụ truy xuất đoạn tiếp theo là quét phạm vi hoàn toàn không đáng kể. Tất cả chúng ta cần là chỉ số sau đây:

CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, VersionNumber) INCLUDE(Date, Value);

Tất nhiên, chúng ta cần đảm bảo rằng VersionNumber bắt đầu bằng một và không có khoảng trống. Điều này là dễ dàng để làm với các ràng buộc.


Bạn có nghĩa là một địa phương toàn cầu hoặc Id VersionNumber? Dù thế nào đi nữa, tôi không thể thấy điều đó sẽ giúp gì cho câu hỏi, bạn có thể nói rõ hơn không?
Paciv

0

Những gì tôi sẽ làm:

Trong trường hợp này, PK của bạn phải là Trường nhận dạng "Khóa thay thế" tự động tăng.
Vì bạn đã có hàng tỷ, nên tốt nhất là đi cùng với BigInt.
Hãy gọi nó là DataID .
Điều này sẽ:

  • Thêm 8 byte vào mỗi bản ghi trong Chỉ mục cụm của bạn.
  • Lưu 16 byte trên mỗi bản ghi trong mọi Chỉ mục không được nhóm.
  • Những gì bạn đã có là "Khóa tự nhiên": UniqueIdentifyer (16 Byte) w / a DateTime (8 Byte).
  • Đó là 24 byte trong mỗi Bản ghi chỉ mục để tham chiếu lại Chỉ mục được nhóm!
  • Đây là lý do tại sao chúng ta có Khóa thay thế là số nguyên tăng dần nhỏ hơn.


Đặt PK BigInt mới ( DataID ) của bạn để sử dụng Chỉ mục cụm :
Điều này sẽ:

  • Đảm bảo các bản ghi được tạo gần đây nhất được đặt gần cuối.
  • Cho phép lập chỉ mục nhanh hơn với các chỉ mục không phân cụm khác.
  • Cho phép mở rộng trong tương lai dưới dạng FK sang các Bảng khác.


Tạo một Chỉ mục không phân cụm xung quanh (Ngày, Id).
Điều này sẽ:

  • Tăng tốc các truy vấn thường được sử dụng nhất của bạn.
  • Bạn có thể thêm "Giá trị", nhưng nó sẽ tăng kích thước chỉ mục của bạn, làm cho nó chậm hơn.
  • Tôi khuyên bạn nên thử nó trong và ngoài Index để xem liệu có sự khác biệt lớn về hiệu suất hay không.
  • Tôi khuyên bạn không nên sử dụng "Bao gồm" nếu bạn thêm nó.
  • Chỉ cần xử lý như vậy (Ngày, Id, Giá trị) - nhưng chỉ khi thử nghiệm của bạn cho thấy nó cải thiện hiệu suất.


Tạo một chỉ mục không được nhóm trên (RV, ID).
Điều này sẽ:

  • Luôn giữ Chỉ số của bạn càng nhỏ càng tốt.
  • Trừ khi bạn nhận thấy mức tăng hiệu suất cực lớn khi có Ngày và Giá trị trong Chỉ mục của mình, tôi khuyên bạn nên bỏ chúng ra để tiết kiệm dung lượng đĩa. Hãy thử mà không có chúng đầu tiên.
  • Nếu bạn thêm Ngày hoặc Giá trị, không sử dụng "Bao gồm", thay vào đó hãy thêm chúng vào thứ tự của Chỉ mục.
  • Nhờ vào Tăng dữ liệu trên các Chèn mới vào PK được nhóm của bạn, RV gần đây của bạn thường sẽ xuất hiện ở gần cuối (trừ khi bạn luôn cập nhật các luồng dữ liệu từ quá khứ).
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.