设计数据库的小tips

1、数据库表字段不建议设置值时允许为null。

  • NULL 的比较特性:任何与 NULL 的比较(=, !=, <, >)结果都不是 TRUEFALSE,而是 UNKNOWN。这使得 WHERE 子句的行为变得难以预测。

  • 函数与运算的行为不确定:大多数聚合函数(如 SUM, AVG, COUNT)会忽略 NULL 值,这可能与开发者的直觉不符。

  • NULL 值会增加存储开销,为null它代表的是数据未知,他会在数据的行数据中单独用一列来表示哪些列是空的(用位图表示,如00010,表示第3行(以0为开始)表示为null),不过数据部分就不占用空间了。

2、将数据库查询逻辑显示暴露到逻辑上而避免使用复杂的sql语句
方便后续定位问题。

3、在数据库中表设计中,不建议设置外键。
对记录的删除和插入都会对关联表做数据完整性检查,有的时候这可能是不必要的,这些应该把它暴露到业务逻辑层来做。主要还是增加性能开销。

Gorm操作mysql 最佳实践(设计接口常用)

场景 1: 根据条件查询行记录 (SELECT *)

Sql

1
SELECT * FROM student WHERE age = 26 AND grade = "1年级";

GORM 操作

1
2
var students []Student
result := db.Where("age = ? AND grade = ?", 26, "1年级").Find(&students)

场景 2: 根据条件查询行记录的指定列的值

sql

1
SELECT id, grade, age FROM student WHERE age = 26;

GORM 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 方式一,注意一定要用.Model(&Student{})!!否则不生效
var results []map[string]interface{}
result := db.Model(&Student{}).Select("id", "grade", "age").Where("age = ?", 26).Find(&results)


// 方式二,注意一定要用.Model(&Student{})!!否则不生效
type StudentInfo struct {
ID uint
Grade string
Age int
}
var infos []StudentInfo
db.Model(&Student{}).Select("id", "grade", "age").Where("age = ?", 26).Find(&infos)

注意⚠️:当使用 Find To Map时,一定要在你的查询中包含 Model 或者 Table ,以此来显式地指定表名。 这能确保 GORM 正确的理解哪个表要被查询。https://gorm.io/zh_CN/docs/advanced_query.html

场景 3: 根据条件(包括关联表的列)查询行记录的指定列的值

Sql

1
2
3
4
5
6
7
-- 连表查询
SELECT s.id, s.grade, s.age, b.name as book_name FROM student s LEFT JOIN book b ON b.id = s.book_id WHERE s.age = 26;

-- 先查主表,再批量查关联表
SELECT * FROM student WHERE age = 26;
SELECT * FROM book WHERE book_id IN (1,2,3,4);

GORM 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方法一:使用 Preload (GORM 的推荐方式,先查主表,再批量查关联表)
var students []Student
// Preload("Book") 会自动执行一次 JOIN 或额外的 SELECT 来加载关联的 Book
db.Preload("Book").Where("age = ?", 26).Find(&students)

// 方法二:使用 Joins + Scan (适用于需要从关联表选择特定列)
type StudentBookInfo struct {
ID uint
Grade string
Age int
BookName string `gorm:"column:book_name"`
}
var infos []StudentBookInfo

db.Table("students s").
Select("s.id, s.grade, s.age, b.name as book_name").
Joins("LEFT JOIN books b ON b.id = s.book_id").
Where("s.age = ?", 26).
Scan(&infos)

场景 4: 根据主键 ID 更新指定列的数据,并返回更新行的记录

Sql

1
UPDATE student SET grade = "3年级", age = 27 WHERE id = 1;

GORM 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var updatedStudent Student
db.First(&updatedStudent, studentID)

// 使用 Updates 只更新指定字段
result = db.Model(&updatedStudent).Updates(map[string]interface{}{
"grade": "4年级",
"age": 28,
})
// 或者使用结构体(注意:零值不会被更新,如 int 0, string "")
// db.Model(&updatedStudent).Updates(Student{Grade: "4年级", Age: 28})

if result.Error != nil {
fmt.Printf("更新失败: %v\n", result.Error)
return
}

场景 5: 插入一条新数据,如果数据已存在则更新(UPSERT)

Sql

1
2
3
4
5
6
INSERT INTO student (name, age, grade, book_id) 
VALUES ('张三', 25, '2年级', 1)
ON DUPLICATE KEY UPDATE
age = VALUES(age),
grade = VALUES(grade),
updated_at = CURRENT_TIMESTAMP;

注意: 此语句依赖于表上的唯一索引(UNIQUE KEY)。例如,你需要在 name 字段上建立唯一索引,当插入重复的 name 时,ON DUPLICATE KEY 子句才会触发。

GORM 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 假设 name 字段是唯一索引
student := Student{
Name: "张三", // 这个名字在数据库中已存在
Age: 26, // 新的年龄
Grade: "3年级", // 新的年级
BookID: 1,
}

// 使用 Clauses 实现 UPSERT
result := db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}}, // 唯一索引的列
DoUpdates: clause.Assignments(map[string]interface{}{
"age": student.Age,
"grade": student.Grade,
// GORM 会自动处理 updated_at
}),
}).Create(&student)