SoFunction
Updated on 2025-03-01

Whether the transaction is really ended when the Go transaction is aborted

background

I recently saw an article and really lamented the author's insight. I may make such mistakes during development, so I must learn more and practice more. The problem is that when you submit a transaction, if there are other businesses in the middle, the operation will be cancelled, and then will the transaction be closed?

Practice

When the server interacts with the database, we may use transactions to ensure the idempotence of data for some scenarios. For example, the basic operation process in an updated scenario is as follows:

  • Start database transactions
  • Get data records with ID
  • Confirm whether the update operation can be performed
  • If you can update the operation, update the record
  • Submit transactions
  • If an error is encountered, roll back the transaction

When obtaining data from the database, other services or programs can be prevented from operating on this record by locking the row, such as using select ... for update Get data and lock the record. The following is a simple way to use transaction manipulation data:

func (user *UserResp) DeleteUser(ctx , id string) error {
	tx, err := (ctx, nil)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			()
		}
	}()
	result, err := (id)
	if err != nil {
		return err
	}
	if  {
		return nil
	}
	if err = (id); err != nil {
		return err
	}
	if err = (); err != nil {
		return err
	}
	return nil
}

Transaction description

From the above source code, it seems that there is no problem. When performing related operations, the transaction will be completed after deleting data from db normally. However, if a problem occurs during the period, an error will be returned, which will trigger a rollback operation.

But there is another point that needs to be noted. When the data is obtained, it will end the operation when it is judged that the record has been deleted. However, the end operation does not perform a release operation on the transaction, which will cause a big problem: when the amount of data is large, all subsequent requests will time out, resulting in all requests that cannot complete the operation.

(err)

You can look at the source code of the transaction implementation. Whether in rollback or commit, releaseConn will release the connection. Therefore, in the previous example, the defer function only calls rollback when an error occurs. If it does not commit or rollback, it will cause the transaction to be active and will always hold the transaction. When its request comes back, it will cause the transaction to time out.

Optimization solution

There is a very simple solution to solve the problem, which is to roll back under the condition of judgment error. You can also directly change the defer function to a rollback transaction. After committing the transaction, you will not perform any operation.

	defer func() {
			()
	}()

But no changes are made as well, and then rollback is done only if an error occurs, which may affect the readability of the code. In the method that starts the transaction, you will see that in the method that calls beginDC, there is a context service context for rolling back the transaction. So there is another solution to cancel the context to end the transaction and release the lock.

// Code snippet in method beginDCctx, cancel := (ctx)
	tx = &Tx{
		db:                 db,
		dc:                 dc,
		releaseConn:        release,
		txi:                txi,
		cancel:             cancel,
		keepConnOnRollback: keepConnOnRollback,
		ctx:                ctx,
	}
	go ()

So we can directly use the cancel context method. We can first create a new cancel context. If there is no rollback or commit, the final execution of cancel will notify the transaction that has completed and then the transaction will be closed.

func (user *UserResp) DeleteUser(ctx , id string) error {
ctx, cancel := (ctx)
    defer cancel()
    tx, err := (ctx, nil)
    if err != nil {
        return nil, err
    }
    defer func() {
        if err != nil {
            ()
        }
    }()
......
}

Summarize

Therefore, when using transaction processing services, you must pay attention to business logic. If certain conditions occur in the business logic, if the database is not operated, remember to close the transaction when ending this business processing. Either close the transaction or commit the transaction regardless of whether it is processed or not. This may be a small problem, but if you don't pay attention to it in the trading scenario, it may cause a big problem.

References:

  • /a-billion-d…

The above is the detailed content of whether the transaction is really ended when the Go transaction is aborted. For more information about the end of the Go transaction, please pay attention to my other related articles!