Có một huyền thoại dai dẳng trong thế giới SQL Server rằng cả DROP TABLE
và TRUNCATE TABLE
các lệnh không được ghi lại.
Họ không. Cả hai đều được ghi nhật ký đầy đủ nhưng được ghi nhật ký hiệu quả.
Bạn có thể dễ dàng chứng minh điều này cho chính mình. Chạy mã sau để thiết lập cơ sở dữ liệu và bảng thử nghiệm, đồng thời hiển thị rằng chúng tôi có 10.000 hàng trong bảng của mình:
CREATE DATABASE [TruncateTest]; GO ALTER DATABASE [TruncateTest] SET RECOVERY SIMPLE; GO USE [TruncateTest]; GO CREATE TABLE [TestTable] ( [c1] INT IDENTITY, [c2] CHAR (8000) DEFAULT 'a'); GO SET NOCOUNT ON; GO INSERT INTO [TestTable] DEFAULT VALUES; GO 10000 SELECT COUNT (*) AS N'RowCount' FROM [TestTable]; GO
Kết quả:
RowCount———–
10000
Và sau đó là mã sau, cắt ngắn bảng trong một giao dịch và kiểm tra số lượng hàng:
BEGIN TRAN; GO TRUNCATE TABLE [TestTable]; GO SELECT COUNT (*) AS N'RowCount' FROM [TestTable]; GO
Kết quả:
RowCount———–
0
Bây giờ bàn trống. Nhưng tôi có thể khôi phục giao dịch và đặt lại tất cả dữ liệu:
ROLLBACK TRAN; GO SELECT COUNT (*) AS N'RowCount' FROM [TestTable]; GO
Kết quả:
RowCount———–
10000
Rõ ràng là TRUNCATE
hoạt động phải được ghi lại nếu không hoạt động quay lại sẽ không hoạt động.
Vậy quan niệm sai lầm bắt nguồn từ đâu?
Nó xuất phát từ hành vi của DROP
và TRUNCATE
các thao tác trên bảng lớn. Chúng sẽ hoàn thành gần như ngay lập tức và nếu bạn xem nhật ký giao dịch bằng fn_dblog
ngay sau đó, bạn sẽ chỉ thấy một số lượng nhỏ các bản ghi nhật ký được tạo từ hoạt động. Con số nhỏ đó không tương quan với kích thước của bảng bị cắt bớt hoặc giảm xuống, vì vậy có vẻ như DROP
và TRUNCATE
hoạt động không được ghi lại.
Nhưng chúng được ghi lại đầy đủ, như tôi đã trình bày ở trên. Vậy các bản ghi nhật ký cho các hoạt động ở đâu?
Câu trả lời là các bản ghi nhật ký sẽ được tạo, không phải ngay lập tức, bởi một cơ chế được gọi là 'thả chậm', đã được thêm vào SQL Server 2000 SP3.
Khi một bảng bị xóa hoặc bị cắt bớt, tất cả các trang tệp dữ liệu được phân bổ cho bảng phải được phân bổ. Cơ chế cho điều này trước SQL Server 2000 SP3 như sau:
Đối với mỗi phạm vi được phân bổ cho bảngBắt đầu
Nhận khóa phân bổ eXclusive trên phạm vi
khóa trang cho từng trang trong phạm vi (có được khóa ở chế độ eXclusive và ngay lập tức thả nó ra, đảm bảo không ai khác khóa trang)
KHÔNG ĐƯỢC giải phóng khóa phạm vi, đảm bảo rằng không ai khác có thể sử dụng phạm vi đó
Di chuyển đến phạm vi tiếp theo
Kết thúc
Vì tất cả các khóa phạm vi được giữ cho đến khi kết thúc hoạt động và mỗi khóa chiếm một lượng nhỏ bộ nhớ, nên trình quản lý khóa có thể hết bộ nhớ khi DROP
hoặc TRUNCATE
của một bảng rất lớn xảy ra. Một số khách hàng SQL Server bắt đầu nhận thấy rằng họ gặp phải tình trạng hết bộ nhớ trên SQL Server 2000, vì các bảng phát triển rất lớn và vượt xa tốc độ phát triển của bộ nhớ hệ thống.
Cơ chế trả chậm mô phỏng DROP
hoặc TRUNCATE
hoạt động hoàn thành ngay lập tức, bằng cách bỏ chọn các phân bổ cho bảng và đưa chúng vào 'hàng đợi thả chậm', để xử lý sau bởi một tác vụ nền. Thao tác tháo và chuyển này chỉ tạo ra một số ít bản ghi nhật ký. Đây là thao tác đang được thực hiện và quay trở lại trong ví dụ mã của tôi ở trên.
'Tác vụ nền thả chậm' quay lên sau vài giây và xử lý tất cả các trang và phạm vi trên hàng đợi thả chậm lô, đảm bảo rằng hoạt động sẽ không hết bộ nhớ. Tất cả các giao dịch phân bổ này đều được ghi nhật ký đầy đủ, nhưng hãy nhớ rằng việc phân bổ một trang chứa đầy dữ liệu hoặc các bản ghi chỉ mục không ghi nhật ký các lần xóa riêng lẻ của các bản ghi đó; thay vào đó, toàn bộ trang chỉ được đánh dấu là đã được phân bổ trong bản đồ byte phân bổ PFS (Page Free Space) có liên quan.
Từ SQL Server 2000 SP3 trở đi, khi bạn thực hiện DROP
hoặc TRUNCATE
của một bảng, bạn sẽ chỉ thấy một vài bản ghi nhật ký đang được tạo. Nếu bạn đợi một phút hoặc lâu hơn, sau đó xem lại nhật ký giao dịch, bạn sẽ thấy hàng nghìn bản ghi nhật ký đã được tạo bằng thao tác trả chậm, mỗi bản ghi phân bổ một trang hoặc phạm vi. Hoạt động được ghi lại đầy đủ và hiệu quả.
Dưới đây là một ví dụ, sử dụng tình huống chúng tôi đã tạo ở trên:
CHECKPOINT; GO TRUNCATE TABLE [TestTable]; GO SELECT COUNT (*) AS N'LogRecCount' FROM fn_dblog (NULL, NULL); GO
Kết quả:
LogRecCount———–
25
Như bạn có thể thấy, rõ ràng không có bản ghi nhật ký nào phân bổ 10.000 trang, cộng với 1.250 phạm vi trong bảng TestTable.
Nếu tôi đợi vài giây rồi chạy fn_dblog
mã lại, tôi nhận được:
———–
3811
Bạn có thể thắc mắc tại sao không có ít nhất 10.000 bản ghi nhật ký - một bản ghi cho mỗi trang được phân bổ. Đó là bởi vì các vị trí giao dịch trang thậm chí được ghi lại một cách hiệu quả - với một bản ghi nhật ký phản ánh các thay đổi phân bổ trang PFS cho 8 trang tệp dữ liệu liên tiếp, thay vì một bản ghi nhật ký cho mỗi trang tệp dữ liệu phản ánh trạng thái phân bổ của nó thay đổi trong trang PFS.
SQL Server luôn cố gắng tạo ra ít nhật ký giao dịch nhất có thể, trong khi vẫn tuân thủ các quy tắc về ghi nhật ký đầy đủ hoặc tối thiểu dựa trên mô hình khôi phục hiện tại. Nếu bạn muốn xem các bản ghi nhật ký thực tế được tạo bởi cơ chế bỏ chuyển và trả chậm, chỉ cần thay thế * cho COUNT (*) trong mã fn_dblog ở trên và tìm kiếm một giao dịch có Tên giao dịch được đặt thành DeferredAllocUnitDrop::Process
.
Trong các bài đăng trong tương lai, tôi sẽ thảo luận về các yếu tố bên trong làm cơ sở cho những lầm tưởng dai dẳng khác về các khía cạnh hiệu suất của SQL Server Storage Engine.