Init
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
---
|
||||
tags: [MySQL, 架构, 进程模型, Server]
|
||||
create time: 2026-05-16 00:00
|
||||
---
|
||||
|
||||
# MySQL 架构与进程模型
|
||||
|
||||
## 概述
|
||||
|
||||
理解 MySQL 的内部架构是性能调优和故障排查的前提。本文将从 Client-Server 模型出发,逐层拆解 MySQL 的四大功能层以及不同存储引擎下的线程调度机制。
|
||||
|
||||
## 四层架构
|
||||
|
||||
MySQL 遵循经典的三层 Client-Server 架构,但在服务端内部被清晰划分为四层:
|
||||
|
||||
```mermaid
|
||||
graph BT
|
||||
subgraph "应用层"
|
||||
A["应用程序<br/>(Go / Java / Python)"]
|
||||
end
|
||||
|
||||
subgraph "连接层<br/>Connection Layer"
|
||||
B["Thread Pool"]
|
||||
C["Authentication"]
|
||||
D["Connection Management"]
|
||||
end
|
||||
|
||||
subgraph "SQL 层<br/>SQL Layer"
|
||||
E["Query Cache ⚠️ 8.0 已移除"]
|
||||
F["Parser → Preprocessor"]
|
||||
G["Optimizer"]
|
||||
H["Executor"]
|
||||
end
|
||||
|
||||
subgraph "存储引擎层<br/>Storage Engine"
|
||||
I["InnoDB"]
|
||||
J["MyISAM"]
|
||||
K["Memory"]
|
||||
L["Archive"]
|
||||
end
|
||||
|
||||
subgraph "文件系统"
|
||||
M["Data Files"]
|
||||
N["Binlog"]
|
||||
O["Redo Log"]
|
||||
end
|
||||
|
||||
A --> B
|
||||
B --> F
|
||||
F --> G --> H --> I
|
||||
H --> J
|
||||
H --> K
|
||||
H --> L
|
||||
I --> M
|
||||
I --> N
|
||||
I --> O
|
||||
```
|
||||
|
||||
### 各层职责
|
||||
|
||||
**连接层**负责处理客户端的连接请求,包括身份验证、权限校验、SSL/TLS 协商以及线程分配。MySQL 采用「一连接一线程」模型——每个客户端连接对应一个独立的服务器线程,这意味着万级并发时会产生巨大的线程开销。
|
||||
|
||||
> [!NOTE] 为什么高并发场景要考虑线程池?
|
||||
> MySQL 原生没有内置线程池(Percona Server 提供)。当并发量超过数千时,上下文切换的开销会成为瓶颈。解决方案有三:
|
||||
> - **ProxySQL / MaxScale**:前端代理层做连接复用
|
||||
> - **MySQL Thread Pool Plugin**:企业版功能或 Percona 分支
|
||||
> - **连接池**:在应用层控制并发连接数(如 Go 的 `sql.DB`)
|
||||
|
||||
**SQL 层**是整个数据库的核心,完成 SQL 语句的解析、优化和执行。这一层实现了 SQL 标准语法、查询优化策略、函数系统和事务管理。值得注意的是,**很多用户熟悉的 SQL 特性其实都在这一层实现**——比如子查询改写、JOIN 顺序优化等。
|
||||
|
||||
**存储引擎层**提供了可插拔的存储方案。不同的引擎对索引、锁、事务的支持各不相同。InnoDB 承担了默认的事务型负载,而 MyISAM、Memory 等引擎则针对特定场景做了优化。由于大多数生产环境只使用 InnoDB,现代运维中这一层其实非常安静。
|
||||
|
||||
**文件系统层**将页(Page,默认 16KB)写入磁盘。InnoDB 有自己的 Buffer Pool 来管理数据缓存,同时也依赖操作系统的 Page Cache。**Linux 内核的 I/O 调度器对 MySQL 性能有直接影响**。
|
||||
|
||||
## 线程模型详解
|
||||
|
||||
MySQL 服务启动时会创建一组后台线程,同时为每个连接分配专用线程:
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class MasterThread {
|
||||
+binlog dump
|
||||
+purge redo log
|
||||
+truncate binary log
|
||||
+DD event cleanup
|
||||
+change buffer merge
|
||||
}
|
||||
|
||||
class ConnectionThread {
|
||||
+parse query
|
||||
+optimize
|
||||
+execute
|
||||
+send result
|
||||
}
|
||||
|
||||
class WorkerThreads {
|
||||
+InnoDB read worker
|
||||
+InnoDB log writer
|
||||
+InnoDB page cleaner
|
||||
}
|
||||
|
||||
class ThreadPool {
|
||||
<<optional>>
|
||||
+acceptor thread
|
||||
+worker pool
|
||||
+idle stack management
|
||||
}
|
||||
|
||||
MasterThread ..> WorkerThreads : "coordinates"
|
||||
ThreadPool --> ConnectionThread : "dispatches"
|
||||
```
|
||||
|
||||
### 关键后台线程
|
||||
|
||||
| 线程 | 职责 |
|
||||
|------|------|
|
||||
| **Master Thread** | InnoDB 专属,协调写操作、刷新脏页、合并 Change Buffer |
|
||||
| **Log Thread** | 将 Redo Log Buffer 刷入 Redo Log File |
|
||||
| **Page Cleaner** | 主动将脏页从 Buffer Pool 刷盘,避免崩溃恢复时间过长 |
|
||||
| **Purge Thread** | 删除 Undo Log 中标记为废弃的行记录 |
|
||||
| **DD Event Cleanup** | 清理数据字典中的过期对象信息 |
|
||||
|
||||
### 连接线程生命周期
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> New: 客户端发起 TCP 连接
|
||||
New --> Handshake: 握手协议 + 认证
|
||||
Handshake --> Authenticated: 认证成功
|
||||
Handshake --> Closed: 认证失败
|
||||
Authenticated --> Sleeping: 发送命令
|
||||
Sleeping --> Executing: 收到新命令
|
||||
Executing --> Sending: 执行完毕
|
||||
Sending --> Sleeping: 返回结果集
|
||||
Sleeping --> Closed: 客户端断开 / 超时
|
||||
Sleeping --> Killed: 管理员 kill 掉
|
||||
```
|
||||
|
||||
> [!QUESTION] 什么是 MySQL 连接超时?
|
||||
> 如果客户端空闲太久不发消息会发生什么?MySQL 有两个关键超时参数:
|
||||
> - **`wait_timeout`**(默认 28800 秒 = 8 小时):非交互式连接的闲置超时
|
||||
> - **`interactive_timeout`**(默认 28800 秒):交互式连接(如 mysql CLI)的闲置超时
|
||||
>
|
||||
> 连接池中如果未正确配置这些参数,可能出现大量 ZOMBIE 连接占满 `MaxConnections`。这就是为什么 Go 的 `sql.DB.ConnMaxLifetime` 应该设为比 wait_timeout 更短的值。
|
||||
|
||||
## mysqld 启动流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["加载 my.cnf 配置"] --> B["初始化全局变量"]
|
||||
B --> C["加载插件"]
|
||||
C --> D["初始化存储引擎<br/>innodb_init()"]
|
||||
D --> E["打开数据目录"]
|
||||
E --> F["恢复未提交事务<br/>redo log crash recovery"]
|
||||
F --> G["启动后台线程"]
|
||||
G --> H["监听端口"]
|
||||
H --> I["就绪 ✨"]
|
||||
```
|
||||
|
||||
启动过程中的关键阶段:
|
||||
|
||||
1. **配置加载**:按优先级读取配置文件 `/etc/my.cnf` → `/etc/mysql/my.cnf` → `~/.my.cnf`,命令行参数优先级最高
|
||||
2. **引擎初始化**:InnoDB 会在启动时进行 Crash Recovery——重放 Redo Log 恢复到一致状态。如果数据文件损坏严重,这一步会卡住或报错
|
||||
3. **Crash Recovery**:innodb_force_recovery 参数可以在恢复期间限制操作级别(1~6),用于紧急导出数据
|
||||
|
||||
## 核心架构图
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "连接管理"
|
||||
C1["Accept Thread"] --> C2["Connection Pool"]
|
||||
C2 --> C3["Read Command"]
|
||||
end
|
||||
|
||||
subgraph "SQL 处理流水线"
|
||||
C3 --> P1["Syntax Parser"]
|
||||
P1 --> P2["AST Semantic Check"]
|
||||
P2 --> P3["Preprocessing"]
|
||||
P3 --> P4["Optimization"]
|
||||
P4 --> P5["Execution"]
|
||||
end
|
||||
|
||||
subgraph "InnoDB 执行路径"
|
||||
P5 --> I1["Buffer Pool Read Page"]
|
||||
I1 --> I2{"Hit?"}
|
||||
I2 -->|"Yes"| I3["Return Result"]
|
||||
I2 -->|"No"| I4["Read from Disk"]
|
||||
I4 --> I1
|
||||
I5["Change Buffer"] -.-> I1
|
||||
end
|
||||
|
||||
C1 --> C3
|
||||
style P4 fill:#FF9F43,color:#000
|
||||
style I1 fill:#00B6BC,color:#fff
|
||||
style I3 fill:#00D866,color:#fff
|
||||
```
|
||||
|
||||
## 实战:在线诊断工具
|
||||
|
||||
在生产环境中快速定位问题,需要掌握以下几组命令行工具。它们分别作用于不同的架构层级:
|
||||
|
||||
### 1. SHOW PROCESSLIST — SQL 层视角
|
||||
|
||||
```sql
|
||||
-- 最基础的线程状态查看
|
||||
SHOW FULL PROCESSLIST;
|
||||
|
||||
-- 等效的 SQL 查询(可结合 WHERE 过滤)
|
||||
SELECT * FROM information_schema.processlist WHERE COMMAND != 'Sleep';
|
||||
```
|
||||
|
||||
每个线程在 `PROCESSLIST` 中表现为一条记录,关键列:
|
||||
|
||||
| 列 | 含义 | 常见值 |
|
||||
|----|------|--------|
|
||||
| `ID` | 线程 ID,`KILL id` 使用 | |
|
||||
| `State` | 当前执行阶段 | `Sending data`, `Creating sort index`, `Locked` |
|
||||
| `Time` | 当前状态的持续秒数 | > 5s 需警惕 |
|
||||
| `Info` | 正在执行的 SQL | NULL 表示空闲 |
|
||||
|
||||
> [!TIP] State 解读口诀
|
||||
> - **`Waiting for handler lock`** → 行锁竞争,配合 `Innodb_row_lock_waits` 排查
|
||||
> - **`Creating sort index`** → 大 ORDER BY/GROUP BY,检查是否缺少索引
|
||||
> - **`Sending data`** → 不仅是在"发送结果",而是**正在扫描和生成数据**,往往是最耗时的阶段
|
||||
|
||||
### 2. SHOW STATUS — 全局计数器
|
||||
|
||||
```sql
|
||||
-- 查看慢查询总数(取决于 slow_query_log 配置)
|
||||
SHOW GLOBAL STATUS LIKE 'Slow_queries';
|
||||
|
||||
-- 当前并发连接数 vs 上限
|
||||
SHOW GLOBAL STATUS LIKE 'Threads_connected';
|
||||
SHOW GLOBAL STATUS LIKE 'Max_connections';
|
||||
|
||||
-- InnoDB 缓冲池命中率(接近 100% 为佳)
|
||||
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
|
||||
```
|
||||
|
||||
> [!QUESTION] 为什么 Buffer Pool 命中率不是越高越好?
|
||||
> 理论上 Hit Rate 越接近 100% 越好,但当数据集小于 Buffer Pool 时,Hit Rate 会固定在 100% 以上(因为缓存了多次)。生产环境中 **98%~99%** 已经足够优秀,不必盲目增大 `innodb_buffer_pool_size`。
|
||||
|
||||
### 3. Performance Schema — MySQL 5.7+ 的深层监控
|
||||
|
||||
`performance_schema` 是 MySQL 内置的性能采集框架,比 `SHOW STATUS` 更细粒度:
|
||||
|
||||
```sql
|
||||
-- 当前等待事件(锁、I/O、网络)TOP 10
|
||||
SELECT EVENT_NAME, COUNT_STAR, AVG_TIMER_WAIT
|
||||
FROM performance_schema.events_waits_summary_global_by_event_name
|
||||
ORDER BY COUNT_STAR DESC LIMIT 10;
|
||||
|
||||
-- 最近 10 条耗时 > 1s 的语句
|
||||
SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT/1e12 AS avg_ms
|
||||
FROM performance_schema.events_statements_summary_by_digest
|
||||
WHERE AVG_TIMER_WAIT > 1e12
|
||||
ORDER BY AVG_TIMER_WAIT DESC LIMIT 10;
|
||||
```
|
||||
|
||||
> [!NOTE] Performance Schema 的性能代价
|
||||
> 开启 PS 会带来约 **5%~15%** 的 CPU 开销。建议在低峰期采样分析,或仅对目标线程启用 instrumentation。
|
||||
|
||||
---
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/GORM/01-安装与初始化]] — GORM 连接 MySQL 的配置参数
|
||||
- [[hhs/Redis/01-安装与部署]] — Redis 单线程 vs MySQL 多线程模型对比
|
||||
@@ -0,0 +1,338 @@
|
||||
---
|
||||
tags: [MySQL, 安装, Docker, 配置]
|
||||
create time: 2026-05-16 00:00
|
||||
---
|
||||
|
||||
# MySQL 安装与初始化
|
||||
|
||||
## 概述
|
||||
|
||||
本文系统讲解 MySQL Community Edition 的安装部署和初始化配置,涵盖三种主流方式:**Docker Compose**(推荐开发环境)、**YUM/RPM 包安装**、**通用二进制包安装**。同时深入解析 `my.cnf` 配置文件的核心参数体系,以及 `mysql_install_db` 初始化时的底层行为。
|
||||
|
||||
> [!TIP] 学习建议
|
||||
>
|
||||
> 1. **初学者**优先掌握 Docker Compose 方式,快速搭建可复现的开发环境;
|
||||
> 2. **运维/生产场景**参考二进制安装流程,理解服务初始化的完整链条;
|
||||
> 3. `my.cnf` 参数部分按优先级阅读,先吃透 InnoDB 和网络两大块,日志和 Binlog 可在后续进阶时补充。
|
||||
>
|
||||
> *思考题:为什么 InnoDB Buffer Pool 通常建议设为物理内存的 50%~70%,而不是 90%?*(答案见下文 InnoDB 参数段落的注释)
|
||||
|
||||
## Docker Compose 方式(推荐开发环境)
|
||||
|
||||
这是最轻量且可复现的方案,适合日常开发和 CI 测试。
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: mysql-dev
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpassword # 首次启动自动创建 root 用户
|
||||
MYSQL_DATABASE: app_db # 自动创建初始数据库
|
||||
MYSQL_USER: app_user # 创建普通应用账户
|
||||
MYSQL_PASSWORD: app_password # 该账户对 MYSQL_DATABASE 拥有完全权限
|
||||
ports:
|
||||
- "3306:3306" # 宿主机 3306 → 容器内 3306
|
||||
volumes:
|
||||
- ./init-db:/docker-entrypoint-initdb.d # 首次启动自动执行 SQL
|
||||
- mysql-data:/var/lib/mysql # 持久化数据(容器删除后不丢失)
|
||||
- ./my-custom.cnf:/etc/mysql/conf.d/my.cnf # 覆盖默认配置
|
||||
command: >
|
||||
--character-set-server=utf8mb4 # 统一字符集
|
||||
--collation-server=utf8mb4_unicode_ci # 排序规则(不区分大小写)
|
||||
--default-authentication-plugin=mysql_native_password # 兼容旧版客户端认证
|
||||
--max-connections=500 # 最大并发连接数
|
||||
--innodb-buffer-pool-size=256M # 开发机无需太大
|
||||
|
||||
volumes:
|
||||
mysql-data:
|
||||
```
|
||||
|
||||
### 启动与常用操作
|
||||
|
||||
```bash
|
||||
docker compose up -d # 后台启动
|
||||
docker compose down # 停止并移除容器(保留 volume)
|
||||
docker compose logs -f # 查看实时日志
|
||||
docker compose exec mysql mysql -u root -p # 进入容器内客户端
|
||||
docker compose ps # 查看容器状态
|
||||
docker compose restart # 重启(配置变更后适用)
|
||||
```
|
||||
|
||||
> [!QUESTION] 生产环境应该硬编码密码吗?
|
||||
> 当然不。上述 `docker-compose.yml` 中的明文密码仅适用于本地开发。
|
||||
> 生产环境应使用 Docker Secrets、环境变量文件 `.env` 或密钥管理工具(如 HashiCorp Vault)来注入凭据。
|
||||
>
|
||||
> ```bash
|
||||
> # .env 文件示例(加入 .gitignore!)
|
||||
> MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASS}
|
||||
> MYSQL_DATABASE=myapp_prod
|
||||
> ```
|
||||
>
|
||||
> Docker Compose 会自动将 `${VAR}` 替换为环境变量值。
|
||||
|
||||
### init-db 目录妙用
|
||||
|
||||
`docker-entrypoint-initdb.d/` 下只要是 `.sql` 或 `.sh` 脚本,会在数据库**首次启动**时按字母序自动执行:
|
||||
|
||||
| 文件类型 | 执行时机 | 典型用途 |
|
||||
|---------|---------|---------|
|
||||
| `.sql` | 首次初始化 | DDL建表、默认数据、权限分配 |
|
||||
| `.sh` | 首次初始化 | 调用 `mysql` CLI 执行动态逻辑 |
|
||||
|
||||
> [!WARNING] 注意
|
||||
> - 容器已有数据时,init-db 脚本**不会被重新执行**。修改脚本后需删除 volume 重建:`docker compose down -v && docker compose up -d`
|
||||
> - `.sh` 脚本中若需连接 MySQL,务必等待 ready(可通过 `docker compose wait` 或健康检查机制)
|
||||
|
||||
## 二进制安装(生产环境参考)
|
||||
|
||||
对于需要精细控制的场景,官方推荐的两种方式:YUM/RPM 包适合标准发行版;通用二进制包提供最大灵活性。
|
||||
|
||||
### 方式一:YUM/RPM(CentOS / RHEL / Amazon Linux)
|
||||
|
||||
```bash
|
||||
# 1. 下载 YUM 仓库配置(以 EL9 为例)
|
||||
sudo rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el9-3.noarch.rpm
|
||||
|
||||
# 2. 安装 MySQL Server
|
||||
sudo yum install mysql-server -y
|
||||
|
||||
# 3. 启动服务并设置开机自启
|
||||
sudo systemctl start mysqld
|
||||
sudo systemctl enable mysqld
|
||||
|
||||
# 4. 获取临时 Root 密码(首次安装后打印到日志)
|
||||
sudo grep 'temporary password' /var/log/mysqld.log
|
||||
|
||||
# 5. 安全加固(修改默认密码、移除匿名账户等)
|
||||
sudo mysql_secure_installation
|
||||
```
|
||||
|
||||
> [!TIP] 临时密码在哪?
|
||||
> MySQL 8.0 首次启动时会在 `/var/log/mysqld.log` 中生成一个随机临时密码。
|
||||
> 找到类似 `A temporary password is generated for root@localhost: xxxxxxxx` 的行,使用该密码登录 `mysql_secure_installation`。
|
||||
|
||||
### 方式二:通用二进制包(跨发行版兼容)
|
||||
|
||||
适用于非标准发行版或需要自定义目录结构的场景:
|
||||
|
||||
```bash
|
||||
# 1. 下载并解压(替换实际版本号)
|
||||
wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.xx-linux-glibc2.28-x86_64.tar.xz
|
||||
tar -xvf mysql-8.0.xx-linux-glibc2.28-x86_64.tar.xz
|
||||
sudo mv mysql-8.0.xx-linux-glibc2.28-x86_64 /usr/local/mysql
|
||||
|
||||
# 2. 创建专用系统用户(不建议用 root 运行 MySQL)
|
||||
cd /usr/local/mysql
|
||||
sudo groupadd mysql
|
||||
sudo useradd -r -g mysql -s /bin/false mysql
|
||||
|
||||
# 3. 设置权限 + 初始化数据目录
|
||||
sudo chown -R mysql:mysql .
|
||||
sudo bin/mysqld --initialize --user=mysql --datadir=/data/mysql
|
||||
|
||||
# 4. 配置文件(可选,放在 /etc/my.cnf)
|
||||
# 指定 basedir=/usr/local/mysql 和 datadir=/data/mysql
|
||||
|
||||
# 5. 初始化 SSL 证书(MySQL 8.0 必需)
|
||||
sudo bin/mysql_ssl_rsa_setup --datadir=/data/mysql
|
||||
|
||||
# 6. 启动服务
|
||||
sudo bin/mysqld_safe --user=mysql &
|
||||
|
||||
# 7. 验证登录
|
||||
sudo bin/mysql -u root -p
|
||||
```
|
||||
|
||||
> [!QUESTION] caching_sha2_password vs mysql_native_password?
|
||||
> MySQL 8.0 将默认认证插件从 `mysql_native_password` 切换为 `caching_sha2_password`:
|
||||
> - **caching_sha2_password**(新默认):更强的 SHA2 加密缓存机制,推荐所有新版客户端使用
|
||||
> - **mysql_native_password**(旧版):兼容性最好,老旧语言驱动可能不支持 SHA2
|
||||
>
|
||||
> *如果你的连接报 `client does not support authentication protocol` 错误,临时解决方案是切换回旧版认证:*
|
||||
> ```sql
|
||||
> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'new_password';
|
||||
> ```
|
||||
>
|
||||
> *但长期方案应是升级客户端库。*
|
||||
|
||||
## 配置文件 my.cnf
|
||||
|
||||
### 配置文件加载优先级
|
||||
|
||||
MySQL 按以下顺序查找配置文件,**后读取的覆盖先读取的**(命令行参数始终最高):
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
E["~/.my.cnf"] --> D["$MYSQL_HOME/my.cnf"]
|
||||
D --> C["/etc/mysql/my.cnf"]
|
||||
C --> B["/etc/my.cnf"]
|
||||
B --> A["--defaults-file=<指定路径>"]
|
||||
A --> F["命令行参数"]
|
||||
F -.->|"最终生效"| Z["生效配置"]
|
||||
```
|
||||
|
||||
> [!NOTE] 关键理解
|
||||
> - `/etc/my.cnf` 通常是入口文件,内部可能 `!include` 其他目录;
|
||||
> - `--defaults-file` 是**唯一**使用的配置文件,跳过所有默认路径(极少场景使用);
|
||||
> - Docker 中常用 `--config` 或 `conf.d/` 目录方式挂载自定义配置。
|
||||
|
||||
### 核心配置参数详解
|
||||
|
||||
MySQL 配置集中在 `[mysqld]` 段落下。按功能域分类如下:
|
||||
|
||||
#### 网络
|
||||
|
||||
```ini
|
||||
# ==================== 网络 ====================
|
||||
port = 3306 # MySQL 默认端口
|
||||
bind-address = 0.0.0.0 # 监听所有网卡(Docker 内用 127.0.0.1)
|
||||
max_connections = 500 # 最大并发连接数
|
||||
max_connect_errors = 1000000 # 同一主机连续中断次数上限,超限将被 block_host
|
||||
```
|
||||
|
||||
#### 字符集
|
||||
|
||||
```ini
|
||||
# ==================== 字符集 ====================
|
||||
character-set-server = utf8mb4 # 支持 emoji 的完整 UTF-8 实现
|
||||
collation-server = utf8mb4_unicode_ci # 不区分大小写的排序规则
|
||||
```
|
||||
|
||||
*为什么用 `utf8mb4` 而非 `utf8`?* MySQL 的 `utf8` 只支持最多 3 字节,无法存储 emoji;`utf8mb4` 才是标准 UTF-8(最长 4 字节)。
|
||||
|
||||
#### InnoDB 存储引擎
|
||||
|
||||
```ini
|
||||
# ==================== InnoDB ====================
|
||||
innodb_buffer_pool_size = 1G # 缓存数据和索引的物理内存占比
|
||||
innodb_log_file_size = 512M # Redo Log 文件大小
|
||||
innodb_flush_log_at_trx_commit = 1 # ACID 一致性级别控制
|
||||
innodb_flush_method = O_DIRECT # 绕过 OS page cache,避免双重缓冲
|
||||
innodb_file_per_table = 1 # 每张表独立 .ibd 文件
|
||||
innodb_io_capacity = 2000 # SSD 建议调高,HDD 约 100~200
|
||||
```
|
||||
|
||||
**关键概念解释:**
|
||||
|
||||
| 参数 | 原理 | 调整建议 |
|
||||
|------|------|---------|
|
||||
| `innodb_buffer_pool_size` | InnoDB 的核心缓存区域,存放数据页和索引页 | **物理内存的 50%~70%**——为 OS 和其他进程预留空间,占满 90% 反而导致性能下降 |
|
||||
| `innodb_log_file_size` | Redo Log 保证持久性(WAL 机制) | 越大容纳未刷盘事务越多,但重启恢复越慢;`512M` 是通用起点 |
|
||||
| `O_DIRECT` | 直接 I/O 模式 | 启用后 MySQL 自己管理 Buffer Pool,不再经过 OS Cache(避免双重缓冲浪费) |
|
||||
|
||||
### 慢查询日志
|
||||
|
||||
```ini
|
||||
# ==================== 日志 ====================
|
||||
slow_query_log = 1
|
||||
slow_query_log_file = /var/log/mysql/slow.log
|
||||
long_query_time = 2
|
||||
log_error = /var/log/mysql/error.log
|
||||
```
|
||||
|
||||
**使用方式:** 启用后可以通过 `mysqldumpslow` 分析或直接用文本工具查看超时查询:
|
||||
|
||||
```bash
|
||||
# 找出执行时间最长的 10 条
|
||||
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
|
||||
|
||||
# 实时追踪慢查询(运维调试利器)
|
||||
tail -f /var/log/mysql/slow.log
|
||||
```
|
||||
|
||||
### Binlog(二进制日志)配置
|
||||
|
||||
Binlog 是 MySQL 最核心的日志类型,用于主从复制和数据恢复:
|
||||
|
||||
```ini
|
||||
# ==================== Binlog ====================
|
||||
server_id = 1 # 集群内唯一标识(1~2^32-1)
|
||||
log_bin = /var/log/mysql/mysql-bin
|
||||
binlog_format = ROW # 记录每一行变更明细
|
||||
binlog_expire_logs_seconds = 604800 # 7 天后自动清理
|
||||
max_binlog_size = 100M # 单文件上限(实际可能略超)
|
||||
gtid_mode = ON # 全局事务 ID,简化主从切换
|
||||
enforce_gtid_consistency = ON # 只允许 GTID 安全的事务
|
||||
```
|
||||
|
||||
> [!TIP] Binlog Format 对比
|
||||
> | 格式 | 内容 | 优点 | 缺点 |
|
||||
> |------|------|------|------|
|
||||
> | **STATEMENT** | 原始 SQL | 文件小,审计直观 | 函数调用可能导致主从不一致 |
|
||||
> | **ROW**(推荐) | 每行变更前后值 | 数据一致性最强 | 文件较大 |
|
||||
> | **MIXED** | 混合模式 | 兼顾两者 | 逻辑复杂,难以预测 |
|
||||
>
|
||||
> **结论**:生产环境统一用 `ROW`,现代磁盘成本已不再是瓶颈。
|
||||
|
||||
> [!QUESTION] innodb_flush_log_at_trx_commit 怎么选?
|
||||
> | 值 | 行为 | 丢失数据风险 | 性能 |
|
||||
> |---|------|------------|------|
|
||||
> | **1** | 每次事务 commit 都刷盘 | **零**(ACID 完全符合) | 最低 |
|
||||
> | **2** | 每次 commit 写入 OS 缓存,每秒刷盘 | 系统断电丢失 < 1 秒 | 高 |
|
||||
> | **0** | 每秒都写并刷盘,commit 仅写入缓冲区 | 可能丢多秒数据 | 最高 |
|
||||
>
|
||||
> **建议**:金融/支付类业务必须用 1;内容类业务可以用 2 换取性能提升。
|
||||
|
||||
## mysql_install_db 初始化原理
|
||||
|
||||
当首次启动 MySQL(或手动执行 `mysql_install_db`)时,底层会完成以下几件事:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
S["mysqld 启动"] --> I["数据目录检查"]
|
||||
I -->|为空| D["执行 mysql_install_db"]
|
||||
I -->|非空| C["跳过初始化,直接加载现有数据"]
|
||||
|
||||
D --> A["创建系统数据库"]
|
||||
A --> A1["mysql schema — 用户权限、角色、插件"]
|
||||
A --> A2["sys schema — 性能分析视图集合"]
|
||||
A --> A3["performance_schema — 运行时指标采集"]
|
||||
A --> A4["information_schema — SQL 元数据虚拟表"]
|
||||
|
||||
A --> B["创建管理员账户"]
|
||||
B --> B1["root@localhost — 超级管理员"]
|
||||
B --> B2["使用 caching_sha2_password 认证"]
|
||||
|
||||
A --> E["写入其他系统文件"]
|
||||
E --> E1["ibdata1 — InnoDB 系统表空间"]
|
||||
E --> E2["auto.cnf — Server UUID"]
|
||||
|
||||
C --> F["加载 my.cnf 配置并监听端口"]
|
||||
```
|
||||
|
||||
**逐段拆解:**
|
||||
|
||||
| 步骤 | 说明 | 常见关联问题 |
|
||||
|------|------|-------------|
|
||||
| 创建 system schemas | `mysql` 存权限,`performance_schema` 供性能监控,`information_schema` 是 SQL 标准提供的元数据字典 | 删了这些库会导致实例无法正常运行 |
|
||||
| 创建 root 账户 | 8.0 不再允许匿名访问 `test` 库;密码通过临时日志输出而非自动设为空 | 忘记临时密码 → 删除 data dir 重新初始化 |
|
||||
| 初始化 InnoDB 表空间 | `ibdata1` 存放系统表和 undo log;`ib_logfile*` 是 Redo Log(两份互为镜像) | Redo Log 损坏 → 实例无法启动 |
|
||||
| 写入 auto.cnf | 包含唯一的 Server UUID,主从复制和 GTID 依赖此标识 | 克隆虚拟机未重置 UUID → 主从冲突 |
|
||||
|
||||
### 数据目录结构
|
||||
|
||||
初始化完成后,数据目录的组织方式如下:
|
||||
|
||||
```
|
||||
/var/lib/mysql/ (datadir 根目录)
|
||||
├── ibdata1 # InnoDB 系统表空间(存放系统表 + undo log)
|
||||
├── ib_logfile0 # Redo Log 文件 1
|
||||
├── ib_logfile1 # Redo Log 文件 2(与 1 互为镜像)
|
||||
├── auto.cnf # Server UUID(UUID 不变,主从复制不冲突)
|
||||
├── mysql/ # 系统数据库(user, role, db, columns_priv 等 .ibd 文件)
|
||||
├── performance_schema/ # 运行时性能数据采集
|
||||
├── sys/ # 基于 performance_schema 的友好视图
|
||||
├── app_db/ # 每个用户数据库一个目录
|
||||
│ ├── users.ibd # 单表独立表空间(innodb_file_per_table=1)
|
||||
│ └── orders.ibd
|
||||
└── relay-log.index # 主从复制的中继日志索引(仅作为 replica 时存在)
|
||||
```
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/Redis/01-安装与部署]] — Redis 的安装方式与 MySQL 对比
|
||||
- [[hhs/EXAM/Week05]] — Docker Compose 多服务编排示例
|
||||
- [[hhs/DEV/Go-Database]] — Go 驱动连接 MySQL 的参数配置
|
||||
@@ -0,0 +1,345 @@
|
||||
---
|
||||
tags: [MySQL, 客户端, CLI, 工具]
|
||||
create time: 2026-05-16T00:00
|
||||
---
|
||||
|
||||
# MySQL 客户端工具
|
||||
|
||||
## 概述
|
||||
|
||||
本文件汇总 MySQL 生态中的各类客户端工具及其用法,从终端到图形界面,帮助你在不同场景下高效地操作数据库。
|
||||
|
||||
> [!TIP] 如何选择客户端?
|
||||
> - **日常排查** → `mysql` CLI 或 DataGrip / DBeaver
|
||||
> - **脚本自动化** → `mysql -N -s` 非交互式模式
|
||||
> - **结构设计与 ER 图** → MySQL Workbench
|
||||
> - **多库种混用** → DBeaver 社区版
|
||||
> - **Cluster 管理** → `mysqlsh` JS 模式
|
||||
|
||||
## mysql CLI 命令行客户端
|
||||
|
||||
`mysql` 是 MySQL 自带的交互式客户端,适合快速调试、脚本自动化和远程服务器操作。
|
||||
|
||||
### 基本连接
|
||||
|
||||
```bash
|
||||
# 最简方式(本地,root 无密码模式)
|
||||
mysql
|
||||
|
||||
# 指定用户、主机、端口
|
||||
mysql -u root -p -h 127.0.0.1 -P 3306
|
||||
|
||||
# 通过 Unix Socket 连接(更快,绕过 TCP 栈)
|
||||
mysql -u root -S /var/run/mysqld/mysqld.sock
|
||||
|
||||
# SSH 隧道方式(连接远程服务器上的 MySQL)
|
||||
# 另开终端执行 tunnel:
|
||||
ssh -L 3306:127.0.0.1:3306 user@remote-server
|
||||
# 然后本地直接连:
|
||||
mysql -u root -p --protocol=TCP
|
||||
```
|
||||
|
||||
> [!NOTE] 三种连接通道对比
|
||||
> | 方式 | 延迟 | 适用场景 | 安全 |
|
||||
> |------|------|---------|------|
|
||||
> | TCP (`-h 127.0.0.1`) | ~1ms | 本地开发、负载均衡后端 | 明文传输 |
|
||||
> | Unix Socket (`-S`) | <1μs | 本机直连,性能最优 | 文件系统权限控制 |
|
||||
> | SSH Tunnel | ~10-50ms | 跨机房、生产环境 | SSH 加密通道 |
|
||||
|
||||
```bash
|
||||
# 使用 SSL 连接生产环境(推荐)
|
||||
mysql -u app_user -p --ssl-mode=VERIFY_IDENTITY \
|
||||
--ssl-ca=/etc/mysql/ca.pem \
|
||||
--ssl-cert=/etc/mysql/client-cert.pem \
|
||||
--ssl-key=/etc/mysql/client-key.pem \
|
||||
-h prod-db.example.com
|
||||
|
||||
# 压缩传输大结果集
|
||||
mysql -u root -p --compress -h 192.168.1.10 -e "SELECT * FROM large_table;"
|
||||
```
|
||||
|
||||
### 配置文件加载顺序
|
||||
|
||||
`mysql` 启动时按以下顺序读取配置文件,后读到的覆盖前面的值:
|
||||
|
||||
```bash
|
||||
# 查看当前配置加载路径(从低优先级到高优先级)
|
||||
mysql --print-defaults
|
||||
```
|
||||
|
||||
```text
|
||||
mysql would have been started with the following arguments:
|
||||
--socket=/var/run/mysqld/mysqld.sock --port=3306 --default-character-set=utf8mb4
|
||||
```
|
||||
|
||||
> [!IMPORTANT] 常见配置优化项
|
||||
>
|
||||
> 在 `~/.my.cnf`(仅自己可见,chmod 600)中声明默认参数,避免每次手动输入:
|
||||
> ```ini
|
||||
> [client]
|
||||
> user = myuser
|
||||
> host = 127.0.0.1
|
||||
> port = 3306
|
||||
> password = secret
|
||||
> default-character-set = utf8mb4
|
||||
>
|
||||
> [mysql]
|
||||
> auto-rehash # 自动补全表名/列名(默认开启)
|
||||
> column-widths = 120 # 加大显示宽度,避免截断
|
||||
> ```
|
||||
|
||||
> ⚠️ 安全提醒:`password` 明文写入配置文件存在风险。生产环境中建议使用 `mysql_config_editor` 生成 `.mylogin.cnf` 加密登录文件:
|
||||
> ```bash
|
||||
> mysql_config_editor set --login-path=local --host=localhost --user=root --password
|
||||
> # 之后只需:
|
||||
> mysql --login-path=local
|
||||
> ```
|
||||
|
||||
### 常用快捷命令
|
||||
|
||||
在 `mysql>` 提示符下,以 `\` 开头的命令是**客户端内置指令**,不会发送到服务端:
|
||||
|
||||
```sql
|
||||
\h -- 显示帮助
|
||||
\? -- 同 \h
|
||||
\G -- 竖排输出(长字段友好)
|
||||
\t -- 切换 Tab / CSV 格式输出
|
||||
\c -- 取消当前输入
|
||||
\u db_name -- 切换数据库
|
||||
\d new_delim -- 修改语句终止符(分号冲突时用)
|
||||
\q -- 退出
|
||||
\e -- 用 $EDITOR 编辑当前 SQL(打开 vi/nano)
|
||||
\s -- 查看服务器状态(版本、字符集等)
|
||||
\. filename -- 执行 SQL 脚本(与 source 等价)
|
||||
pager less -- 设置分页输出(大结果集必备)
|
||||
nopager -- 恢复默认
|
||||
```
|
||||
|
||||
> [!TIP] 实用技巧:`\e` + Enter 编辑
|
||||
>
|
||||
> 当 SQL 较长时,直接敲 `\e` 回车会打开 `$EDITOR` 指定的编辑器,写好 SQL 保存退出后自动执行。配合 `set editor=vim` 可自定义编辑器。
|
||||
|
||||
### 竖排输出示例
|
||||
|
||||
```sql
|
||||
mysql> SELECT * FROM users WHERE id = 1\G
|
||||
*************************** 1. row ***************************
|
||||
id: 1
|
||||
username: alice
|
||||
email: alice@example.com
|
||||
created_at: 2026-01-15 10:30:00
|
||||
updated_at: 2026-05-10 14:22:00
|
||||
profile_json: {"age": 28, "city": "Shanghai", "role": "admin"}
|
||||
status: 1
|
||||
1 row in set (0.00 sec)
|
||||
```
|
||||
|
||||
> [!EXAMPLE] 什么时候用 `\G`?
|
||||
>
|
||||
> 当一个表的字段很多(如 JSON 类型的大字段),横排输出会被截断或挤在一起。竖排模式下每个字段独占一行,可读性大幅提升。
|
||||
|
||||
### 非交互式执行
|
||||
|
||||
```bash
|
||||
# 直接执行 SQL 字符串
|
||||
mysql -u root -p -e "SELECT COUNT(*) FROM users;"
|
||||
|
||||
# 从文件导入(常用于初始化建表)
|
||||
mysql -u root -p db_name < init.sql
|
||||
|
||||
# 导出到文件(适合小数据量提取)
|
||||
mysql -u root -p -e "SELECT * FROM users;" db_name > output.csv
|
||||
|
||||
# 静默模式(适合脚本解析输出)
|
||||
mysql -s -N -e "SHOW DATABASES;"
|
||||
# -s = silent(紧凑格式),-N = 不显示列名
|
||||
```
|
||||
|
||||
> [!TIP] 管道组合技巧
|
||||
>
|
||||
> 将 mysql 与其他 unix 工具组合,实现更强大的数据处理能力:
|
||||
> ```bash
|
||||
> # 只提取某列并去重计数
|
||||
> mysql -N -u root -p -e "SELECT role FROM users;" db_name | sort | uniq -c | sort -rn
|
||||
>
|
||||
> # 实时观察查询变化(类似 watch)
|
||||
> watch -n 1 "mysql -N -u root -p -e 'SELECT COUNT(*) FROM orders WHERE status=\"pending\";' app_db"
|
||||
> ```
|
||||
|
||||
## mysqlsh(MySQL Shell)
|
||||
|
||||
`mysqlsh` 是 Oracle 官方新一代管理工具,支持 **SQL**、**JavaScript**、**Python** 三种模式,内置丰富的 DBA API,是 InnoDB Cluster 管理的核心入口。
|
||||
|
||||
> [!NOTE] mysql vs mysqlsh
|
||||
>
|
||||
> `mysql` 是最原始的客户端,专注纯 SQL 交互;而 `mysqlsh` 更像是一个「数据库开发平台」——它提供了面向对象的数据访问 API、结构化输出格式化,以及集群管理的完整工具链。建议在生产运维场景中优先使用 `mysqlsh`。
|
||||
|
||||
### 启动与模式切换
|
||||
|
||||
```bash
|
||||
# 默认进入 SQL 模式
|
||||
mysqlsh
|
||||
|
||||
# JavaScript 模式(连接 Cluster 管理)
|
||||
mysqlsh --js
|
||||
dba.createCluster('myCluster')
|
||||
|
||||
# Python 模式
|
||||
mysqlsh --py
|
||||
|
||||
# 一步直达指定实例(自动进入 SQL 模式)
|
||||
mysqlsh root@192.168.1.10:3306
|
||||
mysqlsh://root@prod-db:3306/app_db # 指定初始库
|
||||
```
|
||||
|
||||
### 实用功能
|
||||
|
||||
```javascript
|
||||
// ---- JS 模式:Schema 级别的操作 ----
|
||||
const schema = dba.getSchema('app_db');
|
||||
schema.getTable('users').select().where('status = 1').limit(10).execute();
|
||||
|
||||
// ---- SQL 模式:结构化输出 ----
|
||||
\output json // 输出切换为 JSON,便于后续处理
|
||||
SELECT * FROM users LIMIT 5;
|
||||
\output text // 切回文本
|
||||
|
||||
// ---- 元数据概览 ----
|
||||
\status // 连接状态
|
||||
\dba.status() // InnoDB Cluster 集群状态
|
||||
\connect app_user@app-db // 切换连接
|
||||
```
|
||||
|
||||
```python
|
||||
# ---- Python 模式:批量 CRUD ----
|
||||
session = db.create_session("app_user@prod-db:3306/app_db")
|
||||
result = session.sql("SELECT id, name FROM products WHERE price > 100").execute()
|
||||
for row in result.fetch_all():
|
||||
print(f"{row[0]}: {row[1]}")
|
||||
```
|
||||
|
||||
> [!TIP] mysqlsh 的隐藏技能
|
||||
>
|
||||
> - **自动生成文档**: `\status --json` 可直接喂给日志分析系统
|
||||
> - **对象浏览器**: tab 自动补全对 `dba`, `session`, `schema` 全部生效
|
||||
> - **代码片段**: 输入 `dba.` 后 tab 可查看所有可用方法(createCluster, cloneInstance, checkInstanceConfiguration 等)
|
||||
|
||||
## 图形界面工具对比
|
||||
|
||||
| 工具 | 类型 | 平台 | 特点 | 费用 |
|
||||
|------|------|------|------|------|
|
||||
| **MySQL Workbench** | 官方 GUI | Win/Mac/Linux | ER 图设计、迁移工具、执行计划可视化 | 免费 |
|
||||
| **DBeaver** | 社区万能 | Win/Mac/Linux | 支持几乎所有数据库,插件丰富 | 社区版免费 |
|
||||
| **HeidiSQL** | Windows 首选 | Windows | 轻量、启动快、批量操作方便 | 免费开源 |
|
||||
| **Navicat** | 商业旗舰 | Win/Mac/Linux | 功能最全面、同步/备份/结构设计一体化 | 付费 |
|
||||
| **DataGrip** | JetBrains | Win/Mac/Linux | IDE 集成好、代码补全强大 | 付费(JetBrains 全家桶) |
|
||||
| **TablePlus** | 现代审美 | Mac/Win | 原生应用体验流畅、快捷键优秀 | 付费 |
|
||||
|
||||
### 工具选择决策流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[开始选型] --> B{操作系统}
|
||||
B -->|"macOS"| C["TablePlus / DataGrip"]
|
||||
B -->|"Windows"| D{预算?}
|
||||
B -->|"Linux / 跨平台"| E["DBeaver / DataGrip"]
|
||||
D -->|"免费"| F["HeidiSQL"]
|
||||
D -->|"付费"| G{"需要多库兼容?"}
|
||||
G -->|"是"| H["Navicat"]
|
||||
G -->|"仅 MySQL"| I["MySQL Workbench"]
|
||||
C --> J["搭配 mysql CLI 完成全流程"]
|
||||
F --> J
|
||||
H --> J
|
||||
I --> J
|
||||
E --> J
|
||||
```
|
||||
|
||||
### DBeaver 快速上手
|
||||
|
||||
```sql
|
||||
-- 在 DBeaver 中可以享受的功能:
|
||||
-- 1. Ctrl+Space 智能代码补全
|
||||
-- 2. Ctrl+Shift+F 格式化 SQL
|
||||
-- 3. Ctrl+/ 单行注释,Ctrl+Shift+/ 块注释
|
||||
-- 4. 右键表 -> Open Table Data 直接浏览数据
|
||||
-- 5. EXPLAIN 分析器可视化展示执行计划
|
||||
```
|
||||
|
||||
> [!TIP] DBeaver 小技巧
|
||||
>
|
||||
> - 选中某张表按 **F5** 刷新元数据(DDL 变更后必做)
|
||||
> - 右键查询结果 → Export 支持 Excel/CSV/JSON/SQL INSERT 多种格式
|
||||
> - 「SQL Editor」支持数据源模板(Template),一键插入常用查询骨架
|
||||
|
||||
## 其他命令行工具
|
||||
|
||||
```bash
|
||||
# ---- mysqlimport ----
|
||||
# 批量导入 CSV 数据到表中
|
||||
mysqlimport -u root -p --local --fields-terminated-by=',' app_db data.csv
|
||||
|
||||
# ---- mysqldump ----
|
||||
# 逻辑备份(后续章节详述)
|
||||
mysqldump -u root -p --single-transaction --routines --triggers app_db > backup.sql
|
||||
|
||||
# ---- mysqlbinlog ----
|
||||
# 解析 Binary Log
|
||||
mysqlbinlog --start-datetime="2026-05-01 00:00:00" mysql-bin.000001 > decoded.sql
|
||||
|
||||
# ---- mysqlcheck ----
|
||||
# 检查和修复表
|
||||
mysqlcheck -u root -p --auto-repair --check --optimize app_db
|
||||
|
||||
# ---- perror ----
|
||||
# 查看 MySQL 错误码含义
|
||||
perror 1064
|
||||
# 1064 = You have an error in your SQL syntax
|
||||
```
|
||||
|
||||
### mysqldump 进阶用法
|
||||
|
||||
```bash
|
||||
# 只导结构,不导数据(适合迁移 schema)
|
||||
mysqldump -u root -p --no-data app_db > schema.sql
|
||||
|
||||
# 只导数据,不导结构(适合增量同步)
|
||||
mysqldump -u root -p --no-create-info app_db > data.sql
|
||||
|
||||
# 排除特定表(下划线前缀的配置表)
|
||||
mysqldump -u root -p --ignore-table=app_db._config --ignore-table=app_db._history app_db > backup.sql
|
||||
|
||||
# 分库备份(循环所有库)
|
||||
mysqldump -u root -p --all-databases --single-transaction --master-data=2 > all_dbs_$(date +%F).sql
|
||||
|
||||
# 按时间段还原(误删恢复利器)
|
||||
mysqlbinlog --stop-datetime="2026-05-15 14:32:00" mysql-bin.000001 | mysql -u root -p
|
||||
```
|
||||
|
||||
### mysqlbinlog 定位故障时间线
|
||||
|
||||
```bash
|
||||
# 查看所有事件(含二进制语句)
|
||||
mysqlbinlog --force-read mysql-bin.000001 | grep -iE "BEGIN|COMMIT|DROP|TRUNCATE|ALTER"
|
||||
|
||||
# 只看到具体的 SQL(不含内部细节)
|
||||
mysqlbinlog --base64-decode mysql-bin.000001 | grep -C 3 "DELETE FROM users"
|
||||
|
||||
# 按 pos 点精确恢复(精准到事务级别)
|
||||
mysqlbinlog --start-position=154 --stop-position=892 mysql-bin.000001 | mysql -u root -p
|
||||
```
|
||||
|
||||
> [!IMPORTANT] 冷备份 vs 热备份
|
||||
>
|
||||
> - `mysqldump --single-transaction`:**热备份**,基于 MVCC 一致性快照,不影响线上写入(InnoDB 专属)
|
||||
> - `mysqldump --lock-all-tables`:**冷备份**,全局读锁,写请求全部阻塞(MyISAM 需此方式)
|
||||
> - `mydumper`:多线程逻辑备份工具,速度远胜 mysqldump,适合百 GB 级以上数据库
|
||||
|
||||
## 关联笔记
|
||||
|
||||
### MySQL 系列
|
||||
- [[hhs/MySQL/README]] — MySQL 知识库总目录
|
||||
- [[hhs/MySQL/01-入门基础/02-安装与初始化]] — 安装方法与初始化配置
|
||||
- [[hhs/MySQL/07-高可用与分布式/34-备份与恢复]] — mysqldump、xtrabackup 完整指南
|
||||
- [[hhs/MySQL/07-高可用与分布式/29-Binary Log]] — binlog 原理与 mysqlbinlog 深入
|
||||
- [[hhs/MySQL/08-工程实践/39-安全加固]] — 账号权限、SSL、审计最佳实践
|
||||
- [[hhs/MySQL/01-入门基础/01-MySQL 架构与入门]] — 理解客户端与服务端通信基础
|
||||
@@ -0,0 +1,305 @@
|
||||
---
|
||||
tags: [MySQL, 数据类型, INT, VARCHAR, JSON]
|
||||
create time: 2026-05-16 00:30
|
||||
---
|
||||
|
||||
# MySQL 数据类型全景
|
||||
|
||||
## 概述
|
||||
|
||||
MySQL 的数据类型可按存储的内容分为六大类。**选型的黄金法则**:用满足业务需求的最小类型,节省空间、提升索引效率与查询性能。
|
||||
|
||||
| 大类 | 包含类型 | 核心考量 |
|
||||
|------|---------|---------|
|
||||
| **整数** | TINYINT / SMALLINT / MEDIUMINT / INT / BIGINT | 够用就行,别上来就 BIGINT |
|
||||
| **浮点/定点** | FLOAT / DOUBLE / DECIMAL | **金额永远用 DECIMAL** |
|
||||
| **字符串** | CHAR / VARCHAR / TEXT 系列 | 定长选 CHAR,不定长选 VARCHAR |
|
||||
| **日期时间** | DATE / TIME / DATETIME / TIMESTAMP / YEAR | 跨时区用 TIMESTAMP,否则 DATETIME |
|
||||
| **JSON** | JSON | 灵活扩展字段,搭配 Generated Column 做索引 |
|
||||
| **枚举/集合** | ENUM / SET | 值域固定且极少变才考虑 |
|
||||
| **二进制** | BINARY / VARBINARY / BLOB 系列 | 存文件用对象存储,数据库里只存引用 |
|
||||
|
||||
> [!QUESTION] 为什么总强调"用小的就行"?
|
||||
> 想象一张百万行的订单表,如果主键用了 `BIGINT(8 bytes)` 而非 `INT(4 bytes)`,仅此一列就多花 4MB。这还没算上二级索引——InnoDB 的二级索引叶子节点会完整存储主键值,每个二级索引同样多花 4MB。表越大,连锁放大效应越惊人。
|
||||
|
||||
### 整数类型
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
INT_TYPES["整数类型家族"]
|
||||
INT_TYPES --> TINY["TINYINT<br/>1 byte | ±128"]
|
||||
INT_TYPES --> SMALL["SMALLINT<br/>2 bytes | ±32K"]
|
||||
INT_TYPES --> MED["MEDIUMINT<br/>3 bytes | ±8M"]
|
||||
INT_TYPES --> INTN["INT<br/>4 bytes | ±21亿"]
|
||||
INT_TYPES --> BIG["BIGINT<br/>8 bytes | ±9.2×10^18"]
|
||||
|
||||
style INT_TYPES fill:#5F2799,color:#fff
|
||||
style TINY fill:#C44569,color:#fff
|
||||
style SMALL fill:#C44569,color:#fff
|
||||
style MED fill:#C44569,color:#fff
|
||||
style INTN fill:#C44569,color:#fff
|
||||
style BIG fill:#FF6B6B,color:#000
|
||||
```
|
||||
|
||||
| 类型 | 有符号范围 | 无符号范围 | 存储 | 典型用途 |
|
||||
|------|-----------|-----------|------|---------|
|
||||
| **TINYINT** | -128 ~ 127 | 0 ~ 255 | 1 byte | 状态标识、布尔标志、年龄 |
|
||||
| **SMALLINT** | -32K ~ 32K | 0 ~ 65K | 2 bytes | 短编码 ID |
|
||||
| **MEDIUMINT** | -8M ~ 8M | 0 ~ 16M | 3 bytes | 中等范围计数 |
|
||||
| **INT** | ±21 亿 | 0 ~ 42 亿 | 4 bytes | 常规自增主键、外键 |
|
||||
| **BIGINT** | ±9.2×10¹⁸ | — | 8 bytes | Snowflake ID、金额计算 |
|
||||
|
||||
> [!TIP] 能用小的就不用大的
|
||||
> `TINYINT UNSIGNED` 能存 255,够用就别用 `INT`。每列差 3 字节,百万行就是 3MB 额外开销,还会导致索引变厚、缓存命中率下降。
|
||||
|
||||
### 浮点与定点
|
||||
|
||||
| 类型 | 精度 | 存储 | 适用场景 |
|
||||
|------|------|------|---------|
|
||||
| **FLOAT** | 单精度 7 位 | 4 bytes | 科学计算、不需要精确的场景 |
|
||||
| **DOUBLE** | 双精度 15 位 | 8 bytes | 高精度科学计算 |
|
||||
| **DECIMAL(M,D)** | 精确定点 M-D 位整数 + D 位小数 | 可变(约每 9 位数字 4 字节) | **金额计算!绝对不要用浮点数存钱** |
|
||||
|
||||
```sql
|
||||
-- ❌ 错误示范:浮点数误差累积
|
||||
SELECT 0.1 + 0.2; -- 结果可能是 0.30000000000000004
|
||||
|
||||
-- ✅ 正确做法:DECIMAL
|
||||
CREATE TABLE orders (
|
||||
amount DECIMAL(10, 2) NOT NULL -- 最大 99999999.99
|
||||
);
|
||||
INSERT INTO orders VALUES (19.99 + 29.99); -- 精确等于 49.98
|
||||
```
|
||||
|
||||
> [!QUESTION] 为什么不能用 FLOAT/DOUBLE 存金额?
|
||||
> IEEE 754 浮点数无法精确表示 0.1 这样的十进制小数。在财务场景中,微小的舍入误差经过多次加减后会累积成显著差异。DECIMAL 以字符串形式存储每一位数字,保证运算精确。
|
||||
|
||||
## 字符串类型
|
||||
|
||||
| 类型 | 存储规则 | 最大长度 | 特点 |
|
||||
|------|---------|---------|------|
|
||||
| **CHAR(n)** | 固定长度,不足空格填充 | 0~255 | 适合长度固定的数据(MD5 hash、状态码) |
|
||||
| **VARCHAR(n)** | 变长,前缀记录实际长度 | 0~65535(受行大小限制) | 最常用的字符串类型 |
|
||||
| **TINYTEXT** | 1 字节长度前缀 | 255 | 极短文本 |
|
||||
| **TEXT** | 2 字节前缀 | 65535 | 文章摘要、评论 |
|
||||
| **MEDIUMTEXT** | 3 字节前缀 | 16MB | 长文章、富文本 |
|
||||
| **LONGTEXT** | 4 字节前缀 | 4GB | 超大文本(日志、JSON 文档) |
|
||||
|
||||
### CHAR vs VARCHAR 的选择
|
||||
|
||||
```sql
|
||||
-- ✅ 适合 CHAR:定长数据
|
||||
CREATE TABLE users (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
status TINYINT DEFAULT 1,
|
||||
country_code CHAR(3) NOT NULL, -- ISO 3166-1 alpha-3
|
||||
phone_prefix CHAR(4), -- +86, +1, +44...
|
||||
email VARCHAR(255) UNIQUE -- 不定长,用 VARCHAR
|
||||
);
|
||||
|
||||
-- 为什么 gender 有时用 CHAR(1) 而非 TINYINT?
|
||||
-- CHAR(1) 语义更明确,但本质上两者存储相同。
|
||||
-- 关键是保持一致性——团队规范比个人偏好更重要。
|
||||
```
|
||||
|
||||
> [!NOTE] VARCHAR 的长度陷阱
|
||||
> MySQL 行大小上限 65535 字节,但这不只是所有 VARCHAR 加起来的大小。还要考虑:
|
||||
> - 每列的 1~2 字节长度前缀
|
||||
> - NULL 位图(允许 NULL 的列)
|
||||
> - 实际存储使用 **utf8mb4** 的话,每个字符最多占 4 字节
|
||||
>
|
||||
> 所以 `VARCHAR(255)` 在 utf8mb4 下最大占用 255 × 4 + 2 ≈ 1022 字节。
|
||||
|
||||
## 日期和时间类型
|
||||
|
||||
| 类型 | 格式 | 存储 | 时区感知 |
|
||||
|------|------|------|---------|
|
||||
| **DATE** | `YYYY-MM-DD` | 3 bytes | 无 |
|
||||
| **TIME** | `HH:MM:SS` | 3 bytes | 无(时间段) |
|
||||
| **DATETIME** | `YYYY-MM-DD HH:MM:SS` | 8 bytes | 无(存储原始值) |
|
||||
| **TIMESTAMP** | `YYYY-MM-DD HH:MM:SS` | 4 bytes | **有**(UTC 存储,显示时转换) |
|
||||
| **YEAR** | `YYYY` | 1 byte | — |
|
||||
|
||||
```sql
|
||||
-- TIMESTAMP 的时区自动转换特性
|
||||
CREATE TABLE events (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(100),
|
||||
event_time DATETIME, -- 存入什么就读出什么
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 自动填当前 UTC 时间
|
||||
);
|
||||
|
||||
INSERT INTO events (name, event_time) VALUES ('Meeting', '2026-05-16 14:00:00');
|
||||
-- 在上海时区 (UTC+8) 显示:14:00:00
|
||||
-- 在纽约时区 (UTC-4) 显示:14:00:00(DATETIME 不变)
|
||||
-- 但如果用 TIMESTAMP,它会转换成纽约本地时间 02:00:00
|
||||
```
|
||||
|
||||
> [!WARNING] TIMESTAMP 有保质期
|
||||
> `TIMESTAMP` 的范围是 `1970-01-01 00:00:01` 到 `2038-01-19 03:14:07`(32-bit 上限)。如果你的系统需要支持 2038 年之后的数据,请用 `DATETIME`。
|
||||
|
||||
## JSON 类型
|
||||
|
||||
MySQL 5.7+ 引入原生 JSON 类型,支持部分查询和索引能力。
|
||||
|
||||
```sql
|
||||
CREATE TABLE employees (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(100),
|
||||
attributes JSON -- 灵活扩展字段
|
||||
);
|
||||
|
||||
INSERT INTO employees VALUES
|
||||
(1, 'Alice', '{"dept": "Engineering", "skills": ["Go", "Python"], "level": 5}'),
|
||||
(2, 'Bob', '{"dept": "Marketing", "skills": ["SEO", "Content"], "level": 3}');
|
||||
|
||||
-- JSON 路径查询
|
||||
SELECT name, attributes->>'$.dept' AS department
|
||||
FROM employees
|
||||
WHERE attributes->>'$.level' >= 4;
|
||||
|
||||
-- JSON 数组包含判断
|
||||
SELECT name FROM employees
|
||||
WHERE JSON_CONTAINS(attributes->'$[*]', '"Go"');
|
||||
|
||||
-- 生成虚拟列 + 索引(最佳实践)
|
||||
ALTER TABLE employees
|
||||
ADD COLUMN dept VARCHAR(50) GENERATED ALWAYS AS (attributes->>'$.dept') VIRTUAL,
|
||||
ADD INDEX idx_dept (dept);
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["JSON Column<br/>原始存储"] --> B["JSON Document"]
|
||||
B --> C["Scalar Values"]
|
||||
B --> D["Arrays"]
|
||||
B --> E["Nested Objects"]
|
||||
|
||||
C --> F["Generated Column"]
|
||||
D --> F
|
||||
E --> F
|
||||
F --> G["Index<br/>Virtual / Stored"]
|
||||
|
||||
style A fill:#00B6BC,color:#fff
|
||||
style F fill:#FF9F43,color:#000
|
||||
style G fill:#C44569,color:#fff
|
||||
```
|
||||
|
||||
> [!TIP] JSON vs 规范化表设计
|
||||
> - **适合 JSON**:配置项、标签集合、表单动态字段、低频更新的属性
|
||||
> - **不适合 JSON**:需要 JOIN 关联、频繁条件过滤、强一致性约束的字段
|
||||
> - 关键技巧:用 **Generated Column + Index** 让 JSON 字段的筛选走索引
|
||||
|
||||
## 枚举与集合
|
||||
|
||||
```sql
|
||||
-- ENUM: 限定可选值列表
|
||||
CREATE TABLE tasks (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
title VARCHAR(200),
|
||||
status ENUM('todo', 'in_progress', 'done', 'cancelled') DEFAULT 'todo',
|
||||
priority ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium'
|
||||
);
|
||||
|
||||
-- SET: 多选值(逗号分隔存储)
|
||||
CREATE TABLE tags (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(100),
|
||||
categories SET('tech', 'business', 'lifestyle', 'health')
|
||||
);
|
||||
|
||||
INSERT INTO tags VALUES (1, 'Go Tips', 'tech,business');
|
||||
```
|
||||
|
||||
### ENUM vs SET 对比
|
||||
|
||||
| 特性 | ENUM | SET |
|
||||
|------|------|-----|
|
||||
| **语义** | 单选:从列表中取一个值 | 多选:从列表中取零个或多个值 |
|
||||
| **内部存储** | 整数索引(1 byte 或 2 bytes) | 位图(最多 64 个成员占 8 bytes) |
|
||||
| **排序规则** | 按索引顺序,非字母序 | 按位数值排序 |
|
||||
| **典型场景** | 工单状态、优先级 | 标签分类、权限角色 |
|
||||
|
||||
### ENUM 还是 TINYINT?这是永恒争论
|
||||
|
||||
```sql
|
||||
-- 方案 A:ENUM — 数据库层校验 + 省空间
|
||||
status ENUM('open', 'closed') NOT NULL -- 1 byte
|
||||
|
||||
-- 方案 B:TINYINT — 代码层校验 + 灵活
|
||||
status TINYINT UNSIGNED NOT NULL -- 1 byte
|
||||
-- 应用层保证只写入 0/1/2/3
|
||||
```
|
||||
|
||||
| 维度 | 选 ENUM | 选 TINYINT |
|
||||
|------|---------|-----------|
|
||||
| **校验** | 数据库自动拒绝非法值 | 需应用层保证 |
|
||||
| **调试** | 查出是数字 2,还要查 schema 才知道含义 | 直接读出原始数字,一目了然 |
|
||||
| **修改成本** | 增删值需 `ALTER TABLE`(大表很贵) | 随时在应用层加常量枚举 |
|
||||
| **代码可追溯性** | IDE 找不到所有使用处 | `grep` 就能定位 |
|
||||
|
||||
> [!WARNING] ENUM 的反模式警告
|
||||
> - **不要用 ENUM 存用户可见的文案**——后台查出来是 `2`,前端还得映射回去
|
||||
> - **不要把 ENUM 当文档用**——队友看不懂 `status = 3` 是什么意思
|
||||
> - **推荐方案**:中小项目用 TINYINT + Go `const` / Python `IntEnum` 在代码里维护枚举定义;只有在值域极稳定且不需要跨语言共享时再用原生 ENUM
|
||||
|
||||
> [!NOTE] ENUM 的本质
|
||||
> ENUM 在内部存储为整数索引(1, 2, 3...),而不是字符串。这意味着:
|
||||
> - ENUM 的排序是按索引而非字母顺序
|
||||
> - 插入不在列表中的值会导致错误(或空字符串,取决于 sql_mode)
|
||||
> - 修改 ENUM 列表顺序会影响已有数据的解释——**谨慎维护**
|
||||
|
||||
## 二进制类型
|
||||
|
||||
| 类型 | 说明 | 典型用途 |
|
||||
|------|------|---------|
|
||||
| **BINARY(n)** | 定长二进制 | 哈希值(SHA256 = 32 bytes)|
|
||||
| **VARBINARY(n)** | 变长二进制 | 短二进制数据 |
|
||||
| **TINYBLOB** | ≤ 255 bytes | — |
|
||||
| **BLOB** | ≤ 65KB | 缩略图、序列化对象 |
|
||||
| **MEDIUMBLOB** | ≤ 16MB | 文件附件 |
|
||||
| **LONGBLOB** | ≤ 4GB | 大文件存储 |
|
||||
|
||||
```sql
|
||||
-- 存储 SHA-256 hash 的推荐方式
|
||||
CREATE TABLE file_metadata (
|
||||
id BIGINT PRIMARY KEY,
|
||||
filename VARCHAR(500),
|
||||
sha256_hash BINARY(32) NOT NULL, -- CHAR(64) HEX 也可以,但 BINARY 省一半
|
||||
UNIQUE KEY uk_sha256 (sha256_hash)
|
||||
);
|
||||
```
|
||||
|
||||
### BINARY vs VARBINARY — 定长与变长的选择
|
||||
|
||||
```sql
|
||||
-- BINARY(32):始终占 32 bytes,不足补 0x00
|
||||
INSERT INTO t VALUES (X'61'); -- 实际存储: 61 00 00 ... 00(32 bytes)
|
||||
SELECT HEX(col) FROM t; -- 输出: 610000...(永远 64 个十六进制字符)
|
||||
|
||||
-- VARBINARY(32):只存真实长度 + 1 byte 长度前缀
|
||||
INSERT INTO t VALUES (X'61'); -- 实际存储: 61 01(2 bytes,01 表示长度)
|
||||
SELECT HEX(col) FROM t; -- 输出: 61(只有 2 个十六进制字符)
|
||||
```
|
||||
|
||||
| 维度 | BINARY(n) | VARBINARY(n) |
|
||||
|------|-----------|-------------|
|
||||
| **存储** | 固定 n 字节,右侧用 `0x00` 补齐 | 实际长度 + 1~2 bytes 前缀 |
|
||||
| **比较规则** | 补齐 0x00 后再逐字节比较 | 按实际长度比较 |
|
||||
| **适用场景** | 哈希值、加密密钥等定长数据 | 短二进制流、序列化片段 |
|
||||
|
||||
> [!TIP] BINARY vs CHAR 的对称性
|
||||
> 理解 `BINARY` 就理解了 `CHAR`——它们是对称的定长类型。区别仅在于:**CHAR 补空格,BINARY 补零**。同理 `VARBINARY` 对标 `VARCHAR`。类比记忆比死记硬背更可靠。
|
||||
|
||||
> [!WARNING] 大文件不要存在数据库里
|
||||
> BLOB 系列看似方便,但会带来三个问题:
|
||||
> 1. **备份膨胀**——数据库 dump 体积翻倍,恢复时间成倍增加
|
||||
> 2. **内存压力**——SELECT 整行时 BLOB 内容也加载到内存,即使你不需要它
|
||||
> 3. **无法 CDN 加速**——图片/附件走 OSS + Signed URL 是标准做法
|
||||
>
|
||||
> **最佳实践**:数据库只存文件引用(OSS URL / 文件系统路径),文件本体放对象存储。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/GORM/02-模型定义]] — GORM Struct Tag 如何映射这些 MySQL 类型
|
||||
- [[hhs/Redis/02-核心数据类型]] — MySQL 与 Redis 数据类型的选型对比
|
||||
@@ -0,0 +1,272 @@
|
||||
---
|
||||
tags: [MySQL, 字符集, Collation, utf8mb4, utf8mb3, character_set]
|
||||
create time: 2026-05-16 00:00
|
||||
---
|
||||
|
||||
# 字符集与排序规则
|
||||
|
||||
## 概述
|
||||
|
||||
字符集(Character Set)决定「哪些字符能被存储」,排序规则(Collation)决定「这些字符如何比较和排序」。选错字符集不仅会导致乱码,还可能引发索引失效和安全漏洞。
|
||||
|
||||
## 字符集速览
|
||||
|
||||
MySQL 支持超过 80 种字符集,但生产环境中真正有用的只有几个:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "单字节"
|
||||
A["latin1"] -->|"遗留系统"| A1["西欧语言"]
|
||||
end
|
||||
subgraph "多字节变长"
|
||||
B["utf8<br/>MySQL 特有"] -->|"仅支持 BMP"| B1["最多 3 字节"]
|
||||
C["utf8mb4"] -->|"完整 UTF-8"| C1["最多 4 字节"]
|
||||
end
|
||||
subgraph "中文相关"
|
||||
D["gbk"] -->|"国标简体"| D1["2 字节"]
|
||||
E["gb2312"] -->|"老国标"| E1["2 字节"]
|
||||
F["big5"] -->|"繁体(台湾)"| F1["2 字节"]
|
||||
end
|
||||
subgraph "Unicode 其他"
|
||||
G["ucs2"] -->|"UTF-16 BE"| G1["定长 2 字节"]
|
||||
H["utf32"] -->|"UTF-32"| H1["定长 4 字节"]
|
||||
end
|
||||
|
||||
style C fill:#00B6BC,color:#fff
|
||||
style B fill:#EE5A24,color:#fff
|
||||
```
|
||||
|
||||
### 为什么一定要用 utf8mb4?
|
||||
|
||||
MySQL 中的 `utf8` 实际上只是 **utf8mb3** —— 它最多支持 3 字节编码,无法表示四字节字符(emoji、生僻汉字、部分符号)。
|
||||
|
||||
```sql
|
||||
-- 用 utf8 插入 emoji 会报错!
|
||||
SET NAMES utf8;
|
||||
INSERT INTO posts (content) VALUES ('Hello 🚀');
|
||||
-- ERROR 1366: Incorrect string value: '\xF0\x9F\x9A\x80'
|
||||
|
||||
-- 换 utf8mb4 就没有问题
|
||||
SET NAMES utf8mb4;
|
||||
INSERT INTO posts (content) VALUES ('Hello 🚀'); -- OK ✅
|
||||
```
|
||||
|
||||
> [!WARNING] utf8mb4 的性能影响
|
||||
> 相比 latin1,utf8mb4 每个字符多占 1~3 字节。这会带来:
|
||||
> - 同样的 VARCHAR 长度存的内容更少
|
||||
> - 索引体积增大,缓存命中率下降
|
||||
> - 网络传输量增加
|
||||
>
|
||||
> **建议**:除非明确知道不需要中文/emoji,否则一律使用 utf8mb4。现代 SSD 和内存足够应对这个开销。
|
||||
|
||||
## 排序规则 Collation
|
||||
|
||||
排序规则命名格式:`<字符集>_<语言>_<后缀>`,后缀含义:
|
||||
|
||||
| 后缀 | 含义 | 示例 |
|
||||
|------|------|------|
|
||||
| `_ci` | Case **I**nsensitive(忽略大小写) | `utf8mb4_general_ci` |
|
||||
| `_cs` | Case **S**ensitive(区分大小写) | `utf8mb4_general_cs` |
|
||||
| `_bin` | Binary(按字节比较) | `utf8mb4_bin` |
|
||||
|
||||
> [!NOTE] MySQL 8.0 的新后缀
|
||||
> 升级到 8.0 后,你会发现默认排序规则多了两个新后缀:
|
||||
> - `_ai_ci` = **A**ccent **I**nsensitive — 忽略重音差异(é = e)
|
||||
> - `_as_ci` = **A**ccent **S**ensitive — 保留重音差异(é ≠ e)
|
||||
>
|
||||
> 这解决了一个历史痛点:`utf8mb4_general_ci` 在处理带重音的拉丁字母时不够精确。
|
||||
|
||||
### 常见 Collation 对比
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph "不区分大小写 ci"
|
||||
GC["utf8mb4_general_ci<br/>⚡ 快速但略不精确"]
|
||||
UC["utf8mb4_unicode_ci<br/>📐 基于 Unicode 标准"]
|
||||
AC["utf8mb4_0900_ai_ci<br/>✅ MySQL 8.0 默认"]
|
||||
end
|
||||
|
||||
subgraph "区分大小写"
|
||||
GS["utf8mb4_general_cs"]
|
||||
US["utf8mb4_unicode_cs"]
|
||||
AS["utf8mb4_0900_as_ci<br/>Accent/Space Insensitive"]
|
||||
end
|
||||
|
||||
subgraph "严格二进制 bin"
|
||||
B2["utf8mb4_bin<br/>直接比较字节值"]
|
||||
end
|
||||
|
||||
IN1["输入 'abc' vs 'ABC'"] --> GC
|
||||
IN1 --> UC
|
||||
IN1 --> AC
|
||||
IN2["输入 'abc' vs 'ABC'"] --> GS
|
||||
IN2 --> US
|
||||
IN2 --> AS
|
||||
IN3["输入 'A' vs 'a'"] --> B2
|
||||
|
||||
GC --> R1["结果: a = b = c"]
|
||||
UC --> R1
|
||||
AC --> R1
|
||||
GS --> R2["结果: a ≠ A"]
|
||||
US --> R2
|
||||
AS --> R2
|
||||
B2 --> R3["结果: A ≠ a (0x41 ≠ 0x61)"]
|
||||
|
||||
style AC fill:#00B6BC,color:#fff
|
||||
style B2 fill:#EE5A24,color:#fff
|
||||
style GC fill:#C44569,color:#fff
|
||||
```
|
||||
|
||||
### 该选哪个 Collation?
|
||||
|
||||
| 场景 | 推荐 | 理由 |
|
||||
|------|------|------|
|
||||
| **中文项目** | `utf8mb4_0900_as_ci` | MySQL 8.0 默认排序,基于 ICU 标准 |
|
||||
| **英文项目** | `utf8mb4_0900_ai_ci` | AI = Accent Insensitive,忽略重音符号 |
|
||||
| **邮箱/用户名** | `utf8mb4_bin` | 严格区分大小写 |
|
||||
| **密码存储** | 永远不用 Collation——用 Hash | bcrypt/argon2 |
|
||||
|
||||
> [!TIP] 一个常见的选型误区
|
||||
> 很多人看到中文项目就选 `utf8mb4_unicode_ci`,但在 MySQL 8.0 下推荐优先使用 `utf8mb4_0900_*` 系列。它们基于 ICU 标准,对亚洲语言(中日韩)的排序更准确,性能也更好。`utf8mb4_unicode_ci` 本质上是 Unicode 4.0.0 的实现,而 0900 基于 Unicode 9.0.0。
|
||||
|
||||
```sql
|
||||
-- 设置整个数据库的默认字符集和排序规则
|
||||
CREATE DATABASE app_db
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
-- 单个表的覆盖
|
||||
CREATE TABLE usernames (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(50)
|
||||
) ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_bin; -- 用户名严格区分大小写
|
||||
```
|
||||
|
||||
## 层级关系:字符集 → Collation → 作用域
|
||||
|
||||
```mermaid
|
||||
graph BT
|
||||
subgraph "Server 层(全局默认)"
|
||||
S["server<br/>utf8mb4_0900_ai_ci"]
|
||||
end
|
||||
subgraph "Database 层(库级覆盖)"
|
||||
DB["database<br/>utf8mb4_unicode_ci"]
|
||||
end
|
||||
subgraph "Table 层(表级覆盖)"
|
||||
T["table<br/>utf8mb4_bin"]
|
||||
end
|
||||
subgraph "Column 层(列级最高优先级)"
|
||||
C["column<br/>utf8mb4_general_ci"]
|
||||
end
|
||||
|
||||
S --> DB --> T --> C
|
||||
|
||||
INFO["优先级:Column > Table > Database > Server<br/>每层都会覆盖上层的设置"]
|
||||
|
||||
style S fill:#5F2799,color:#fff
|
||||
style DB fill:#3D97BE,color:#fff
|
||||
style T fill:#FF9F43,color:#000
|
||||
style C fill:#C44569,color:#fff
|
||||
style INFO fill:#333,color:#fff
|
||||
```
|
||||
|
||||
查询当前配置:
|
||||
|
||||
```sql
|
||||
-- 查看所有层级的字符集和排序规则
|
||||
SHOW VARIABLES LIKE 'character_set_server';
|
||||
SHOW VARIABLES LIKE 'collation_server';
|
||||
|
||||
SHOW CREATE DATABASE app_db;
|
||||
SHOW CREATE TABLE users\G
|
||||
|
||||
-- 当前会话的字符集
|
||||
SHOW SESSION VARIABLES LIKE 'character_set%';
|
||||
```
|
||||
|
||||
|
||||
## Go 应用层字符集配置
|
||||
|
||||
光在数据库层面设置 utf8mb4 还不够——连接通道必须一致,否则客户端发送的字节会被服务端用错误的字符集解析,轻则乱码,重则报错。
|
||||
|
||||
### DSN 中指定 charset
|
||||
|
||||
```go
|
||||
// ✅ 标准写法:DSN 中加上 charset=utf8mb4
|
||||
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
db, err := sql.Open("mysql", dsn)
|
||||
```
|
||||
|
||||
> [!QUESTION] 为什么 DSN 里也要写 charset?
|
||||
> MySQL 客户端和服务端建立连接时有一个「握手阶段」,双方会协商使用哪个字符集。如果不在 DSN 中声明,MySQL 驱动会使用服务器默认的字符集。当服务器默认不是 utf8mb4 时(比如 legacy 系统的 latin1),就会出现连接层数据面编码不一致的问题。
|
||||
|
||||
### 运行时动态切换
|
||||
|
||||
```go
|
||||
// 某个极端场景下需要临时切换会话字符集
|
||||
_, err := db.Exec("SET NAMES utf8mb4 COLLATE utf8mb4_0900_ai_ci")
|
||||
// ⚠️ 注意:这影响整个连接,在连接池场景下要避免频繁调用
|
||||
```
|
||||
|
||||
> [!WARNING] 连接池中的陷阱
|
||||
> `sql.Open` 不会立即创建连接,而是在首次查询时懒加载。如果你的程序启动后从未执行过 SET NAMES,而服务端的 `character_set_server` 恰好不是 utf8mb4 —— 那第一个请求就可能触发乱码。
|
||||
>
|
||||
> **最佳实践**:在初始化连接池后立刻跑一次健康检查,确保连接已正确初始化。
|
||||
|
||||
```go
|
||||
// 建完连接池后立即验证
|
||||
err = db.Ping() // 这一步会实际创建一个连接
|
||||
```
|
||||
|
||||
## utf8 → utf8mb4 迁移指南
|
||||
|
||||
这是一个经典的线上改造场景。如果你的历史系统用了 MySQL 的 `utf8`(其实是 utf8mb3),逐步迁移到 utf8mb4 的步骤如下:
|
||||
|
||||
### 迁移步骤
|
||||
|
||||
```sql
|
||||
-- Step 1: 检查当前哪些表用了旧 utf8
|
||||
SELECT table_schema, table_name, column_name, character_set_name
|
||||
FROM information_schema.columns
|
||||
WHERE character_set_name IS NOT NULL
|
||||
AND character_set_name != 'utf8mb4'
|
||||
ORDER BY table_schema, table_name;
|
||||
|
||||
-- Step 2: 修改表的默认字符集(不影响已有数据)
|
||||
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
-- Step 3: 修改数据库默认字符集(新建表会自动继承)
|
||||
ALTER DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
-- Step 4: 修改应用层 DSN,确保连接层也使用 utf8mb4
|
||||
-- user:pass@tcp(host:3306)/db?charset=utf8mb4&parseTime=True
|
||||
```
|
||||
|
||||
> [!NOTE] ALTER TABLE CONVERT 的影响
|
||||
> - 对于小表(万行级别),几乎是瞬时完成
|
||||
> - 对于大表(千万行+),`CONVERT TO` 会重建整张表:复制全量数据 → 重建索引 → 替换原表
|
||||
> - **建议在低峰期操作**,或使用 `pt-online-schema-change` 进行在线 DDL,避免业务中断
|
||||
>
|
||||
> 如果只是改默认字符集而不影响现有列定义,可以用:
|
||||
> ```sql
|
||||
> ALTER TABLE users DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
> -- 注意:这是 DEFAULT,不改变已有列的字符集!
|
||||
> ```
|
||||
|
||||
### 常见踩坑
|
||||
|
||||
| 坑 | 现象 | 解法 |
|
||||
|----|------|------|
|
||||
| **只改了表,没改连接** | 能插入 emoji,但查出来是 `???` | DSN 加 `charset=utf8mb4` |
|
||||
| **只改了表默认值,没 CONVERT** | 已有列仍是 utf8mb3 | 必须用 `CONVERT TO` 重新编码 |
|
||||
| **GORM AutoMigrate 覆盖** | 手动改了字符集,下次 AutoMigrate 又被还原 | 禁用自动迁移,改用 migration 脚本管理 |
|
||||
|
||||
### 关联笔记
|
||||
|
||||
- [[hhs/Redis/02-核心数据类型]] — Redis 的 STRING 类型对多字节字符的处理
|
||||
- [[hhs/GORM/02-模型定义]] — GORM 建模时的字符串长度设置要点
|
||||
- [[hhs/MySQL/02-SQL核心/06-DDL 建表与结构变更]] — 大表 DDL 的在线变更策略
|
||||
- [[hhs/MySQL/08-工程实践/40-常见踩坑]] — 隐式转换等更多常见问题
|
||||
- [[hhs/MySQL/07-高可用与分布式/29-Binary Log]] — Binlog Format(ROW vs STATEMENT)对字符集的影响
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
tags: [MySQL, Database, 入门]
|
||||
create time: 2026-05-20 23:55
|
||||
---
|
||||
|
||||
# 一、入门基础
|
||||
|
||||
## 概述
|
||||
|
||||
本章从零开始介绍 MySQL:它的整体架构长什么样、怎么装起来、用什么工具连接、以及数据类型和字符集这些最基础但最容易踩坑的知识点。
|
||||
|
||||
## 本章文档
|
||||
|
||||
| # | 主题 | 说明 |
|
||||
|---|------|------|
|
||||
| 1 | [[hhs/MySQL/01-入门基础/01-MySQL 架构与入门]] | MySQL 分几层(客户端 → SQL → 引擎 → 存储);`mysqld` 是怎么启动的 |
|
||||
| 2 | [[hhs/MySQL/01-入门基础/02-安装与初始化]] | 本地安装、Docker Compose 快速启动、基本配置文件 |
|
||||
| 3 | [[hhs/MySQL/01-入门基础/03-客户端工具]] | 命令行 `mysql`、DBeaver、Workbench,选一个顺手的就行 |
|
||||
| 4 | [[hhs/MySQL/01-入门基础/04-数据类型全景]] | 整型、浮点、字符串、日期时间、JSON——每种类型适合存什么 |
|
||||
| 5 | [[hhs/MySQL/01-入门基础/05-字符集与排序规则]] | 为什么一定要用 `utf8mb4`、中文搜索不出来的坑 |
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/MySQL/README]] — MySQL 知识库总目录
|
||||
Reference in New Issue
Block a user