Skip to content

Commit 1391d94

Browse files
committed
docs: update README and latency guide
1 parent 10328da commit 1391d94

File tree

4 files changed

+102
-8
lines changed

4 files changed

+102
-8
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ See [Supported Connection Properties](documentation/connection_properties.md) fo
109109
supported connection properties.
110110

111111
#### Commonly Used Properties
112+
- default_isolation_level (String): Spanner supports isolation levels REPEATABLE_READ or SERIALIZABLE. SERIALIZABLE is the default. Using isolation level REPEATABLE_READ improves performance by reducing the amount of locks that are taken by transactions that execute a large number of queries in read/write transactions. See https://cloud.google.com/spanner/docs/isolation-levels for more information on the supported isolation levels in Spanner.
112113
- credentials (String): URL for the credentials file to use for the connection. If you do not specify any credentials at all, the default credentials of the environment as returned by `GoogleCredentials#getApplicationDefault()` is used. Example: `jdbc:cloudspanner:/projects/my-project/instances/my-instance/databases/my-db;credentials=/path/to/credentials.json`
113114
- autocommit (boolean): Sets the initial autocommit mode for the connection. Default is true.
114115
- readonly (boolean): Sets the initial readonly mode for the connection. Default is false.

documentation/latency-debugging-guide.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,43 @@ queries and transactions and to determine whether transactions or requests are b
55
addition, all metrics described in [Latency points in a Spanner request](https://cloud.google.com/spanner/docs/latency-points)
66
are also collected by the JDBC driver and can be used for debugging.
77

8+
## Isolation Level
9+
10+
A common reason for high latency in read/write transactions is lock contention. Spanner by default
11+
uses isolation level `SERIALIZABLE`. This causes all queries in read/write transactions to take
12+
locks for all rows that are scanned by a query. Using isolation level `REPEATABLE_READ` reduces the
13+
number of locks that are taken during a read/write transaction, and can significantly improve
14+
performance for applications that execute many and/or large queries in read/write transactions.
15+
16+
Enable isolation level `REPEATABLE_READ` by default for all transactions that are executed by the
17+
JDBC driver by setting the `default_isolation_level` connection property like this in the connection
18+
URL:
19+
20+
```java
21+
String projectId = "my-project";
22+
String instanceId = "my-instance";
23+
String databaseId = "my-database";
24+
String isolationLevel = "REPEATABLE_READ";
25+
26+
try (Connection connection =
27+
DriverManager.getConnection(
28+
String.format(
29+
"jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?default_isolation_level=%s",
30+
projectId, instanceId, databaseId, isolationLevel))) {
31+
try (Statement statement = connection.createStatement()) {
32+
try (ResultSet rs = statement.executeQuery("SELECT CURRENT_TIMESTAMP()")) {
33+
while (rs.next()) {
34+
System.out.printf(
35+
"Connected to Cloud Spanner at [%s]%n", rs.getTimestamp(1).toString());
36+
}
37+
}
38+
}
39+
}
40+
```
41+
42+
See https://cloud.google.com/spanner/docs/isolation-levels for more information on the supported
43+
isolation levels in Spanner.
44+
845
## Configuration
946

1047
You can configure the OpenTelemetry instance that should be used in two ways:

samples/snippets/pom.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,6 @@
2323
<!-- [START spanner-jdbc_install_with_bom] -->
2424
<dependencyManagement>
2525
<dependencies>
26-
<dependency>
27-
<groupId>com.google.cloud</groupId>
28-
<artifactId>google-cloud-spanner-bom</artifactId>
29-
<version>6.91.1</version>
30-
<type>pom</type>
31-
<scope>import</scope>
32-
</dependency>
3326
<dependency>
3427
<groupId>com.google.cloud</groupId>
3528
<artifactId>libraries-bom</artifactId>
@@ -44,7 +37,6 @@
4437
<dependency>
4538
<groupId>com.google.cloud</groupId>
4639
<artifactId>google-cloud-spanner-jdbc</artifactId>
47-
<version>2.29.1</version>
4840
<exclusions>
4941
<exclusion>
5042
<groupId>com.google.api.grpc</groupId>

src/test/java/com/google/cloud/spanner/jdbc/JdbcTransactionOptionsTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@
2727
import com.google.cloud.spanner.connection.AbstractMockServerTest;
2828
import com.google.cloud.spanner.connection.SpannerPool;
2929
import com.google.spanner.v1.CommitRequest;
30+
import com.google.spanner.v1.ExecuteSqlRequest;
31+
import com.google.spanner.v1.TransactionOptions.IsolationLevel;
32+
import java.sql.Connection;
3033
import java.sql.DriverManager;
3134
import java.sql.ResultSet;
3235
import java.sql.SQLException;
3336
import java.time.Duration;
37+
import java.util.Arrays;
38+
import java.util.stream.Collectors;
3439
import org.junit.After;
3540
import org.junit.Test;
3641
import org.junit.runner.RunWith;
@@ -138,4 +143,63 @@ public void testMaxCommitDelay() throws SQLException {
138143
assertEquals(Duration.ofMillis(50).toNanos(), request.getMaxCommitDelay().getNanos());
139144
}
140145
}
146+
147+
@Test
148+
public void testDefaultIsolationLevel() throws SQLException {
149+
for (IsolationLevel isolationLevel :
150+
Arrays.stream(IsolationLevel.values())
151+
.filter(level -> !level.equals(IsolationLevel.UNRECOGNIZED))
152+
.collect(Collectors.toList())) {
153+
try (java.sql.Connection connection =
154+
DriverManager.getConnection(
155+
"jdbc:" + getBaseUrl() + ";default_isolation_level=" + isolationLevel.name())) {
156+
connection.setAutoCommit(false);
157+
try (ResultSet resultSet =
158+
connection.createStatement().executeQuery(SELECT1_STATEMENT.getSql())) {
159+
while (resultSet.next()) {
160+
// ignore
161+
}
162+
}
163+
connection.commit();
164+
assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
165+
ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0);
166+
assertTrue(request.hasTransaction());
167+
assertTrue(request.getTransaction().hasBegin());
168+
assertTrue(request.getTransaction().getBegin().hasReadWrite());
169+
assertEquals(isolationLevel, request.getTransaction().getBegin().getIsolationLevel());
170+
assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
171+
172+
mockSpanner.clearRequests();
173+
}
174+
}
175+
}
176+
177+
@Test
178+
public void testSetIsolationLevel() throws SQLException {
179+
try (java.sql.Connection connection = createJdbcConnection()) {
180+
connection.setAutoCommit(false);
181+
for (int isolationLevel :
182+
new int[] {Connection.TRANSACTION_REPEATABLE_READ, Connection.TRANSACTION_SERIALIZABLE}) {
183+
connection.setTransactionIsolation(isolationLevel);
184+
try (ResultSet resultSet =
185+
connection.createStatement().executeQuery(SELECT1_STATEMENT.getSql())) {
186+
while (resultSet.next()) {
187+
// ignore
188+
}
189+
}
190+
connection.commit();
191+
assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
192+
ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0);
193+
assertTrue(request.hasTransaction());
194+
assertTrue(request.getTransaction().hasBegin());
195+
assertTrue(request.getTransaction().getBegin().hasReadWrite());
196+
assertEquals(
197+
IsolationLevelConverter.convertToSpanner(isolationLevel),
198+
request.getTransaction().getBegin().getIsolationLevel());
199+
assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
200+
201+
mockSpanner.clearRequests();
202+
}
203+
}
204+
}
141205
}

0 commit comments

Comments
 (0)