[+/-]
This section documents the methods that must be implemented to add support for transactions to a storage engine.
Please note that transaction management can be complicated and
involve methods such as row versioning and redo logs, which is
beyond the scope of this document. Instead coverage is limited to
a description of required methods and not their implementation.
For examples of implementation, please see
ha_innodb.cc.
Transactions are not explicitly started on the storage engine
level, but are instead implicitly started through calls to
either start_stmt() or
external_lock(). If the preceding methods are
called and a transaction already exists the transaction is not
replaced.
The storage engine stores transaction information in
per-connection memory and also registers the transaction in the
MySQL server to allow the server to later issue
COMMIT and ROLLBACK
operations.
As operations are performed the storage engine will have to implement some form of versioning or logging to permit a rollback of all operations executed within the transaction.
After work is completed, the MySQL server will call either the
commit() method or the
rollback() method defined in the storage
engine's handlerton.
[+/-]
A transaction is started by the storage engine in response to a
call to either the start_stmt() or
external_lock() methods.
If there is no active transaction, the storage engine must start
a new transaction and register the transaction with the MySQL
server so that ROLLBACK or
COMMIT can later be called.
The first method call that can start a transaction is the
[custom-engine.html#custom-engine-transactions-starting-start-stmt
start_stmt()] method.
The following example shows how a storage engine could register a transaction:
int my_handler::start_stmt(THD *thd, thr_lock_type lock_type)
{
int error= 0;
my_txn *txn= (my_txn *) thd->ha_data[my_handler_hton.slot];
if (txn == NULL)
{
thd->ha_data[my_handler_hton.slot]= txn= new my_txn;
}
if (txn->stmt == NULL && !(error= txn->tx_begin()))
{
txn->stmt= txn->new_savepoint();
trans_register_ha(thd, FALSE, &my_handler_hton);
}
return error;
}
THD is the current client connection. It
holds state relevant data for the current client, such as
identity, network connection and other per-connection data.
thd->ha_data[my_handler_hton.slot] is a
pointer in thd to the connection-specific
data of this storage engine. In this example we use it to
store the transaction context.
An additional example of implementing
start_stmt() can be found in
ha_innodb.cc.
MySQL calls
[custom-engine.html#custom-engine-api-reference-external_lock
handler::external_lock()] for every table
it is going to use at the beginning of every statement. Thus,
if a table is touched for the first time, it implicitly starts
a transaction.
Note that because of pre-locking, all tables that can be
potentially used between the beginning and the end of a
statement are locked before the statement execution begins and
handler::external_lock() is called for all
these tables. That is, if an INSERT fires a
trigger, which calls a stored procedure, that invokes a stored
method, and so forth, all tables used in the trigger, stored
procedure, method, etc., are locked in the beginning of the
INSERT. Additionally, if there's a
construct like
IF .. use one table ELSE .. use another table
both tables will be locked.
Also, if a user calls LOCK TABLES, MySQL
will call handler::external_lock only once.
In this case, MySQL will call
handler::start_stmt() at the beginning of
the statement.
The following example shows how a storage engine can start a transaction and take locking requests into account:
int my_handler::external_lock(THD *thd, int lock_type)
{
int error= 0;
my_txn *txn= (my_txn *) thd->ha_data[my_handler_hton.slot];
if (txn == NULL)
{
thd->ha_data[my_handler_hton.slot]= txn= new my_txn;
}
if (lock_type != F_UNLCK)
{
bool all_tx= 0;
if (txn->lock_count == 0)
{
txn->lock_count= 1;
txn->tx_isolation= thd->variables.tx_isolation;
all_tx= test(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN | OPTION_TABLE_LOCK));
}
if (all_tx)
{
txn->tx_begin();
trans_register_ha(thd, TRUE, &my_handler_hton);
}
else
if (txn->stmt == 0)
{
txn->stmt= txn->new_savepoint();
trans_register_ha(thd, FALSE, &my_handler_hton);
}
}
else
{
if (txn->stmt != NULL)
{
/* Commit the transaction if we're in auto-commit mode */
my_handler_commit(thd, FALSE);
delete txn->stmt; // delete savepoint
txn->stmt= NULL;
}
}
return error;
}
Every storage engine must call
trans_register_ha() every time it starts a
transaction. The trans_register_ha() method
registers a transaction with the MySQL server to allow for
future COMMIT and
ROLLBACK calls.
An additional example of implementing
external_lock() can be found in
ha_innodb.cc.
Of the two major transactional operations,
ROLLBACK is the more complicated to
implement. All operations that occurred during the transaction
must be reversed so that all rows are unchanged from before the
transaction began.
To support ROLLBACK, create a method that
matches this definition:
int (*rollback)(THD *thd, bool all);
The method name is then listed in the
rollback (thirteenth) entry of
[custom-engine.html#custom-engine-handlerton the handlerton].
The THD parameter is used to identify the
transaction that needs to be rolled back, while the
bool all parameter indicates whether the
entire transaction should be rolled back or just the last
statement.
Details of implementing a ROLLBACK operation
will vary by storage engine. Examples can be found in
ha_innodb.cc.
During a commit operation, all changes made during a transaction are made permanent and a rollback operation is not possible after that. Depending on the transaction isolation used, this may be the first time such changes are visible to other threads.
To support COMMIT, create a method that
matches this definition:
int (*commit)(THD *thd, bool all);
The method name is then listed in the commit
(twelfth) entry of [custom-engine.html#custom-engine-handlerton
the handlerton].
The THD parameter is used to identify the
transaction that needs to be committed, while the bool
all parameter indicates if this is a full transaction
commit or just the end of a statement that is part of the
transaction.
Details of implementing a COMMIT operation
will vary by storage engine. Examples can be found in
ha_innodb.cc.
If the server is in auto-commit mode, the storage engine should
automatically commit all read-only statements such as
SELECT.
In a storage engine, "auto-committing" works by counting locks.
Increment the count for every call to
external_lock(), decrement when
external_lock() is called with an argument of
F_UNLCK. When the count drops to zero,
trigger a commit.
[+/-]
First, the implementor should know how many bytes are required to store savepoint information. This should be a fixed size, preferably not large as the MySQL server will allocate space to store the savepoint for all storage engines with each named savepoint.
The implementor should store the data in the space preallocated by mysqld - and use the contents from the preallocated space for rollback or release savepoint operations.
When a COMMIT or ROLLBACK
operation occurs (with bool all set to
true), all savepoints are assumed to be
released. If the storage engine allocates resources for
savepoints, it should free them.
The following handlerton elements need to be implemented to support savepoints (elements 7,9,10,11):
uint savepoint_offset; int (*savepoint_set)(THD *thd, void *sv); int (*savepoint_rollback)(THD *thd, void *sv); int (*savepoint_release)(THD *thd, void *sv);
The seventh element of the handlerton is the
savepoint_offset:
uint savepoint_offset;
The savepoint_offset must be initialized
statically to the size of the needed memory to store
per-savepoint information.
The savepoint_set() method is called
whenever a user issues the SAVEPOINT
statement:
int (*savepoint_set)(THD *thd, void *sv);
The *sv parameter points to an
uninitialized storage area of the size defined by
savepoint_offset.
When savepoint_set() is called, the storage
engine needs to store savepoint information into
sv so that the server can later roll back
the transaction to the savepoint or release the savepoint
resources.
The savepoint_rollback() method is called
whenever a user issues the ROLLBACK TO
SAVEPOINT statement:
int (*savepoint_rollback) (THD *thd, void *sv);
The *sv parameter points to the storage
area that was previously passed to the
savepoint_set() method.
The savepoint_release() method is called
whenever a user issues the RELEASE
SAVEPOINT statement:
int (*savepoint_release) (THD *thd, void *sv);
The *sv parameter points to the storage
area that was previously passed to the
savepoint_set() method.
