[Phần 1 | Phần 2 | Phần 3 | Phần 4]
Một vấn đề mà tôi đã thấy gần đây bị cắt xén một vài lần là trường hợp bạn đã tạo cột IDENTITY dưới dạng INT, và bây giờ đã gần đến giới hạn trên và cần phải làm cho nó lớn hơn (BIGINT). Nếu bàn của bạn đủ lớn để bạn đạt đến giới hạn trên của một số nguyên (hơn 2 tỷ), thì đây không phải là một phép toán mà bạn có thể thực hiện giữa bữa trưa và giờ nghỉ giải lao vào thứ Ba. Loạt bài này sẽ khám phá cơ chế đằng sau sự thay đổi như vậy và những cách khác nhau để biến nó thành hiện thực với những tác động khác nhau đến thời gian hoạt động. Trong phần đầu tiên, tôi muốn xem xét kỹ tác động vật lý của việc thay đổi INT thành BIGINT mà không có bất kỳ biến nào khác.
Điều gì thực sự xảy ra khi bạn mở rộng INT?
INT và BIGINT là các kiểu dữ liệu có kích thước cố định, do đó chuyển đổi từ loại này sang loại khác phải tiếp xúc với trang, làm cho thao tác này trở thành thao tác kích thước của dữ liệu. Điều này là phản trực quan, vì có vẻ như việc thay đổi kiểu dữ liệu từ INT thành BIGINT sẽ không thể yêu cầu không gian bổ sung trên trang ngay lập tức (và đối với cột IDENTITY). Suy nghĩ một cách logic, đây là không gian có thể không cần thiết cho đến sau này, khi giá trị INT hiện tại được thay đổi thành giá trị> 4 byte. Nhưng đây không phải là cách nó hoạt động ngày nay. Hãy tạo một bảng đơn giản và xem:
CREATE TABLE dbo.FirstTest ( RowID int IDENTITY(1,1), Filler char(2500) NOT NULL DEFAULT 'x' ); GO INSERT dbo.FirstTest WITH (TABLOCKX) (Filler) SELECT TOP (20) 'x' FROM sys.all_columns AS c; GO
Một truy vấn đơn giản có thể cho tôi biết trang cao và thấp được phân bổ cho đối tượng này, cũng như tổng số trang:
SELECT lo_page = MIN(allocated_page_page_id), hi_page = MAX(allocated_page_page_id), page_count = COUNT(*) FROM sys.dm_db_database_page_allocations ( DB_ID(), OBJECT_ID(N'dbo.FirstTest'), NULL, NULL, NULL );
Bây giờ nếu tôi chạy truy vấn đó trước và sau khi thay đổi kiểu dữ liệu từ INT thành BIGINT:
ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint;
Tôi thấy những kết quả sau:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 243 319 33
Rõ ràng là 16 trang mới đã được thêm vào để nhường chỗ cho không gian bổ sung cần thiết (mặc dù chúng tôi biết rằng không có giá trị nào trong bảng thực sự yêu cầu 8 byte). Nhưng điều này không thực sự được thực hiện theo cách bạn có thể nghĩ - thay vì mở rộng cột trên các trang hiện có, các hàng đã được chuyển sang các trang mới, với các con trỏ bị bỏ lại ở vị trí của chúng. Nhìn vào trang 243 trước và sau (với DBCC PAGE
không có tài liệu ):
-- ******** Page 243, before: ******** Slot 0 Offset 0x60 Length 12 Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 12 Memory Dump @0x000000E34B9FA060 0000000000000000: 10000900 01000000 78020000 .. .....x... Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4 RowID = 1 Slot 0 Column 2 Offset 0x8 Length 1 Length (physical) 1 filler = x -- ******** Page 243, after: ******** Slot 0 Offset 0x60 Length 9 Record Type = FORWARDING_STUB Record Attributes = Record Size = 9 Memory Dump @0x000000E34B9FA060 0000000000000000: 04280100 00010078 01 .(.....x. Forwarding to = file 1 page 296 slot 376
Sau đó, nếu chúng ta nhìn vào mục tiêu của con trỏ, trang 296, vị trí 376, chúng ta thấy:
Slot 376 Offset 0x8ca Length 34 Record Type = FORWARDED_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 34 Memory Dump @0x000000E33BBFA8CA 0000000000000000: 32001100 01000000 78010000 00000000 00030000 2.......x........... 0000000000000014: 01002280 0004f300 00000100 0000 .."...ó....... Forwarded from = file 1 page 243 slot 0 Slot 376 Column 67108865 Offset 0x4 Length 0 Length (physical) 4 DROPPED = NULL Slot 376 Column 2 Offset 0x8 Length 1 Length (physical) 1 filler = x Slot 376 Column 1 Offset 0x9 Length 8 Length (physical) 8 RowID = 1
Rõ ràng đây là một thay đổi rất đột ngột đối với cấu trúc của bảng. (Và một quan sát phụ thú vị:thứ tự vật lý của các cột, RowID và phần phụ, đã được lật trên trang.) Không gian dự trữ tăng từ 136 KB lên 264 KB và độ phân mảnh trung bình tăng nhẹ từ 33,3% lên 40%. Không gian này không được phục hồi bằng cách xây dựng lại, trực tuyến hay không, hoặc một nhà thiết kế lại, và - như chúng ta sẽ thấy ngay sau đây - điều này không phải do bảng quá nhỏ để có lợi.
Lưu ý:điều này đúng ngay cả trong các bản dựng mới nhất của SQL Server 2016 - mặc dù ngày càng có nhiều hoạt động như thế này được cải thiện để trở thành các hoạt động chỉ siêu dữ liệu trong các phiên bản hiện đại, nhưng điều này vẫn chưa được sửa, mặc dù rõ ràng nó có thể là - một lần nữa, đặc biệt là trong trường hợp cột là cột IDENTITY, cột này không thể được cập nhật theo định nghĩa.
Thực hiện thao tác với cú pháp ALTER COLUMN / ONLINE mới mà tôi đã nói vào năm ngoái, mang lại một số khác biệt:
-- drop / re-create here ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint WITH (ONLINE = ON);
Bây giờ trước và sau trở thành:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 307 351 17
Trong trường hợp này, đó vẫn là thao tác kích thước dữ liệu, nhưng các trang hiện có đã được sao chép và tạo lại do tùy chọn TRỰC TUYẾN. Bạn có thể thắc mắc tại sao khi chúng tôi thay đổi kích thước cột dưới dạng thao tác TRỰC TUYẾN, bảng có thể nhồi nhét nhiều dữ liệu hơn vào cùng một số trang? Mỗi trang bây giờ dày đặc hơn (ít hàng hơn nhưng nhiều dữ liệu hơn trên mỗi trang), với chi phí phân tán - phân mảnh tăng gấp đôi từ 33,3% lên 66,7%. Dung lượng được sử dụng hiển thị nhiều dữ liệu hơn trong cùng một không gian dành riêng (từ 72 KB / 136 KB đến 96 KB / 136 KB).
Và ở quy mô lớn hơn?
Hãy thả bảng, tạo lại và điền vào bảng với nhiều dữ liệu hơn:
CREATE TABLE dbo.FirstTest ( RowID INT IDENTITY(1,1), filler CHAR(1) NOT NULL DEFAULT 'x' ); GO INSERT dbo.FirstTest WITH (TABLOCKX) (filler) SELECT TOP (5000000) 'x' FROM sys.all_columns AS c1 CROSS JOIN sys.all_columns AS c2;
Ngay từ đầu, chúng tôi hiện có 8.657 trang, mức độ phân mảnh 0,09% và dung lượng được sử dụng là 69.208 KB / 69.256 KB.
Nếu chúng ta thay đổi kiểu dữ liệu thành bigint, chúng ta sẽ chuyển đến 25.630 trang, độ phân mảnh giảm xuống còn 0,06% và dung lượng được sử dụng là 205,032 KB / 205,064 KB. Việc xây dựng lại trực tuyến không có gì thay đổi, và cũng không thay đổi việc xây dựng lại. Toàn bộ quá trình, bao gồm cả việc xây dựng lại, mất khoảng 97 giây trên máy của tôi (toàn bộ dữ liệu mất 2 giây).
Nếu chúng tôi thay đổi kiểu dữ liệu thành bigint bằng cách sử dụng ONLINE, phần tăng chỉ là 11.140 trang, phân mảnh lên tới 85,5% và dung lượng được sử dụng là 89.088 KB / 89160 KB. Các bản dựng lại và sửa chữa trực tuyến vẫn không có gì thay đổi. Lần này, toàn bộ quá trình chỉ mất khoảng một phút. Vì vậy, cú pháp mới chắc chắn dẫn đến các hoạt động nhanh hơn và ít dung lượng đĩa bổ sung hơn, nhưng phân mảnh cao. Tôi sẽ lấy nó.
Tiếp theo
Tôi chắc chắn rằng bạn đang xem các bài kiểm tra của tôi ở trên và tự hỏi về một vài điều. Quan trọng nhất, tại sao bảng lại là một đống? Tôi muốn điều tra những gì thực sự xảy ra với cấu trúc trang và số lượng trang mà không có chỉ mục, khóa hoặc ràng buộc làm mờ chi tiết. Bạn cũng có thể thắc mắc tại sao sự thay đổi này lại dễ dàng như vậy - trong trường hợp bạn phải thay đổi một cột IDENTITY thực sự, nó có thể cũng là khóa chính được phân cụm và có phụ thuộc khóa ngoại trong các bảng khác. Điều này chắc chắn giới thiệu một số trục trặc cho quá trình. Chúng ta sẽ xem xét kỹ hơn những điều này trong bài đăng tiếp theo của loạt bài này.
-
[Phần 1 | Phần 2 | Phần 3 | Phần 4]