Claran's blog

路漫漫其修远兮

前置并发知识

并发

goroutine

生产者&消费者 模型

Go-pool2.png
生产者-消费者模型是一种经典的并发编程模式,通过缓冲区解耦生产者和消费者,使它们可以独立、异步地工作。

核心组件

  1. 生产者(Producer)

    • 数据的产生者

    • 负责创建任务或数据

    • 将数据放入缓冲区

  2. 消费者(Consumer)

    • 数据的处理者

    • 从缓冲区获取数据

    • 执行具体的业务逻辑

  3. 缓冲区(Buffer/Queue)

    • 生产者和消费者之间的桥梁

    • 平衡生产速度和消费速度的差异

    • 提供流量控制和数据暂存

Go-pool3.png

任务分发的必要性

为什么需要任务分发?

直接创建Goroutine的问题

1
2
3
4
// ❌ 不推荐:无限制创建goroutine
for i := 0; i < 10000; i++ {
go processTask(i) // 可能创建过多goroutine!
}

问题分析

  1. 资源耗尽 - 内存、CPU过载
  2. 调度开销 - 上下文切换成本高
  3. 难以管理 - 无法控制并发数量
    `

协程池

梗概

什么是协程池?

协程池是一种复用Goroutine的技术,通过预先创建固定数量的工作协程,重复使用它们来处理任务,避免频繁创建和销毁的开销。

核心思想

Go-pool1.png

生产者-消费者模型的扩展

  • 生产者:提交任务到任务队列
  • 消费者:工作协程从队列获取任务执行
  • 缓冲区:任务队列平衡生产消费速度

实现思路

核心组件设计

1
2
3
4
5
6
7
8
9
10
11

type WorkerPool struct {
taskChan chan Task // 任务通道(缓冲队列)
resultChan chan Result // 结果通道
stopChan chan struct{} // 停止信号
wg sync.WaitGroup // 等待组(协调goroutine)

// 统计信息(原子操作保证线程安全)
SubmitSum int64 // 已提交任务数
CompleteSum int64 // 已完成任务数
}

工作流程

  1. 初始化阶段:创建指定数量的worker
  2. 任务提交:生产者向taskChan发送任务
  3. 任务处理:worker从taskChan接收并执行
  4. 结果收集:处理结果发送到resultChan
  5. 优雅关闭:通过stopChan协调关闭

代码实现

核心结构定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package workerPool

import (
"errors"
"runtime"
"sync"
"sync/atomic"
)

// TaskFunc 任务函数类型
type TaskFunc func() (interface{}, error)

// Task 任务结构
type Task struct {
ID int // 任务ID(用于追踪)
Func TaskFunc // 要执行的任务函数
}

// TaskResult 任务执行结果
type TaskResult struct {
ID int // 对应任务ID
Result interface{} // 执行结果
Err error // 错误信息
}

// WorkerPool 协程池主体
type WorkerPool struct {
taskChan chan Task // 任务通道(缓冲队列)
resultChan chan TaskResult // 结果通道
stopChan chan struct{} // 停止信号通道
wg sync.WaitGroup // 等待组(协调goroutine生命周期)
// 原子操作统计(线程安全)
SubmitSum int64 // 已提交任务总数
CompleteSum int64 // 已完成任务总数
}

初始化协程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

// NewWorkerPool 创建新的协程池

func NewWorkerPool(workerCount, queueSize int) *WorkerPool {
// 参数校验和默认值设置
if workerCount < 1 {
workerCount = runtime.NumCPU() * 2 // 默认:CPU核心数×2
}

if queueSize < workerCount {
queueSize = workerCount * 100 // 默认队列大小:worker数×100
}

// 初始化协程池实例
pool := &WorkerPool{
taskChan: make(chan Task, queueSize), // 带缓冲的任务通道
resultChan: make(chan TaskResult, queueSize), // 带缓冲的结果通道
stopChan: make(chan struct{}), // 无缓冲停止信号
}

// 创建worker协程
for i := 0; i < workerCount; i++ {
pool.wg.Add(1)
go pool.worker() // 启动worker goroutine
}

return pool
}

生产者:提交任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// Produce 提交任务到协程池(生产者)

func (p *WorkerPool) Produce(taskFunc TaskFunc) error {
// 封装任务
task := Task{
ID: int(atomic.AddInt64(&p.SubmitSum, 1)), // 原子操作生成任务ID
Func: taskFunc,
}

// 非阻塞发送任务
select {
case p.taskChan <- task: // 正常提交
return nil
case <-p.stopChan: // 协程池已关闭
return errors.New("pool stopped")
}
}

消费者:工作协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

// worker 工作协程(消费者)

func (p *WorkerPool) worker() {
defer p.wg.Done() // 确保goroutine结束时通知WaitGroup

for {
select {
case task, ok := <-p.taskChan:
if !ok { // 通道已关闭且无剩余任务
return
}
// 执行具体任务
result, err := task.Func()
// 发送处理结果
p.resultChan <- TaskResult{
ID: task.ID,
Result: result,
Err: err,
}
// 原子操作更新完成计数
atomic.AddInt64(&p.CompleteSum, 1)
case <-p.stopChan: // 收到停止信号
return
}
}
}

结果收集和管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// GetResults 获取结果通道(只读)

func (p *WorkerPool) GetResults() <-chan TaskResult {
return p.resultChan
}

// GetInfo 获取统计信息(线程安全)

func (p *WorkerPool) GetInfo() (int64, int64) {
return atomic.LoadInt64(&p.SubmitSum), atomic.LoadInt64(&p.CompleteSum)
}

// Close 优雅关闭协程池

func (p *WorkerPool) Close() {
close(p.taskChan) // 关闭任务通道(停止接收新任务)
p.wg.Wait() // 等待所有worker完成任务
close(p.resultChan) // 关闭结果通道
close(p.stopChan) // 关闭停止信号
}

🎯 实战案例:文件关键词搜索

业务逻辑层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package service

import (
"bufio"
"fmt"
"os"
"strings"
"sync/atomic"
)

// Result 搜索结果结构

type Result struct {
Path string // 文件路径
Info []LineInfo // 匹配行信息
Err error // 错误信息
}

// LineInfo 行信息结构

type LineInfo struct {
Line int // 行号
Content string // 行内容
}

// Task 搜索任务

type Task struct {
Path string // 文件路径
Keyword string // 搜索关键词
}

// Search 文件搜索函数

func Search(task Task) (interface{}, error) {

// 打开文件

file, err := os.Open(task.Path)
if err != nil {
return Result{
Path: task.Path,
Err: fmt.Errorf("无法打开文件: %v", err),
}, err
}
defer file.Close()

var info []LineInfo
scanner := bufio.NewScanner(file)
lineNum := 0

// 逐行扫描
for scanner.Scan() {
lineNum++
line := scanner.Text()
if strings.Contains(line, task.Keyword) {
info = append(info, LineInfo{
Line: lineNum,
Content: strings.TrimSpace(line),
})
}
}

// 检查扫描错误
if err := scanner.Err(); err != nil {
return Result{
Path: task.Path,
Err: fmt.Errorf("读取文件错误: %v", err),
}, err
}

return Result{
Path: task.Path,
Info: info,
}, nil
}

// 全局统计(原子操作保证线程安全)

var (
totalFiles int64 // 总文件数
foundFiles int64 // 包含关键词的文件数
totalLines int64 // 总匹配行数
)

// SetTotal 设置总文件数

func SetTotal(num int64) {
atomic.StoreInt64(&totalFiles, num)
}

// AddFound 增加找到的文件计数

func AddFound(num int64) {
atomic.AddInt64(&foundFiles, num)
}

// AddLines 增加匹配行计数

func AddLines(num int64) {
atomic.AddInt64(&totalLines, num)
}

// GetInfo 获取统计信息

func GetInfo() (int, int, int) {
return int(atomic.LoadInt64(&totalFiles)),
int(atomic.LoadInt64(&foundFiles)),
int(atomic.LoadInt64(&totalLines))
}

主程序入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

package main

import (
"Lesson_1/Lanshan-lesson5/service"
"Lesson_1/Lanshan-lesson5/workerPool"
"fmt"
"io/fs"
"os"
"path/filepath"
"runtime"
"sort"
"time"
)

func main() {
// 参数验证
if len(os.Args) != 3 {
fmt.Printf("使用方法: %s [目录路径] [搜索关键词]\n", os.Args[0])
os.Exit(1)
}
dir := os.Args[1]
keyword := os.Args[2]

// 目录存在性检查
if _, err := os.Stat(dir); os.IsNotExist(err) {
fmt.Printf("目录 '%s' 不存在\n", dir)
os.Exit(1)
}

// 初始化配置
workerCount := runtime.NumCPU() * 2
fmt.Printf("搜索目录: '%s', 关键词: '%s'\n", dir, keyword)

startTime := time.Now()

// 创建协程池
pool := workerPool.NewWorkerPool(workerCount, workerCount*100)

// 遍历目录获取文件列表
paths, err := walkDirectory(dir)
if err != nil {
fmt.Printf("遍历目录错误: %v\n", err)
os.Exit(1)
}

service.SetTotal(int64(len(paths)))
fmt.Printf("发现文件数: %d\n", len(paths))

// 结果收集通道
results := make(chan workerPool.TaskResult, workerCount*100)
done := make(chan bool, 1)

// 启动结果收集器
go collectResults(results, done, len(paths))

// 提交搜索任务
submittedTasks := 0
for _, path := range paths {
task := service.Task{Path: path, Keyword: keyword}

err := pool.Produce(func() (interface{}, error) {
return service.Search(task)
})

if err != nil {
fmt.Printf("任务提交失败: %v\n", err)
} else {
submittedTasks++
}
}

fmt.Printf("成功提交任务数: %d\n", submittedTasks)

// 转发结果
go forwardResults(pool.GetResults(), results)

// 等待所有任务完成
pool.Close()
close(results)

<-done // 等待结果收集完成

// 输出统计信息
total, found, lines := service.GetInfo()
elapsed := time.Since(startTime)

fmt.Printf("\n============= 搜索完成 =============\n")
fmt.Printf("总文件数: %d\n", total)
fmt.Printf("包含关键词的文件数: %d\n", found)
fmt.Printf("总匹配行数: %d\n", lines)
fmt.Printf("耗时: %v\n", elapsed)

submitted, completed := pool.GetInfo()
fmt.Printf("任务提交数: %d\n", submitted)
fmt.Printf("任务完成数: %d\n", completed)
}

// walkDirectory 遍历目录获取文件列表

func walkDirectory(dir string) ([]string, error) {
var paths []string

err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
fmt.Printf("访问错误 '%s': %v\n", path, err)
return nil // 跳过错误文件
}

if !d.IsDir() {
paths = append(paths, path)
}

return nil
})

if err != nil {
return nil, err
}

return paths, nil
}

// forwardResults 结果转发
func forwardResults(source <-chan workerPool.TaskResult, dest chan<- workerPool.TaskResult) {
for result := range source {
dest <- result
}
}

// collectResults 收集和处理结果
func collectResults(results <-chan workerPool.TaskResult, done chan<- bool, total int) {
var finalResults []service.Result
processed := 0

// 处理每个结果
for result := range results {
processed++

// 进度显示
if processed%100 == 0 {
progress := float64(processed) / float64(total) * 100
fmt.Printf("处理进度: %d/%d (%.2f%%)\n", processed, total, progress)
}

// 类型断言获取搜索结果
if searchResult, ok := result.Result.(service.Result); ok {
finalResults = append(finalResults, searchResult)

// 更新统计
if len(searchResult.Info) > 0 {
service.AddFound(1)
service.AddLines(int64(len(searchResult.Info)))
}
}
}
// 输出最终结果
fmt.Printf("\n================搜索结果================\n")
printResults(finalResults)
done <- true
}

// printResults 格式化输出结果
func printResults(results []service.Result) {
// 按文件路径排序
sort.Slice(results, func(i, j int) bool {
return results[i].Path < results[j].Path
})

for _, result := range results {
if result.Err != nil {
fmt.Printf("\n错误: %s - %v\n", result.Path, result.Err)
continue
}
if len(result.Info) > 0 {
fmt.Printf("\n%s:\n", result.Path)
for _, info := range result.Info {
fmt.Printf(" %d: %s\n", info.Line, info.Content)
}
}
}
}

数据库

📚 数据库核心概念

数据库定义

数据库是用于长期保存数据、高并发访问数据、快速查询数据的系统,是后端的核心基础设施。

数据库五大特性

  • 持久化存储 - 数据写入后不会丢失
  • 并发处理能力 - 多用户同时访问,保证数据正确性
  • 高效查询 - 通过索引、优化器实现快速检索
  • 安全与权限控制 - 精确控制数据访问权限
  • 事务一致性 - 通过ACID保证数据完整性

数据库分类

关系型数据库(本课重点)

特点:

  • 表结构
  • SQL标准
  • 强一致性

代表:

  • MySQL
  • PostgreSQL
  • Oracle

适用:订单管理、用户系统、业务数据

非关系型数据库

特点:

  • 无固定结构
  • 高扩展性

代表:

  • Redis(键值)
  • MongoDB(文档)
  • ElasticSearch(搜索)

适用:缓存、日志、大数据分析

ACID 原则

ACID 是数据库事务的四个核心特性,用于确保数据的可靠性和一致性,尤其在关系型数据库(如 MySQL、PostgreSQL)中至关重要:

  • A:原子性(Atomicity)​

    事务被视为一个不可分割的最小单元,事务中的所有操作要么全部成功,要么全部失败回滚,不会停留在中间状态。例如,银行转账必须同时完成扣款和收款,否则回滚到初始状态。

  • C:一致性(Consistency)​

    事务执行前后,数据库必须保持一致性状态,即所有数据约束、规则(如唯一性、外键)都得到遵守。例如,转账前后账户总金额应保持不变。

  • I:隔离性(Isolation)​

    多个并发事务同时执行时,彼此隔离,互不干扰。数据库通过锁或并发控制机制防止脏读、不可重复读等问题。

  • D:持久性(Durability)​

    事务一旦提交,其对数据的修改就是永久性的,即使系统发生故障(如断电)也不会丢失,通常通过持久化存储(如硬盘)实现。

CAP 原则

CAP 理论是分布式系统设计的基础理论,由 Eric Brewer 提出,指出在分布式系统中,以下三个特性无法同时完全满足,最多只能实现其中两个:

  • C:一致性(Consistency)​

    在分布式系统的所有节点上,同一时刻读取的数据都是最新的相同版本。例如,用户在任何节点查询数据,都会得到最新的写入结果,否则返回错误。

  • A:可用性(Availability)​

    系统始终能够响应请求(不保证数据最新),即使部分节点故障,每个请求都能获得非错误响应。例如,即使数据未同步,系统也返回当前可用的数据。

  • P:分区容错性(Partition Tolerance)​

    系统在遇到网络分区(即节点之间因网络问题无法通信)时,仍然能够继续运行。这是分布式系统的基本要求,因为网络分区难以避免。

CAP 的权衡(常见于分布式数据库设计):

  • CP 系统:保证一致性和分区容错性,牺牲可用性。例如,发生网络分区时,系统可能拒绝写入或读取以确保数据一致。代表:MongoDB(通常配置为 CP)、HBase。

  • AP 系统:保证可用性和分区容错性,牺牲一致性。例如,发生网络分区时,系统仍可读写,但数据可能临时不一致。代表:Cassandra、DynamoDB。

  • CA 系统:保证一致性和可用性,牺牲分区容错性。这类系统通常不是真正的分布式系统,如单机关系数据库。

MySQL

数据类型

📊 MySQL数据类型速查

类型 占用空间 范围 用途 注意事项
INT 4字节 -2^31~2^31-1 主键、计数 最常用
BIGINT 8字节 -2^63~2^63-1 分布式ID 雪花ID
VARCHAR(n) 可变 0~65535字符 姓名、标题 最推荐
DECIMAL(10,2) 可变 精确小数 金额、财务 无精度误差
DATETIME 8字节 1000~9999年 创建时间 最推荐
TIMESTAMP 4字节 1970~2038年 自动时间戳 2038年问题

基础语法

SQL三大组成部分

DDL(数据定义语言)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

-- 创建数据库

CREATE DATABASE school CHARACTER SET utf8mb4;

-- 创建表

CREATE TABLE students (

id INT PRIMARY KEY AUTO_INCREMENT,

name VARCHAR(50) NOT NULL,

age INT,

created_at DATETIME DEFAULT CURRENT_TIMESTAMP

);

-- 修改表结构

ALTER TABLE students ADD COLUMN email VARCHAR(100);

DML(数据操作语言)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

-- 插入数据

INSERT INTO students(name, age) VALUES ('张三', 18);

-- 查询数据

SELECT * FROM students WHERE age > 17;

SELECT * FROM students WHERE name LIKE '%张%';

-- 更新数据

UPDATE students SET age = 20 WHERE id = 1;

-- 删除数据

DELETE FROM students WHERE id = 1;

DCL(权限控制)

1
2
3
4
5
6
7
8
9
10
11
12

-- 创建用户

CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';

-- 授权

GRANT SELECT, INSERT ON school.* TO 'username'@'localhost';

-- 撤销权限

REVOKE INSERT ON school.* FROM 'username'@'localhost';

事务(Transaction)

ACID原则

  • Atomicity(原子性) - 全部成功或全部失败
  • Consistency(一致性) - 数据状态合法转移
  • Isolation(隔离性) - 事务间互不干扰
  • Durability(持久性) - 提交后数据不丢失

事务流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

-- 开启事务

START TRANSACTION;

-- 执行多条SQL

UPDATE account SET balance = balance - 100 WHERE id = 1;

UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 提交或回滚

COMMIT; -- 成功提交

-- ROLLBACK; -- 失败回滚

并发问题与隔离级别

隔离级别 脏读 不可重复读 幻读 MySQL默认
读未提交
读已提交
可重复读 ✅*
串行化

⚠️ 注意:MySQL默认使用**可重复读(Repeatable Read)**级别,是性能与一致性的最佳平衡。

Go操作MySQL

原生SQL方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 安装驱动

// go get github.com/go-sql-driver/mysql

import (

"database/sql"

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

)

// 连接数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/school?charset=utf8mb4&parseTime=True&loc=Local"
db, err := sql.Open("mysql",dsn)

// 插入数据

result, err := db.Exec("INSERT INTO students(name, age) VALUES (?, ?)", "王五", 16)

// 查询单条

var name string

var age int

err := db.QueryRow("SELECT name, age FROM students WHERE id = ?", 1).Scan(&name, &age)

// 查询多条

rows, err := db.Query("SELECT id, name FROM students WHERE age > ?", 15)

defer rows.Close()

for rows.Next() {

var id int

var name string

rows.Scan(&id, &name)

fmt.Println(id, name)

}

// 事务

tx, err := db.Begin()

tx.Exec("UPDATE students SET age = age + 1 WHERE id = 1")

tx.Exec("INSERT INTO score_log(student_id, change_amount) VALUES (1, 1)")

err = tx.Commit() // 或 tx.Rollback()

Gorm

安装与连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 安装GORM

// go get -u gorm.io/gorm

// go get -u gorm.io/driver/mysql

import (

"gorm.io/driver/mysql"

"gorm.io/gorm"

)

// 连接数据库

dsn := "root:123456@tcp(127.0.0.1:3306)/school?charset=utf8mb4&parseTime=True&loc=Local"

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

定义模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type Student struct {

ID uint gorm:"primaryKey"

Name string

Age int

Grade string

CreatedAt time.Time

}

// 自定义表名

func (Student) TableName() string {

return "students"

}

// 自动建表

db.AutoMigrate(&Student{})

CRUD操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 创建(Create)

student := Student{Name: "张三", Age: 18, Grade: "高三"}

result := db.Create(&student)

fmt.Printf("插入成功,ID: %d\n", student.ID)

// 查询单条(Read)

var stu Student

db.First(&stu, 1) // 按主键查询

db.First(&stu, "name = ?", "张三") // 按条件查询

// 查询多条

var students []Student

db.Where("age > ?", 17).Find(&students)

// 更新(Update)

db.Model(&Student{}).Where("id = ?", 1).Update("age", 20)

db.Model(&Student{}).Where("id = ?", 1).Updates(Student{Age: 20, Grade: "高三"})

// 删除(Delete)

db.Delete(&Student{}, 1)

链式查询

1
2
3
4
5
6
7
8
9
10
11
12

var list []Student

db.Where("age > ?", 16).

Where("grade = ?", "高三").

Order("age desc").

Limit(10).

Find(&list)

GORM事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

// 写法1:自动事务(推荐)

err := db.Transaction(func(tx *gorm.DB) error {

if err := tx.Create(&Student{Name: "A"}).Error; err != nil {

return err // 自动回滚

}

if err := tx.Create(&Student{Name: "B"}).Error; err != nil {

return err // 自动回滚

}

return nil // 自动提交

})

// 写法2:手动事务

tx := db.Begin()

tx.Create(&Student{Name: "A"})

tx.Model(&Student{}).Where("id=1").Update("age", 20)

tx.Commit() // 或 tx.Rollback()

原生SQL vs GORM

场景 原生SQL GORM 推荐
简单查询 直接灵活 简洁易读 根据复杂度选择
复杂联表 更可控 关联查询 原生SQL
快速开发 代码量大 高效 GORM
性能优化 极致优化 自动优化 原生SQL
事务处理 手动控制 自动封装 GORM

总结

核心要点总结:

  1. 关系型数据库是业务系统的基石,MySQL是最常用选择
  2. 事务ACID保证数据一致性,是金融、电商场景的必须品
  3. GORM框架极大提升开发效率,是Go语言数据库操作的首选
  4. 原生SQL在复杂查询和性能优化时仍有价值
  5. 合理选择隔离级别和数据类型是数据库设计的关键

拓展:其他数据库

0%