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

Không thể sử dụng mệnh đề UPDATE với OUTPUT khi có trình kích hoạt trên bảng

Cảnh báo về khả năng hiển thị :Đừng trả lời khác. Nó sẽ cung cấp các giá trị không chính xác. Đọc để biết tại sao nó sai.

Cung cấp k bùn cần thiết để thực hiện UPDATE với OUTPUT làm việc trong SQL Server 2008 R2, tôi đã thay đổi truy vấn của mình từ:

UPDATE BatchReports  
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid

tới:

SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid

UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid

Về cơ bản, tôi đã ngừng sử dụng OUTPUT . Điều này không quá tệ như chính Entity Framework sử dụng chính bản hack này!

Hy vọng rằng 2012 2014 2016 2018 2019 Năm 2020 sẽ triển khai tốt hơn.

Cập nhật:sử dụng OUTPUT có hại

Sự cố mà chúng tôi bắt đầu gặp phải là cố sử dụng OUTPUT mệnh đề để truy xuất "sau" giá trị trong bảng:

UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid

Sau đó, điều đó đạt đến giới hạn biết rõ ( "sẽ không khắc phục được" lỗi) trong SQL Server:

Bảng đích 'BatchReports' của câu lệnh DML không được có bất kỳ trình kích hoạt nào được bật nếu câu lệnh chứa mệnh đề OUTPUT mà không có mệnh đề INTO

Nỗ lực Giải pháp # 1

Vì vậy, chúng tôi thử một cái gì đó mà chúng tôi sẽ sử dụng TABLE trung gian biến để giữ OUTPUT kết quả:

DECLARE @t TABLE (
   LastModifiedDate datetime,
   RowVersion timestamp, 
   BatchReportID int
)
  
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid

SELECT * FROM @t

Ngoại trừ trường hợp không thành công vì bạn không được phép chèn timestamp vào bảng (thậm chí là một biến bảng tạm thời).

Nỗ lực Giải pháp # 2

Chúng tôi bí mật biết rằng một timestamp thực sự là một số nguyên không dấu 64 bit (hay còn gọi là 8 byte). Chúng ta có thể thay đổi định nghĩa bảng tạm thời của mình để sử dụng binary(8) thay vì timestamp :

DECLARE @t TABLE (
   LastModifiedDate datetime,
   RowVersion binary(8), 
   BatchReportID int
)
  
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid

SELECT * FROM @t

Và điều đó hoạt động, ngoại trừ việc giá trị bị sai .

Dấu thời gian RowVersion chúng tôi trả về không phải là giá trị của dấu thời gian vì nó đã tồn tại sau khi hoàn thành CẬP NHẬT:

  • dấu thời gian được trả lại :0x0000000001B71692
  • dấu thời gian thực tế :0x0000000001B71693

Đó là vì các giá trị OUTPUT vào bảng của chúng tôi không các giá trị như chúng ở cuối câu lệnh UPDATE:

  • Bắt đầu câu lệnh UPDATE
    • sửa đổi hàng
      • dấu thời gian được cập nhật (ví dụ:2 → 3)
    • OUTPUT truy xuất dấu thời gian mới (tức là 3)
    • kích hoạt chạy
      • sửa đổi lại hàng
        • dấu thời gian được cập nhật (ví dụ:3 → 4)
  • Hoàn thành câu lệnh UPDATE
  • OUTPUT trả về 3 (giá trị sai)

Điều này có nghĩa là:

  • Chúng tôi không nhận được dấu thời gian vì nó tồn tại ở cuối câu lệnh UPDATE ( 4 )
  • Thay vào đó, chúng tôi nhận được dấu thời gian như ở giữa không xác định của câu lệnh UPDATE ( 3 )
  • Chúng tôi không nhận được dấu thời gian chính xác

Điều này cũng đúng với bất kỳ kích hoạt sửa đổi bất kỳ giá trị trong hàng. OUTPUT sẽ không OUTPUT giá trị vào cuối CẬP NHẬT.

Điều này có nghĩa là bạn không thể tin tưởng OUTPUT trả lại bất kỳ giá trị chính xác nào.

Thực tế đau đớn này được ghi lại trong BOL:

Các cột được trả về từ OUTPUT phản ánh dữ liệu như cũ sau khi câu lệnh INSERT, UPDATE hoặc DELETE hoàn thành nhưng trước khi các trình kích hoạt được thực thi.

Entity Framework đã giải quyết nó như thế nào?

Khuôn khổ thực thể .NET sử dụng chuyển đổi hàng hóa cho Đồng thời Lạc quan. EF phụ thuộc vào việc biết giá trị của timestamp vì nó tồn tại sau khi họ phát hành CẬP NHẬT.

Vì bạn không thể sử dụng OUTPUT đối với bất kỳ dữ liệu quan trọng nào, Khung thực thể của Microsoft sử dụng cùng một cách giải quyết mà tôi thực hiện:

Giải pháp # 3 - Cuối cùng - Không sử dụng mệnh đề OUTPUT

Để truy xuất after giá trị, các vấn đề về Khung thực thể:

UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))

SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1

Không sử dụng OUTPUT .

Có, nó gặp phải tình trạng chạy đua, nhưng đó là điều tốt nhất mà SQL Server có thể làm.

Còn INSERTs thì sao

Làm những gì Entity Framework làm:

SET NOCOUNT ON;

DECLARE @generated_keys table([CustomerID] int)

INSERT Customers (FirstName, LastName)
OUTPUT inserted.[CustomerID] INTO @generated_keys
VALUES ('Steve', 'Brown')

SELECT t.[CustomerID], t.[CustomerGuid], t.[RowVersion], t.[CreatedDate]
FROM @generated_keys AS g
   INNER JOIN Customers AS t
   ON g.[CustomerGUID] = t.[CustomerGUID]
WHERE @@ROWCOUNT > 0

Một lần nữa, họ sử dụng SELECT để đọc hàng, thay vì đặt bất kỳ sự tin tưởng nào vào mệnh đề OUTPUT.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Có "HOẶC" trong điều kiện INNER JOIN có phải là một ý tưởng tồi không?

  2. cách gán giá trị cte cho biến

  3. Nhật ký giao dịch cho cơ sở dữ liệu đã đầy

  4. Kết nối các ứng dụng chạy trên Linux với Amazon Relational Database Services (RDS) for SQL Server

  5. Làm thế nào để nhận được một kết quả float bằng cách chia hai giá trị số nguyên bằng T-SQL?