Skip to the content.

Trasactions

Why transactions and what it is

As mentioned in the book, there are so many types of failure:

Transaction is a mechanism to simplify these issues. Transaction is a group of several read and writes as a logical unit.

Meaning of ACID

Atomicity

Operations are grouped into one unit, they either succeed(commit) or fail(abort) entirely and no halfway failure.

Consisitency

A certain statement about the data is always true. E.g. You credit and debit of your bank acount is always balanced.

Isolation

Concurrently executing transactions are isolated from each other.

Durability

Any data has been committed successfully will not be lost.

Single object operation and Multiple objects operation

Sometimes we just need to update a single object in database, like updating a cell of a table. But sometimes we need to update multiple objects with a single transaction, like multiple tables need to be updated, multiple documents need to be updated and multiple indexes need to be updated.

Weak isolation level (Nonserializability)

Read committed

No dirty reads

no-dirty-reads

With dirty writes with-dirty-writes

Implementing read committed

Snapshot isolation

Read committed could not solve the read skew issue as below:

read-skew

So, we need a stronger isolation level to prevent this from happening, which snapshot isolution is commonly used.

Each transaction reads from a consistent of the snapshot, so that it sees all data that was committed at the start of current transaction. Even the data is updated during the transaction, it alwasys sees the old data from a PIT.

Writes never block reads, and reads never block writes. So snapshot isolation has a good performance.

Implementing snapshot isolation

snapshot-isolation-workflow

Visibility rules:

Implementation comparision between read committed and snapshot isolation

How indexes work in multi-version database

There are two options:

Problems with writes

Dirty writes

The race condition could result in dirty writes if one operation writes to a data which has not been committed by another operation. row-level-lock could solve this problem.

Data lost

Both read committed and snapshot isolation does not prevent data loss when two writes meet the non-dirty write condition, but one overwrites the result from another one. E.g. two users edit the wiki page at the same time (read-modify-write cycle).

data-loss-example

Important: The lock and compare-and-set assumes the situation on single node, but when it comes to leaderless replication case or multi-leader replication case, those two won’t work unless using distributed lock.

Write skew

From following diagram, it demostrate the write skew in the case of doctor night shifts. We need at least one doctor on the shift, but write skew could cause zero doctor in the end.

write-skew-example

Pattern of which causes the write skew:

The write made by application code could change the result of first query step, which is called phantom

Possible solutions for write skew

Strong isolcation level (Serializability)

It guarantees that even though transactions may execute in parallel, the end result is the same as if they had executed one at a time.

Serial execution

Executing transactions in serial order might have low performance and bottleneck. So transaction must be small and fast. And dataset needs to be able to fit in memory. In the case of doctor shifts, we could encapsulate the query and update into one stored procedure to let them be executed in sequence.

Locking

Two-Phase Locking

Reads do not block reads, it only blocks writes; Writes block writes and reads. This is the key difference from snapshot isolation which has the notion of “readers never block writers and writers never block readers”

Implementing two phase locking

The biggest downside is the performance.

Predicate lock

It works as putting locks on multiple objects(rows) that match some search condition.

Implementing predicate lock

Two phase lock + predicate lock could prevent all kinds of write skew and race conditions. But the biggest downside is performance.

Index range locks

A simplified approximation of predicate locking by making it match a greater set of objects. For example, if you have a predicate lock for bookings of room 123 between noon and 1 p.m., you can approximate it by locking bookings for room 123 at any time, or you can approximate it by locking all rooms (not just room 123) between noon and 1 p.m. So that we could attach the shared lock to this index value.

Serializable snapshot isolation (SSI)

Detect stale MVCC reads

Keep track of the versions of data being ignored on read, when a transaction wants to commit, databasae checks if any of the ingored versions of data have now been committed. If so, current transaction needs to be aborted.

detect-mvcc-reads

Detect writes affect prior reads

Keep track of the trasactions which is affected by a commit. When the affected transaction wants to commit, we abort it.

detect-writes-affect-prior-reads