今日、データベースを保存するハードドライブがいっぱいであることを発見しました。これは以前に発生したことがあり、通常、原因は非常に明白です。通常、不正なクエリがあり、ディスクがいっぱいになるまでtempdbに巨大な流出が発生します。今回は、tempdbがドライブ全体の原因ではなかったため、データベース自体が原因で、何が起こったのか少しわかりにくくなりました。
事実:
考えられる原因を見つけました。非常に多くの行を選択するクエリが1つあります(結合が悪いと、数十万が予想される場合、110億行が選択されます)。これはSELECT INTO
クエリなので、次のシナリオが発生する可能性があるかどうか疑問に思いました。
ただし、この状況では、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'.
それでもターゲットテーブルは存在します。実際のクエリは明示的なトランザクションで実行されませんでしたが、それはターゲットテーブルの存在を説明できますか?
ここでスケッチした仮定は正しいですか?これは起こりそうなシナリオですか?
実際のクエリは明示的なトランザクションで実行されませんでしたが、それはターゲットテーブルの存在を説明できますか?
はい、そうです。
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 into
のinsert
部分はrolled back
でしたが、データスペースは解放されません。これはsp_spaceused
を実行して確認できます。たくさんのunallocated space
が表示されます。
データベースでこの未割り当て領域を解放したい場合は、データファイルをshrink
する必要があります。
あなたは正しい、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テーブルがあります。
だから、あなたが疑ったように、CREATE TABLE
は成功しましたが、INSERT
部分はすべてロールバックされました。回避策は、明示的なトランザクションを使用することです(既に質問で述べています)。