Gin (六) mysql的操作 gin操作mysql

mysql介绍

mysql属于关系型数据库。

ubuntu安装mysql

c 复制代码
//1 更新
sudo apt update
sudo apt upgrade -y

//2 安装mysql 默认是MySQL8.0
sudo apt install -y mysql-server

//3 查看当前的版本号
mysql --version

//4 检查服务状态
sudo systemctl status mysql

//5 开启自启动默认
sudo systemctl enable mysql

//6 修改密码,默认没有密码
//6.1进入mysql
sudo mysql -u root

//6.2 修改密码
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '123456';
FLUSH PRIVILEGES; 
exit;

//6.3 之后就可以使用新的密码了
mysql -u root -p

//7 修改安全配置
sudo mysql_secure_installation

//8 修改绑定地址
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
// 8.1 bind-address = 127.0.0.1改成bind-address = 0.0.0.0。这样就可以远程登录到服务器
// 8.2授权root从任意IP登录
sudo mysql -u root -p
CREATE USER 'root'@'%' IDENTIFIED BY '你的强密码'; 
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES; 
exit;

//8.3 重启mysql
sudo systemctl restart mysql

//8.4 放行防火墙
sudo ufw allow 3306

//9 测试远程连接,客户端需要安装sudo apt install -y mysql-client
mysql -h 你的UbuntuIP -u root -p

mysql基本概念

理解为文件夹,可以存放很多表

sql 复制代码
CREATE DATABASE test; -- 创建库 
CREATE DATABASE IF NOT EXISTS test; -- 安全创建 
SHOW DATABASES; -- 看所有库 
USE test; -- 进入库 
DROP DATABASE test; -- 删除库

sql 复制代码
// 增加
INSERT INTO user(username, password, age) values ("张三","123456",20);
//查询
SELECT *  FROM user;                    -- 查所有数据
SELECT username, password FROM user;    -- 查指定列
SELECT * FROM user where id=1;          -- 条件查询
SELECT * FROM user where age>18;        -- 范围查询
SELECT * FROM user ORDER BY id DESC;    -- 排序
//修改
UPDATE user SET age=21 where id=1;      -- 必须加where,否则全表被改
//删除
DELETE FROM  user where id=1;           -- 必须加where,否则全部删除

//常用条件命令
WHERE id=1               等于
WHERE age>18             大于
WHERE age<30             小于
WHERE name LIKE '%zhang%' 包含zhang
WHERE id IN (1,2,3)     在列表里
WHERE age BETWEEN 18 AND 30
AND                     并且
OR                      或者

//聚合查询,统计
SELECT COUNT(*) FROM user;     -- 总条数
SELECT MAX(age) FROM user;     -- 最大年龄
SELECT MIN(age) FROM user;     -- 最小年龄
SELECT AVG(age) FROM user;     -- 平均年龄

//分页
SELECT * FROM user LIMIT 10;          -- 前10条
SELECT * FROM user LIMIT 10 OFFSET 0; -- 第1页
SELECT * FROM user LIMIT 10 OFFSET 10;-- 第2页

// 关联查询INNER JOIN 内连接
SELECT
  article.title,    -- 文章标题
  user.username     -- 作者名字
FROM article
INNER JOIN user
  ON article.user_id = user.id;

// 关联查询LEFT JOIN 左连接,必须把小表放左边,性能损失小
SELECT
  article.title,
  user.username
FROM article
LEFT JOIN user
  ON article.user_id = user.id;

存多行的数据,

字段

一行有多列的数据

保存一条数据的基本单位

数据类型

整数

类型 占用字节 取值范围 使用场景
TINYINT 1 -128 ~ 127 状态、开关、性别、布尔标识 (0/1)
INT 4 ±21 亿左右 普通编号、数量、年龄
BIGINT 8 超大范围 主键 ID、订单号、分布式 ID(推荐主键)

浮点 & 定点数

类型 特点 使用建议
DECIMAL(m,n) 定点精确存储 金额、价钱必用 ,例:DECIMAL(10,2) 总 10 位,小数 2 位
FLOAT/DOUBLE 二进制浮点,存在精度丢失 禁止存金额,仅科学计算

字符串类型

类型 特性 适用场景
CHAR(N) 定长,不足 N 自动补空格,占用固定 N 字节 固定短文本:手机号 char (11)、验证码 char (6)
VARCHAR(N) 变长,实际存多少占多少 + 1~2 字节长度头 用户名、昵称、地址、邮箱(绝大多数字符串)
TEXT 超大文本,无法创建普通索引 文章详情、长篇备注

时间日期类型

类型 格式 用途
DATETIME yyyy-MM-dd HH:mm:ss 创建时间、修改时间(首选)
DATE yyyy-MM-dd 仅年月日:生日、账单日期
TIME HH:mm:ss 仅时分秒
TIMESTAMP 时间戳自动转时区 跨时区业务,范围偏小

布尔类型 MySQL无原生 bool ,统一用:TINYINT(1)

  • 0:false、禁用、否
  • 1:true、启用、是

建表例子

sql 复制代码
CREATE TABLE `user` (
  id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
  username VARCHAR(50) NOT NULL COMMENT '用户名',
  phone CHAR(11) COMMENT '手机号',
  age TINYINT COMMENT '年龄',
  balance DECIMAL(10,2) DEFAULT 0.00 COMMENT '账户余额',
  status TINYINT DEFAULT 1 COMMENT '状态:0禁用1正常',
  create_time DATETIME DEFAULT NOW() COMMENT '创建时间',
  update_time DATETIME DEFAULT NOW() COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

事务

sql 复制代码
begin;
update
update
commit;

//回滚
rollback

索引优化

索引的作业可以实现不用一页页翻(全表扫描),直接查目录快速定位数据。

索引的分类

  • 主键索引 叶子节点存数据,每行的数据的主键要唯一
  • 唯一索引 列的值不能重复,可以为null
  • 普通索引 加速查询,没有其它功能
  • 联合索引 多个字段组成一个索引
  • 全文搜索 用like %abc%这种搜索
  • 空间索引 地理坐标用
sql 复制代码
-- 主键索引
CREATE TABLE t(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20),
  phone VARCHAR(11) UNIQUE,  -- 唯一索引
  INDEX idx_name(name)       -- 普通索引
);

-- 联合索引
CREATE TABLE t(
  id INT PRIMARY KEY,
  a INT,
  b INT,
  c INT,
  INDEX idx_a_b_c(a,b,c)  -- 联合索引
);

-- 对于已经存在的表加索引
-- 普通索引
CREATE INDEX idx_name ON t(name);

-- 唯一索引
CREATE UNIQUE INDEX idx_phone ON t(phone);

-- 联合索引
CREATE INDEX idx_a_b ON t(a,b);

-- 全文索引
CREATE FULLTEXT INDEX idx_content ON t(content);

-- 查看索引
SHOW INDEX FROM t;

-- 删除索引
DROP INDEX idx_name ON t;

-- 查看SQL是否命中索引
EXPLAIN SELECT * FROM t WHERE name = 'abc';
看 type 列:
ref / range → 用到索引
ALL → 全表扫描(没用到)

联合索引的条件,最左前缀原则,比如(a,b,c)查询必须查a,不能只查询B和C

索引优化

  • 优先给 WHERE 条件字段建索引
  • 禁止在列上操作
sql 复制代码
-- 坏
WHERE YEAR(create_time) = 2025

-- 好
WHERE create_time >= '2025-01-01'
  • 禁止 select *只查需要的字段,避免回表
  • 模糊查询 %开头 不走索引
sql 复制代码
-- 不走
WHERE name LIKE '%abc'

-- 走
WHERE name LIKE 'abc%'
  • 联合索引等值放前面,范围放后面`WHERE a=1 AND b=2 AND c>3
  • 数据少的表不要建索引(性别、状态)
  • OR或导致索引失效WHERE a=1 OR b=2union
  • 数据类型转换不走索引WHERE phone = 12345 -- phone 是字符串,却传数字 → 索引失效
  • 单表索引建议 ≤ 5 个
  • 主键用自增 ID 最好,避免 UUID 导致页分裂
  • 大表加索引要小心,会锁表,建议用 pt-online-schema-change
  • 覆盖索引(性能最高),查询的字段刚好在索引里,不需要回表SELECT name FROM t WHERE name='xx'
  • 不要用 NOT IN!=IS NOT NULL容易导致索引失效
  • 排序尽量用索引字段
  • 分组也走索引GROUP BY 索引字段

按粒度分

  1. 全局锁(锁整个库)
  2. 表级锁(锁整张表)
  3. 行级锁(锁某一行)

按属性分

  1. 共享锁(S 锁) :读锁,大家可以一起读
  2. 排他锁(X 锁) :写锁,只能一个人写
  3. 意向锁:表锁 + 行锁之间的协调锁

InnoDB 特有(最重要)

  1. 记录锁(Record Lock)
  2. 间隙锁(Gap Lock)
  3. 临键锁(Next-Key Lock) ------ 默认锁,导致死锁最多
  4. 自增锁(AUTO-INC)
sql 复制代码
// 手动加 **表读锁(共享)**
//  释放之前不能写操作
lock table user read; -- 所有人可读,不可写

unlock user;

// 手动加 **表写锁(排他)**
lock table user write -- 只有自己能读写,别人全卡住
unlock user;

//手动加 **行读锁(共享锁)**
BEGIN; 
SELECT * FROM user WHERE id=1 LOCK IN SHARE MODE; 
COMMIT; -- 提交才解锁

//手动加 **行写锁(排他锁)**
BEGIN; 
SELECT * FROM user WHERE id=1 FOR UPDATE; -- 最常用 
UPDATE user SET name='a' WHERE id=1; 
COMMIT;

//查看当前锁等待
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;

// 查看死锁日志
SHOW ENGINE INNODB STATUS;

数据备份与恢复

css 复制代码
mysqldump -uroot -p --single-transaction --master-data=2 --databases db1 > db_20260601.sql

mysql -uroot -p < db_20260601.sql

gin操作mysql

实际项目中不会代码创建数据库和表。代码都只是负责增删改查。

创建数据库

go 复制代码
package main

import (
        "errors"
        "fmt"
        "gorm.io/driver/mysql"
        "gorm.io/gorm"
        "gorm.io/gorm/logger"
        "time"
)

// ===================== 1. 模型定义(和 SQLite 完全通用,不用改) =====================
// gorm.Model = ID、CreatedAt、UpdatedAt、DeletedAt(支持软删除)
type User struct {
        gorm.Model
        Name   string `gorm:"size:32;not null;comment:用户名"`
        Age    uint8  `gorm:"comment:年龄"`
        Email  string `gorm:"uniqueIndex;comment:唯一邮箱"` // 唯一索引
        Status int    `gorm:"default:1;comment:状态 1正常 2禁用"`
}

// ===================== 2. MySQL 数据库初始化(重点在这里!) =====================
func initDB() (*gorm.DB, error) {
        // ===================== MySQL 连接配置(你必须改这里!) =====================
        // 格式:用户名:密码@tcp(IP:端口)/数据库名?charset=utf8mb4&parseTime=True&loc=Local
        dsn := "root:123456@tcp(127.0.0.1:3306)/gorm_demo?charset=utf8mb4&parseTime=True&loc=Local"

        // 连接 MySQL
        db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
                Logger: logger.Default.LogMode(logger.Info), // 打印SQL(开发必开)
        })
        if err != nil {
                return nil, fmt.Errorf("MySQL连接失败: %w", err)
        }

        // ===================== MySQL 连接池配置(生产必须配) =====================
        sqlDB, err := db.DB()
        if err != nil {
                return nil, err
        }
        // 是正在的和空闲的总数。实际最大并发就是100,空闲是0
        sqlDB.SetMaxOpenConns(100) // 最大连接数

        // 是用完一个连接之后,这个就属于空闲的。当超过20个空闲的就会断开
        sqlDB.SetMaxIdleConns(20) // 最大空闲连接

        // 连接从创建到现在,1个小时就会关闭这个连接
        // 不论最近1分钟内这个连接是否使用过
        sqlDB.SetConnMaxLifetime(1 * time.Hour) // 连接最大存活时间

        // 一个连接最近30分钟内没有使用,也直接关闭。
        // 和上面的maxlifetime有一个满足就关闭
        sqlDB.SetConnMaxIdleTime(30 * time.Minute) // 空闲最大时间

        // ===================== 自动建表(自动创建、更新表结构,不删字段) =====================
        // 只会增加,不会删除
        // 1 删除多余字段
        // db.Migrator().DropColumn(&User{}, "email")
        // 2 删除废弃索引
        // db.Migrator().DropIndex(&User{}, "idx_mobile")
        // 3 再执行自动迁移,补齐新增字段/新增索引
        // db.AutoMigrate(&User{})
        // 然后实际生产过程,一般不会通过代码去做这些操作,都是直接操作sql
        // 删除无用字段
        // alter table users drop column email;
        // 删除废弃索引
        // drop index idx_mobile on users;
        // 实际生产过程,连AutoMigrate都不会执行。代码只负责增删改查
        err = db.AutoMigrate(&User{})
        if err != nil {
                return nil, fmt.Errorf("建表失败: %w", err)
        }

        fmt.Println("✅ MySQL 连接 + 表迁移 成功!")
        return db, nil
}

func main() {
        // 初始化数据库
        db, err := initDB()
        if err != nil {
                fmt.Printf("❌ 初始化失败: %v\n", err)
                return
        }

        // ===================== 3. 新增(Create) =====================
        fmt.Println("\n========== 1. 新增操作 ==========")

        // 单条插入
        user := User{
                Name:  "李白",
                Age:   25,
                Email: "libai@test.com",
        }
        db.Create(&user)
        fmt.Printf("✅ 单条新增成功,ID: %d\n", user.ID)

        // 批量插入
        users := []User{
                {Name: "杜甫", Age: 30, Email: "dufu@test.com"},
                {Name: "王维", Age: 28, Email: "wangwei@test.com"},
                {Name: "孟浩然", Age: 32, Email: "menghaoran@test.com"},
        }
        // 第二个参数是每一批插入多少条数据
        // 如果有300条数据,第二个参数100,则会分3批插入。
        // 当前只有3条,插入1次
        db.CreateInBatches(users, 100)
        fmt.Println("✅ 批量新增 3 条成功")

        // ===================== 4. 查询(Query) =====================
        fmt.Println("\n========== 2. 查询操作 ==========")
        var u User

        // 1. 根据ID查询
        // 如果数字,直接和主键id搜索。First做了limit=1操作
        err = db.First(&u, 1).Error
        if errors.Is(err, gorm.ErrRecordNotFound) {
                fmt.Println("ID=1 用户不存在")
        } else {
                fmt.Printf("ID查询: %+v\n", u)
        }

        // 2. 条件查询
        // 这里First只返回第一个找到的
        // 如果返回所有叫李白的
        // var list []User
        // db.where("name = ?", "李白"),Find(&list)
        db.Where("name = ?", "李白").First(&u)
        fmt.Printf("姓名=李白: %+v\n", u)

        // 3. 模糊查询
        // 李%可以走索引,但是%李就不走索引了,会全盘搜索
        // 如果有走索引则需要优化
        // ALTER TABLE user ADD FULLTEXT INDEX idx_name(name);
        // db.Where("MATCH(name) AGAINST(?)", "李").Find(&list)
        // 虽然支持倒排索引,但是功能很差。
        // 比如李白好帅,李白可以搜到。李就搜不到了
        // 比如这李白好帅,这李可以搜到,李白不能搜到
        // 如果要模糊查找,使用elasticsearch。
        var list []User
        db.Where("name LIKE ?", "%李%").Find(&list)
        fmt.Printf("名字含李:共 %d 条\n", len(list))

        // 4. 排序 + 分页
        db.Order("age DESC").Limit(2).Offset(0).Find(&list)
        fmt.Printf("分页查询:共 %d 条\n", len(list))

        // 5. 统计数量
        var count int64
        db.Model(&User{}).Where("age > 25").Count(&count)
        fmt.Printf("年龄>25:%d 人\n", count)

        // ===================== 5. 更新(Update) =====================
        fmt.Println("\n========== 3. 更新操作 ==========")

        // 先查询
        var updateUser User
        db.First(&updateUser, 1)

        // 单字段更新
        // 上面是演示,只是为了构建user这个对象
        // 直接创建也是一样的。这里一定要对userid建立索引,否则性能很差很差
        // var updateUser = User{userid:2002203}
        db.Model(&updateUser).Update("age", 26)

        // 多字段更新(结构体)
        db.Model(&updateUser).Updates(User{Name: "李太白", Status: 2})

        // 多字段更新(map,支持零值)
        db.Model(&updateUser).Updates(map[string]any{"status": 1})

        fmt.Println("✅ 更新成功")

        // ===================== 6. 删除(Delete) =====================
        fmt.Println("\n========== 4. 删除操作 ==========")

        // 软删除(数据还在,标记删除,查询自动过滤)
        db.Delete(&User{}, 1)
        fmt.Println("✅ 软删除 ID=1 成功")

        // 批量条件删除
        db.Where("age = ?", 32).Delete(&User{})
        fmt.Println("✅ 批量删除 age=32 成功")

        // 物理删除(永久删除,慎用)
        // db.Unscoped().Delete(&User{}, 1)

        // ===================== 7. 事务(Transaction) =====================
        fmt.Println("\n========== 5. 事务操作 ==========")
        err = db.Transaction(func(tx *gorm.DB) error {
                // 操作1
                if err := tx.Create(&User{Name: "王安石", Age: 40, Email: "wanganshi@test.com"}).Error; err != nil {
                        return err // 回滚
                }
                // 操作2
                if err := tx.Create(&User{Name: "苏轼", Age: 35, Email: "sushi@test.com"}).Error; err != nil {
                        return err // 回滚
                }

                fmt.Println("✅ 事务提交成功")

                // 如果这里return error.New("xxx")返回了错误,会自动回滚。
                return nil // 提交
        })
        if err != nil {
                fmt.Printf("❌ 事务回滚: %v\n", err)
        }

        // ===================== 8. 查询已软删除的数据 =====================
        var deletedUsers []User
        db.Unscoped().Where("deleted_at IS NOT NULL").Find(&deletedUsers)
        fmt.Printf("\n已删除用户数量:%d\n", len(deletedUsers))

        fmt.Println("\n🎉 MySQL + GORM 全套运行完成!")
}
相关推荐
JustHappy1 小时前
古法编程秘籍(三):为什么需要函数?因为程序员讨厌重复劳动
前端·javascript·后端
AI打工人1 小时前
Python并发编程:多线程与多进程实战指南
后端
Jiude1 小时前
AI面对真机调试也束手无策?我将方法论形成了一套SKILL 🛠️🤖
前端·后端·测试
千云2 小时前
AI Coding 落地探索日志·实践篇·提效操作指南
后端
DigitalOcean2 小时前
DigitalOcean 的 AI 推理路由器是如何构建的
后端·aigc·agent
TYKJ0232 小时前
租GPU服务器前必须确认的5个隐藏成本
服务器·后端·ai编程
回家路上绕了弯3 小时前
LangChain4j 万字实战:Java生态最火大模型框架,从入门到企业级RAG与Agent落地
后端
东风微鸣3 小时前
Rook-Ceph v1.20.0 CSI ServiceAccount 命名不匹配 Bug 及修复方案
后端