挿入中にディスク容量がいっぱいになりました、どうなりますか?


17

今日、データベースを保存するハードドライブがいっぱいであることを発見しました。これは以前にも起こりましたが、通常、原因は非常に明白です。通常、不適切なクエリがあり、tempdbに大量の流出が発生し、ディスクがいっぱいになるまで増大します。今回は、tempdbがドライブ全体の原因ではなく、データベースそのものであるため、何が起こったのかが少しわかりにくくなりました。

事実:

  • 通常のデータベースサイズは約55 GBでしたが、605 GBに増加しました。
  • ログファイルのサイズは標準で、データファイルは巨大です。
  • データファイルには85%の使用可能なスペースがあります(これを「空気」と解釈します:使用されたが、解放されたスペースです。SQLServerは、割り当てられるとすべてのスペースを予約します)。
  • tempdbのサイズは正常です。

考えられる原因を見つけました。あまりにも多くの行を選択するクエリが1つあります(不正な結合により、数十万が予想される110億行が選択されます)。これはSELECT INTOクエリであり、次のシナリオが発生したのではないかと思いました。

  • SELECT INTOが実行されます
  • ターゲット表が作成されます
  • データは選択されたとおりに挿入されます
  • ディスクがいっぱいになり、挿入が失敗する
  • SELECT INTOは中止され、ロールバックされます
  • ロールバックは領域を解放します(既に挿入されたデータは削除されます)が、SQL Serverは解放された領域を解放しません。

ただし、この状況では、によって作成されたテーブルSELECT INTOがまだ存在することを期待していなかったため、ロールバックによって削除されるはずです。私はこれをテストしました:

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

ROLLBACK

SELECT  * 
FROM    TMP.test

これは次の結果になります。

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

ただし、ターゲットテーブルは存在します。実際のクエリは明示的なトランザクションで実行されませんでしたが、ターゲットテーブルの存在を説明できますか?

ここでスケッチした仮定は正しいですか?これは起こりそうなシナリオですか?

回答:


17

実際のクエリは明示的なトランザクションで実行されませんでしたが、ターゲットテーブルの存在を説明できますか?

はい、そうです。

select into外部で単純な操作を行う場合、自動コミットモードexplicit transactionには2つあります。1つtransactions目はを作成しtable、2つ目はそれを埋めます。

この方法で自分自身に証明できます:

databaseテストサーバー専用でsimple recovery model、最初にを作成checkpointし、ログにに関連する数行(2016年の場合は3行)のみが含まれるようにしますcheckpoint。次に、a select intoの1行を実行し、log再度チェックして、にbegin tran関連付けられているものを探し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';

2つの行が表示され、2があることがわかりますtransactions

ここでスケッチした仮定は正しいですか?これは起こりそうなシナリオですか?

はい、正しいです。

insert部分はselect intoでしたがrolled back、データ領域を解放しません。これを確認するにはsp_spaceused、; を実行します。あなたはたくさん見るでしょうunallocated space

データベースにこの未割り当て領域を解放させたい場合はshrink、データファイルを作成する必要があります。


15

あなたは正しい、SELECT...INTOコマンドはアトミックではありません。これは元の投稿の時点では文書化されていませんでしたが、MS Docs のSELECT-INTO句(Transact-SQL)ページで具体的に呼び出されています(オープンソースです!)。

SELECT...INTO新しいテーブルが作成され、その後、行が挿入されている-の文は二つの部分で動作します。これは、挿入が失敗した場合、それらはすべてロールバックされますが、新しい(空の)テーブルが残ることを意味します。操作全体を成功または失敗させる必要がある場合は、明示的なトランザクションを使用します。

完全復旧モデルを使用するデータベースを作成します。かなり小さなログファイルを指定し、ログファイルが自動拡張できないことを伝えます。

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
)

そして、StackOverflow2010データベースのコピーからすべての投稿を挿入しようとします。これにより、大量のログファイルが書き込まれます。

USE [SelectIntoTestDB];
GO

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

これにより、4秒間実行した後に次のエラーが発生しました。

メッセージ9002、レベル17、状態4、行1
データベース「SelectIntoTestDB」のトランザクションログは、「ACTIVE_TRANSACTION」が原因でいっぱいです。

しかし、私の新しいデータベースには空のPostsテーブルがあります。

新しく作成されたテーブルの結果がゼロのスクリーンショット

あなたが疑ったように、CREATE TABLE成功しましたが、そのINSERT部分はすべてロールバックされました。回避策は、明示的なトランザクションを使用することです(これについては、既に質問で指摘しています)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.