Time | T1 | T2 | T3 |
---|---|---|---|
| | +W(A) |
+ + | + |
| | ++ | W(A) |
+ + |
| | ++ | W(B) |
+ + |
| | ++ | + | W(B) |
+
↓ | +W(B) |
+ + | + |
T1's write to A "happens before" T2's write
+T2's write to B "happens before" T3's write
+T2's write to B "happens before" T1's write
+ +Cycle! No equivalent serial schedule!
+An acyclic "Happens Before" or Dependency Graph is conflict serializable.
+Goal: Enforce acyclic conflict graphs
+Observation: Conflicts only occur on accesses to the same object
+Idea: When a second transaction tries to access an object, require the first to COMMIT or ABORT first.
+Idea: When a second transaction tries to access an object, require the first to COMMIT or ABORT first agree to never create more conflicts.
Create one lock for each object.
+Each transaction operates in two "phases".
+In practice, the release phase happens all at once at the end
+Time | T1 | T2 | T3 |
---|---|---|---|
| | ++ | + | R(C) |
+
| | +W(A) |
+ + | + |
| | ++ | R(B) |
+ + |
| | ++ | + | W(C) |
+
| | +W(B) |
+ + | + |
↓ | ++ | W(C) |
+ + |
Conflict Serializable.
+Can 2PL create this schedule?
+L(...) | Acquire (Lock) |
U(...) | Release (Unlock) |
2PL can create this schedule
+Time | T1 | T2 | T3 |
---|---|---|---|
| | |
+ + | + | L(C) R(C) |
+
| | |
+ L(A) W(A) |
+ + | + |
| | |
+ + | L(B) R(B) |
+ + |
| | |
+ + | + | W(C) U(C) |
+
| | |
+ + | L(C) U(B) |
+ + |
| | |
+ L(B) W(B) |
+ + | + |
| ↓ |
+ + | L(C) W(C) |
+ + |
Observation 1: Read-Read conflicts aren't a problem
+ +Solution: Reader/Writer Locks
+ +(Any number of readers or one writer can hold the lock)
+ +... also called Shared (S)/Exclusive (X) locks
+Requested | |||
S | X | ||
H e l d |
+ S | +Allow | +Block |
X | +Block | +Block |
Which should be used?
+Observation 1: Too coarse locking prevents concurrency
+Observation 2: Too fine locking is slow
+Idea 1: Separate locks for tables, pages, rows, cells.
+Doesn't Work! Need to use the same lock for all conflicts.
+Idea 2: Organize locks hierarchically. Set a flag in the parent when a child is locked.
+Before acquiring a descendant lock, a thread must first intent-acquire all ancestors (top-down) +
Requested | |||||
+ | IS | +IX | +S | +X | +|
H e l d |
+ None | +Allow | +Allow | +Allow | +Allow |
IS | +Allow | +Allow | +Allow | +Block | |
IX | +Allow | +Allow | +Block | +Block | |
S | +Allow | +Block | +Allow | +Block | |
X | +Block | +Block | +Block | +Block |
Time | T1 | T2 | T3 | T4 |
---|---|---|---|---|
<%=op[:bar]%> | + <% (1..4).each do |i| + if i == op[:t] then %> +<%= op[:op] %> |
+ <% else %>
+ + <% end end %> + |
A cycle of transactions waiting on each other's locks
+Approach 1 Detect deadlock situations as they occur and abort deadlocked transaction
+Approach 2 Anticipate deadlock situations before they happen
+Time | T1 | T2 | T3 | T4 |
---|---|---|---|---|
<%=op[:bar]%> | + <% (1..4).each do |i| + if i == op[:t] then %> +<%= op[:op].gsub('', '') %> |
+ <% else %>
+ + <% end end %> + |
A cycle is a set of transactions that will never finish
+Periodically check for cycles in the waits-for graph
+Abort transactions until the cycle is broken
+Idea 2: Create an order over the locks (give each a #)
Only allow locks to be acquired in sequence order
Time | T1 | T2 | T3 | T4 |
---|---|---|---|---|
<%=op[:bar]%> | + <% (1..4).each do |i| + if i == op[:t] then %> +<%= if op[:t] != 3 then op[:op].gsub('', '') else op[:op] end %> |
+ <% else %>
+ + <% end end %> + |
T3 can't acquire X(A) because it already has S(C)
+Idea 2.B: Acquire all locks at the start of a transaction.
+Pro: No deadlocks... ever.
+Pro: No (expensive) cycle detection.
+Con: Not all transactions are supported
or transactions need to know all necessary locks in advance.
Idea 3: False positive deadlock detections are ok
+Trivial solution: Timeouts
+... but how long should the timeout be?
+Alternative: Create an order over transactions
Only allow a transaction to block on "older" transactions
"Wait-Die"
+"Wait-Wound"
+