B+ Tree lookup
B+ Tree insert
B+ Tree delete
B+ Tree range scan
Compound key comparison
compound keys are compared lexicographically:
(a_1, a_2) < (b_1, b_2) ⇔ (a_1 < b_1) ∨ (a_1 = b_1 ∧ a_2 < b_2)
(and so on with larger keys)
Compound key lookup
if all attributes of the compund key are bound, it's the same as atomic keys
otherwise, the unbound properties are specified as range between −∞ and ∞ (so they can have any value)
Indexing non-unique values
index on the value but also secretly index on the tupleID, so when value is equal the tupleID can be compared.
this guarantees O(logn) access
When to index a value
if condition value == c is only true for a maximum of 10% of the entries for any given c constant (otherwise linear read is faster)
Lock vs. Latch
a lock holds for the whole transaction, while a latch only holds for a short period of time
Lock coupling
classical technique for locking multiple pages in a B+ Tree
first latch the root, latch the first level, release root, latch second level, release first level, ...
Safe inner pages
extension on the lock coupling technique, ensures that we can insert without deadlocks
when traversing down the tree, check for every node if the node would have enough space for one more entry. If not, split it.
However, doesn't work for variable-length keys (strings)
Restart method
alternative extension on the lock coupling technique, ensures that we can insert without deadlocks but also works with variable-length keys
first naively try to insert using simple lock coupling. If fails (we need to split a node), restart the operation from root, but this time don't release any of the latches
B-Link Tree
B+ Tree but every inner node is also connected via next pointer (not just the leaf nodes)
using this variation, we can do a lookup with only latching the appropriate child page (because even if it has been split during our lookup, we can use the next pointer to get to the other half of the split)
Bulkloading a B+ Tree
build-up technique for B+ Trees for large amounts of data
Partitioned B-Tree
problem: bulk operations make the B-Tree offline
solution: partition the B-Tree into columns, and only load into the right-most column
Prefix B+ Tree
technique to optimize space consumption on B+Trees with variable-length keys.
Instead of storing the complete keys in each node, only the differing parts of the keys are stored, along with a common prefix for the keys in that node.
Such B+Trees have their data usually lexicographically sorted
Properties of Hash-Based indexes
Extendible hashing
technique to allow hash-table growth on the disk without rehashing
Linear Hashing
extendible hashing, but instead of adding a new bit to the whole table, only add 1 bit to 1 index entry on demand (e.g. there is the table entries 0 and 1, we only add 1 more bit to entry 0 so now we have entries 00, 10 and 1)
Multilevel extendible hashing
dynamic hashing technique for large datasets. Minimizes collision.
creates a tree-like structure of hash tables:
problem: when uniformly filled, there will be a point where each bucket has just been split, so we have a lot of 2nd level tables with only a few entries.
solution: share the inner page between buddies (pointers to the same page) and undo split if at specific buckets if all pointers of the bucket point to the same value
Bitmap indexes
indexing technique for small domains where only a combination of attributes is selective
create a bitmap for every possible value of a given attribute, where a tuple has bit 1 if its value of that attribute is that signed by the bitmap (e.g. create a bitmap for a=5, here every tuple where a is actually 5 will have bit 1, the others bit 0)
makes queries answerable by simple bit-operations
bitmaps are also sparse and contain only 1 bit per tuple, so they can be well compressed
Small materialized aggregates
some data chunks rarely change (the old static chunks), so we can calculate frequent aggregates on them (like min, max, count, sum, ...) and answer queries using only the aggregates
Transaction operations
ACID properties
properties all transactions should adhere to
Lost update
problem between 2 concurrent transactions if not run as serializable
happens if T1 writes x and T2 also writes x before T1 commits (T1's changes are now gone)
Dirty read
problem between 2 concurrent transactions if not run as serializable
happens if T1 writes x, after this T2 reads x, but T1 actually aborts at the end (T2 read a false value)
Non-repeatable read
problem between 2 concurrent transactions if not run as serializable
happens if T1 works with x multiple times during its run, and T2 writes x while T1 is running (T1 now read 2 different values for x)
Phantom problem
problem between 2 concurrent transactions if not run as serializable
happens if T1 works with all tuples of relation R during its run, but T2 inserts a new tuple into R while T1 is running (T1 now reads a new tuple during second access)
solution: add a lock for the "next" pseudo-tuple. Scans lock it shared, inserts lock it exclusive and apply trick to enable concurrent inserts.
Formal definition of a transaction
transaction Ti is a partial order of operations, where
Schedules
partial ordering over the operations of a set of the transactions made by the scheduler
a schedule shall contain all operations of the contained transactions, operation order in any given transaction shall remain the same, and conflicting operations shall not be executed concurrently
Conflicting operations
operations of two transactions are in conflict if they both operate on the same data and at least one of them is write
conflicting operations must not be executed in parallel
Conflict equivalent schedules
Two schedules are conflict equivalent if they contain the same transactions and they order the conflicting operations of those transactions the same way
in other words, they always yield the same result
Serializable schedules
a schedule is serializable, if there exists a serial (not concurrent, every transaction runs after one another) schedule, which yields the same committed transactions and end-results as the schedule (their comitted projections are equal)
a serializable schedule's serializability graph must be acyclic
Serializability graph
a directed graph where the nodes are comitted transactions, and there is a directed edge between T1 and T2 if they have conflicting operations p_1∈ T1 and q_1∈T2 where p_1 is ordered before q_1 in the schedule
Recoverability
a schedule is recoverable if for all T1 T2 transaction pairs in the schedule: if T1 reads value x after that x is written by T2 and T1 commits, then T2 must commit before T1
(transactions must adhere to a certain commit order)
example: w1[x] r2[x] w2[y] c2 a1 is not recoverable
Cascading aborts
can happen if T1 writes value x, T2 reads value x after this and writes value y, T3 reads value y and writes value z, T4 reads value z....
Now if T1 (or any of the subsequent transactions) aborts, all the subsequent transactions after it must abort too
avoided by: whenever a transaction T1 reads value x after it has been modified by T2, the schedule must make sure that T2 commits before T1 reads
Strictness
a schedule is strict if for any two transactions T1 and T2 in the schedule, if T1 writes value x T2 only works (reads or writes) with x after T1 has committed or aborted
Scheduler
creates schedules
3 options for transactions:
2 main classes:
Lock-based scheduler
uses two-phase locking:
Deadlock detection
done via waits-for graph: directed graph, nodes are transactions, there is an edge between two nodes T1 and T2 if T1 is waiting for a lock that T2 has.
The schedule has a deadlock if the waits-for graph is cyclic
Calculating waits-for graphs is expensive => use time-out too: at time-out, calculate waits-for graph and abort one transaction if needed