Indexing
Indexing
Jehan-François Pâris
Spring 2015
Overview
Three main techniques
Conventional indexes
Think of a page table, …
B and B+ trees
Perform better when records are constantly
added or deleted
Hashing
Conventional
indexes
Indexes
A database index is a data structure that
improves the speed of data retrieval operations
on a database table at the cost of additional
writes and storage space to maintain the index
data structure.
Wikipedia
Types of indexes
An index can be
Sparse
One entry per data block
Dense
One entry per record
Faster access
Dense
Can tell if a given record exists without
AKA
Master Index
Top Index
Updating indexed tables
Can be painful
No silver bullet
B-trees and B+
trees
Motivation
To have dynamic indexing structures that can evolve
when records are added and deleted
Not the case for static indexes
Would have to be completely rebuilt
a block is cheap!
B trees
Generalization of binary search trees
Not binary trees
The B stands for Bayer (or Boeing)
Designed for searching data stored on block-
oriented devices
A very small B tree
To 7 To 16 To -- --
Null Null
Leaf leaf Leaf Null Null
Organization
Each non-terminal node can have a variable number
of child nodes
Must all be in a specific key range
Number of child nodes typically vary between d
and 2d
Will split nodes that would otherwise have
nodes
Searching the tree
Step 2:
Step 3:
1
Split node in middle
1 2
1 2 3 2
1 3
Insertions
Step 4:
Step 5: 2
1 3 4
Split
Move up
2
1 3 4 5
2 4
1 3 5
Insertions
Step 6:
2 4
1 3 5 6
Step 7:
2 4
1 3 5 6 7
Step 7 continued
2 4
1 3 6
Split 4 7
2 4 6
1 3
Promote
5 7
Step 7 continued
2 4 6
1 3
5 7
Split after
the promotion 4
2 6
1 3 5 7
Two basic operations
5 6 7
Split:
When trying to add to a full node
6
Split node at central value
5 7
Promote:
Must insert root of split
node higher up
May require a new split
B+ trees
Variant of B trees
Two types of nodes
Internal nodes have no data pointers
Leaf nodes have no in-tree pointers
Were all null!
B+ tree nodes
In In In In In In
tree Key tree Key tree Key tree Key tree Key tree
ptr ptr ptr ptr ptr ptr
Data ptr Data ptr Data ptr Data ptr Data ptr Data ptr
More about internal nodes
Consist of n -1 key values K1, K2, …, Kn-1 ,and n tree
pointers P1, P2, …, Pn :
< P1,K1, P2, K2, P3, …, Pn-1, Kn-1,, Pn>
The keys are ordered K1 < K2 < … < Kn-1
For each tree value X in the subtree pointed at by tree
pointer Pi, we have:
X > Ki-1 for 1 ≤ i ≤ n
X ≤ Ki for 1 ≤ i ≤ n - 1
Warning
Other authors assume that
For each tree value X in the subtree pointed
at by tree pointer Pi, we have:
X ≥ Ki-1 for 1 ≤ i ≤ n
X < Ki for 1 ≤ i ≤ n - 1
Changes the key value that is promoted when
an internal node is split
Advantages
Removing unneeded pointers allows to pack
more keys in each node
Higher fan-out for a given node size
Normally one block
else :
Allocate new leaf L'
Step 1:
Step 2:
Step 3:
1
Split node in middle
1 2
1 2 3 2
1 2 3
Insertions
Step 4:
Step 5: 2
1 2 3 4
Split
Move up
2
1 2 3 4 5
2 4
1 2 3 4 5
Insertions
Step 6:
2 4
1 2 3 4 5 6
Step 7:
2 4
1 2 3 4 5 6 7
Step 7 continued
2 4
1 2 3 4 6
Split 5 6 7
2 4 6
1 2
3 4
Promote
5 6 7
Step 7 continued
2 4 6
1 3
5 7
Split after
the promotion 4
2 6
1 3 5 7
Importance
B+ trees are used by
NTFS, ReiserFS, NSS, XFS, JFS, ReFS, and
BFS file systems for metadata indexing
BFS for storing directories.
IBM DB2, Informix, Microsoft SQL Server,
Oracle 8, Sybase ASE, and SQLite for table
indexes
Not on
An interesting variant Spring 2015
first quiz
Can simplify entry deletion by never merging
nodes that have less than ⌈m ⁄ 2⌉ entries
Wait instead until there are empty and can be
deleted
Requires more space
Seems to be a reasonable tradeoff assuming
random insertions and deletions
Hashing
Fundamentals
Define m target addresses (the "buckets")
Create a hash function h(k) that is defined for
all possible values of the key k and returns an
integer value h such that 0 ≤ h ≤ m – 1
Key h(k)
The idea
Key
Hash
value
is
Bucket
address
Bucket sizes
Each bucket consists of one or more blocks
Need some way to convert the hash value into a
logical block address
Selecting large buckets means we will have to
search the contents of the target bucket to find the
desired record
If search time is critical and the database
infrequently updated, we should consider
sorting the records inside each bucket
Bucket organization
Two possible solutions
Buckets contain records
When bucket is full, records go to an
overflow bucket
Buckets contain pairs <key, address>
When bucket is full, pairs <key, address>
go to an overflow bucket
Buckets contain records
Assume each
bucket contains Overflow bucket
two records
Buckets contain records
A record
KEY
A bucket can
Many
contain many
more
more keys KEY records
than records
Finding a good hash function
Should distribute records evenly among the
buckets
A bad hash function will have too many
overflowing buckets and too many empty or
near-empty buckets
A good starting point
If the key is numeric
Divide the key by the number of buckets
If the number of buckets is a power of two,
Directory
K = 010 Records with
000 d=1
001
key = 0*
010
K = 111 Records with d = 1
001
100
key = 1*
101
110
Both buckets are at same depth d
101
Extendible hashing
When a bucket overflows, we split it
Directory
K = 000 Records with d = 2
000
001
key = 00*
010
K = 111 Records with d = 1
001
100
key = 1*
101 Records with d = 2
K = 010
110
K = 011 key = 01*
101
Explanations (I)
Choice of a bucket is based on the most
significant bits (MSBs) of hash value
Start with a single bit
Will have two buckets
One for MSB = 0
Depth of bucket is 1
Explanations (II)
Each time a bucket overflows, we split it
Assume first bucket overflows
Will add a new bucket containing records
of hash value = 00
Depths of these two bucket is 2
Explanations (III)
At any given time, the hash table will contain
buckets at different depths
In our example, buckets 00 and 01 are at
depth 2 while bucket 1 is at depth 1
Each bucket will include a record of its depth
Just a few bits
Discussion
Extendible hashing
Allows hash table contents
To grow, by splitting buckets
but
Adds one level of indirection
No problem if the directory can reside in
main memory
Linear hashing
Does not add an additional level of indirection
Reduces but does not eliminate overflow buckets
Uses a family of hash functions
hi(K) = K mod m
hi+1(K) = K mod 2m
hi+2(K) = K mod 4m
…
How it works (I)
Start with
m buckets
hi(K) = K mod m
When any bucket overflows
Create an overflow bucket
Create a new bucket at location m
Apply hash function hi+1(K)= K mod 2m to the contents
of bucket 0
Will now be split between buckets 0 and m
How it works (II)
When a second bucket overflows
Create an overflow bucket
Create a new bucket at location m + 1
Apply hash function hi+1(K)= K mod 2m to the
contents of bucket 1
Will now be split between buckets 1 and
m+1
How it works (III)
Each time a bucket overflows
Create an overflow bucket
Apply hash function hi+1(K)= K mod 2m to the contents of
the successor s + 1 of the last bucket that was split
Contents of bucket s + 1 will now be split between
buckets s and m + s – 1
The size of the hash table grows linearly at each split until
all buckets use the new hash function
Advantages
The hash table goes linearly
As we split buckets in linear order, bookkeeping is
very simple:
Need only to keep track of the last bucket s that
was split
Buckets 0 to s use the new hash function
hi+1(K)= K mod 2m
Buckets s + 1 to m – 1 still use the old hash
function hi(K)= K mod m
Example (I)
Assume m = 4 and one record per bucket
Table contains two records
Hash value = 0
Hash value = 2
Example (II)
We add one record with hash value = 2
Overflow bucket
Hash value = 2 Hash value = 2