在事务日志文件里查找page split记录
在事务日志文件里查找page split记录
http://www.sqlskills.com/blogs/paul/tracking-page-splits-using-the-transaction-log/
Tracking page splits using the transaction log
Whenever I’m teaching about index fragmentation I get asked how to track page splits proactively. This can be useful to discover fragmentation occurring in indexes you didn’t know had fragmentation problems, without running the sys.dm_db_index_physical_stats DMV (see here for how that works) against all the indexes in your databases. Today this came up multiple times, both in class and in email, so it’s time to bubble this blog post up to the top of the list.
You might think this is easy, as there’s a page split counter in sys.dm_db_index_operational_stats and in the Access Methods perfmon object. However, neither of these distinguish between ‘good’ splits and ‘nasty’ splits, which are my terms :-). A ‘nasty’ split is what we think of a just a page split – a data or index page having to split into two pages to make space for a record to be inserted or an existing record to expand. A ‘good’ split is what the Storage Engine calls adding a page on the right-hand side of the index leaf level as part of inserting new records in an ascending key index (e.g. a clustered index with a bigint identity column as the cluster key).
This is a really annoying as it makes both these methods of tracking page splits essentially useless.
If you’re running SQL Server 2012 or later, the solution is to use Extended Events, based on the new sqlserver.transaction_log event. Jonathan wrote a great posthere that gives you the Extended Events sessions to use.
If you’re not running SQL Server 2012 or later, read on.
Before the sqlserver.transaction_log event was added, there was (and still is) the sqlserver.page_split event but that does not distinguish between ‘good’ splits and ‘nasty’ splits either, so some post processing is involved (essentially reading the page referenced in the event to see if it really split or not).
So what’s the answer?
Scanning the log for page splits
The easiest way to proactively see page splits occurring is to look in the transaction log. Whenever a page splits, an LOP_DELETE_SPLIT log record is generated so querying the transaction log can let you know what’s going on.
Some simple code to do this is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
SELECT [AllocUnitName] AS N 'Index' , ( CASE [Context] WHEN N 'LCX_INDEX_LEAF' THEN N 'Nonclustered' WHEN N 'LCX_CLUSTERED' THEN N 'Clustered' ELSE N 'Non-Leaf' END ) AS [SplitType], COUNT (1) AS [SplitCount] FROM fn_dblog ( NULL , NULL ) WHERE [Operation] = N 'LOP_DELETE_SPLIT' GROUP BY [AllocUnitName], [Context]; GO |
However, I don’t recommend doing this, for two reasons:
- Running fn_dblog will cause read I/Os on the transaction log, which can cause performance issues, especially if you’re running the scanner regularly and it happens to coincide with a log backup, for instance.
- Log clearing is disabled while fn_dblog is running, so on a system with a large amount of log to scan, this could interrupt the ability of the log to clear and cause log growth.
If you’re running in the full or bulk-logged recovery model, I recommend scanning your log backups for page splits instead of your actual log. If you’re only running in the simple recovery model, and you *really* want to run the script regularly, you’re going to have to run the script just before each checkpoint operation clears the log. But still, be careful you don’t interrupt the log clearing process.
Scanning a log backup for page splits
There are two options for this, using the fn_dump_dblog function I blogged about here:
- Scanning a log backup on a system other than the production system.
- Scanning a log backup on the production system.
If you choose to use a system other than the production system, then unless you have a restored copy of the database, you will not be able to get the index name, as fn_dump_dblog does not give you the name and you will not have the metadata to allow looking up the index name from the allocation unit ID in the log.
Edit 8/15/13: Beware – we just found out from a customer system that uses this extensively that every time fn_dump_dblog is called, it creates a new hidden SQLOS scheduler and up to three threads, which will never go away and never be reused. Use with caution.
So I’ve created two scripts for you, for when the database is and isn’t available on the server where the backup is located. I’ll extend these in future posts.
Have fun!
Scanning a log backup where the database is not available
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
SELECT [AllocUnitId], ( CASE [Context] WHEN N 'LCX_INDEX_LEAF' THEN N 'Nonclustered' WHEN N 'LCX_CLUSTERED' THEN N 'Clustered' ELSE N 'Non-Leaf' END ) AS [SplitType], COUNT (1) AS [SplitCount] FROM fn_dump_dblog ( NULL , NULL , N 'DISK' , 1, N 'C:\SQLskills\SplitTest_log.bck' , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT ) WHERE [Operation] = N 'LOP_DELETE_SPLIT' GROUP BY [AllocUnitId], [Context]; GO |
Scanning a log backup where the database is available
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
SELECT CAST ([s].[ name ] AS VARCHAR ) + '.' + CAST ([o].[ name ] AS VARCHAR ) + '.' + CAST ([i].[ name ] AS VARCHAR ) AS [ Index ], [f].[SplitType], [f].[SplitCount] FROM ( SELECT [AllocUnitId], ( CASE [Context] WHEN N 'LCX_INDEX_LEAF' THEN N 'Nonclustered' WHEN N 'LCX_CLUSTERED' THEN N 'Clustered' ELSE N 'Non-Leaf' END ) AS [SplitType], COUNT (1) AS [SplitCount] FROM fn_dump_dblog ( NULL , NULL , N 'DISK' , 1, N 'C:\SQLskills\SplitTest_log.bck' , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT , DEFAULT ) WHERE [Operation] = N 'LOP_DELETE_SPLIT' GROUP BY [AllocUnitId], [Context]) f JOIN sys.system_internals_allocation_units [a] ON [a].[allocation_unit_id] = [f].[AllocUnitId] JOIN sys.partitions [p] ON [p].[partition_id] = [a].[container_id] JOIN sys.indexes [i] ON [i].[index_id] = [p].[index_id] AND [i].[object_id] = [p].[object_id] JOIN sys.objects [o] ON [o].[object_id] = [p].[object_id] JOIN sys.schemas [s] ON [s].[schema_id] = [o].[schema_id]; GO |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战