Transactions
This page discusses transactions in Stardog.
Page Contents
ACID Transactions
This section discusses Stardog’s transactional semantics and guarantees. Generally speaking, Stardog supports ACID transactions. A good overview of transaction isolation in the context of J2EE is this beginner’s guide.
Atomicity
Databases may guarantee atomicity – groups of database actions (i.e. mutations) are irreducible and indivisible: either all the changes happen or none of them happens. Stardog’s transacted writes are atomic.
Stardog does not support nested transactions.
Consistency
Data stored should be valid according to the data model (in this case, RDF) and to the guarantees offered by the database, as well as to any application-specific integrity constraints that may exist. Stardog’s transactions are guaranteed not to violate integrity constraints during execution. A transaction that would leave a database in an inconsistent or invalid state is aborted.
See the Data Quality Constraints section for a more detailed consideration of Stardog’s integrity constraint mechanism.
Isolation
A Stardog connection will run in SNAPSHOT
isolation level if it has not started an explicit transaction. The isolation level can be set to SNAPSHOT
or SERIALIZABLE
with the database configuration option transaction.isolation
. In either of these modes, uncommitted changes will only be visible to the connection that made the changes: no other connection can see those values before they are committed. Thus, “dirty reads” can never occur. Additionally, a transaction will only see changes which were committed before the transaction began, so there are no “non-repeatable reads”.
SNAPSHOT
isolation does suffer from the write skew anomaly, which poses a problem when operating under external logical constraints. We illustrate this with the following example, where the database initially has two triples :a :val 100
and :b :val 50
, and the application imposes the constraint that the total can never be less than 0.
Example of write-skew anomaly:
Time | Connection 1 | Connection 2 | Connection 3 |
---|---|---|---|
0 | *BEGIN TX* | *BEGIN TX* | |
1 | SELECT ?val {:a :val ?val} <= 100 | SELECT ?val {:b :val ?val} <= 50 | |
2 | INSERT {:a :val 0} | INSERT {:b :val 0} | |
3 | *COMMIT* | *COMMIT* | |
4 | *BEGIN TX* | ||
5 | SELECT ?val {?a :val ?val} <= 0 | ||
6 | SELECT ?val {?b :val ?val} <= 0 |
At the end of this scenario, Connection 1 believes the state of the database to be :a :val 0
and :b :val 50
, so the constraint is not violated. Similarly, Connection 2 believes the state of the database to be :a :val 100
and :b :val 0
, which also does not violate the constraint. However, Connection 3 sees :a :val 0
and :b :val 0
, which violates the logical constraint.
No locks are taken, nor is any conflict resolution performed, for concurrent transactions at the SNAPSHOT
isolation level. If there are conflicting changes, the transaction with the highest commit timestamp (functionally, the transaction which committed “last”) will be the result held in the database. This may yield unexpected results since every transaction reads from a snapshot that was created at the time its transaction started.
Consider the following query being executed by two concurrent threads at the SNAPSHOT
isolation level against a database having the triple :counter :val 1
initially:
INSERT {
:counter :val ?newValue
}
DELETE {
:counter :val ?oldValue
}
WHERE {
:counter :val ?oldValue
BIND (?oldValue+1 AS ?newValue)
}
Since each transaction will read the current value from its snapshot, it is possible that both transactions will read the value 1
and insert the value 2
even though we expect the final value to be 3
.
The SERIALIZABLE
isolation level can be used to avoid these situations. In SERIALIZABLE
mode, an exclusive lock needs to be acquired before a transaction begins. This ensures concurrent updates cannot interfere with each other. However, update throughput will decrease since only one transaction can run at a time.
Durability
By default, Stardog’s transacted writes are durable, and no other actions are required.
Begin Transaction
To begin a transaction:
-
Use the
tx begin
command:$ stardog tx begin myDatabase 65e93faa-b26a-43ab-b39d-c5392eb93859
A transaction id (e.g.
65e93faa-b26a-43ab-b39d-c5392eb93859
) will be returned. This id can be provided to many CLI commands to indicate that the operation should take place within the transaction. For example, see the--tx
option forquery execute
. curl -u username:password -X POST http://localhost:5820/myDatabase/transaction/begin
Output :
d2c91a85-5b33-4993-8b67-7d95db815377
A transaction id is returned which can then be provided to multiple different operations. For example, see the API to generate a SHACL a report within a transaction.
try (Connection aConn = ConnectionConfiguration .to("myDatabase") // the name of the db to connect to .server("http://localhost:5820") //server url .credentials("admin", "admin") // credentials to use while connecting .connect()) { // begin transaction aConn.begin(); // perform other operations and eventually commit/rollback the current transaction }
It is required to begin a new transaction before making any updates through the connection. See
com.complexible.stardog.api.Connection#begin
for more information.
Commit Transaction
To commit a transaction:
-
Provide the transaction id to the
tx commit
command to be committed:stardog tx commit myDatabase <tx-id>
-
Provide the transaction id for the transaction to be committed.
curl -u username:password -X POST http://localhost:5820/myDatabase/transaction/commit/{txid}
See the HTTP API for more information.
try (Connection aConn = ConnectionConfiguration .to("myDatabase") // the name of the db to connect to .server("http://localhost:5820") //server url .credentials("admin", "admin") // credentials to use while connecting .connect()) { // begin transaction aConn.begin(); //create some data and add to a named graph Collection<Statement> aGraph = Collections.singleton( Values.statement(Values.iri("urn:subj"), Values.iri("urn:pred"), Values.iri("urn:obj"))); Resource aContext = Values.iri("urn:graph"); aConn.add().graph(aGraph, aContext); //commit the current transaction aConn.commit() }
See
com.complexible.stardog.api.Connection#commit
for more information.
Rollback Transaction
To rollback a transaction:
-
Provide the transaction id to the
tx rollback
command to be committed:stardog tx rollback myDatabase <tx-id>
-
Provide the transaction id for the transaction to be rolled back.
curl -u username:password -X POST http://localhost:5820/myDatabase/transaction/rollback/{txid}
See the HTTP API for more information.
try (Connection aConn = ConnectionConfiguration .to("myDatabase") // the name of the db to connect to .server("http://localhost:5820") //server url .credentials("admin", "admin") // credentials to use while connecting .connect()) { // begin transaction aConn.begin(); //create some data and add to a named graph Collection<Statement> aGraph = Collections.singleton( Values.statement(Values.iri("urn:subj"), Values.iri("urn:pred"), Values.iri("urn:obj"))); Resource aContext = Values.iri("urn:graph"); aConn.add().graph(aGraph, aContext); //rollback the current transaction aConn.rollback() }
See
com.complexible.stardog.api.Connection#rollback
for more information.
List All Open Transactions on a Database
Only superusers can see all open transactions on a database. If a non-superuser attempts to list all open transactions, they will only see the ones they have opened. To list all open transactions on a database:
stardog tx list myDatabase
Sample Output:
+--------------------------------------+-------+-------------------------------+ | Tx ID | User | Start Time | +--------------------------------------+-------+-------------------------------+ | 4923b094-e1c4-4804-8e3a-d0e8e36134c8 | admin | 2021-07-21T16:03:24.894-04:00 | +--------------------------------------+-------+-------------------------------+ 1 transaction(s)
curl -u username:password -X GET http://localhost:5820/myDatabase/transaction
See the HTTP API for more information.
try (Connection aConn = ConnectionConfiguration .to("myDatabase") // the name of the db to connect to .server("http://localhost:5820") //server url .credentials("admin", "admin") // credentials to use while connecting .connect()) { for (TxInfo tx : aConn.transactions()){ System.out.println("TX ID: " + tx.getID()); System.out.println("USER: " + tx.getUser()); System.out.println("START TIME: " + tx.getStartTime()); } }
See
com.complexible.stardog.api.Connection#transactions
for more information.