refactor: use columns

This commit is contained in:
Kristofers Solo 2024-05-06 17:35:09 +03:00
parent a9bb553cf3
commit 928bd8554f

358
main.typ
View File

@ -1,34 +1,34 @@
#set page(margin: ( #import "@preview/tablex:0.0.8": tablex, rowspanx, colspanx
top: 0.6cm, #set page(margin: 0.6cm, columns: 3)
bottom: 0.6cm,
right: 0.6cm,
left: 0.6cm,
))
#set text(6.2pt) #set text(6pt)
#show heading: it => { #show heading: it => {
if it.level == 1 { if it.level == 1 {
// pagebreak(weak: true) text(1em, upper(it))
text(8.5pt, upper(it))
} else if it.level == 2 {
text(8pt, smallcaps(it))
} else { } else {
text(8pt, smallcaps(it)) text(1em, smallcaps(it))
} }
} }
#set enum(numbering: "1aiA.")
= Indices = Indices
== Bitmap == Bitmap
Each bit in a bitmap corresponds to a possible item or condition, with a bit Each bit in a bitmap corresponds to a possible item or condition, with a bit set
set to 1 indicating presence or true, and a bit set to 0 indicating absence or to 1 indicating presence or true, and a bit set to 0 indicating absence or
false. false.
#figure( #tablex(
image("img/bitmap.png", width: 30%) stroke: 0.5pt, columns: 4, [record number], `ID`, `gender`, `income_level`, `0`, `76766`, `m`, `L1`, `1`, `22222`, `f`, `L2`, `2`, `12121`, `f`, `L1`, `3`, `15151`, `m`, `L4`, `4`, `58583`, `f`, `L3`,
) )
#grid(
columns: 3, gutter: 2em, tablex(
stroke: 0.5pt, columns: 2, colspanx(2)[Bitmaps for `gender`], `m`, `10010`, `f`, `01101`,
), tablex(
stroke: 0.5pt, columns: 2, colspanx(2)[Bitmaps for `income_level`], `L1`, `10010`, `L2`, `01000`, `L3`, `00001`, `L4`, `00010`, `L5`, `00000`,
),
)
== B+ tree == B+ tree
@ -38,32 +38,32 @@ logarithmic time. It is an extension of the B-tree and is extensively used in
databases and filesystems for indexing. B+ tree is *Balanced*; Order (n): databases and filesystems for indexing. B+ tree is *Balanced*; Order (n):
Defined such that each node (except root) can have at most $n$ children Defined such that each node (except root) can have at most $n$ children
(pointers) and at least $⌈n/2⌉$ children; *Internal nodes hold* between (pointers) and at least $⌈n/2⌉$ children; *Internal nodes hold* between
$⌈n/2⌉1$ and $n1$ keys (values); Leaf nodes hold between $⌈frac(n 1,2)⌉$ and $⌈n/2⌉1$ and $n1$ keys (values); Leaf nodes hold between $⌈frac(n 1, 2)⌉$ and
$n1$ keys, but also store all data values corresponding to the keys; *Leaf $n1$ keys, but also store all data values corresponding to the keys; *Leaf
Nodes Linked*: Leaf nodes are linked together, making range queries and Nodes Linked*: Leaf nodes are linked together, making range queries and
sequential access very efficient. sequential access very efficient.
- *Insert (key, data)*: - *Insert (key, data)*:
- Insert key in the appropriate leaf node in sorted order; - Insert key in the appropriate leaf node in sorted order;
- If the node overflows (more than $n1$ keys), split it, add the middle - If the node overflows (more than $n1$ keys), split it, add the middle key to
key to the parent, and adjust pointers; the parent, and adjust pointers;
+ Leaf split: $1$ to $ceil(frac(n,2)) $ and $ceil(frac(n,2)) + 1 $ to + Leaf split: $1$ to $ceil(frac(n, 2)) $ and $ceil(frac(n, 2)) + 1 $ to
$n$ as two leafs. Promote the lowest from the 2nd one. $n$ as two leafs. Promote the lowest from the 2nd one.
+ Node split: $1$ to $ceil(frac(n+1, 2)) - 1 $ and $ceil(frac(n,2)) + 1$ to $n$. + Node split: $1$ to $ceil(frac(n+1, 2)) - 1 $ and $ceil(frac(n, 2)) + 1$ to $n$.
$ceil(frac(n+1, 2))$ gets moved up. $ceil(frac(n+1, 2))$ gets moved up.
- If a split propagates to the root and causes the root to overflow, split - If a split propagates to the root and causes the root to overflow, split the
the root and create a new root. Note: root can contain less than root and create a new root. Note: root can contain less than
$ceil(frac(n,2)) - 1$ keys. $ceil(frac(n, 2)) - 1$ keys.
- *Delete (key)*: - *Delete (key)*:
- Remove the key from the leaf node. - Remove the key from the leaf node.
- If the node underflows (fewer than $⌈n/2⌉1$ keys), keys and pointers are - If the node underflows (fewer than $⌈n/2⌉1$ keys), keys and pointers are
redistributed or nodes are merged to maintain minimum occupancy. - redistributed or nodes are merged to maintain minimum occupancy. -
Adjustments may propagate up to ensure all properties are maintained. Adjustments may propagate up to ensure all properties are maintained.
== Hash-index == Hash-index
*Hash indices* are a type of database index that uses a hash function to *Hash indices* are a type of database index that uses a hash function to compute
compute the location (hash value) of data items for quick retrieval. They are the location (hash value) of data items for quick retrieval. They are
particularly efficient for equality searches that match exact values. particularly efficient for equality searches that match exact values.
*Hash Function*: A hash function takes a key (a data item's attribute used for *Hash Function*: A hash function takes a key (a data item's attribute used for
@ -73,18 +73,16 @@ position in the hash table where the corresponding record's pointer is stored.
database. Each entry in the hash table corresponds to a potential hash value database. Each entry in the hash table corresponds to a potential hash value
generated by the hash function. generated by the hash function.
= Algorithms = Algorithms
== Nested-loop join == Nested-loop join
*Nested Loop Join*: A nested loop join is a database join operation where each *Nested Loop Join*: A nested loop join is a database join operation where each
tuple of the outer table is compared against every tuple of the inner table to tuple of the outer table is compared against every tuple of the inner table to
find all pairs of tuples which satisfy the join condition. This method is find all pairs of tuples which satisfy the join condition. This method is simple
simple but can be inefficient for large datasets due to its high computational but can be inefficient for large datasets due to its high computational cost.
cost.
```python ```
Simplified version (to get the idea) Simplified version (to get the idea)
for each tuple tr in r: (for each tuple ts in s: test pair (tr, ts)) for each tuple tr in r: (for each tuple ts in s: test pair (tr, ts))
``` ```
@ -96,51 +94,48 @@ seek per block, leading to a total of $2 b_r$ seeks.
== Block-nested join == Block-nested join
*Block Nested Loop Join*: A block nested loop join is an optimized version of the *Block Nested Loop Join*: A block nested loop join is an optimized version of
nested loop join that reads and holds a block of rows from the outer table in the nested loop join that reads and holds a block of rows from the outer table
memory and then loops through the inner table, reducing the number of disk in memory and then loops through the inner table, reducing the number of disk
accesses and improving performance over a standard nested loop join, especially accesses and improving performance over a standard nested loop join, especially
when indices are not available. when indices are not available.
```
```python
Simplified version (to get the idea) Simplified version (to get the idea)
for each block Br of r: for each block Bs of s: for each block Br of r: for each block Bs of s:
for each tuple tr in r: (for each tuple ts in s: test pair (tr, ts)) for each tuple tr in r: (for each tuple ts in s: test pair (tr, ts))
``` ```
// TODO: Add seek information // TODO: Add seek information
Block transfer cost: $b_r b_s + b_r$, $b_r$ -- blocks in relation $r$, same Block transfer cost: $b_r dot b_s + b_r$, $b_r$ -- blocks in relation $r$, same
for $s$. for $s$.
== Merge join == Merge join
*Merge Join*: A merge join is a database join operation where both the outer *Merge Join*: A merge join is a database join operation where both the outer and
and inner tables are first sorted on the join key, and then merged together by inner tables are first sorted on the join key, and then merged together by
sequentially scanning through both tables to find matching pairs. This method sequentially scanning through both tables to find matching pairs. This method is
is highly efficient when the tables are *already sorted* or can be *sorted highly efficient when the tables are *already sorted* or can be *sorted
quickly*, minimizes random disk access. Merge-join method is efficient; the quickly*, minimizes random disk access. Merge-join method is efficient; the
number of block transfers is equal to the sum of the number of blocks in both number of block transfers is equal to the sum of the number of blocks in both
files, $b_r + b_s$. files, $b_r + b_s$. Assuming that $b_b$ buffer blocks are allocated to each
Assuming that $bb$ buffer blocks are allocated to each relation, the number of disk relation, the number of disk seeks required would be $ceil(b_r/b_b) + ceil(b_s/b_b)$ disk
seeks required would be $⌈b_rb_b⌉+ ⌈b_sb_b⌉$ disk seeks seeks
+ Sort Both Tables: If not already sorted, the outer table and the inner table + Sort Both Tables: If not already sorted, the outer table and the inner table are
are sorted based on the join keys. sorted based on the join keys.
+ Merge: Once both tables are sorted, the algorithm performs a merging + Merge: Once both tables are sorted, the algorithm performs a merging operation
operation similar to that used in merge sort: similar to that used in merge sort:
+ Begin with the first record of each table. + Begin with the first record of each table.
+ Compare the join keys of the current records from both tables. + Compare the join keys of the current records from both tables.
+ If the keys match, join the records and move to the next record in both tables. + If the keys match, join the records and move to the next record in both tables.
+ If the join key of the outer table is smaller, move to the next record in + If the join key of the outer table is smaller, move to the next record in the
the outer table. outer table.
+ If the join key of the inner table is smaller, move to the next record in + If the join key of the inner table is smaller, move to the next record in the
the inner table. inner table.
+ Continue this process until all records in either table have been examined. + Continue this process until all records in either table have been examined.
+ Output the Joined Rows; + Output the Joined Rows;
== Hash-join == Hash-join
*Hash Join*: A hash join is a database join operation that builds an in-memory *Hash Join*: A hash join is a database join operation that builds an in-memory
@ -148,68 +143,67 @@ hash table using the join key from the smaller, often called the build table,
and then probes this hash table using the join key from the larger, or probe and then probes this hash table using the join key from the larger, or probe
table, to find matching pairs. This technique is very efficient for *large table, to find matching pairs. This technique is very efficient for *large
datasets* where *indexes are not present*, as it reduces the need for nested datasets* where *indexes are not present*, as it reduces the need for nested
loops. loops.
- $h$ is a hash function mapping JoinAttrs values to ${0, 1, , n_h}$, where - $h$ is a hash function mapping JoinAttrs values to ${0, 1, ... , n_h}$, where
JoinAttrs denotes the common attributes of r and s used in the natural join. JoinAttrs denotes the common attributes of $r$ and $s$ used in the natural join.
- $r_0$, $r_1$, … , rnh denote partitions of r tuples, each initially empty. - $r_0, r_1, ..., r_n_h$ denote partitions of $r$ tuples, each initially empty.
Each tuple $t_r in r$ is put in partition $r_i$, where $i = h(t_r [#[JoinAttrs]])$. Each tuple $t_r in r$ is put in partition $r_i$, where $i = h(t_r ["JoinAttrs"])$.
- $s_0$, $s_1$, ..., $s_n_h$ denote partitions of s tuples, each initially empty. - $s_0$, $s_1$, ..., $s_n_h$ denote partitions of s tuples, each initially empty.
Each tuple $t_s in s$ is put in partition $s_i$, where $i = h(t_s [#[JoinAttrs]])$. Each tuple $t_s in s$ is put in partition $s_i$, where $i = h(t_s ["JoinAttrs"])$.
Cost of block transfers: $3(b_r + b_s) + 4 n_h$. The hash join thus requires Cost of block transfers: $3(b_r + b_s) + 4 n_h$. The hash join thus requires
$2(⌈b_rb_b⌉+⌈b_sb_b⌉)+ 2n_h$ seeks. $2(ceil(b_r/b_b) + ceil(b_s/b_b))+ 2n_h$ seeks.
$b_b$ blocks are allocated for the input buffer and each output buffer. $b_b$ blocks are allocated for the input buffer and each output buffer.
+ Build Phase: + Build Phase:
+ Choose the smaller table (to minimize memory usage) as the "build table." + Choose the smaller table (to minimize memory usage) as the "build table."
+ Create an in-memory hash table. For each record in the build table, + Create an in-memory hash table. For each record in the build table, compute a
compute a hash on the join key and insert the record into the hash table hash on the join key and insert the record into the hash table using this hash
using this hash value as an index. value as an index.
+ Probe Phase: + Probe Phase:
+ Take each record from the larger table, which is often referred to as the + Take each record from the larger table, which is often referred to as the
"probe table." "probe table."
+ Compute the hash on the join key (same hash function used in the build + Compute the hash on the join key (same hash function used in the build phase).
phase). + Use this hash value to look up in the hash table built from the smaller table.
+ Use this hash value to look up in the hash table built from the smaller + If the bucket (determined by the hash) contains any entries, check each entry to
table. see if the join key actually matches the join key of the record from the probe
+ If the bucket (determined by the hash) contains any entries, check each table (since hash functions can lead to collisions).
entry to see if the join key actually matches the join key of the record
from the probe table (since hash functions can lead to collisions).
+ Output the Joined Rows. + Output the Joined Rows.
= Relational-algebra = Relational-algebra
== Equivalence rules == Equivalence rules
+ $ sigma_(theta_1 and theta_2)(E) = sigma_theta_1(sigma_theta_2(E)) $
+ $ sigma_theta_1(sigma_theta_2(E)) = sigma_theta_2(sigma_theta_1(E)) $
+ $ Pi_L_1(Pi_L_2(...(Pi_L_n (E))...)) = Pi_L_1(E) $ -- only the last one matters.
+ Selections can be combined with Cartesian products and theta joins:
$ sigma_theta (E_1 times E_2) = E_1 join_theta E_2 $
$ sigma_theta_1 (E_1 join_theta_2 E_2) = E_1 join_theta_1 and theta_2 E_2 $
+ $ E_1 join_theta E_2 = E_2 join_theta E_1 $
+ Join associativity: $ (E_1 join E_2) join E_3 = E_1 join (E_2 join E_3) $
$ (E_1 join_theta_1 E_2) join_(theta_2 and theta_3) E_3 = E_1 join_(theta_1 and theta_3) (E_2 join_theta_2 E_3) $
+ Selection distribution:
$ sigma_theta_1 (E_1 join_theta E_2) = (sigma_theta_0(E_1)) join_theta E_2 $
$ sigma_(theta_1 and theta_2)(E_1 join_theta E_2) = (sigma_theta_1 (E)1)) join_theta (sigma_theta_2 (E_2)) $
+ Projection distribution:
$ Pi_(L_1 union L_2) (E_1 join_theta E_2) = (Pi_L_1 (E_1) join_theta (Pi_L_2 (E_2))) $
$ Pi_(L_1 union L_2) (E_1 join_theta E_2) = Pi_(L_1 union L_2) ((Pi_(L_1 union L_3) (E_1)) join_theta (Pi_(L_2 union L_4) (E_2))) $
+ Union and intersection commmutativity:
$ E_1 union E_2 = E_2 union E_1 $
$ E_1 sect E_2 = E_2 sect E_1 $
+ Set union and intersection are associative:
$ (E_1 union E_2) union E_3 = E_1 union (E_2 union E_3) $
$ (E_1 sect E_2) sect E_3 = E_1 sect (E_2 sect E_3) $
+ The selection operation distributes over the union, intersection, and
set-difference operations:
$ sigma_P (E_1 - E_2) = sigma_P(E_1) - E_2 = sigma_P(E_1) - sigma_P(E_2) $
+ The projection operation distributes over the union operation:
$ Pi_L (E_1 union E_2) = (Pi_L(E_1)) union (Pi_L(E_2)) $
// FROM Database concepts // FROM Database concepts
+ $σ_(θ_1∧θ_2)(E) ≡σ_(θ_1) (σ_(θ_2)(E))$
+ $σ_(θ_1)(σ_(θ_2)(E)) ≡σ_(θ_2)(σ_(θ_1)(E))$
+ $Π_(L_1)(Π_(L_2)(… (Π_(L_n)(E)) …)) ≡Π_(L_1)(E)$ -- only the last one matters.
+ Selections can be combined with Cartesian products and theta joins: $σ_θ(E_1
× E_2) ≡E_1 ⋈_θ E_2$ - This expression is just the definition of the theta
join |||| $σ_(θ_1)(E_1 ⋈_(θ_2) E_2) ≡E_1 ⋈_(θ_1) ∧ θ_2 E_2$
+ $E_1 ⋈_θ E_2 ≡E_2 ⋈_θ E_1$
+ Join associativity: $(E_1 ⋈ E_2) ⋈ E_3 ≡E_1 ⋈(E_2 ⋈E_3)$ |||| $(E_1 join_theta_1
E_2) join_(theta_2 and theta_3) |||| E_3 ≡E_1 join_(theta_1 or theta_3) (E_2
join_theta_2 E_3)$
+ Selection distribution: $σ_(θ_1)(E_1 ⋈_θ E_2) ≡(σ_(θ_1) (E_1)) ⋈_θ E_2$;
$σ_(θ_1∧θ_2)(E_1 ⋈_θ E_2) ≡ (σ_(θ_1)(E_1)) ⋈_θ (σ_(θ_2)(E_2))$
+ Projection distribution: - $Π_(L_1L_2) (E_1 ⋈_θ E_2) ≡(Π_(L_1(E_1)) ⋈_θ
(Π_(L_2)(E_2))$ |||| $Π(L_1L_2) (E_1 ⋈_θ E_2) ≡Π_(L_1L_2) ((Π_(L_1L_3) (E_1))
⋈_θ (Π_(L_2L_4) (E_2)))$
+ Union and intersection commmutativity: $E_1 E_2 ≡E_2 E_1 |||| - E_1 ∩E_2 ≡E_2 ∩E_1$
+ Set union and intersection are associative: $(E_1 E_2) E_3 ≡E_1 (E_2 E_3) |||| (E_1
∩E_2) ∩E_3 ≡E_1 ∩(E_2 ∩E_3)$;
+ The selection operation distributes over the union, intersection, and
set-difference operations: $σ_θ(E_1 E_2) ≡σ_θ(E_1) σ_θ(E_2) |||| σ_θ(E_1 ∩E_2) ≡σ_θ(E_1)
σ_θ(E_2) |||| σ_θ(E_1 E_2) ≡σ_θ(E_1) σ_θ(E_2) |||| σ_θ(E_1 ∩E_2) ≡σ_θ(E_1) ∩E_2 |||| σ_θ(E_1 E_2) ≡σ_θ(E_1)
E_2$;
+ The projection operation distributes over the union operation - $Π_L(E_1
E_2) ≡(Π_L_(E_1)) (Π_L(E_2))$.
// == Operations // == Operations
// //
@ -243,17 +237,17 @@ $b_b$ blocks are allocated for the input buffer and each output buffer.
// Right Outer Join: $R join.r S$. Purpose: Extends join to include non-matching // Right Outer Join: $R join.r S$. Purpose: Extends join to include non-matching
// tuples from one or both relations, filling with nulls. // tuples from one or both relations, filling with nulls.
= Concurrency
= Concurrency
=== Conflict === Conflict
We say that I and J conflict if they are operations by *different transactions* on the We say that $I$ and $J$ conflict if they are operations by *different
*same data item*, and at least one of these instructions is a *write* operation. transactions* on the *same data item*, and at least one of these instructions is
For example: I = read(Q), J = read(Q) -- Not a conflict; I = read(Q), J = a *write* operation. For example:
write(Q) -- Conflict; I = write(Q), J = read(Q) -- Conflict; I = write(Q), J = - $I = #[`read(Q)`]$ , $J = #[`read(Q)`]$ -- Not a conflict;
write(Q) -- Conflict. - $I = #[`read(Q)`]$ , $J = #[`write(Q)`]$ -- Conflict;
- $I = #[`write(Q)`]$, $J = #[`read(Q)`]$ -- Conflict;
- $I = #[`write(Q)`]$, $J = #[`write(Q)`]$ -- Conflict.
// + I = read(Q), J = read(Q). The order of I and J *does not matter*, since the same // + I = read(Q), J = read(Q). The order of I and J *does not matter*, since the same
// value of Q is read by $T_i$ and $T _j$, regardless of the order. // value of Q is read by $T_i$ and $T _j$, regardless of the order.
@ -280,33 +274,35 @@ equivalent*. We can swap only _adjacent_ operations.
The concept of conflict equivalence leads to the concept of conflict The concept of conflict equivalence leads to the concept of conflict
serializability. We say that a schedule $S$ is *conflict serializable* if it is serializability. We say that a schedule $S$ is *conflict serializable* if it is
conflict equivalent to a serial schedule. conflict equivalent to a serial schedule.
// TODO: rename to precedence // TODO: rename to precedence
=== Precedence graph === Precedence graph
Simple and efficient method for determining the conflict Simple and efficient method for determining the conflict seriazability of a
seriazability of a schedule. Consider a schedule $S$. We construct a directed schedule. Consider a schedule $S$. We construct a directed graph, called a
graph, called a precedence graph, from $S$. The set of vertices precedence graph, from $S$. The set of vertices consists of all the transactions
consists of all the transactions participating in the schedule. The set of participating in the schedule. The set of edges consists of all edges $T_i arrow T_j$ for
edges consists of all edges $T_i arrow T_j$ for which one of three conditions holds: which one of three conditions holds:
+ $T_i$ executes `write(Q)` before $T_j$ executes `read(Q)`. + $T_i$ executes `write(Q)` before $T_j$ executes `read(Q)`.
+ $T_i$ executes `read(Q)` before $T_j$ executes `write(Q)`. + $T_i$ executes `read(Q)` before $T_j$ executes `write(Q)`.
+ $T_i$ executes `write(Q)` before $T_j$ executes `write(Q)`. + $T_i$ executes `write(Q)` before $T_j$ executes `write(Q)`.
If the precedence graph for $S$ has a cycle, then schedule $S$ is not conflict If the precedence graph for $S$ has a cycle, then schedule $S$ is not conflict
serializable. If the graph contains no cycles, then the schedule $S$ is serializable. If the graph contains no cycles, then the schedule $S$ is conflict
conflict serializable. serializable.
== Standard isolation levels == Standard isolation levels
- *Serializable* usually ensures serializable execution. - *Serializable* usually ensures serializable execution.
- *Repeatable* read allows only committed data to be read and further requires that, - *Repeatable* read allows only committed data to be read and further requires
between two reads of a data item by a transaction, no other transaction is allowed that, between two reads of a data item by a transaction, no other transaction is
to update it. However, the transaction may not be serializable allowed to update it. However, the transaction may not be serializable
- *Read committed* allows only committed data to be read, but does not require re- peatable reads. - *Read committed* allows only committed data to be read, but does not require re-
- *Read uncommitted* allows uncommitted data to be read. Lowest isolation level allowed by SQL. peatable reads.
- *Read uncommitted* allows uncommitted data to be read. Lowest isolation level
allowed by SQL.
== Protocols == Protocols
@ -326,50 +322,51 @@ Every cascadeless schedule is also recoverable.
=== Lock-based === Lock-based
*Shared Lock* -- If a transaction $T_i$ has obtained a shared-mode lock (denoted by $S$) on *Shared Lock* -- If a transaction $T_i$ has obtained a shared-mode lock (denoted
item Q, then Ti can read, but cannot write, $Q$. \ by $S$) on item Q, then Ti can read, but cannot write, $Q$.
*Exclusive Lock* -- If a transaction $T_i$ has obtained an exclusive-mode lock *Exclusive Lock* -- If a transaction $T_i$ has obtained an exclusive-mode lock
(denoted by $X$) on item Q, then Ti can both read and write $Q$. (denoted by $X$) on item Q, then Ti can both read and write $Q$.
==== 2-phased lock protocol ==== 2-phased lock protocol
*The Two-Phase Locking (2PL)* Protocol is a concurrency control method used in *The Two-Phase Locking (2PL)* Protocol is a concurrency control method used in
database systems to ensure serializability of transactions. The protocol database systems to ensure serializability of transactions. The protocol
involves two distinct phases: *Locking Phase (Growing Phase):* A transaction involves two distinct phases: *Locking Phase (Growing Phase):* A transaction may
may acquire locks but cannot release any locks. During this phase, the acquire locks but cannot release any locks. During this phase, the transaction
transaction continues to lock all the resources (data items) it needs to continues to lock all the resources (data items) it needs to execute.
execute. \ *Unlocking Phase (Shrinking Phase):* The transaction releases locks
and cannot acquire any new ones. Once a transaction starts releasing locks, it
moves into this phase until all locks are released.
==== Problems of locks *Unlocking Phase (Shrinking Phase):* The transaction releases locks and cannot
acquire any new ones. Once a transaction starts releasing locks, it moves into
this phase until all locks are released.
*Deadlock* is a condition where two or more tasks are each waiting for the ==== Problems of locks
other to release a resource, or more than two tasks are waiting for resources
in a circular chain. *Deadlock* is a condition where two or more tasks are each waiting for the other
\ *Starvation* (also known as indefinite blocking) occurs to release a resource, or more than two tasks are waiting for resources in a
when a process or thread is perpetually denied necessary resources to process circular chain.
its work. Unlike deadlock, where everything halts, starvation only affects some
while others progress. *Starvation* (also known as indefinite blocking) occurs when a process or thread
is perpetually denied necessary resources to process its work. Unlike deadlock,
where everything halts, starvation only affects some while others progress.
=== Timestamp-based === Timestamp-based
*Timestamp Assignment:* Each transaction is given a unique timestamp when it *Timestamp Assignment:* Each transaction is given a unique timestamp when it
starts. This timestamp determines the transaction's temporal order relative to starts. This timestamp determines the transaction's temporal order relative to
others. *Read Rule:* A transaction can read an object if the last write others. *Read Rule:* A transaction can read an object if the last write occurred
occurred by a transaction with an earlier or the same timestamp. *Write Rule:* by a transaction with an earlier or the same timestamp. *Write Rule:* A
A transaction can write to an object if the last read and the last write transaction can write to an object if the last read and the last write occurred
occurred by transactions with earlier or the same timestamps. by transactions with earlier or the same timestamps.
=== Validation-based === Validation-based
Assumes that conflicts are rare and checks for them only at the end of a transaction. Assumes that conflicts are rare and checks for them only at the end of a
*Working Phase:* Transactions execute without acquiring locks, recording all transaction. *Working Phase:* Transactions execute without acquiring locks,
data reads and writes. *Validation Phase:* Before committing, each transaction recording all data reads and writes. *Validation Phase:* Before committing, each
must validate that no other transactions have modified the data it accessed. transaction must validate that no other transactions have modified the data it
*Commit Phase:* If the validation is successful, the transaction commits and accessed. *Commit Phase:* If the validation is successful, the transaction
applies its changes. If not, it rolls back and may be restarted. commits and applies its changes. If not, it rolls back and may be restarted.
// === Version isolation // === Version isolation
@ -388,39 +385,42 @@ In the *redo phase*, the system replays updates of all transactions by scanning
the log forward from the last checkpoint. The specific steps taken while the log forward from the last checkpoint. The specific steps taken while
scanning the log are as follows: scanning the log are as follows:
+ The list of transactions to be rolled back, undo-list, is initially set to the list + The list of transactions to be rolled back, undo-list, is initially set to the
$L$ in the $<#[checkpoint] L>$ log record. list
+ Whenever a normal log record of the form $<T_i, X_j, V_1, V_2>$, or a redo- $L$ in the $<#[checkpoint] L>$ log record.
only log record of the form $<T_i, X_j, V_2>$ is encountered, the operation is + Whenever a normal log record of the form $<T_i, X_j, V_1, V_2>$, or a redo- only
log record of the form $<T_i, X_j, V_2>$ is encountered, the operation is
redone; that is, the value $V_2$ is written to data item $X_j$. redone; that is, the value $V_2$ is written to data item $X_j$.
+ Whenever a log record of the form $<T_i #[start]>$ is found, $T_i$ is added to + Whenever a log record of the form $<T_i #[start]>$ is found, $T_i$ is added to
undo-list. undo-list.
+ Whenever a log record of the form $<T_i #[abort]>$ or $<T_i #[commit]>$ is found, + Whenever a log record of the form $<T_i #[abort]>$ or $<T_i #[commit]>$ is
found,
$T_i$ is removed from undo-list. $T_i$ is removed from undo-list.
At the end of the redo phase, undo-list contains the list of all transactions that At the end of the redo phase, undo-list contains the list of all transactions
are incomplete, that is, they neither committed nor completed rollback before the crash. that are incomplete, that is, they neither committed nor completed rollback
\ In the *undo phase*, the system rolls back all transactions in the undo-list. before the crash.
It performs rollback by scanning the log backward from the end:
In the *undo phase*, the system rolls back all transactions in the undo-list. It
performs rollback by scanning the log backward from the end:
+ Whenever it finds a log record belonging to a transaction in the undo-list, it + Whenever it finds a log record belonging to a transaction in the undo-list, it
performs undo actions just as if the log record had been found during the performs undo actions just as if the log record had been found during the
rollback of a failed transaction. rollback of a failed transaction.
+ When the system finds a $<T_i #[start]>$ log record for a transaction $T_i$ in undo- + When the system finds a $<T_i #[start]>$ log record for a transaction $T_i$ in
list, it writes a $<T_i #[abort]>$ log record to the log and removes $T_i$ from undo- undo- list, it writes a $<T_i #[abort]>$ log record to the log and removes $T_i$ from
list. undo- list.
+ The undo phase terminates once undo-list becomes empty, that is, the system + The undo phase terminates once undo-list becomes empty, that is, the system has
has found $<T_i #[start]>$ log records for all transactions that were initially found $<T_i #[start]>$ log records for all transactions that were initially in
in undo-list. undo-list.
== Log types == Log types
- $<T_i, X_j, V_1, V_2>$ -- an update log record, indicating that transaction - $<T_i, X_j, V_1, V_2>$ -- an update log record, indicating that transaction
$T_i$ has performed a write on data item $X_j$. $X_j$ had value $V_1$ before $T_i$ has performed a write on data item $X_j$. $X_j$ had value $V_1$ before the
the write and has value $V_2$ after the write; write and has value $V_2$ after the write;
- $<T_i #[start]>$ -- $T_i$ has started; - $<T_i #[start]>$ -- $T_i$ has started;
- $<T_i #[commit]>$ -- $T_i$ has committed; - $<T_i #[commit]>$ -- $T_i$ has committed;
- $<T_i #[abort]>$ -- $T_i$ has aborted; - $<T_i #[abort]>$ -- $T_i$ has aborted;
- $<#[checkpoint] {T_0, T_1, dots, T_n}>$ -- a checkpoint with a list of active - $<#[checkpoint] {T_0, T_1, dots, T_n}>$ -- a checkpoint with a list of active
transactions at the moment of checkpoint. transactions at the moment of checkpoint.