Implementing database transactions with gorm in Golang

Golang Mar 8, 2023

Transactions are crucial in maintaining data consistency and integrity in database management. A transaction is a set of operations executed as a single unit of work, making sure that either all or none of the operations are completed. In this blog post, we will explore how to implement transactions in the Go programming language.

1. DB transactions

a. What are DB transactions

A database transaction is a set of database operations that are executed as a single unit. Its purpose is to ensure the consistency and integrity of the database, even in the case of system errors or failures. It is a unit of work that has to be completed as a whole; otherwise, the entire transaction will be rolled back to its previous state.

It usually involves starting by sending begin transaction command to the database. Statements are executed in a series that manipulate data in the DB. If any step fails in the process, previously executed statements are also rolled back. If all steps are completed, the transaction is committed which makes the changes permanent. After this, the transaction is terminated with an end transaction command to the DB.

b. Key characteristics

The critical characteristics of database transactions are Atomicity, Consistency, Isolation, and Durability (ACID). Atomicity means the transaction is unbreakable and must be completely executed or discarded. Consistency means that a transaction transforms the database from one valid state to another. Isolation means that the impact of one transaction is separated from other transactions. Durability requires that the results of a transaction persist even in the case of system failure.

2. Advantages and disadvantages

a. Advantages

Using database transactions helps in improved performance by grouping multiple database operations into a single unit. It reduces the number of interactions with the database, resulting in faster response times.

If any action within the transaction fails, the entire transaction is rolled back. This leads to undoing all changes made so far during that transaction. This ensures that the database remains in a consistent state and that any partial updates or incomplete changes are discarded. This provides a consistent and predictable way to manage errors as well.

These also allow for multiple transactions to be executed concurrently without affecting each other, improving the system's overall performance.

In summary, database transactions play a crucial role in maintaining the consistency and integrity of a database by grouping multiple operations into a single and indivisible unit. The ACID properties ensure that transactions are executed reliably and consistently.

b. Disadvantages

Transactions can limit the scope of scalability by locking resources such as rows or tables causing contention and reducing concurrency. These can consume significant resources such as memory and CPU time, thus limiting the number of concurrent transactions that can be processed. These also usually increase the code complexity, making it more difficult to maintain and troubleshoot.  These require additional processing and this can cause longer response times and decreased performance, especially for high-volume applications. If the service needs to maintain a record of all changes made, this would be problematic as any changes made would be lost. If we need to maintain this record, transactions should not be implemented. Instead, we can maintain records with a failed status.

3. DB transactions in Go with SQL DB

Go's database/sql package provides a convenient way to interact with relational databases, but it does not offer direct support for transactions. Therefore, to work with transactions in Go, we need to use the database/sql package along with an appropriate database driver.

a. Libraries

There are multiple libraries providing DB interaction in Go. Here we are using the gorm (Go-ORM) package for our example. It is the most popular ORM package in the Go ecosystem. It is built on the database/sql package and has several necessary ORM functionalities. To interact with DB, it uses structs as models and also has the option to build raw SQL queries for unsupported operations.

b. How to implement

To initiate a transaction, we can use the Begin() method provided by the database driver. This method starts a new transaction and returns a Tx object that represents the transaction. With the Tx object, we can execute database operations like Save(), Rollback(), and Commit().

c. Example:

We have the below functions in main.go file on github.com/jinzhu/gorm package defined like this:

We can create user_dao.go into the package daos and define functions like this:

Gorm supports calling transaction control functions (commit / rollback) directly. We can create user_service.go in the package services and define these functions with this implementation:

There is another approach available that is simpler but has a convention that it will auto rollback if an error is returned and commit if nil is returned. This can be implemented by disabling it during initialization if it is not required. One can gain about 30%+ performance improvement after that[1]

In this example, we start a transaction by calling db.Begin() and store the result in a Tx object. We then execute two statements, which insert and update data in the users' table. If any of these statements fail, we roll back the transaction using tx.Rollback(). Finally, we commit the transaction using tx.Commit().

d. Error handling

When working with transactions in Go, it's important to handle errors properly to maintain the integrity of the database. If any of the operations in a transaction fail, rolling back the transaction is necessary to keep the database in a consistent state.

Summary

In summary, implementing transactions in Go is a fairly straightforward process. By using the database/sql package along with an appropriate database driver, we can easily start, execute, rollback, and commit transactions. Proper error-handling practices are necessary to ensure that our transactions are executed correctly and that the integrity of our database is maintained.

Join us

Scalability, reliability, and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at careers.india@halodoc.com.

About Halodoc

Halodoc is the number 1 all-around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek, and many more. We recently closed our Series C round and In total have raised around USD$180 million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalized for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.

7. References

[1]. Gorm documentation - Transactions