web-dev-qa-db-ja.com

挿入中にディスク領域がいっぱいになった場合、どうなりますか?

今日、データベースを保存するハードドライブがいっぱいであることを発見しました。これは以前に発生したことがあり、通常、原因は非常に明白です。通常、不正なクエリがあり、ディスクがいっぱいになるまで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'.

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

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

18
HoneyBadger

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

はい、そうです。

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

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

simple recovery modelのテストサーバーの専用databaseで、最初にcheckpointを作成し、ログにcheckpointに関連するいくつかの行(2016年の場合は3)のみが含まれていることを確認します。次に、1行のselect intoを実行し、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が表示されます。

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

はい、正しいです。

select intoinsert部分はrolled backでしたが、データスペースは解放されません。これはsp_spaceusedを実行して確認できます。たくさんのunallocated spaceが表示されます。

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

17
sepupic

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

SELECT...INTOステートメントは2つの部分で動作します。新しいテーブルが作成され、次に行が挿入されます。つまり、挿入が失敗した場合、それらはすべてロールバックされますが、新しい(空の)テーブルは残ります。全体として操作全体を成功または失敗させる必要がある場合は、 explicit transaction を使用します。

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

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
'ACTIVE_TRANSACTION'により、データベース 'SelectIntoTestDB'のトランザクションログがいっぱいです。

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

screenshot of zero results from the newly created table

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

15
Josh Darnell