三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

数据库约束 和 Struct Tag 标签到底是干什么的?

数据库约束 和 Struct Tag 标签到底是干什么的?

🥰个人主页:会编程的土豆(欢迎来访)
💎作者简介:后端学习者
❄️个人专栏:数据结构与算法,数据库,leetcode
那些你一个人走过的夜路,终将化作照亮未来的光

适合对象:刚接触 MySQL + GORM 的新手
结合场景:影院票务(users / seats / orders 等表)
读完你能分清:SQL 里的约束管什么,Go 结构体上的 tag 又管什么


一、先建立整体印象

做 Web 后端时,数据会经过两层“说明书”:

层级写在哪作用
数据库约束CREATE TABLE数据库强制执行的规则,违规会直接报错
Struct TagGo 的struct字段后面`...`给框架看的标注:GORM 怎么映射表、JSON 怎么输出

可以记一句:

  • 约束:数据库的“铁规矩”
  • Tag:Go 代码和框架之间的“说明书便签”

两者经常对应(比如表里username唯一,模型上也写uniqueIndex),但职责不同:
真正拦非法数据的,主要是数据库约束;Tag 更多是让 GORM/JSON 知道怎么读写。


二、数据库约束是什么?

建表时,除了写“有哪些列”,还会写“这些列必须遵守什么规则”。这些规则就叫约束(Constraint)

2.1 为什么需要约束?

假如没有约束:

  • 可以插入两个相同的用户名 → 登录时到底算谁?
  • 订单可以写一个不存在的user_id→ 脏数据
  • 座位状态乱填 999 → 业务判断全乱

约束就是在数据库层把这些问题挡掉。

2.2 票务项目里常见约束(对照理解)

1)主键 PRIMARY KEY
id BIGINT PRIMARY KEY AUTO_INCREMENT
  • 每一行的唯一身份证
  • AUTO_INCREMENT:插入时 id 自动增长
  • 一张表通常一个主键
2)非空 NOT NULL
username VARCHAR(50) NOT NULL

这一列不允许是NULL(“没填”)。
注意:空字符串''NULL不是一回事。

3)唯一 UNIQUE
username VARCHAR(50) NOT NULL UNIQUE

同一张表里,这个值不能重复。
用户名、订单号常用。

4)默认值 DEFAULT
role TINYINT NOT NULL DEFAULT 0

插入时如果不写role,就默认是0(顾客)。

5)外键 FOREIGN KEY
CONSTRAINT fk_sch_movie FOREIGN KEY (movie_id) REFERENCES movies(id)

含义:schedules.movie_id必须能在movies.id里找到。
作用:防止场次挂到一部不存在的电影上。

常见连带策略(了解即可):

  • ON DELETE CASCADE:主表删了,子表相关行也删(如删场次时座位跟着删)
  • ON DELETE SET NULL:主表删了,子表外键置空
6)索引 INDEX / UNIQUE KEY
INDEX idx_order_user (user_id) UNIQUE KEY uk_seat (schedule_id, row_no, col_no)
  • 索引:让按某列查询更快(如查某用户订单)
  • 唯一索引:既加速,又保证组合不重复
    例如同一场次不能有两个“3排5座”
7)引擎 ENGINE=InnoDB

购票要用事务和行锁(FOR UPDATE),一般用 InnoDB。
这不是列约束,但是建表时很重要的选择。


三、结合座位和订单理解约束

座位和订单的关系可以记成:

一笔订单对应多个座位;座位通过order_id挂到订单上。

相关约束帮助保证:

  1. orders.user_id必须是真实用户(外键)
  2. orders.schedule_id必须是真实场次(外键)
  3. 同一场次的(schedule_id, row_no, col_no)唯一(唯一索引)
  4. 订单状态、座位状态用整数表示业务含义(配合代码常量,不一定全靠数据库枚举)

没有这些约束,超卖、脏订单会更容易出现(当然超卖还要靠事务和行锁,约束是基础防线)。


四、Go 里的 Tag 标签是什么?

看模型代码时,常看到:

type User struct { ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` Username string `gorm:"size:50;uniqueIndex;not null" json:"username"` Password string `gorm:"size:32;not null" json:"-"` }

字段后面的反引号字符串,就是struct tag(标签)

特点:

  • 不影响 Go 语法本身能不能编译通过(写错 tag 内容往往仍能编译)
  • 主要给反射 + 框架读取,例如 GORM、encoding/json

一个字段上可以写多个 tag,用空格分隔:

`gorm:"..." json:"..."`

五、json tag:控制接口返回长什么样

写法含义
json:"username"JSON 里字段名用username
json:"-"不要序列化出去(密码、盐常用)
json:"nickname,omitempty"空值时可以省略该字段

示例:

Password string `json:"-"` Salt string `json:"-"`

登录成功返回用户信息时,就不会把密码哈希泄露给前端。这是非常基础的安全习惯。


六、gorm tag:控制怎么和数据库打交道

GORM 通过 tag 知道:

  • 主键是谁
  • 列名是什么
  • 是否唯一
  • 字段长度
  • 是否只读(联表查出来的字段)等

6.1 常用 gorm tag(入门够用)

tag含义
primaryKey主键
autoIncrement自增
not null非空(模型层提示/迁移时有用)
size:50字符串长度
uniqueIndex唯一索引
index普通索引
column:rows_num数据库列名和 Go 字段名不一致时指定列名
type:decimal(10,2)指定数据库类型
default:0默认值
->只读:查可以填充,一般不用于写入该表
-忽略该字段(完全不映射)

6.2 TableName:指定表名

func (User) TableName() string { return "users" }

告诉 GORM:User对应表名users
否则 GORM 可能按默认规则猜表名(有时是users,有时你想精确控制)。

6.3 为什么有的字段是指针?

GroupID *int64 LockUserID *int64 PaidAt *time.Time

因为数据库里这些列可能是NULL

  • 用指针:可以区分“没值(nil)”和“有值 0”
  • 用普通int64:很难表达 SQL 的NULL

七、约束 vs Tag:别混在一起

对比数据库约束Struct Tag
写在哪SQL / 数据库Go 结构体
谁强制执行MySQL框架约定(GORM/JSON)
作用保证数据合法性指导映射与序列化
失败时插入/更新直接报错可能映射错、字段丢失、或迁移不一致

注意:

  • 只在 Go 的 gorm tag 写了uniqueIndex,但数据库里没建唯一约束,不一定能防重复(尤其你是用schema.sql建表、而不是全靠 AutoMigrate 时)。
  • 本票务项目是SQL 建表为主,models 上的 gorm tag 更多是让 GORM 读写时行为正确、字段对应正确。
  • 真正的底线仍是数据库约束 + 业务代码(事务/行锁)

八、一张对照表(users)

SQLGo
id BIGINT PRIMARY KEY AUTO_INCREMENTID int64 \gorm:“primaryKey;autoIncrement” json:“id”``
username VARCHAR(50) NOT NULL UNIQUEUsername string \gorm:“size:50;uniqueIndex;not null” json:“username”``
password CHAR(32) NOT NULLPassword string \gorm:“size:32;not null” json:“-”``
role TINYINT DEFAULT 0Role int \gorm:“not null;default:0” json:“role”``

看到没有:左边是库的规矩,右边是 Go 的说明书。两边对齐,项目才稳

← 返回列表