Thay đổi khóa chính từ IDENTITY thành cột được tính toán bằng COALESCE


10

Trong nỗ lực tách ứng dụng khỏi cơ sở dữ liệu nguyên khối của chúng tôi, chúng tôi đã cố gắng thay đổi các cột INT IDENTITY của các bảng khác nhau thành một cột được tính toán PERSISTED sử dụng COALESCE. Về cơ bản, chúng ta cần ứng dụng tách rời khả năng vẫn cập nhật cơ sở dữ liệu cho dữ liệu chung được chia sẻ trên nhiều ứng dụng trong khi vẫn cho phép các ứng dụng hiện tại tạo dữ liệu trong các bảng này mà không cần sửa đổi mã hoặc quy trình.

Vì vậy, về cơ bản, chúng tôi đã chuyển từ một định nghĩa cột;

PkId INT IDENTITY(1,1) PRIMARY KEY

đến;

PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL

Trong tất cả các trường hợp, LOLId cũng là một KHÓA CHÍNH và trong tất cả trừ một trường hợp, đó là CLUSTERED. Tất cả các bảng có các khóa và chỉ mục nước ngoài giống như trước đây. Về bản chất, định dạng mới cho phép ứng dụng được cung cấp bởi ứng dụng tách rời (dưới dạng bên ngoài), nhưng cũng cho phép LOLId trở thành giá trị cột IDENTITY do đó cho phép mã hiện có dựa trên cột IDENTITY thông qua việc sử dụng SCOPE_IDENTITY và @@ IDENTITY để làm việc như trước đây.

Vấn đề chúng tôi gặp phải là chúng tôi đã bắt gặp một vài truy vấn đã từng chạy trong một thời gian có thể chấp nhận được đến bây giờ đã hoàn toàn nổ ra. Các kế hoạch truy vấn được tạo bởi các truy vấn này không giống như trước đây.

Cho rằng cột mới là một KHÓA CHÍNH, cùng loại dữ liệu như trước đây và ĐƯỢC CHẤP NHẬN, tôi sẽ có các truy vấn và kế hoạch truy vấn dự kiến ​​sẽ hoạt động giống như trước đây. Có nên về cơ bản tính toán INT PEDISTED INT LOLId hoạt động giống như một định nghĩa INT rõ ràng về cách SQL Server sẽ tạo ra kế hoạch thực hiện? Có những vấn đề có khả năng khác với phương pháp này mà bạn có thể thấy?

Mục đích của thay đổi này được cho là cho phép chúng tôi thay đổi định nghĩa bảng mà không cần phải sửa đổi các quy trình và mã hiện có. Với những vấn đề này, tôi không cảm thấy như chúng ta có thể đi theo phương pháp này.


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Paul White 9

Câu trả lời:


4

ĐẦU TIÊN

Bạn có thể không cần tất cả ba cột: old_id, external_id, new_id. Các new_idcột, là một IDENTITY, sẽ có một giá trị mới được tạo ra cho mỗi hàng, ngay cả khi bạn chèn vào external_id. Nhưng, giữa old_idexternal_id, những thứ đó khá nhiều loại trừ lẫn nhau: hoặc đã có một old_idgiá trị hoặc cột đó, theo quan niệm hiện tại, sẽ chỉ là NULLnếu sử dụng external_idhoặc new_id. Vì bạn sẽ không thêm id "bên ngoài" mới vào một hàng đã tồn tại (tức là một id có old_idgiá trị) và sẽ không có bất kỳ giá trị mới nào xuất hiện old_id, nên có thể có một cột được sử dụng cho cả hai mục đích

Vì vậy, hãy thoát khỏi external_idcột và đổi tên old_idthành một cái gì đó giống như old_or_external_idhoặc bất cứ điều gì. Điều này không yêu cầu bất kỳ thay đổi thực sự đối với bất cứ điều gì, nhưng làm giảm một số biến chứng. Nhiều nhất bạn có thể cần gọi cột external_id, ngay cả khi nó chứa các giá trị "cũ", nếu mã ứng dụng đã được viết để chèn vào external_id.

Điều đó làm giảm cấu trúc mới chỉ là:

PkId AS AS COALESCE(old_or_external_id, new_id, -1) PERSISTED NOT NULL,
old_or_external_id INT NULL, -- values from existing record OR passed in from app
new_id INT IDENTITY(2000000, 1) NOT NULL

Bây giờ bạn chỉ thêm 8 byte mỗi hàng thay vì 12 byte (giả sử rằng bạn không sử dụng SPARSEtùy chọn hoặc Nén dữ liệu). Và bạn không cần thay đổi bất kỳ mã, mã T-SQL hoặc mã ứng dụng nào.

THỨ HAI

Tiếp tục con đường đơn giản hóa này, chúng ta hãy nhìn vào những gì chúng ta còn lại:

  • Các old_or_external_idcột hoặc có giá trị đã có, hoặc sẽ được cung cấp một giá trị mới từ ứng dụng, hoặc sẽ bị bỏ lại như NULL.
  • Di new_idchúc luôn có một giá trị mới được tạo, nhưng giá trị đó sẽ chỉ được sử dụng nếu old_or_external_idcột là NULL.

Không bao giờ có lúc bạn sẽ cần các giá trị trong cả hai old_or_external_idnew_id. Có, sẽ có lúc cả hai cột có giá trị do new_idlà một IDENTITY, nhưng những new_idgiá trị đó bị bỏ qua. Một lần nữa, hai lĩnh vực này là loại trừ lẫn nhau. Giờ thì sao?

Bây giờ chúng ta có thể xem xét lý do tại sao chúng ta cần external_idở nơi đầu tiên. Xem xét rằng có thể chèn vào một IDENTITYcột bằng cách sử dụng SET IDENTITY_INSERT {table_name} ON;, bạn có thể thoát khỏi việc không thực hiện thay đổi lược đồ nào và chỉ sửa đổi mã ứng dụng của bạn để bọc các INSERTcâu lệnh / thao tác SET IDENTITY_INSERT {table_name} ON;SET IDENTITY_INSERT {table_name} OFF;câu lệnh. Sau đó, bạn cần xác định phạm vi bắt đầu nào để đặt lại IDENTITYcột thành (đối với các giá trị mới được tạo) vì nó sẽ cần cao hơn các giá trị mà mã ứng dụng sẽ chèn vì việc chèn giá trị cao hơn sẽ khiến giá trị được tạo tự động tiếp theo lớn hơn giá trị MAX hiện tại. Nhưng bạn luôn có thể chèn một giá trị nằm dưới giá trị IDENT_CURRENT .

Việc kết hợp các cột old_or_external_idnew_idcột cũng không làm tăng cơ hội chạy vào tình huống giá trị chồng chéo giữa các giá trị được tạo tự động và giá trị do ứng dụng tạo ra do ý định có các cột 2 hoặc thậm chí 3, là kết hợp chúng thành một giá trị Khóa chính, và đó luôn là những giá trị độc nhất

Trong phương pháp này, bạn chỉ cần:

  • Để lại các bảng như:

    PkId INT IDENTITY(1,1) PRIMARY KEY

    Điều này thêm 0 byte vào mỗi hàng, thay vì 8 hoặc thậm chí 12.

  • Xác định phạm vi bắt đầu cho các giá trị do ứng dụng tạo. Giá trị này sẽ lớn hơn giá trị MAX hiện tại trong mỗi bảng, nhưng nhỏ hơn giá trị tối thiểu sẽ trở thành giá trị tối thiểu cho các giá trị được tạo tự động.
  • Xác định giá trị nào phạm vi được tạo tự động sẽ bắt đầu tại. Cần có nhiều phòng giữa giá trị MAX hiện tại nhiều phòng để phát triển, biết ở giới hạn trên chỉ là hơn 2,14 tỷ. Sau đó, bạn có thể đặt giá trị hạt giống tối thiểu mới này thông qua DBCC CHECKIDENT .
  • Bọc mã ứng dụng INSERTs trong SET IDENTITY_INSERT {table_name} ON;SET IDENTITY_INSERT {table_name} OFF;báo cáo.

THỨ HAI, Phần B

Một biến thể của cách tiếp cận được ghi chú trực tiếp ở trên sẽ có các giá trị chèn mã ứng dụng bắt đầu bằng -1 và đi xuống từ đó. Điều này để lại các IDENTITYgiá trị như là những người duy nhất đi lên . Lợi ích ở đây là bạn không chỉ không làm phức tạp lược đồ, bạn cũng không cần lo lắng về việc chạy vào các ID chồng chéo (nếu các giá trị do ứng dụng tạo ra chạy trong phạm vi được tạo tự động mới). Đây chỉ là một tùy chọn nếu bạn chưa sử dụng các giá trị ID âm (và dường như khá hiếm khi mọi người sử dụng các giá trị âm trên các cột được tạo tự động, do đó, điều này sẽ có khả năng rõ ràng trong hầu hết các tình huống).

Trong phương pháp này, bạn chỉ cần:

  • Để lại các bảng như:

    PkId INT IDENTITY(1,1) PRIMARY KEY

    Điều này thêm 0 byte vào mỗi hàng, thay vì 8 hoặc thậm chí 12.

  • Phạm vi bắt đầu cho các giá trị do ứng dụng tạo ra sẽ là -1.
  • Bọc mã ứng dụng INSERTs trong SET IDENTITY_INSERT {table_name} ON;SET IDENTITY_INSERT {table_name} OFF;báo cáo.

Ở đây bạn vẫn cần phải thực hiện IDENTITY_INSERT, nhưng: bạn không thêm bất kỳ cột mới nào, không cần "đổi mới" bất kỳ IDENTITYcột nào và không có nguy cơ chồng chéo trong tương lai.

THỨ HAI, Phần 3

Một biến thể cuối cùng của phương pháp này là có thể hoán đổi các IDENTITYcột và thay vào đó sử dụng Chuỗi . Lý do để thực hiện phương pháp này là để có thể có các giá trị chèn mã ứng dụng là: dương, trên phạm vi được tạo tự động (không phải bên dưới) và không cần SET IDENTITY_INSERT ON / OFF.

Trong phương pháp này, bạn chỉ cần:

  • Tạo chuỗi bằng CREATE SEQUENCE
  • Sao chép IDENTITYcột sang một cột mới không có thuộc IDENTITYtính, nhưng có DEFAULTràng buộc bằng cách sử dụng chức năng GIÁ TRỊ TIẾP THEO :

    PkId INT PRIMARY KEY CONSTRAINT [DF_TableName_NextID] DEFAULT (NEXT VALUE FOR...)

    Điều này thêm 0 byte vào mỗi hàng, thay vì 8 hoặc thậm chí 12.

  • Phạm vi bắt đầu cho các giá trị được tạo bởi ứng dụng sẽ vượt xa những gì bạn nghĩ rằng các giá trị được tạo tự động sẽ tiếp cận.
  • Bọc mã ứng dụng INSERTs trong SET IDENTITY_INSERT {table_name} ON;SET IDENTITY_INSERT {table_name} OFF;báo cáo.

TUY NHIÊN , do yêu cầu mã với một SCOPE_IDENTITY()hoặc @@IDENTITYvẫn hoạt động đúng, chuyển sang Chuỗi hiện không phải là một tùy chọn vì dường như không có chức năng nào tương đương với các hàm đó cho Chuỗi :-(. Buồn!


Cảm ơn rất nhiều về câu trả lời của bạn. Bạn nêu lên một vài điểm đã được thảo luận ở đây. Thật không may, một số trong số này sẽ không làm việc cho chúng tôi vì một vài lý do. Cơ sở dữ liệu của chúng tôi khá cũ và hơi dễ vỡ và chạy trong chế độ tương thích năm 2005 vì vậy SEQUENCES không hoạt động. Đẩy dữ liệu ứng dụng của chúng tôi xảy ra thông qua một công cụ tải dữ liệu có được các bản ghi mới từ hàng đợi môi giới dịch vụ và đẩy chúng qua nhiều luồng. IDENTITY_INSERT chỉ có thể được sử dụng cho một bảng mỗi phiên và suy nghĩ hiện tại là kiến ​​trúc của chúng tôi không thể phục vụ cho điều đó mà không có thay đổi đáng kể. Tôi đang thử nghiệm đề nghị nắm tay của bạn bây giờ.
Ông Moose

@MrMoose Vâng, tôi đã cập nhật câu trả lời của mình để bao gồm thêm thông tin về Chuỗi ở cuối. Nó sẽ không hoạt động trong tình huống của bạn nào. Và tôi đã tự hỏi về các vấn đề tương tranh tiềm ẩn với IDENTITY_INSERT, nhưng chưa thử nghiệm nó. Không chắc chắn tùy chọn số 1 sẽ giải quyết vấn đề tổng thể của bạn, nó chỉ là một quan sát để giảm sự phức tạp không cần thiết. Tuy nhiên, nếu bạn có nhiều luồng chèn ID "bên ngoài" mới, làm thế nào để bạn đảm bảo rằng chúng là duy nhất?
Solomon Rutzky

@MrMoose Trên thực tế, liên quan đến " IDENTITY_INSERT chỉ có thể được sử dụng cho một bảng mỗi phiên ", vấn đề chính xác ở đây là gì? 1) bạn chỉ có thể chèn vào một bảng cùng một lúc, vì vậy bạn tắt nó cho TableA trước khi chèn vào TableB và 2) Tôi chỉ kiểm tra và trái với những gì tôi đã nghĩ, không có vấn đề tương tranh nào - tôi đã có thể có IDENTITY_INSERT ONcùng một bảng trong hai phiên và được chèn vào cả hai không có vấn đề.
Solomon Rutzky

1
Như bạn đề xuất, thay đổi 1 làm cho sự khác biệt nhỏ. ID chúng tôi sẽ sử dụng sẽ được phân bổ bên ngoài cơ sở dữ liệu hiện tại và được sử dụng để liên quan đến hồ sơ. Có thể là sự hiểu biết của tôi về các phiên không hoàn toàn đúng nên IDENTITY_INSERT có thể hoạt động. Mặc dù vậy, tôi sẽ mất một ít thời gian để điều tra về vấn đề đó, vì vậy tôi sẽ không thể báo cáo lại một chút. Cảm ơn một lần nữa cho đầu vào. Nó được nhiều đánh giá cao.
Ông Moose

1
Tôi nghĩ rằng đề xuất của bạn về việc sử dụng IDENTITY_INSERT (với giá trị hạt giống cao cho các ứng dụng hiện có) sẽ hoạt động tốt. Aaron Bertrand đã cung cấp một câu trả lời ở đây với một ví dụ nhỏ về việc thử nghiệm nó với sự tương tranh. Chúng tôi đã sửa đổi công cụ tải dữ liệu của mình để có thể xử lý các bảng cần chỉ định giá trị nhận dạng và chúng tôi sẽ tiến hành thử nghiệm thêm trong vài tuần tới.
Ông Moose
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.