Transactions

Transactions ensure data integrity by grouping multiple database operations into a single atomic unit. In CQL, all operations within a transaction either succeed together or are rolled back together, maintaining consistency even when errors occur.


What are Transactions?

A transaction is a sequence of database operations that are treated as a single unit of work. Transactions provide the ACID properties:

  • Atomicity: All operations succeed or all are rolled back

  • Consistency: Database remains in a valid state

  • Isolation: Transactions don't interfere with each other

  • Durability: Committed changes are permanent

Common Use Case: Transferring money between bank accounts requires both debiting one account and crediting another. If either operation fails, both must be rolled back to prevent data corruption.


Basic Transaction Usage

CQL provides transaction support through the Transactional module. Include it in your models to access the transaction class method:

class User
  include CQL::ActiveRecord::Model(Int32)
  include CQL::ActiveRecord::Transactional  # Enables transaction support

  db_context AppDB, :users

  property id : Int32?
  property name : String
  property email : String
  property balance : Float64 = 0.0

  def initialize(@name : String, @email : String, @balance : Float64 = 0.0)
  end
end

Simple Transaction Block

Use the Model.transaction method to wrap operations in a transaction:

Handling Rollbacks

Transactions automatically roll back if an exception is raised:

Manual Rollback with DB::Rollback

Use DB::Rollback to roll back without raising an unhandled exception:

Explicit Transaction Control

You can manually control transaction state using the yielded transaction object:


Nested Transactions (Savepoints)

CQL supports nested transactions using database savepoints. This allows you to create sub-transactions within a larger transaction:

Nested Transaction Examples

Inner Success, Outer Success

Inner Rollback, Outer Success

Inner Exception, Full Rollback


Practical Example: Bank Transfer

Here's a complete example showing how to use transactions for a bank transfer operation:


Error Handling

Catching Transaction Errors

Transaction without Exception Handling

If you don't want exceptions to be raised, check operation success manually:


Best Practices

Keep Transactions Short

Validate Before Transactions

Handle Concurrent Access

Nested Transaction Guidelines


Performance Considerations

  • Transaction Duration: Keep transactions as short as possible to reduce lock contention

  • Batch Operations: Group related operations together in a single transaction

  • Avoid External Calls: Don't make HTTP requests or other I/O operations within transactions

  • Index Usage: Ensure proper indexes exist on columns used in transaction queries

  • Connection Pooling: CQL automatically manages database connections within transactions


Common Pitfalls

  1. Long-running transactions: Avoid operations that take a long time within transaction blocks

  2. Nested transaction confusion: Remember that DB::Rollback only affects the current transaction level

  3. Forgetting to reload: When dealing with concurrent access, reload records within transactions

  4. Exception handling: Don't catch and ignore exceptions within transactions unless you explicitly roll back


The CQL transaction system provides a robust foundation for maintaining data integrity in your Crystal applications. Use transactions whenever you need to ensure that multiple database operations succeed or fail together.

Last updated

Was this helpful?