插入期间磁盘空间已满,会发生什么?


17

今天,我发现存储我的数据库的硬盘驱动器已满。这是以前发生的,通常原因很明显。通常会有一个错误的查询,这会导致大量的溢出到tempdb,直到磁盘装满为止。这次不太清楚发生了什么,因为tempdb不是驱动器满载的原因,而是数据库本身。

事实:

  • 通常的数据库大小约为55 GB,后来增加到605 GB。
  • 日志文件大小正常,数据文件很大。
  • 数据文件具有85%的可用空间(我将其解释为“空气”:已使用但已释放的空间。SQLServer分配后将保留所有空间)。
  • Tempdb大小正常。

我发现了可能的原因;有一个查询选择了太多行的查询(错误的联接会导致选择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'.

但是目标表确实存在。但是,实际查询不是在显式事务中执行的,这可以解释目标表的存在吗?

我在这里勾勒出的假设是否正确?这是否可能发生过?

Answers:


17

但是,实际查询不是在显式事务中执行的,这可以解释目标表的存在吗?

是的,完全是这样。

如果您在select into之外进行简单的操作explicit transaction,则transactions在自动提交模式下有两种:第一种创建table,第二种填充它。

您可以通过以下方式向自己证明:

在专用database于测试服务器的服务器中simple recovery model,首先创建一个checkpoint并确保日志仅包含与相关的几行(如果是2016年,则为3行)checkpoint。然后运行select into一行的a并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

我在这里勾勒出的假设是否正确?这是否可能发生过?

是的,它们是正确的。

was 的insert一部分,但不会释放任何数据空间。您可以执行以下命令进行验证:你会看到很多。select intorolled backsp_spaceusedunallocated 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
由于“ ACTIVE_TRANSACTION”,数据库“ SelectIntoTestDB”的事务日志已满。

但是我的新数据库中有一个空的Posts表:

新创建的表的零结果的屏幕截图

因此,正如您所怀疑的,CREATE TABLE成功了,但是该INSERT部分全部回滚了。一种解决方法是使用显式事务(您已在问题中指出)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.