- Author(s): crazycs520
- Tracking Issue: pingcap#6840
This proposes a design of transaction savepoint.
Some Applications need to use transaction savepoint feature. And in order to be better compatible with some ORM frameworks, such as gorm.
- Support transaction savepoint syntax.
- Support transaction savepoint feature, and compatible with MySQL.
- Not for implementing savepoint on the entire database, but savepoint on transaction modifications.
SAVEPOINT
is used to set a savepoint of a specified name in the current transaction. If a savepoint with the same name already exists,
it will be deleted and a new savepoint with the same name will be set.
SAVEPOINT
need to record a snapshot of current transaction, when users execute ROLLBACK TO SAVEPOINT
statement, current transaction
will roll back to the specified savepoint.
Specifically, SAVEPOINT
needs to record the snapshot(checkpoint) of transaction MemDB
, and the snapshot of TransactionContext
.
The savepoint data struct is:
// SavepointRecord indicates a transaction's savepoint record.
type SavepointRecord struct {
// name is the name of the savepoint
Name string
// MemDBCheckpoint is the transaction's memdb checkpoint.
MemDBCheckpoint *tikv.MemDBCheckpoint
// TxnCtxSavepoint is the savepoint of TransactionContext
TxnCtxSavepoint TxnCtxNeedToRestore
}
// TxnCtxNeedToRestore stores transaction variables which need to be restored when rolling back to a savepoint.
type TxnCtxNeedToRestore struct {
// TableDeltaMap is used in the schema validator for DDL changes in one table not to block others.
// It's also used in the statistics updating.
// Note: for the partitioned table, it stores all the partition IDs.
TableDeltaMap map[int64]TableDelta
// pessimisticLockCache is the cache for pessimistic locked keys,
// The value never changes during the transaction.
pessimisticLockCache map[string][]byte
// CachedTables is not nil if the transaction write on cached table.
CachedTables map[int64]interface{}
}
// Transaction Savepoints will be stored in TransactionContext.
type TransactionContext struct {
...
Savepoints []SavepointRecord
}
The pseudocode about SAVEPOINT
implementation is following:
func executeSavepoint(savepointName string){
// If a savepoint with the same name already exists, it will be deleted.
deleteSavepoint(savepointName)
memDBCheckpoint := txn.GetMemDBCheckpoint()
txnCtxSavepoint := txnCtx.GetCurrentSavepoint()
txnCtx.Savepoints = append(txnCtx.Savepoints, SavepointRecord
Name: savepointName,
MemDBCheckpoint: memDBCheckpoint,
TxnCtxSavepoint: txnCtxSavepoint}
)
}
Warning Savepoint is not support when TiDB binlog enabled. Since binlog is not recommended after TiDB 4.0.
ROLLBACK TO SAVEPOINT
rolls back a transaction to the savepoint of a specified name and does not terminate the transaction.
Data changes made to the table data after the savepoint will be reverted in the rollback, and all savepoints after the savepoint are deleted.
Specifically, ROLLBACK TO SAVEPOINT
needs to roll back following information:
- Transaction
MemDB
- TransactionContext
The pseudocode about ROLLBACK TO SAVEPOINT
implementation is following:
func RollbackToSavepoint(savepointName string){
for idx, sp := range TxnCtx.Savepoints {
if savepointName == sp.Name {
TxnCtx.RollbackToSavepoint(sp.TxnCtxSavepoint)
txn.RollbackMemDBToCheckpoint(sp.MemDBCheckpoint)
// Delete the later savepoint.
TxnCtx.Savepoints = sessVars.TxnCtx.Savepoints[:idx+1]
return nil
}
}
return errSavepointNotExists
}
Also need to roll back the LazyTxn.stagingHandle
information. Since LazyTxn.stagingHandle
stores the checkpoint of
transaction MemDB
when start to execute the current SQL statement, so after ROLLBACK TO SAVEPOINT
execute,
LazyTxn.stagingHandle
should store the roll backed MemDB
checkpoint handle.
Warning
ROLLBACK TO SAVEPOINT
statements won't roll backAUTO_INCREMENT
andSEQUENCE
, which means the assigned AUTO_INCREMENT/SEQUENCE ID won't be reclaimed.
RELEASE SAVEPOINT
statement removes the named savepoint and all savepoints after this savepoint from the current transaction,
without committing or rolling back the current transaction. If the savepoint of the specified name does not exist, the following error is returned:
ERROR 1305 (42000): SAVEPOINT identifier does not exist
When ROLLBACK TO SAVEPOINT
is used to roll back a transaction to a specified savepoint, MySQL releases the locks
held only after the specified savepoint, while in TiDB pessimistic transaction, TiDB does not immediately release the locks
held after the specified savepoint. Instead, TiDB releases all locks when the transaction is committed or rolled back.