-
Notifications
You must be signed in to change notification settings - Fork 133
feat: support read lock mode for R/W transactions #4010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ | |
import com.google.spanner.v1.ReadRequest.OrderBy; | ||
import com.google.spanner.v1.RequestOptions.Priority; | ||
import com.google.spanner.v1.TransactionOptions.IsolationLevel; | ||
import com.google.spanner.v1.TransactionOptions.ReadWrite.ReadLockMode; | ||
import java.io.Serializable; | ||
import java.time.Duration; | ||
import java.util.Objects; | ||
|
@@ -155,9 +156,44 @@ public static TransactionOption commitStats() { | |
* process in the commit phase (when any needed locks are acquired). The validation process | ||
* succeeds only if there are no conflicting committed transactions (that committed mutations to | ||
* the read data at a commit timestamp after the read timestamp). | ||
* | ||
* @deprecated Use {@link Options#readLockMode(ReadLockMode)} instead. | ||
*/ | ||
@Deprecated | ||
public static TransactionOption optimisticLock() { | ||
return OPTIMISTIC_LOCK_OPTION; | ||
return Options.readLockMode(ReadLockMode.OPTIMISTIC); | ||
} | ||
|
||
/** | ||
* Returns a {@link TransactionOption} to set the desired {@link ReadLockMode} for a read-write | ||
* transaction. | ||
* | ||
* <p>This option controls the locking behavior for read operations and queries within a | ||
* read-write transaction. It works in conjunction with the transaction's {@link IsolationLevel}. | ||
* | ||
* <ul> | ||
* <li>{@link ReadLockMode#PESSIMISTIC}: Read locks are acquired immediately on read. This mode | ||
* primarily applies to transactions with {@code SERIALIZABLE} isolation. | ||
shobhitsg marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this sentence mean?
That it has no effect on RR? Or that it has a strong effect on Serializable? What I'm aiming at is: Can we make this more concrete? As a customer, I would have no idea what this means. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated the doc. Currently, we do not allow setting the |
||
* <li>{@link ReadLockMode#OPTIMISTIC}: Locks for reads within the transaction are not acquired | ||
* on read. Instead the locks are acquired on a commit to validate that read/queried data | ||
shobhitsg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* has not changed since the transaction started. If a conflict is detected, the transaction | ||
* will fail. This mode applies to transactions with {@code SERIALIZABLE} isolation. | ||
* <li>{@link ReadLockMode#READ_LOCK_MODE_UNSPECIFIED}: This is the default if no mode is set. | ||
* The locking behavior depends on the isolation level: | ||
* <ul> | ||
* <li>For {@code REPEATABLE_READ} isolation: Locking semantics default to {@code | ||
* OPTIMISTIC}. However, validation checks at commit are only performed for reads | ||
* within queries using {@code SELECT FOR UPDATE}, statements with {@code | ||
shobhitsg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* LOCK_SCANNED_RANGES} hints, or DML statements. <br> | ||
shobhitsg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* Note: It is an error to explicitly set {@code ReadLockMode} when the isolation | ||
* level is {@code REPEATABLE_READ}. | ||
* <li>For all other isolation levels: If the read lock mode is not set, it defaults to | ||
* {@code PESSIMISTIC} locking. | ||
* </ul> | ||
* </ul> | ||
*/ | ||
public static TransactionOption readLockMode(ReadLockMode readLockMode) { | ||
return new ReadLockModeOption(readLockMode); | ||
} | ||
|
||
/** | ||
|
@@ -367,16 +403,6 @@ void appendToOptions(Options options) { | |
} | ||
} | ||
|
||
/** Option to request Optimistic Concurrency Control for read/write transactions. */ | ||
static final class OptimisticLockOption extends InternalOption implements TransactionOption { | ||
@Override | ||
void appendToOptions(Options options) { | ||
options.withOptimisticLock = true; | ||
} | ||
} | ||
|
||
static final OptimisticLockOption OPTIMISTIC_LOCK_OPTION = new OptimisticLockOption(); | ||
|
||
/** Option to request the transaction to be excluded from change streams. */ | ||
static final class ExcludeTxnFromChangeStreamsOption extends InternalOption | ||
implements UpdateTransactionOption { | ||
|
@@ -516,6 +542,20 @@ void appendToOptions(Options options) { | |
} | ||
} | ||
|
||
/** Option to set read lock mode for read/write transactions. */ | ||
static final class ReadLockModeOption extends InternalOption implements TransactionOption { | ||
private final ReadLockMode readLockMode; | ||
|
||
public ReadLockModeOption(ReadLockMode readLockMode) { | ||
this.readLockMode = readLockMode; | ||
} | ||
|
||
@Override | ||
void appendToOptions(Options options) { | ||
options.readLockMode = readLockMode; | ||
} | ||
} | ||
|
||
private boolean withCommitStats; | ||
|
||
private Duration maxCommitDelay; | ||
|
@@ -530,7 +570,6 @@ void appendToOptions(Options options) { | |
private String tag; | ||
private String etag; | ||
private Boolean validateOnly; | ||
private Boolean withOptimisticLock; | ||
private Boolean withExcludeTxnFromChangeStreams; | ||
private Boolean dataBoostEnabled; | ||
private DirectedReadOptions directedReadOptions; | ||
|
@@ -540,6 +579,7 @@ void appendToOptions(Options options) { | |
private Boolean lastStatement; | ||
private IsolationLevel isolationLevel; | ||
private XGoogSpannerRequestId reqId; | ||
private ReadLockMode readLockMode; | ||
|
||
// Construction is via factory methods below. | ||
private Options() {} | ||
|
@@ -644,10 +684,6 @@ Boolean validateOnly() { | |
return validateOnly; | ||
} | ||
|
||
Boolean withOptimisticLock() { | ||
return withOptimisticLock; | ||
} | ||
|
||
Boolean withExcludeTxnFromChangeStreams() { | ||
return withExcludeTxnFromChangeStreams; | ||
} | ||
|
@@ -704,6 +740,10 @@ IsolationLevel isolationLevel() { | |
return isolationLevel; | ||
} | ||
|
||
ReadLockMode readLockMode() { | ||
return readLockMode; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
StringBuilder b = new StringBuilder(); | ||
|
@@ -740,9 +780,6 @@ public String toString() { | |
if (validateOnly != null) { | ||
b.append("validateOnly: ").append(validateOnly).append(' '); | ||
} | ||
if (withOptimisticLock != null) { | ||
b.append("withOptimisticLock: ").append(withOptimisticLock).append(' '); | ||
} | ||
if (withExcludeTxnFromChangeStreams != null) { | ||
b.append("withExcludeTxnFromChangeStreams: ") | ||
.append(withExcludeTxnFromChangeStreams) | ||
|
@@ -772,6 +809,9 @@ public String toString() { | |
if (reqId != null) { | ||
b.append("requestId: ").append(reqId.toString()); | ||
} | ||
if (readLockMode != null) { | ||
b.append("readLockMode: ").append(readLockMode).append(' '); | ||
} | ||
return b.toString(); | ||
} | ||
|
||
|
@@ -807,15 +847,15 @@ public boolean equals(Object o) { | |
&& Objects.equals(tag(), that.tag()) | ||
&& Objects.equals(etag(), that.etag()) | ||
&& Objects.equals(validateOnly(), that.validateOnly()) | ||
&& Objects.equals(withOptimisticLock(), that.withOptimisticLock()) | ||
&& Objects.equals(withExcludeTxnFromChangeStreams(), that.withExcludeTxnFromChangeStreams()) | ||
&& Objects.equals(dataBoostEnabled(), that.dataBoostEnabled()) | ||
&& Objects.equals(directedReadOptions(), that.directedReadOptions()) | ||
&& Objects.equals(orderBy(), that.orderBy()) | ||
&& Objects.equals(isLastStatement(), that.isLastStatement()) | ||
&& Objects.equals(lockHint(), that.lockHint()) | ||
&& Objects.equals(isolationLevel(), that.isolationLevel()) | ||
&& Objects.equals(reqId(), that.reqId()); | ||
&& Objects.equals(reqId(), that.reqId()) | ||
&& Objects.equals(readLockMode(), that.readLockMode()); | ||
} | ||
|
||
@Override | ||
|
@@ -857,9 +897,6 @@ public int hashCode() { | |
if (validateOnly != null) { | ||
result = 31 * result + validateOnly.hashCode(); | ||
} | ||
if (withOptimisticLock != null) { | ||
result = 31 * result + withOptimisticLock.hashCode(); | ||
} | ||
if (withExcludeTxnFromChangeStreams != null) { | ||
result = 31 * result + withExcludeTxnFromChangeStreams.hashCode(); | ||
} | ||
|
@@ -887,6 +924,9 @@ public int hashCode() { | |
if (reqId != null) { | ||
result = 31 * result + reqId.hashCode(); | ||
} | ||
if (readLockMode != null) { | ||
result = 31 * result + readLockMode.hashCode(); | ||
} | ||
return result; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also add some generic advice when to use what? (Otherwise, I think that most users will automatically assume that 'optimistic is always better than pessimistic').
So for example something like this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially added some advice to the document but removed it because I felt it deserved a more detailed explanation in a separate link. I've now added the advice back, and we will update this document once the RR + Pessimistic feature is launched.