Có an toàn không khi dựa vào thứ tự của mệnh đề OUTPUT của INSERT?


19

Đưa ra bảng này:

CREATE TABLE dbo.Target (
   TargetId int identity(1, 1) NOT NULL,
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL, -- of course this should be normalized
   Code int NOT NULL,
   CONSTRAINT PK_Target PRIMARY KEY CLUSTERED (TargetId)
);

Trong hai kịch bản hơi khác nhau, tôi muốn chèn các hàng và trả về các giá trị từ cột định danh.

cảnh 1

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
   (VALUES
      ('Blue', 'New', 1234),
      ('Blue', 'Cancel', 4567),
      ('Red', 'New', 5678)
   ) t (Color, Action, Code)
;

Kịch bản 2

CREATE TABLE #Target (
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL,
   Code int NOT NULL,
   PRIMARY KEY CLUSTERED (Color, Action)
);

-- Bulk insert to the table the same three rows as above by any means

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM #Target
;

Câu hỏi

Tôi có thể dựa vào các giá trị danh tính được trả về từ dbo.Targetbảng chèn để được trả về theo thứ tự chúng tồn tại trong VALUESmệnh đề 1) và 2) #Targetđể tôi có thể tương quan chúng theo vị trí của chúng trong hàng đầu ra trở lại đầu vào ban đầu không?

Để tham khảo

Dưới đây là một số mã C # được cắt bớt để trình bày những gì đang xảy ra trong ứng dụng (kịch bản 1, sắp được chuyển đổi sang sử dụng SqlBulkCopy):

public IReadOnlyCollection<Target> InsertTargets(IEnumerable<Target> targets) {
   var targetList = targets.ToList();
   const string insertSql = @"
      INSERT dbo.Target (
         CoreItemId,
         TargetDateTimeUtc,
         TargetTypeId,
      )
      OUTPUT
         Inserted.TargetId
      SELECT
         input.CoreItemId,
         input.TargetDateTimeUtc,
         input.TargetTypeId,
      FROM
         (VALUES
            {0}
         ) input (
            CoreItemId,
            TargetDateTimeUtc,
            TargetTypeId
         );";
   var results = Connection.Query<DbTargetInsertResult>(
      string.Format(
         insertSql,
         string.Join(
            ", ",
            targetList
               .Select(target => $@"({target.CoreItemId
                  }, '{target.TargetDateTimeUtc:yyyy-MM-ddTHH:mm:ss.fff
                  }', {(byte) target.TargetType
                  })";
               )
         )
      )
      .ToList();
   return targetList
      .Zip( // The correlation that relies on the order of the two inputs being the same
         results,
         (inputTarget, insertResult) => new Target(
            insertResult.TargetId, // with the new TargetId to replace null.
            inputTarget.TargetDateTimeUtc,
            inputTarget.CoreItemId,
            inputTarget.TargetType
         )
      )
      .ToList()
      .AsReadOnly();
}

Câu trả lời:


22

Tôi có thể dựa vào các giá trị danh tính được trả về từ bảng chèn dbo.Target để được trả về theo thứ tự chúng tồn tại trong mệnh đề 1) GIÁ TRỊ và 2) Bảng #Target để tôi có thể tương quan chúng theo vị trí của chúng trong hàng đầu ra trở lại đến đầu vào ban đầu?

Không, bạn không thể dựa vào bất cứ điều gì để được đảm bảo mà không có sự bảo đảm bằng văn bản thực tế. Các tài liệu nói rõ rằng không có sự đảm bảo như vậy.

SQL Server không đảm bảo thứ tự các hàng được xử lý và trả về bởi các câu lệnh DML bằng mệnh đề OUTPUT. Tùy thuộc vào ứng dụng bao gồm một mệnh đề WHERE thích hợp có thể đảm bảo ngữ nghĩa mong muốn hoặc hiểu rằng khi nhiều hàng có thể đủ điều kiện cho hoạt động DML, không có thứ tự được đảm bảo.

Điều này sẽ dựa trên rất nhiều giả định không có giấy tờ

  1. Thứ tự các hàng được xuất ra từ quá trình quét không đổi theo cùng thứ tự với mệnh đề giá trị (Tôi chưa bao giờ thấy chúng khác nhau nhưng AFAIK điều này không được đảm bảo).
  2. Thứ tự các hàng được chèn sẽ giống như thứ tự chúng được xuất ra từ quá trình quét liên tục (chắc chắn không phải luôn luôn như vậy).
  3. Nếu sử dụng kế hoạch thực hiện "rộng" (trên mỗi chỉ mục), các giá trị từ mệnh đề đầu ra sẽ được kéo từ toán tử cập nhật chỉ mục cụm và không phải của bất kỳ chỉ mục phụ nào.
  4. Rằng trật tự được đảm bảo sẽ được bảo quản sau đó - ví dụ như khi đóng gói hàng để truyền qua mạng .
  5. Ngay cả khi thứ tự xuất hiện có thể dự đoán được thì việc triển khai sẽ thay đổi thành các tính năng như chèn song song sẽ không thay đổi thứ tự trong tương lai (hiện tại nếu mệnh đề OUTPUT được chỉ định trong câu lệnh INSERT LỰA CHỌN để trả về kết quả cho máy khách, thì các kế hoạch song song là nói chung bị vô hiệu hóa, bao gồm cả INSERT )

Có thể thấy một ví dụ về điểm hai (giả sử PK cụm (Color, Action)) nếu bạn thêm 600 hàng vào VALUESmệnh đề. Sau đó, kế hoạch có một toán tử sắp xếp trước khi chèn để mất thứ tự ban đầu của bạn trong VALUESmệnh đề.

Có một cách được ghi lại bằng văn bản để đạt được mục tiêu của bạn và đây là để thêm một số vào nguồn và sử dụng MERGEthay vìINSERT

MERGE dbo.Target
USING (VALUES (1, 'Blue', 'New', 1234),
              (2, 'Blue', 'Cancel', 4567),
              (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ON 1 = 0
WHEN NOT MATCHED THEN
  INSERT (Color,
          Action,
          Code)
  VALUES (Color,
          Action,
          Code)
OUTPUT t.SourceId,
       inserted.TargetId; 

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

@một con ngựa không có tên

Là sự hợp nhất thực sự cần thiết? Bạn không thể làm một insert into ... select ... from (values (..)) t (...) order by sourceid?

Đúng bạn có thể. Đặt hàng đảm bảo trong SQL Server 'nhà nước tuyên bố rằng

Các truy vấn INSERT sử dụng CHỌN với ORDER BY để điền vào các hàng đảm bảo cách tính giá trị nhận dạng nhưng không theo thứ tự các hàng được chèn

Vì vậy, bạn có thể sử dụng

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
(VALUES (1, 'Blue', 'New', 1234),
        (2, 'Blue', 'Cancel', 4567),
        (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ORDER BY t.SourceId

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

Điều này sẽ đảm bảo rằng các giá trị danh tính được gán theo thứ tự t.SourceIdnhưng không phải là đầu ra theo bất kỳ thứ tự cụ thể nào hoặc các giá trị cột nhận dạng được gán không có khoảng trống (ví dụ: nếu cố gắng chèn đồng thời).


2
Điều cuối cùng này về tiềm năng của các khoảng trống và đầu ra không theo một thứ tự cụ thể làm cho mọi thứ thú vị hơn một chút khi cố gắng tương quan trở lại đầu vào. Tôi cho rằng một đơn đặt hàng trong ứng dụng sẽ thực hiện công việc, nhưng có vẻ an toàn và rõ ràng hơn khi chỉ sử dụng MERGE.
ErikE

Sử dụng OUTPUT ... INTO [#temp]cú pháp, sau đó SELECT ... FROM [#temp] ORDER BYđể đảm bảo thứ tự đầu ra.
Max Vernon

Phiên bản TL; DR: Với SQL Server và tôi tin rằng việc triển khai SQL nói chung, trừ khi có mệnh đề ORDER BY, thứ tự không được đảm bảo.
nateirvin

Re. các lỗ hổng: Có kèm theo InsertTuyên bố trong một Transactionkhoảng trống không?
Tom
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.