How to avoid LOST UPDATE in database

A lost update will happen when there are many transactions which concurrently modify on one column

This is one transaction that update amount for the person which has id = 1
func updatePersonWith(db *sqlx.DB, ctx context.Context, extra int) {
   context := ctx
   tx, err := db.BeginTx(context, &sql.TxOptions{Isolation: sql.LevelDefault})
   if err != nil {
      log.Infof("update1 Create tx fail %v", err)
   }
   rows, err := tx.Query(`select amount from person where id=$1`, 1)
   if err != nil {
      log.Infof("Exec sql failed %v", err)
   }

   var amount int   for rows.Next() {
      if err := rows.Err(); err != nil {
         log.Infof("Exect sql get amount1 failed %v", err)
      }

      err = rows.Scan(&amount)
      log.Infof("amount= %d", amount)
      break
   }
   rows.Close()

   //just for test
   time.Sleep(time.Second*3)
   amount = amount + extra   log.Infof("amount= %d with extra= %d", amount,extra)

   _, err = tx.ExecContext(context, `update person SET amount =$2 where id=$1`, 1, amount)
   if err != nil {
      log.Infof("update1 failed %v with extra %d", err,extra)

   }

   //time.Sleep(time.Second*5)   log.Infof("Starting commit with extra %d",extra)
   err = tx.Commit()
   if err != nil {
      log.Infof("commit failed %v with extra %d", err,extra)
   }
}

func selectAmout(db *sqlx.DB, ctx context.Context){

   context := ctx
   tx, err := db.BeginTx(context, &sql.TxOptions{Isolation: sql.LevelDefault})
   if err != nil {
      log.Infof("select amount tx fail %v", err)
   }
   rows, err := tx.Query(`select amount from person where id=$1`, 1)
   if err != nil {
      log.Infof("Exect sql failed %v", err)
   }



   var amount int   for rows.Next() {
      if err := rows.Err(); err != nil {
         log.Infof("Exect sql get amount1 failed %v", err)
      }

      err = rows.Scan(&amount)
      log.Infof("amount= %d", amount)
      break
   }
   rows.Close()
}

func update(db *sqlx.DB, ctx context.Context) {
   go update1(db, ctx, 10)
   go update1(db, ctx, 20)

   // just wait for complete
   time.Sleep(time.Second * 10)
   selectAmout(db, ctx)
}


------log----------------

INFO[0000] amount= 130
INFO[0000] amount= 130
INFO[0003] amount= 150 with extra= 20
INFO[0003] Starting commit with extra 20
INFO[0003] amount= 140 with extra= 10
INFO[0003] Starting commit with extra 10


INFO[0010] amount= 140  


==> both transactions was succeeded => result should be 160, but it lost update with extra = 20

Because
func updatePersonWith(db *sqlx.DB, ctx context.Context, extra int) {
   context := ctx
   tx, err := db.BeginTx(context, &sql.TxOptions{Isolation: sql.LevelDefault})


So what is the solution for this case


func update1(db *sqlx.DB, ctx context.Context, extra int) {
   context := ctx
   tx, err := db.BeginTx(context, &sql.TxOptions{Isolation: sql.LevelSerializable})




-------------log-----------------

INFO[0000] amount= 140                                  
INFO[0000] amount= 140                                  
INFO[0003] amount= 160 with extra= 20                   
INFO[0003] amount= 150 with extra= 10                   
INFO[0003] Starting commit with extra 10                
INFO[0003] update1 failed pq: could not serialize access due to concurrent update with extra 20 
INFO[0003] Starting commit with extra 20                
INFO[0003] commit failed pq: Could not complete operation in a failed transaction with extra 20 
INFO[0010] amount= 150   

==>one transaction with extra=20 failed => result should be 150
===> in this case, we should try to excute the transaction with extra =20 again


PS: You can avoid lost update by use versioning for another column in your table then
your table should have a column called VERSION and type is INT


then the Update command: "update table_name Set amount = $1 where id = $2 and version =$3"
with $3 = current version



Comments

Popular posts from this blog

Fixing the DeepSpeed Import Error While Fine-Tuning the Qwen Model

Amazon Linux 2023 - User data configuration for launch templates to connect to the EKS cluster

How to create ISM policy and rotate logs in opensearch