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

适用于先读后更新的数据竞争场景,且应该将加锁操作放到事务中,防止锁被自动释放,原因参考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框架提供的这些数据库迁移的能便派上用场了。

(0)

相关推荐

  • golang常用库之操作数据库的orm框架-gorm基本使用详解

    golang常用库:gorilla/mux-http路由库使用 golang常用库:配置文件解析库-viper使用 golang常用库:操作数据库的orm框架-gorm基本使用 一:字段映射-模型定义 gorm中通常用struct来映射字段. gorm教程中叫模型定义 比如我们定义一个模型Model: type User struct { gorm.Model UserId int64 `gorm:"index"` //设置一个普通的索引,没有设置索引名,gorm会自动命名 Birth

  • golang Gorm与数据库完整性约束详解

    数据库约束要点: 主键约束(非空且唯一)外键约束 子表外键字段的值必须在主表被参照字段值得范围内,或者为NULL:外键参照的必须是主表的主键或唯一键:主表主键/唯一键被子表参照时,主表相应记录不允许被删除 在golang中,采用orm对数据库进行建模是比较方便的.grom是其中一个比较流行的orm工具. 本篇基于golang.grom1.91.和PostgreSQL来进行说明. 注:本文的例子是极端情况,一般情况只是单字段主键. 1.实体完整性: 每个关系(表)有且仅有一个主键,每一个主键值必须

  • golang gorm 结构体的表字段缺省值设置方式

    我就废话不多说了,大家还是直接看代码吧~ type Animal struct { ID int64 Name string `gorm:"default:'galeone'"` Age int64 } 把 name 设置上缺省值 galeone 了. 补充:Golang 巧用构造函数设置结构体的默认值 看代码吧~ package main import "fmt" type s1 struct { ID string s2 s2 s3 s3 } type s2 s

  • golang gorm中格式化时间问题详解

    前言 最近在开发项目时遇到了发现一个问题, gorm虽然可以自动帮你维护 created_at.updated_at.deleted_at这些关键时间字段.但是其原理与弊端需要了解一下. 1.使用方法 通过自定义一个localtime的结构,来控制时间的格式 package utils import ( "time" //"strconv" "fmt" "database/sql/driver" "strconv&q

  • golang gorm 计算字段和获取sum()值的实现

    计算表lb_ytt_user_money_log 中,字段money的和 代码如下: var total_money []int sqlstr := `select SUM(money) as total_money from lb_ytt_user_money_log where user_id = ? and l_type = 1 and status=1 and (create_time> ? and create_time <= ?)` Db.Raw(sqlstr, userID, b

  • gorm golang 并发连接数据库报错的解决方法

    底层报错 error:cannot assign requested address 原因 并发场景下 client 频繁请求端口建立tcp连接导致端口被耗尽 解决方案 root执行即可 sysctl -w net.ipv4.tcp_timestamps=1 开启对于TCP时间戳的支持,若该项设置为0,则下面一项设置不起作用 sysctl -w net.ipv4.tcp_tw_recycle=1 表示开启TCP连接中TIME-WAIT sockets的快速回收 以上这篇gorm golang 并

  • golang gorm多条件筛选查询操作

    案例: 查看陌陌的动态,依次可以按照发布时间,性别,城市进行筛选 如图进行筛选 gorm链式操作 Method Chaining,Gorm 实现了链式操作接口,所以你可以把代码写成这样: // 创建一个查询 tx := db.Where("name = ?", "jinzhu") // 添加更多条件 if someCondition { tx = tx.Where("age = ?", 20) } else { tx = tx.Where(&qu

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

    适用于先读后更新的数据竞争场景,且应该将加锁操作放到事务中,防止锁被自动释放,原因参考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{} // 锁住指定

  • golang gorm框架数据库的连接操作示例

    目录 1. 连接数据库 1.1 MySQL 1.2 PostgreSQL 1.3 Sqlite3 1.4 不支持的数据库 2. 迁移 2.1. 自动迁移 2.2. 检查表是否存在 2.3. 创建表 2.4. 删除表 2.5. 修改列 2.6. 删除列 2.7. 添加外键 2.8. 索引 1. 连接数据库 要连接到数据库首先要导入驱动程序.例如 import _ "github.com/go-sql-driver/mysql" 为了方便记住导入路径,GORM包装了一些驱动. import

  • 一文搞懂Mysql中的共享锁、排他锁、悲观锁、乐观锁及使用场景

    目录 一.常见锁类型 二.Mysql引擎介绍 三.常用引擎间的区别 四.共享锁与排他锁 五.排他锁的实际应用 六.共享锁的实际应用 七.死锁的发生 八.另一种发生死锁的情景 九.死锁的解决方式 十.意向锁和计划锁 十一.乐观锁和悲观锁 总结 一.常见锁类型 表级锁,锁定整张表 页级锁,锁定一页 行级锁,锁定一行 共享锁,也叫S锁,在MyISAM中也叫读锁 排他锁,也叫X锁,在MyISAM中也叫写锁 悲观锁,抽象性质,其实不真实存在 乐观锁,抽象性质,其实不真实存在 常见锁类型 二.Mysql引擎

  • Java的Hibernate框架数据库操作中锁的使用和查询类型

     Hibernate与数据库锁 一.为什么要使用锁? 要想弄清楚锁机制存在的原因,首先要了解事务的概念. 事务是对数据库一系列相关的操作,它必须具备ACID特征: A(原子性):要么全部成功,要么全部撤销. C(一致性):要保持数据库的一致性. I(隔离性):不同事务操作相同数据时,要有各自的数据空间. D(持久性):一旦事务成功结束,它对数据库所做的更新必须永久保持. 我们常用的关系型数据库RDBMS实现了事务的这些特性.其中,原子性. 一致性和持久性都是采用日志来保证的.而隔离性就是由今天我

  • mysql共享锁与排他锁用法实例分析

    本文实例讲述了mysql共享锁与排他锁用法.分享给大家供大家参考,具体如下: mysql锁机制分为表级锁和行级锁,本文就和大家分享一下我对mysql中行级锁中的共享锁与排他锁进行分享交流. 共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改. 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据

  • 利用golang驱动操作MongoDB数据库的步骤

    安装MongoDB驱动程序 mkdr mongodb cd mongodb go mod init go get go.mongodb.org/mongo-driver/mongo 连接MongoDB 创建一个main.go文件 将以下包导入main.go文件中 package main import ( "context" "fmt" "log" "go.mongodb.org/mongo-driver/bson" &quo

  • c#多线程之间的排他锁的实现

    我们很多时候会碰到这样的问题,使用多线程刷一个表的数据时需要多个线程不能重复提取数据,那么这个时候就需要使用到线程的排他锁了. 在c#里面其实很简单,下面先来看一个简单的小例子 Thread pingTask = new Thread(new ThreadStart(delegate { //从数据库获取1000条数 var list = getdata(); })); //启动线程 pingTask.Start(); 如果这个时候我们开启多个线程 代码如下 for (int i = 0; i

  • 一文学习MySQL 意向共享锁、意向排他锁、死锁

    目录 一.InnoDB表级锁 二.意向共享锁和意向排他锁 三.死锁 1. 数据库中的死锁 2. 死锁场景以及解决办法 3. 操作 四.锁的优化建议 一.InnoDB表级锁 我们知道,InnoDB是支持行锁,但不是每次都获取行锁,如果不使用索引的,那还是获取的表锁.而且有的时候,我们希望直接去使用表锁 在绝大部分情况下都应该使用行锁,因为事务的并发效率比表锁更高,但个别情况下也使用表级锁: 事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,给大部分行都加锁(此时不如直接加表锁),不仅这

  • golang MySQL实现对数据库表存储获取操作示例

    目录 新建数据库 config.go gameblog.go http Simplify server.go comment.go gameblog.go server.go postman test api Axios gamelist.go HTTP gamelist.go server.go Axios 新建数据库 将部分数据存储至Mysql,使用axios通过golang搭建的http服务器获取数据. sql DROP DATABASE VUE; create database if n

  • SELECT… FOR UPDATE 排他锁的实现

    目录 1. SELECT…FOR UPDATE 是什么?作用是什么? 2. MYSQL中如何查询是否存在锁信息?相关SQL 2.1MYSQL INFORMATION_SCHEMA 数据库 3. SELECT…FOR UPDATE 怎么使用?如何验证? 3.1 Mysql Config表SQL 3.2 创建SpringBoot工程,结构目录如下 3.2 pom.xml文件内容 3.4 db层接口及其实现类 3.5 Application.java 开启Mapper扫描和开启事务 3.6 [核心]

随机推荐