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=2用union - 数据类型转换不走索引
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 索引字段
锁
按粒度分
- 全局锁(锁整个库)
- 表级锁(锁整张表)
- 行级锁(锁某一行)
按属性分
- 共享锁(S 锁) :读锁,大家可以一起读
- 排他锁(X 锁) :写锁,只能一个人写
- 意向锁:表锁 + 行锁之间的协调锁
InnoDB 特有(最重要)
- 记录锁(Record Lock)
- 间隙锁(Gap Lock)
- 临键锁(Next-Key Lock) ------ 默认锁,导致死锁最多
- 自增锁(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 全套运行完成!")
}