当前位置: 首页 > article >正文

MySql操作指南5--事务与并发控制

数据库事务是保障数据一致性和可靠性的重要手段,并发控制则在多用户环境下确保数据的正确性。风云今天详细探讨数据库事务的管理、并发访问的最佳实践,乐观锁与悲观锁的应用,以及Golang 中的事务实现、并发访问的最佳实践,通过合理设计事务和锁机制,可以在高并发场景下有效保障数据的一致性和系统性能。

1、数据库事务管理

事务是一个操作序列,具有以下四个特性(ACID):

原子性(Atomicity):事务中的操作要么全部完成,要么全部回滚。

一致性(Consistency):事务前后,数据库保持一致性状态。

隔离性(Isolation):事务之间相互隔离,避免相互干扰。

持久性(Durability):事务完成后,其对数据库的更改永久生效。

2、在 Golang 中实现事务

 示例:事务的基本操作

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	// 数据库连接
	dsn := "user:password@tcp(127.0.0.1:3306)/testdb" // dsn格式

	db, err := sql.Open("mysql", dsn) // 打开数据库连接

	if err != nil { // 如果连接失败,则输出错误信息并退出程序
		log.Fatal(err)
	}

	defer db.Close() // 关闭数据库连接

	// 开启事务
	tx, err := db.Begin() // 开启事务

	if err != nil {
		log.Fatal(err)
	}

	// 执行操作
	_, err = tx.Exec("INSERT INTO accounts (id, balance) VALUES (?, ?)", 1, 100) // 执行操作

	if err != nil { // 如果执行失败,则回滚事务并输出错误信息并退出程序
		tx.Rollback() // 回滚事务

		log.Fatal(err)
	}

	_, err = tx.Exec("UPDATE accounts SET balance = balance - 50 WHERE id = ?", 1) // 执行操作

	if err != nil { // 如果执行失败,则回滚事务并输出错误信息并退出程序
		tx.Rollback() // 回滚事务

		log.Fatal(err)
	}

	// 提交事务
	err = tx.Commit()

	if err != nil { // 如果提交失败,则回滚事务并输出错误信息并退出程序

		log.Fatal(err)

	}

	fmt.Println("Transaction completed successfully")
}

3 事务的隔离级别

MySQL 提供四种事务隔离级别:

  1. 读未提交(Read Uncommitted):事务可以读取其他未提交事务的数据,可能导致脏读。
  2. 读已提交(Read Committed):事务只能读取已提交的数据,避免脏读。
  3. 可重复读(Repeatable Read):同一事务中多次读取数据一致,避免不可重复读(MySQL 默认级别)。
  4. 可串行化(Serializable):完全隔离,事务逐一执行,避免幻读。

设置事务隔离级别

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

在 Golang 中可以通过 SET 语句设置隔离级别:

tx.Exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")

4、并发访问的最佳实践

在并发环境中,事务之间可能发生以下冲突:

  • 脏读(Dirty Read):一个事务读取了另一个未提交事务的数据。
  • 不可重复读(Non-repeatable Read):同一事务中多次读取结果不一致。
  • 幻读(Phantom Read):事务中新增或删除的记录导致结果变化。

以下是一些避免冲突的最佳实践:

5、使用事务管理并发

事务能够隔离并发访问,保证数据一致性。结合合适的隔离级别,可以避免大多数并发问题。

使用锁机制

行锁和表锁

  • 行锁:锁定某一行数据,适用于高并发场景。
  • 表锁:锁定整个表,避免全表修改导致的数据不一致问题。

LOCK TABLES accounts WRITE;UPDATE accounts SET balance = balance - 50 WHERE id = 1;

UNLOCK TABLES;

在 Golang 中,利用事务和条件语句实现锁机制:

tx.Exec("SELECT * FROM accounts WHERE id = ? FOR UPDATE", 1)

6、乐观锁与悲观锁

6.1 乐观锁

乐观锁通过检查版本号或时间戳,确保并发访问不会冲突。

乐观锁实现

SELECT version FROM accounts WHERE id = 1;UPDATE accounts SET balance = balance - 50, version = version + 1 WHERE id = 1 AND version = ?;

Golang 示例

func updateBalanceWithOptimisticLock(db *sql.DB, id int, amount int) error { // 乐观锁
	var version int

	err := db.QueryRow("SELECT version FROM accounts WHERE id = ?", id).Scan(&version) // 查询账户的版本号

	if err != nil { // 如果查询失败,则输出错误信息并退出程序
		return err
	}

	result, err := db.Exec("UPDATE accounts SET balance = balance - ?, version = version + 1 WHERE id = ? AND version = ?", amount, id, version) // 更新账户的余额和版本号

	if err != nil {
		return err
	}

	rowsAffected, err := result.RowsAffected() // 获取受影响的行数

	if err != nil {
		return err
	}

	if rowsAffected == 0 { // 如果受影响的行数为0,则说明版本号不一致,返回错误信息
		return fmt.Errorf("transaction conflict, try again")
	}

	return nil
}

6.2 悲观锁

悲观锁假定冲突必然发生,在操作前加锁以避免冲突。

悲观锁实现

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

Golang 示例

func updateBalanceWithPessimisticLock(db *sql.DB, id int, amount int) error { // 悲观锁
	tx, err := db.Begin() // 开启事务

	if err != nil { // 如果开启事务失败,则输出错误信息并退出程序
		return err
	}

	_, err = tx.Exec("SELECT * FROM accounts WHERE id = ? FOR UPDATE", id) // 使用FOR UPDATE关键字锁定记录

	if err != nil { // 如果锁定记录失败,则输出错误信息并退出程序
		tx.Rollback() // 回滚事务

		return err
	}

	_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, id) // 更新账户的余额

	if err != nil { // 如果更新记录失败,则回滚事务并返回错误
		tx.Rollback() // 回滚事务

		return err
	}

	return tx.Commit() // 提交事务并返回nil
}

7、应用场景

  • 电子商务系统:订单支付与库存扣减必须确保一致性。
  • 银行转账系统:多账户之间的资金流转需要事务保证。
  • 多人协作编辑系统:利用乐观锁解决并发编辑冲突。

http://www.kler.cn/a/509366.html

相关文章:

  • 如何在linux系统上完成定时开机和更新github端口的任务
  • Zookeeper 数据迁移实战:基础环境搭建与高效迁移方案全览
  • 联通用户管理系统(一)
  • PyTorch使用教程(6)一文讲清楚torch.nn和torch.nn.functional的区别
  • 建造者模式(或者称为生成器(构建器)模式)
  • 【从零开始使用系列】StyleGAN2:开源图像生成网络——环境搭建与基础使用篇(附大量测试图)
  • 2.1 使用kubectl部署一个简单的nginx-pod
  • 内存与缓存:保姆级图文详解
  • 上位机工作感想-2024年工作总结和来年计划
  • PyCharm中解决依赖冲突
  • ESP8266-01S、手机、STM32连接
  • [Computer Vision]实验一:图像的基本操作
  • 挖掘机检测数据集,准确识别率91.0%,4327张原始图片,支持YOLO,COCO JSON,PASICAL VOC XML等多种格式标注
  • Java中的深拷贝与浅拷贝探究(利用反射+泛型实现深拷贝工具类)
  • iOS - Objective-C 底层实现中的哈希表
  • UiPath发送嵌入图片HTML邮件
  • BGP联盟
  • 窗口门狗实验(WWDG)实验【学习】
  • 【高阶数据结构】位图(BitMap)
  • OSPF - 路由过滤的几种方法
  • C++/QT环境下图像在窗口下等比例渲染绘制
  • OpenEuler学习笔记(一):常见命令
  • UDP 单播、多播、广播:原理、实践
  • 【C++笔记】红黑树封装map和set深度剖析
  • 高性能、并发安全的 Go 嵌入式缓存库 如何使用?
  • 浅谈云计算22 | Kubernetes容器编排引擎