我试图了解为什么以下代码正在写入日志文件。我是初学者并且已经读过,当数据库处于简单恢复模式时,不会写入日志。但是下面的代码是在FULL和SIMPLE恢复模式下编写的。在哪些情况下,日志文件是用简单的恢复模式写的?
代码:
Declare @val int =1
set nocount on
BEGIN TRAN
while @val <= 100000
begin
insert into LoadTable values (REPLICATE('P',1000))
set @val = @val + 1
end
ROLLBACK TRAN
答案 0 :(得分:2)
首先,您了解当数据库处于简单恢复模式时,没有任何内容写入日志文件错误。
SQL Server在所有恢复模式下写入日志文件,唯一的区别是在简单恢复模式下它会自动回收日志空间(如果可以的话)并且还记录最小的东西以维护事务(如果你必须回滚就行了)一)。
在完全恢复模式下,我们必须采用事务日志备份,以便SQL Server可以重用以进一步记录。
现在回到你的例子:
Declare @val int =1
set nocount on
BEGIN TRAN --<-- Your Transaction starts here
while @val <= 100000
begin
insert into LoadTable values (REPLICATE('P',1000))
set @val = @val + 1
end
ROLLBACK TRAN --<-- Your Transaction ends here
在您的示例中,在事务开始之后和结束之前(回滚/提交)有很多活动正在进行,如果您决定回滚事务就像SQL Server需要记录此活动一样你做了,因此越来越多的日志将写入日志文件,直到事务完成(Committed或Rollback)。
在这个具体的例子中,sql server必须保留100000个插入语句的日志,以防出现问题。
您查询的另一个略有不同的版本可能是......
Declare @val int =1
set nocount on
while @val <= 100000
begin
BEGIN TRAN --<-- Your Transaction starts here
insert into LoadTable values (REPLICATE('P',1000))
ROLLBACK TRAN --<-- Your Transaction Ends here
CHECKPOINT;
set @val = @val + 1
end
现在在同一个t-sql命令的这个略有不同的版本中,事务开始之后和它的完成之前会有很少的活动,因此sql server必须记录非常少的数据,并且事务文件将变得非常少如果有的话。
在此示例中,sql server必须一次只保留1个insert语句的日志,因为它在该点之后被提交或回滚。
答案 1 :(得分:2)
我从Pro SQL Server Internals 2014获取了所有信息 https://www.amazon.com/Pro-Server-Internals-Dmitri-Korotkevitch/dp/1430259620
<强> TL; DR; 强>
恢复模式SIMPLE和FULL因SQL Server将如何取消激活虚拟日志文件(VLF)而不同。
总结:
1 - &#34;在SIMPLE恢复模型中,事务日志的活动部分以VLF开始,VLF包含最早的活动事务的最旧LSN或最后一个CHECKPOINT&#34 ;;
2 - &#34;在FULL或BULK-LOGGED恢复模型中,事务日志的活动部分以VLF开始,VLF包含以下最旧的:
最后一次日志备份的LSN
LSN最早的活跃交易
读取事务日志记录&#34;
LSN =日志序列号=唯一的自动递增ID
更详细的解释
假设这是SQL Server内存模型:
1 - 缓冲池是SQL Server存储索引,行等在内存中的位置;
2 - 日志缓冲区是事务日志的一点(每个数据库64KB)缓冲区;
3 - 数据文件是SQL Server将在磁盘中保留索引,行等的地方;
4 - 事务日志是......好吧,磁盘中的事务日志。
假设我们有一个处于以下状态的数据库。
/--------------- IN MEMORY --------------\/------------ IN DISK -----------\
|--------------------------------------------------------------------------|
|Buffer Pool | Log Buffer | Data File |Transaction Log |
|---------------------------|-------------|---------------|----------------|
|Page 1:24312 | |Page: 1:24312 |LSN:7213 |
|IsDirty: False | |LSN: 4845 | |
|LSN: 4845 | |Page: 1:24313 | |
|... | |LSN: 2078 | |
|Page 1:26912 | |... | |
|isDirty:False | |Page: 1:26911 | |
|LSN:1053 | |LSN: 2078 | |
| | |Page: 1:26912 | |
| | |LSN: 2078 | |
|---------------------------|-------------|---------------|----------------|
现在假设进行了更改,这是一个简单的更新 第一步是将日志记录插入日志缓冲区。
/--------------- IN MEMORY --------------\/------------ IN DISK -----------\
|--------------------------------------------------------------------------|
|Buffer Pool | Log Buffer | Data File |Transaction Log |
|---------------------------|-------------|---------------|----------------|
|Page 1:24312 |LSN:7214 |Page: 1:24312 |LSN:7213 |
|IsDirty: False |Op:Update |LSN: 4845 | |
|LSN: 4845 |Page:1:24312 |Page: 1:24313 | |
|... |OldLsn:4845 |LSN: 2078 | |
|Page 1:26912 |Row:2 |... | |
|isDirty:False |Tran:T1 |Page: 1:26911 | |
|LSN:1053 |PrevLSN:7141 |LSN: 2078 | |
| | |Page: 1:26912 | |
| | |LSN: 2078 | |
|---------------------------|-------------|---------------|----------------|
然后更改内存中的数据页面(我只更改IsDirty以简化)
/--------------- IN MEMORY --------------\/------------ IN DISK -----------\
|--------------------------------------------------------------------------|
|Buffer Pool | Log Buffer | Data File |Transaction Log |
|---------------------------|-------------|---------------|----------------|
|Page 1:24312 |LSN:7214 |Page: 1:24312 |LSN:7213 |
|IsDirty: TRUE |Op:Update |LSN: 4845 | |
|LSN: 4845 |Page:1:24312 |Page: 1:24313 | |
|... |OldLsn:4845 |LSN: 2078 | |
|Page 1:26912 |Row:2 |... | |
|isDirty:False |Tran:T1 |Page: 1:26911 | |
|LSN:1053 |PrevLSN:7141 |LSN: 2078 | |
| | |Page: 1:26912 | |
| | |LSN: 2078 | |
|---------------------------|-------------|---------------|----------------|
这将一直持续到日志缓冲区已满或提交事务为止。 Commit在日志缓冲区中生成另一个条目,其中OP为Commit,并将整个缓冲区刷新到磁盘。
/--------------- IN MEMORY --------------\/------------ IN DISK -----------\
|--------------------------------------------------------------------------|
|Buffer Pool | Log Buffer | Data File |Transaction Log |
|---------------------------|-------------|---------------|----------------|
|Page 1:24312 | |Page: 1:24312 |LSN:7213 |
|IsDirty: TRUE | |LSN: 4845 | |
|LSN: 4845 | |Page: 1:24313 |LSN:7214 |
|... | |LSN: 2078 |<ALL PROPERTIES>|
|Page 1:26912 | |... | |
|isDirty:False | |Page: 1:26911 |LSN:7215 |
|LSN:1053 | |LSN: 2078 |Op:Commit |
| | |Page: 1:26912 | |
| | |LSN: 2078 |LSN:7216 |
| | | |Op:Checkpoint |
|---------------------------|-------------|---------------|----------------|
此时SQL Server将回答客户端事务成功。 值得指出的是,内存中的脏页还没有发送到磁盘。 在这一点上,如果发生了什么事,SQL Server将能够恢复到这个确切点的所有更改 这种技术称为Write Ahead Logging,有关详细信息,请参阅:
重复ARIES之外的历史 http://www.vldb.org/conf/1999/P1.pdf
在某些时刻,Checkpoint进程将创建一个CHECKPOINT操作,将所有Dirty页面从Buffer Pool刷新到磁盘。检查点操作也会出现在事务日志中,如上例所示。
考虑到这一点,我们可以看到SQL Server如何处理事务日志。
虚拟日志文件
磁盘上的事务日志被细分为虚拟日志文件(VLF)。你可以看到这个运行:
DBCC LOGINFO
重要的是,虚拟日志文件(VLF)可以分为活动或非活动。
SQL Server仅在其恢复模型中使用事务日志的活动部分。因此,SIMPLE和FULL之间的区别在于VLF成为非活动状态。 SQL Server取消激活VLF,因为事务日志是一个环绕文件,这意味着,当逻辑日志文件的末尾到达物理文件的末尾时,日志将其包裹起来&#34;。例如:
/------ACTIVE-----\/----------------INACTIVE----------------\/--------ACTIVE---\
|------------------------------------------------------------------------------|
| | | | | | | | |
| VLF1 | VLF2 | VLF3 | VLF4 | VLF5 | VLF6 | VLF7 | VLF8 |
| | | | | | | | |
|------------------------------------------------------------------------------|
因此,如果由于某种原因没有VLF变为非活动状态,则事务日志将需要无限增长。
简单恢复
回到这个例子。在检查点之后,所有内容都刷新到磁盘,SIMPLE恢复中的SQL Server将仅保持激活VLF:
1 - 包含最早的活动事务的最旧的LSN;或
2 - 最后一个检查站。
例如:
在检查站之前
/------INACTIVE---\/----------------ACTIVE-------\/---------INACTIVE-----------\
|------------------------------------------------------------------------------|
| | | | | | | | |
| VLF1 | VLF2 | VLF3 | VLF4 | VLF5 | VLF6 | VLF7 | VLF8 |
| | | | | | | | |
|------------------------------------------------------------------------------|
^ ^ ^ ^ ^
| | | | |> End of logical LOG file
| | | |> Current LSN
| | |> Minumin LSN (Oldest Active Transaction)
| |> Last Checkpoint
|> Start of Logical LOG file
检查点之后
/------INACTIVE---------------\/----ACTIVE-------\/---------INACTIVE-----------\
|------------------------------------------------------------------------------|
| | | | | | | | |
| VLF1 | VLF2 | VLF3 | VLF4 | VLF5 | VLF6 | VLF7 | VLF8 |
| | | | | | | | |
|------------------------------------------------------------------------------|
^ ^ ^ ^
| | | |> End of logical LOG file
| | |> Current LSN (Checkpoint Occurs)
| |> Minumin LSN (Oldest Active Transaction)
|> Start of Logical LOG file
SQL Server已停用包含最后一个检查点的VLF3,因为:
1 - 新检查点强制内存中的所有脏页都到磁盘。因此,无需重做存储在VLF3中的任何更改,因为最早的活动事务处于VLF4中;
2 - 但是,因此我们仍然需要VLF4来支持所有活动事务的回滚。
完全恢复
同样的过程在完全恢复中发生,但现在最后一个保持活动的VLF将是最老的:
1 - 最后日志备份的LSN;
2 - OLDEST ACTIVE TRANSACTION的LSN;或
3 - 读取事务日志记录的进程的LSN。
例如
/------INACTIVE---------------\/----ACTIVE-------\/---------INACTIVE-----------\
|------------------------------------------------------------------------------|
| | | | | | | | |
| VLF1 | VLF2 | VLF3 | VLF4 | VLF5 | VLF6 | VLF7 | VLF8 |
| | | | | | | | |
|------------------------------------------------------------------------------|
^ ^ ^ ^ ^
| | | | |> End of logical LOG file
| | | |> Current LSN (Checkpoint Occurs)
| | |> Minumin LSN (Oldest Active Transaction)
| |> Replication log Reader
|> Start of Logical LOG file
在此示例中,复制日志读取器强制VLF4保持活动状态。
或
/------INACTIVE---\/----------------ACTIVE-------\/---------INACTIVE-----------\
|------------------------------------------------------------------------------|
| | | | | | | | |
| VLF1 | VLF2 | VLF3 | VLF4 | VLF5 | VLF6 | VLF7 | VLF8 |
| | | | | | | | |
|------------------------------------------------------------------------------|
^ ^ ^ ^ ^ ^
| | | | | |> End of logical LOG file
| | | | |> Current LSN (Checkpoint Occurs)
| | | |> Minumin LSN (Oldest Active Transaction)
| | |> Replication log Reader
| |> Last Transaction Log Backup
|> Start of logical LOG file
并且在此示例中,&#34;最后一个事务日志备份&#34;迫使VLF3保持活跃状态。
我希望这些有助于更好地了解SQL Server的工作原理。
答案 2 :(得分:0)
查看有关恢复模式的更多详细信息here.
DML查询将始终写入日志以便能够回滚。在基本术语中,简单恢复不会在事务提交后保留日志,但在执行时仍然会写入。