网络编程
位置:首页>> 网络编程>> 数据库>> Golang 使用gorm添加数据库排他锁,for update

Golang 使用gorm添加数据库排他锁,for update

作者:神以灵  发布时间:2024-01-29 09:34:53 

标签:Golang,gorm,数据库,排他锁

适用于先读后更新的数据竞争场景,且应该将加锁操作放到事务中,防止锁被自动释放,原因参考mysql doc


func UpdateUser(db *gorm.DB, id int64) error {
 tx := db.Begin()
 defer func() {
   if r := recover(); r != nil {
     tx.Rollback()
   }
 }()
 if err := tx.Error; err != nil {
   return err
 }
 user := User{}
 // 锁住指定 id 的 User 记录
 if err := tx.Set("gorm:query_option", "FOR UPDATE").First(&user, id).Error; err != nil {
   tx.Rollback()
   return err
 }
 // 更新操作...
 // commit事务,释放锁
 if err := tx.Commit().Error; err != nil {
   return err
 }
 return nil
}

sync.Mutex解法(效率较低):


var lock sync.Mutex
func UpdateUser(db *gorm.DB, id int64) error {
 lock.Lock()
 // 数据库操作...
 lock.Unlock()
 return nil
}

参考

doc

补充:Golang数据库编程之GORM模型定义与数据库迁移

在开发应用程序时,一般而言,我们是先设计好数据表,再使用开发语言建立对应的数据模型,不过,我们今天要讲的是一个逆向操作的过程,即如何通定义GORM框架的数据模型,然后再通过执行GROM框架编写的应用程序,用定义好数据模型在数据库中创建对应的数据表。

因此需要先讲讲怎么定义GORM的数据模型。

模型定义

一般来说,我们说GROM的模型定义,是指定义代表一个数据表的结构体(struct),然后我们可以使用GROM框架可以将结构体映射为相对应的关系数据库的数据表,或者查询数据表中的数据来填充结构体,如下所示,我们定义了一个名为Post的结构体。


type Post struct {
PostId int
Uid int
Title string
Content string
Type int
CreatedAt time.Time
UpdatedAt time.Time
}

创建好一个结构体只是第一步,不过先不着急要怎么去创建数据表,我们要先了解一下结构体与数据表之间的映射规则,主要有以下几点:

Struct tags

我们知道,Go语言的结构体支持使用tags为结构体的每个字段扩展额外的信息,如使用标准库encoding/json包进行JSON编码时,便可以使用tags进行编码额外信息的扩展。

GROM框架有自己的一个tags约定,如下所示:

Column 指定列名

Type 指定列数据类型

Size 指定列大小, 默认值255

PRIMARY_KEY 将列指定为主键

UNIQUE 将列指定为唯一

DEFAULT 指定列默认值

PRECISION 指定列精度

NOT NULL 将列指定为非 NULL

AUTO_INCREMENT 指定列是否为自增类型

INDEX 创建具有或不带名称的索引, 如果多个索引同名则创建复合索引

UNIQUE_INDEX 和 INDEX 类似,只不过创建的是唯一索引

EMBEDDED 将结构设置为嵌入

EMBEDDED_PREFIX 设置嵌入结构的前缀

- 忽略此字段

GROM还支持一些关联数据表的tags约定,有机会我讲讲GROM数据表关联的时候,会说到的。

上面列出的GORM支持的tags,方便我们定制结构体字段到数据表字段之间的映射规则,下面的代码,我们给Post结构体定制一些tags扩展,如下:


type Post struct {
PostId int `gorm:"primary_key;auto_increment"`
Uid int `gorm:"type:int;not null"`
Title string `gorm:"type:varchar(255);not null"`
Content string `gorm:"type:text;not null"`
Type uint8 `gorm:"type:tinyint;default 1;not null"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
}

从上面的例子我们可以看出GORM为数据模型的字段定义tags的格式,每个字段可以用多个类型的tags信息,不同的tag之间用分号分隔。

惯例

除了上面讲的tags定义了字段之间的映射规则外,Go将结构体映射为关系型数据表时,还有自己的一套惯例,或称为约定,主要有以下几点:

主键

GROM的约定中,一般将数据模型中的ID字段映射为数据表的主键,如下面定义的TestModel,ID为主键,TestModel的ID的数据类型为string,如果ID的数据类型为int,则GROM还会为该设置AUTO_INCREMENT,使用ID成为自增主键。


type TestModel struct{
ID int
Name string
}

当然,我们也可以自定义主键字段的名称,如上面的Post结构体,我们设置了PostId字段为主键,如果我们定义了其他字段为主键,那么,就算结构体中仍有ID字段,GROM框架也不会把ID字段当作主键了。


type Post struct {
ID int
PostId int `gorm:"primary_key;auto_increment"`
Uid int `gorm:"type:int;not null"`
Title string `gorm:"type:varchar(255);not null"`
Content string `gorm:"type:text;not null"`
Type uint8 `gorm:"type:tinyint;default 1;not null"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
}

所以,我们在Post结构体中加一个ID字段,PostId字段仍是主键,下面是在数据中使用desc posts语句打印出来的结果:

数据表映射规则

当我们使用结构体创建数据表时,数据表的名称默认为结构体的小写复数形式,如结构体Post对应的数据表名称为posts,当然我们也可以自己指定结构体对应的数据表名称,而不是用默认的。

为结构体加上TableName()方法,通过这个方法可以返回自定义的数据表名,如下:


//指定Post结构体对应的数据表为my_posts
func (p Post) TableName() string{
return "my_posts"
}

数据表前缀

除了指定数据表名外,我们也可以重写gorm.DefaultTableNameHandler这个变量,这样可以为所有数据表指定统一的数据表前缀,如下:


gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string {
return "tb_" + defaultTableName;
}

这样的话,通过结构体Post创建的数据表名称则为tb_posts。

字段映射规则

结构体到数据表的名称映射规则为结构体名称的复数,而结构体的字段到数据表字段的默认映射规则是用下划线分隔每个大写字母开头的单词,如下:


type Prize struct {
ID int
PrizeName string
}

上面的结构体Prize中的PrizeName字段对应的数据表为prize_name,但我们把PrizeName改为Prizename时,则对应的数据表字段名称为prizename,这是为因为只分隔大写字段开头的单词。

当然,我们也可以为结构体的某个字段定义tags扩展信息,这样结构体字段到数据表字段的映规则就在tags中定义。

时间点追踪

前面我们说过,如果结构体中有名称为ID字段,则GORM框架会把该字段作为数据表的主键,除此之外,如果结构体中有CreatedAt,UpdatedAt,DeletedAt这几个字段的话,则GROM框架也会作一些特殊处理,规则如下:

CreatedAt:新增数据表记录的时候,会自动写入这个字段。 UpdatedAt:更新数据表记录的时候,会自动更新这个字段。 DeletedAt:当执行软删除的时候,会自动更新这个字段,表示删除时间

gorm.Model

由于如果结构体中有ID,CreatedAt,UpdatedAt,DeletedAt这几个比较通用的字段,GORM框架会自动处理这几个字段,所以如果我们结构体需要这几个字段时,我们可以直接在自定义结构体中嵌入gorm.Model结构体,gorm.Model的结构体如下:


type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}

所以,如果我们在结构体Prize中嵌入gorm.Model,如下:


type Prize struct{
gorm.Model
Name string
}

这样的话,则结构体Prize包含有五个字段了。

数据库迁移

我们这里所说的数据库迁移,即通过使用GROM提供的一系列方法,根据数据模型定义好的规则,进行创建、删除数据表等操作,也就是数据库的DDL操作。

GORM提供对数据库进行DDL操作的方法,主要以下几类:

数据表操作


//根据模型自动创建数据表
func (s *DB) AutoMigrate(values ...interface{}) *DB
//根据模型创建数据表
func (s *DB) CreateTable(models ...interface{}) *DB
//删除数据表,相当于drop table语句
func (s *DB) DropTable(values ...interface{}) *DB
//相当于drop table if exsist 语句
func (s *DB) DropTableIfExists(values ...interface{}) *DB
//根据模型判断数据表是否存在
func (s *DB) HasTable(value interface{}) bool

列操作


//删除数据表字段
func (s *DB) DropColumn(column string) *DB
//修改数据表字段的数据类型
func (s *DB) ModifyColumn(column string, typ string) *DB

索引操作


//添加外键
func (s *DB) AddForeignKey(field string, dest string, onDelete string, onUpdate string) *DB
//给数据表字段添加索引
func (s *DB) AddIndex(indexName string, columns ...string) *DB
//给数据表字段添加唯一索引
func (s *DB) AddUniqueIndex(indexName string, columns ...string) *DB

数据迁移简单代码示例

注意,下面示例程序中db变量代表gorm.DB对象,其初始化过程本篇不讲了。


type User struct {
Id int //对应数据表的自增id
Username string
Password string
Email string
Phone string
}
func main(){
db.AutoMigrate(&Post{},&User{})//创建posts和users数据表
db.CreateTable(&Post{})//创建posts数据表
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&Post{})//创建posts表时指存在引擎
db.DropTable(&Post{},"users")//删除posts和users表数据表
db.DropTableIfExists(&Post{},"users")//删除前会判断posts和users表是否存在
//先判断users表是否存在,再删除users表
if db.HasTable("users") {
db.DropTable("users")
}
//删除数据表字段
db.Model(&Post{}).DropColumn("id")
//修改字段数据类型
db.Model(&Post{}).ModifyColumn("id","varchar(255)")
//建立posts与users表之间的外键关联
db.Model(&Post{}).AddForeignKey("uid", "users(id)", "RESTRICT", "RESTRICT")
//给posts表的title字段添加索引
db.Model(&Post{}).AddIndex("index_title","title")
//给users表的phone字段添加唯一索引
db.Model(&User{}).AddUniqueIndex("index_phone","phone")
}

小结

可能你会问,直接在数据库中进行数据表创建、删除等操作不就行了吗?为什么要在应用程序里去做这些操作呢?因为有些时候,我们不一定能登录到数据库系统当中,又或者,我们需要开发一个可以管理数据库的应用程序,这时候,GROM框架提供的这些数据库迁移的能便派上用场了。

来源:https://blog.csdn.net/juzipidemimi/article/details/104502385

0
投稿

猜你喜欢

  • 使用场景对手机号码进行地域分析,需要查询归属地;问题描述针对数据集比较大的情况,通过脚本来处理,使用多线程的方法来加快查询速度pool =
  • 本文实例为大家分享了Python实现感知器模型、两层神经网络,供大家参考,具体内容如下python 3.4 因为使用了 numpy这里我们首
  • 前言在我们抓取网页内容的时候,通常是抓取一整个页面的内容,而我们仅仅只是需要该网页中的部分内容,那该如何去提取呢?本章就带你学习xpath插
  • 亮度调整非线性亮度调整:对于R,G,B三个通道,每个通道增加相同的增量。线性亮度调整:利用HSL颜色空间,通过只对其L(亮度)部分调整,可达
  • 本文实例讲述了php实现mysql事务处理的方法。分享给大家供大家参考。具体分析如下:要实现本功能的条件是环境 mysql 5.2 /php
  • 先前在DW教学-Dreamweaver量身打造Wordpress留言板(一) 教学文章中,已经成功的把前端留言机制与界面搞定了,虽然有了留言
  • 前言本文主要介绍了关于Python中TCP socket的写法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。一、 服务器
  • 因为需要对数据处理,将excel数据导入到数据库,记录一下过程。使用到的库:xlrd 和 pymysql (如果需要写到excel可以使用x
  • 1. 介绍通俗的来讲,MobaXterm就是一款SSH客户端,它帮助我们在Windows操作系统下去连接并操作Linux服务器。MobaXt
  • 当你加入到一个项目,相关的CSS文件可能会看得你头昏眼花。时间一长,修改了哪些内容,增加了哪些内容,也都弄不清,维护成本相当的高。正好我们国
  • 1、基于字典的创建规划问题上篇中介绍了使用 LpVariable 对逐一定义每个决策变量,设定名称、类型和上下界,类似地对约束条件也需要逐一
  • 1、前言前面讲到unittest里面setUp可以在每次执行用例前执行,这样有效的减少了代码量,但是有个弊端,比如打开浏览器操作,每次执行用
  • 乍一看到列表推导式你可能会感到疑惑。它们是一种创建和使用列表的简洁方式。理解列表推导式是有用的,因为你可能在其他人的代码里看到列表推导式。下
  • 以下是作者在学习Python中django框架时的学习笔记,并把测试的代码做了详细分析,最后还附上了学习心得,值得大家学习。URL配置(UR
  • 一般情况下,用Mybatis的时候是先设计表结构再进行实体类以及映射文件编写的,特别是用代码生成器的时候。但有时候不想用代码生成器,也不想定
  • 1、找到mysql安装路径D:\xxx\MYSQL\MySQL Workbench CE 6.0.8下的mysqldump.exe,由于脚本
  • 第一个博客,大概就是开始学数据库了,然后自己下载各种搞不定,各种问题,百度也不是很好用,问了下也没什么结果,后来总算搞定,但是花了很长时间,
  • 最近遇到一个问题,是指定参数来运行某个特定的进程,这很类似Linux中一些命令的参数了,比如ls -a,为什么加上-a选项会响应。optpa
  • 如下所示:import pandas as pddef my_min(a, b):  return min(abs(a),abs(
  • 废话还是说太多了 直接上代码import randomimport sys# 牌面列表card_code = ['A', &
手机版 网络编程 asp之家 www.aspxhome.com