mirror of https://gitee.com/bigwinds/arangodb
341 lines
9.1 KiB
Plaintext
341 lines
9.1 KiB
Plaintext
!CHAPTER Transaction invocation
|
|
|
|
ArangoDB transactions are different from transactions in SQL.
|
|
|
|
In SQL, transactions are started with explicit *BEGIN* or *START TRANSACTION*
|
|
command. Following any series of data retrieval or modification operations, an
|
|
SQL transaction is finished with a *COMMIT* command, or rolled back with a
|
|
*ROLLBACK* command. There may be client/server communication between the start
|
|
and the commit/rollback of an SQL transaction.
|
|
|
|
In ArangoDB, a transaction is always a server-side operation, and is executed
|
|
on the server in one go, without any client interaction. All operations to be
|
|
executed inside a transaction need to be known by the server when the transaction
|
|
is started.
|
|
|
|
There are no individual *BEGIN*, *COMMIT* or *ROLLBACK* transaction commands
|
|
in ArangoDB. Instead, a transaction in ArangoDB is started by providing a
|
|
description of the transaction to the *db._executeTransaction* JavaScript
|
|
function:
|
|
|
|
```js
|
|
db._executeTransaction(description);
|
|
```
|
|
|
|
This function will then automatically start a transaction, execute all required
|
|
data retrieval and/or modification operations, and at the end automatically
|
|
commit the transaction. If an error occurs during transaction execution, the
|
|
transaction is automatically aborted, and all changes are rolled back.
|
|
|
|
!SUBSECTION Execute transaction
|
|
<!-- js/server/modules/org/arangodb/arango-database.js -->
|
|
@startDocuBlock executeTransaction
|
|
|
|
!SUBSECTION Declaration of collections
|
|
|
|
All collections which are to participate in a transaction need to be declared
|
|
beforehand. This is a necessity to ensure proper locking and isolation.
|
|
|
|
Collections can be used in a transaction in write mode or in read-only mode.
|
|
|
|
If any data modification operations are to be executed, the collection must be
|
|
declared for use in write mode. The write mode allows modifying and reading data
|
|
from the collection during the transaction (i.e. the write mode includes the
|
|
read mode).
|
|
|
|
Contrary, using a collection in read-only mode will only allow performing
|
|
read operations on a collection. Any attempt to write into a collection used
|
|
in read-only mode will make the transaction fail.
|
|
|
|
Collections for a transaction are declared by providing them in the *collections*
|
|
attribute of the object passed to the *_executeTransaction* function. The
|
|
*collections* attribute has the sub-attributes *read* and *write*:
|
|
|
|
```js
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: [ "users", "logins" ],
|
|
read: [ "recommendations" ]
|
|
}
|
|
});
|
|
```
|
|
|
|
*read* and *write* are optional attributes, and only need to be specified if
|
|
the operations inside the transactions demand for it.
|
|
|
|
The contents of *read* or *write* can each be lists with collection names or a
|
|
single collection name (as a string):
|
|
|
|
```js
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: "users",
|
|
read: "recommendations"
|
|
}
|
|
});
|
|
```
|
|
|
|
**Note**: It is currently optional to specify collections for read-only access.
|
|
Even without specifying them, it is still possible to read from such collections
|
|
from within a transaction, but with relaxed isolation. Please refer to
|
|
[Transactions Locking](../Transactions/LockingAndIsolation.md) for more details.
|
|
|
|
!SUBSECTION Declaration of data modification and retrieval operations
|
|
|
|
All data modification and retrieval operations that are to be executed inside
|
|
the transaction need to be specified in a Javascript function, using the *action*
|
|
attribute:
|
|
|
|
```js
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: "users"
|
|
},
|
|
action: function () {
|
|
// all operations go here
|
|
}
|
|
});
|
|
```
|
|
|
|
Any valid Javascript code is allowed inside *action* but the code may only
|
|
access the collections declared in *collections*.
|
|
*action* may be a Javascript function as shown above, or a string representation
|
|
of a Javascript function:
|
|
|
|
```
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: "users"
|
|
},
|
|
action: "function () { doSomething(); }"
|
|
});
|
|
```
|
|
Please note that any operations specified in *action* will be executed on the
|
|
server, in a separate scope. Variables will be bound late. Accessing any JavaScript
|
|
variables defined on the client-side or in some other server context from inside
|
|
a transaction may not work.
|
|
Instead, any variables used inside *action* should be defined inside *action* itself:
|
|
|
|
```
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: "users"
|
|
},
|
|
action: function () {
|
|
var db = require(...).db;
|
|
db.users.save({ ... });
|
|
}
|
|
});
|
|
```
|
|
|
|
When the code inside the *action* attribute is executed, the transaction is
|
|
already started and all required locks have been acquired. When the code inside
|
|
the *action* attribute finishes, the transaction will automatically commit.
|
|
There is no explicit commit command.
|
|
|
|
To make a transaction abort and roll back all changes, an exception needs to
|
|
be thrown and not caught inside the transaction:
|
|
|
|
```js
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: "users"
|
|
},
|
|
action: function () {
|
|
var db = require("internal").db;
|
|
db.users.save({ _key: "hello" });
|
|
// will abort and roll back the transaction
|
|
throw "doh!";
|
|
}
|
|
});
|
|
```
|
|
|
|
There is no explicit abort or roll back command.
|
|
|
|
As mentioned earlier, a transaction will commit automatically when the end of
|
|
the *action* function is reached and no exception has been thrown. In this
|
|
case, the user can return any legal JavaScript value from the function:
|
|
|
|
```js
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: "users"
|
|
},
|
|
action: function () {
|
|
var db = require("internal").db;
|
|
db.users.save({ _key: "hello" });
|
|
// will commit the transaction and return the value "hello"
|
|
return "hello";
|
|
}
|
|
});
|
|
```
|
|
|
|
!SUBSECTION Examples
|
|
|
|
The first example will write 3 documents into a collection named *c1*.
|
|
The *c1* collection needs to be declared in the *write* attribute of the
|
|
*collections* attribute passed to the *executeTransaction* function.
|
|
|
|
The *action* attribute contains the actual transaction code to be executed.
|
|
This code contains all data modification operations (3 in this example).
|
|
|
|
```js
|
|
// setup
|
|
db._create("c1");
|
|
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: [ "c1" ]
|
|
},
|
|
action: function () {
|
|
var db = require("internal").db;
|
|
db.c1.save({ _key: "key1" });
|
|
db.c1.save({ _key: "key2" });
|
|
db.c1.save({ _key: "key3" });
|
|
}
|
|
});
|
|
db.c1.count(); // 3
|
|
```
|
|
|
|
|
|
|
|
|
|
Aborting the transaction by throwing an exception in the *action* function
|
|
will revert all changes, so as if the transaction never happened:
|
|
|
|
```js
|
|
// setup
|
|
db._create("c1");
|
|
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: [ "c1" ]
|
|
},
|
|
action: function () {
|
|
var db = require("internal").db;
|
|
db.c1.save({ _key: "key1" });
|
|
db.c1.count(); // 1
|
|
db.c1.save({ _key: "key2" });
|
|
db.c1.count(); // 2
|
|
throw "doh!";
|
|
}
|
|
});
|
|
|
|
db.c1.count(); // 0
|
|
```
|
|
|
|
The automatic rollback is also executed when an internal exception is thrown
|
|
at some point during transaction execution:
|
|
|
|
```js
|
|
// setup
|
|
db._create("c1");
|
|
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: [ "c1" ]
|
|
},
|
|
action: function () {
|
|
var db = require("internal").db;
|
|
db.c1.save({ _key: "key1" });
|
|
// will throw duplicate a key error, not explicitly requested by the user
|
|
db.c1.save({ _key: "key1" });
|
|
// we'll never get here...
|
|
}
|
|
});
|
|
|
|
db.c1.count(); // 0
|
|
```
|
|
|
|
As required by the *consistency* principle, aborting or rolling back a
|
|
transaction will also restore secondary indexes to the state at transaction
|
|
start. The following example using a cap constraint should illustrate that:
|
|
|
|
```js
|
|
// setup
|
|
db._create("c1");
|
|
|
|
// limit the number of documents to 3
|
|
db.c1.ensureCapConstraint(3);
|
|
|
|
// insert 3 documents
|
|
db.c1.save({ _key: "key1" });
|
|
db.c1.save({ _key: "key2" });
|
|
db.c1.save({ _key: "key3" });
|
|
|
|
// this will push out key1
|
|
// we now have these keys: [ "key2", "key3", "key4" ]
|
|
db.c1.save({ _key: "key4" });
|
|
|
|
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: [ "c1" ]
|
|
},
|
|
action: function () {
|
|
var db = require("internal").db;
|
|
// this will push out key2. we now have keys [ "key3", "key4", "key5" ]
|
|
db.c1.save({ _key: "key5" });
|
|
// will abort the transaction
|
|
throw "doh!"
|
|
}
|
|
});
|
|
|
|
// we now have these keys back: [ "key2", "key3", "key4" ]
|
|
```
|
|
|
|
!SUBSECTION Cross-collection transactions
|
|
|
|
There's also the possibility to run a transaction across multiple collections.
|
|
In this case, multiple collections need to be declared in the *collections*
|
|
attribute, e.g.:
|
|
|
|
```js
|
|
// setup
|
|
db._create("c1");
|
|
db._create("c2");
|
|
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: [ "c1", "c2" ]
|
|
},
|
|
action: function () {
|
|
var db = require("internal").db;
|
|
db.c1.save({ _key: "key1" });
|
|
db.c2.save({ _key: "key2" });
|
|
}
|
|
});
|
|
|
|
db.c1.count(); // 1
|
|
db.c2.count(); // 1
|
|
```
|
|
|
|
Again, throwing an exception from inside the *action* function will make the
|
|
transaction abort and roll back all changes in all collections:
|
|
|
|
```js
|
|
// setup
|
|
db._create("c1");
|
|
db._create("c2");
|
|
|
|
db._executeTransaction({
|
|
collections: {
|
|
write: [ "c1", "c2" ]
|
|
},
|
|
action: function () {
|
|
var db = require("internal").db;
|
|
for (var i = 0; i < 100; ++i) {
|
|
db.c1.save({ _key: "key" + i });
|
|
db.c2.save({ _key: "key" + i });
|
|
}
|
|
db.c1.count(); // 100
|
|
db.c2.count(); // 100
|
|
// abort
|
|
throw "doh!"
|
|
}
|
|
});
|
|
|
|
db.c1.count(); // 0
|
|
db.c2.count(); // 0
|
|
```
|