principlesqlMinor
B-tree node split strategy in SQL Server for monotonically increasing value
Viewed 0 times
sqlnodevaluesplitmonotonicallyforincreasingstrategyservertree
Problem
Consider a B-tree index on a value that will always increase monotonically, e.g. a column of type IDENTITY. With a conventional B-tree implementation, whenever a node is full, it will be split 50%/50% and we end up with a B-tree in which (almost) all nodes will be only 50% full.
I know that Oracle discovers when a value is ever-increasing and in these cases Oracle performs a 90%/10% split instead. That way, (almost) all nodes will be 90% full and a far better page utilization is obtained for these, quite common, cases.
I have not been able to find documentation for a similar feature in SQL Server. However, I have performed two experiments in which I inserted N random integers, and N consecutive integers in an index, respectively. The former case used far more pages the latter.
Does SQL Server provide a similar functionality? If so: can you point me to some documentation on this feature?
UPDATE:
It seems, by the experiments provided below, that leaf nodes are kept un-splitted and internal nodes are split 50%/50%. That makes B-trees on increasing keys more compact than on random keys. However, the 90%/10%-approach by Oracle is even better, and I still look for some official documentation that can verify the behavior seen in experiments.
I know that Oracle discovers when a value is ever-increasing and in these cases Oracle performs a 90%/10% split instead. That way, (almost) all nodes will be 90% full and a far better page utilization is obtained for these, quite common, cases.
I have not been able to find documentation for a similar feature in SQL Server. However, I have performed two experiments in which I inserted N random integers, and N consecutive integers in an index, respectively. The former case used far more pages the latter.
Does SQL Server provide a similar functionality? If so: can you point me to some documentation on this feature?
UPDATE:
It seems, by the experiments provided below, that leaf nodes are kept un-splitted and internal nodes are split 50%/50%. That makes B-trees on increasing keys more compact than on random keys. However, the 90%/10%-approach by Oracle is even better, and I still look for some official documentation that can verify the behavior seen in experiments.
Solution
If it is adding a row at the end of the index it will just allocate a new page for the row rather than split the current end page. Experimental evidence for this is below (uses the
Returns (Your results will vary)
This does only appear to apply to leaf nodes though. This can be seen by running the below and adjusting the
%%physloc%% function which requires SQL Server 2008). See also the discussion here.CREATE TABLE T
(
id int identity(1,1) PRIMARY KEY,
filler char(1000)
)
GO
INSERT INTO T
DEFAULT VALUES
GO 7
GO
SELECT sys.fn_PhysLocFormatter(%%physloc%%)
FROM T
GO
INSERT INTO T
DEFAULT VALUES
GO
SELECT sys.fn_PhysLocFormatter(%%physloc%%)
FROM T
GO
DROP TABLE TReturns (Your results will vary)
(1:173:0) /*File:Page:Slot*/
(1:173:1)
(1:173:2)
(1:173:3)
(1:173:4)
(1:173:5)
(1:173:6)
(1:110:0) /*Final insert is on a new page*/This does only appear to apply to leaf nodes though. This can be seen by running the below and adjusting the
TOP value. For me 622/623 was the cut off point between requiring one and two first level pages (might vary if you have snapshot isolation enabled?). It does split the page in a balanced manner leading to wasted space at this level.USE tempdb;
CREATE TABLE T2
(
id int identity(1,1) PRIMARY KEY CLUSTERED,
filler char(8000)
)
INSERT INTO T2(filler)
SELECT TOP 622 'A'
FROM master..spt_values v1, master..spt_values v2
DECLARE @index_info TABLE
(PageFID VARCHAR(10),
PagePID VARCHAR(10),
IAMFID tinyint,
IAMPID int,
ObjectID int,
IndexID tinyint,
PartitionNumber tinyint,
PartitionID bigint,
iam_chain_type varchar(30),
PageType tinyint,
IndexLevel tinyint,
NextPageFID tinyint,
NextPagePID int,
PrevPageFID tinyint,
PrevPagePID int,
Primary Key (PageFID, PagePID));
INSERT INTO @index_info
EXEC ('DBCC IND ( tempdb, T2, -1)' );
DECLARE @DynSQL nvarchar(max) = 'DBCC TRACEON (3604);'
SELECT @DynSQL = @DynSQL + '
DBCC PAGE(tempdb, ' + PageFID + ', ' + PagePID + ', 3); '
FROM @index_info
WHERE IndexLevel = 1
SET @DynSQL = @DynSQL + '
DBCC TRACEOFF(3604); '
EXEC(@DynSQL)
DROP TABLE T2Code Snippets
CREATE TABLE T
(
id int identity(1,1) PRIMARY KEY,
filler char(1000)
)
GO
INSERT INTO T
DEFAULT VALUES
GO 7
GO
SELECT sys.fn_PhysLocFormatter(%%physloc%%)
FROM T
GO
INSERT INTO T
DEFAULT VALUES
GO
SELECT sys.fn_PhysLocFormatter(%%physloc%%)
FROM T
GO
DROP TABLE T(1:173:0) /*File:Page:Slot*/
(1:173:1)
(1:173:2)
(1:173:3)
(1:173:4)
(1:173:5)
(1:173:6)
(1:110:0) /*Final insert is on a new page*/USE tempdb;
CREATE TABLE T2
(
id int identity(1,1) PRIMARY KEY CLUSTERED,
filler char(8000)
)
INSERT INTO T2(filler)
SELECT TOP 622 'A'
FROM master..spt_values v1, master..spt_values v2
DECLARE @index_info TABLE
(PageFID VARCHAR(10),
PagePID VARCHAR(10),
IAMFID tinyint,
IAMPID int,
ObjectID int,
IndexID tinyint,
PartitionNumber tinyint,
PartitionID bigint,
iam_chain_type varchar(30),
PageType tinyint,
IndexLevel tinyint,
NextPageFID tinyint,
NextPagePID int,
PrevPageFID tinyint,
PrevPagePID int,
Primary Key (PageFID, PagePID));
INSERT INTO @index_info
EXEC ('DBCC IND ( tempdb, T2, -1)' );
DECLARE @DynSQL nvarchar(max) = 'DBCC TRACEON (3604);'
SELECT @DynSQL = @DynSQL + '
DBCC PAGE(tempdb, ' + PageFID + ', ' + PagePID + ', 3); '
FROM @index_info
WHERE IndexLevel = 1
SET @DynSQL = @DynSQL + '
DBCC TRACEOFF(3604); '
EXEC(@DynSQL)
DROP TABLE T2Context
StackExchange Database Administrators Q#9963, answer score: 4
Revisions (0)
No revisions yet.