Dung lượng đĩa đầy trong khi chèn, điều gì xảy ra?


17

Hôm nay tôi phát hiện ra ổ cứng lưu trữ cơ sở dữ liệu của tôi đã đầy. Điều này đã xảy ra trước đây, thường là nguyên nhân khá rõ ràng. Thông thường có một truy vấn xấu, gây ra sự cố tràn lớn đến tempdb, phát triển cho đến khi đĩa đầy. Lần này nó ít rõ ràng hơn những gì đã xảy ra, vì tempdb không phải là nguyên nhân của toàn bộ ổ đĩa, đó là chính cơ sở dữ liệu.

Sự thật:

  • Kích thước cơ sở dữ liệu thông thường là khoảng 55 GB, nó tăng lên 605 GB.
  • Tệp nhật ký có kích thước bình thường, datafile là rất lớn.
  • Datafile có 85% dung lượng khả dụng (Tôi hiểu đây là 'không khí': không gian đã được sử dụng, nhưng đã được giải phóng. SQL Server dành toàn bộ không gian sau khi được phân bổ).
  • Kích thước Tempdb là bình thường.

Tôi đã tìm thấy nguyên nhân có thể; có một truy vấn chọn quá nhiều hàng (tham gia xấu gây ra lựa chọn 11 tỷ hàng trong đó dự kiến ​​sẽ có một vài trăm nghìn). Đây là một SELECT INTOtruy vấn, khiến tôi tự hỏi liệu kịch bản sau đây có thể xảy ra hay không:

  • CHỌN VÀO được thực thi
  • Bảng mục tiêu được tạo
  • Dữ liệu được chèn khi nó được chọn
  • Đĩa bị đầy, khiến cho việc chèn không thành công
  • CHỌN VÀO bị hủy bỏ và khôi phục
  • Rollback giải phóng không gian (dữ liệu đã chèn được xóa), nhưng SQL Server không giải phóng không gian giải phóng.

Tuy nhiên, trong tình huống này, tôi không mong đợi bảng được tạo bởi SELECT INTOvẫn còn tồn tại, nó sẽ bị loại bỏ bởi rollback. Tôi đã thử nghiệm điều này:

BEGIN TRANSACTION 
SELECT  T.x
INTO    TMP.test
FROM    (VALUES(1))T(x)

ROLLBACK

SELECT  * 
FROM    TMP.test

Kết quả này trong:

(1 row affected)
Msg 208, Level 16, State 1, Line 8
Invalid object name 'TMP.test'.

Tuy nhiên, bảng mục tiêu không tồn tại. Truy vấn thực tế không được thực hiện trong một giao dịch rõ ràng, điều đó có thể giải thích sự tồn tại của bảng mục tiêu không?

Những giả định tôi đã phác thảo ở đây có đúng không? Đây có phải là một kịch bản có khả năng đã xảy ra?

Câu trả lời:


17

Truy vấn thực tế không được thực hiện trong một giao dịch rõ ràng, điều đó có thể giải thích sự tồn tại của bảng mục tiêu không?

Vâng, chính xác là như vậy.

Nếu bạn thực hiện một thao tác đơn giản select intobên ngoài explicit transaction, có hai transactionschế độ tự động: cái thứ nhất tạo ra tablecái thứ hai và cái thứ hai lấp đầy nó.

Bạn có thể chứng minh điều đó với chính mình theo cách này:

Trong một databasemáy chủ chuyên dụng trên máy chủ thử nghiệm simple recovery model, trước tiên hãy tạo checkpointvà đảm bảo rằng nhật ký chỉ chứa một vài hàng (3 trong trường hợp năm 2016) liên quan đến checkpoint. Sau đó chạy một select intohàng và kiểm tra loglại, tìm kiếm begin tranliên kết với select into:

checkpoint;

select *
from sys.fn_dblog(null, null);

select 'a' as col
into dbo.t3;  

select *
from sys.fn_dblog(null, null)
where Operation = 'LOP_BEGIN_XACT'
      and [Transaction Name] = 'SELECT INTO';

Bạn sẽ nhận được 2 hàng, cho thấy bạn có 2 transactions.

Những giả định tôi đã phác thảo ở đây có đúng không? Đây có phải là một kịch bản có khả năng đã xảy ra?

Vâng, họ đúng.

Các insertphần của select intorolled back, nhưng nó không phát hành bất kỳ không gian dữ liệu. Bạn có thể xác minh điều này bằng cách thực hiện sp_spaceused; bạn sẽ thấy rất nhiều unallocated space.

Nếu bạn muốn cơ sở dữ liệu giải phóng không gian chưa phân bổ này, bạn nên shrink(các) tệp dữ liệu của mình.


15

Bạn đã đúng, SELECT...INTOlệnh không phải là nguyên tử. Điều này không được ghi lại tại thời điểm của bài viết gốc, nhưng hiện được gọi cụ thể trên trang CHỌN - INTO khoản (Transact-SQL) trên MS Docs (nguồn mở yay!):

Câu SELECT...INTOlệnh hoạt động trong hai phần - bảng mới được tạo và sau đó các hàng được chèn vào. Điều này có nghĩa là nếu các lần chèn thất bại, tất cả chúng sẽ được khôi phục, nhưng bảng mới (trống) sẽ vẫn còn. Nếu bạn cần toàn bộ hoạt động để thành công hay thất bại toàn bộ, hãy sử dụng một giao dịch rõ ràng .

Tôi sẽ tạo một cơ sở dữ liệu sử dụng mô hình khôi phục đầy đủ. Tôi sẽ cung cấp cho nó một tệp nhật ký khá nhỏ và sau đó nói với nó rằng tệp nhật ký không thể tự động điền:

CREATE DATABASE [SelectIntoTestDB]
ON PRIMARY 
( 
    NAME = N'SelectIntoTestDB', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\SelectIntoTestDB.mdf', 
    SIZE = 8192KB, 
    FILEGROWTH = 65536KB
)
LOG ON 
( 
    NAME = N'SelectIntoTestDB_log', 
    FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\SelectIntoTestDB_log.ldf', 
    SIZE = 8192KB, 
    FILEGROWTH = 0
)

Và sau đó tôi sẽ cố gắng chèn tất cả các bài đăng từ bản sao cơ sở dữ liệu StackOverflow2010 của tôi. Điều này sẽ viết một loạt các công cụ vào tệp nhật ký.

USE [SelectIntoTestDB];
GO

SELECT *
INTO dbo.Posts
FROM StackOverflow2010.dbo.Posts;

Điều này dẫn đến lỗi sau khi chạy trong 4 giây:

Msg 9002, Cấp 17, Trạng thái 4, Dòng 1
Nhật ký giao dịch cho cơ sở dữ liệu 'ChọnIntoTestDB' đã đầy do 'ACTIVE_TRANSACTION'.

Nhưng có một bảng Bài viết trống trong cơ sở dữ liệu mới của tôi:

ảnh chụp màn hình không có kết quả từ bảng vừa tạo

Vì vậy, như bạn nghi ngờ, phần CREATE TABLEđã thành công, nhưng INSERTphần tất cả đã được khôi phục. Một cách giải quyết khác là sử dụng một giao dịch rõ ràng (mà bạn đã lưu ý trong câu hỏi của mình).

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.