MySQL 实践
要点
- MySQL 是国内使用最广泛的关系型数据库,生态成熟、云服务商支持好
- 与 PostgreSQL 相比,JSON 支持较弱,无原生向量扩展
- 与 Hono 集成使用 Drizzle 或 Prisma,schema 定义与 PostgreSQL 类似
- 字符集推荐 utf8mb4,避免中文和 emoji 存储问题
- 自增主键和 UUID 主键都可以,选择取决于业务需求
- 云托管 MySQL(RDS、CDB)是生产环境的常见选择
1. 安装与启动
1.1 本地开发
macOS:
brew install mysql
brew services start mysqlLinux(Ubuntu):
sudo apt install mysql-server
sudo systemctl start mysqlDocker:
docker run -d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=hono_app \
-p 3306:3306 \
mysql:81.2 创建数据库
mysql -u root -p
CREATE DATABASE hono_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;2. 与 Hono 集成
2.1 Drizzle ORM
pnpm add drizzle-orm mysql2
pnpm add -D drizzle-kit// src/schema.ts
import { mysqlTable, varchar, text, json, timestamp, int } from 'drizzle-orm/mysql-core'
export const users = mysqlTable('users', {
id: varchar('id', { length: 36 }).primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 255 }).notNull(),
metadata: json('metadata'),
createdAt: timestamp('created_at').defaultNow(),
})
export const conversations = mysqlTable('conversations', {
id: varchar('id', { length: 36 }).primaryKey(),
userId: varchar('user_id', { length: 36 }).notNull(),
title: varchar('title', { length: 255 }).notNull(),
model: varchar('model', { length: 100 }).notNull(),
messageCount: int('message_count').default(0),
createdAt: timestamp('created_at').defaultNow(),
})// src/lib/db.ts
import { createPool } from 'mysql2/promise'
import { drizzle } from 'drizzle-orm/mysql2'
import * as schema from '../schema'
const pool = await createPool({
uri: process.env.DATABASE_URL,
connectionLimit: 20,
})
export const db = drizzle(pool, { schema })// src/routes/users.ts
import { Hono } from 'hono'
import { db } from '../lib/db'
import { users } from '../schema'
import { eq } from 'drizzle-orm'
const app = new Hono()
app.get('/users', async (c) => {
const allUsers = await db.select().from(users)
return c.json(allUsers)
})
app.post('/users', async (c) => {
const body = await c.req.json()
await db.insert(users).values(body)
return c.json({ ok: true }, 201)
})
export default app2.2 Prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique @db.VarChar(255)
name String @db.VarChar(255)
metadata Json?
createdAt DateTime @default(now())
@@map("users")
}3. 字符集配置
MySQL 默认字符集是 latin1,中文和 emoji 会乱码。必须用 utf8mb4:
-- 创建数据库时指定
CREATE DATABASE hono_app
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- 修改现有数据库
ALTER DATABASE hono_app
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;表级别也需要设置:
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;utf8mb4 支持完整的 Unicode(包括 emoji),utf8 是 MySQL 的历史遗留名称,实际只支持 3 字节 UTF-8。
4. 主键选择
4.1 自增主键
import { mysqlTable, int, varchar } from 'drizzle-orm/mysql-core'
import { autoincrement } from 'drizzle-orm/mysql-core'
export const users = mysqlTable('users', {
id: int('id').primaryKey().autoincrement(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 255 }).notNull(),
})优势:
- 存储空间小(4 字节 vs UUID 的 16 字节或 36 字符)
- 索引紧凑,查询性能好
- 插入性能好(顺序写入)
劣势:
- 可猜测,API 安全性差
- 分布式环境会冲突
4.2 UUID 主键
import { mysqlTable, varchar } from 'drizzle-orm/mysql-core'
import { v4 } from 'uuid'
export const users = mysqlTable('users', {
id: varchar('id', { length: 36 }).primaryKey().$defaultFn(() => v4()),
email: varchar('email', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 255 }).notNull(),
})优势:
- 不可猜测
- 分布式友好
劣势:
- 存储空间大
- 随机插入导致页分裂,索引性能差
4.3 选择建议
- 内部系统、不需要暴露 ID:自增主键
- API 场景、需要安全性:UUID 主键
- 高并发写入:自增主键或 UUID v7(带时间戳,可排序)
5. JSON 类型
MySQL 5.7+ 支持 JSON 类型,但功能不如 PostgreSQL 的 JSONB:
import { mysqlTable, json } from 'drizzle-orm/mysql-core'
export const documents = mysqlTable('documents', {
id: varchar('id', { length: 36 }).primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
metadata: json('metadata'),
})// 插入 JSON
await db.insert(documents).values({
id: 'doc-1',
title: 'Hono Guide',
metadata: { author: 'Alice', tags: ['hono', 'typescript'] },
})
// 查询 JSON 字段
import { sql } from 'drizzle-orm'
const docs = await db
.select()
.from(documents)
.where(sql`JSON_EXTRACT(metadata, '$.author') = 'Alice'`)MySQL 的 JSON 查询不如 JSONB 方便,不能直接建 GIN 索引。如果需要频繁的 JSON 查询,考虑用生成列 + 索引:
ALTER TABLE documents
ADD COLUMN author VARCHAR(255) GENERATED ALWAYS AS (JSON_EXTRACT(metadata, '$.author')) VIRTUAL;
CREATE INDEX idx_documents_author ON documents(author);6. 与 PostgreSQL 的差异
| 特性 | MySQL | PostgreSQL |
|---|---|---|
| JSON 查询 | JSON_EXTRACT() | ->> 操作符 |
| JSON 索引 | 生成列 + 索引 | GIN 索引 |
| 向量支持 | 无原生支持 | pgvector 扩展 |
| UUID 类型 | 无(用 CHAR(36)) | 原生 UUID 类型 |
| 自增主键 | AUTO_INCREMENT | SERIAL 或 GENERATED |
| 事务隔离 | 默认 REPEATABLE READ | 默认 READ COMMITTED |
| 字符串函数 | CONCAT()、SUBSTRING() | ` |
| 日期函数 | NOW()、DATE_FORMAT() | NOW()、TO_CHAR() |
迁移代码时需要注意这些差异。使用 ORM 可以屏蔽大部分差异。
7. 云托管 MySQL
生产环境通常使用云托管 MySQL:
- 阿里云 RDS MySQL:国内首选,生态完善
- 腾讯云 CDB:腾讯云环境
- AWS RDS MySQL:国际化项目
- PlanetScale:Serverless MySQL,基于 Vitess
云托管的优势:
- 自动备份、高可用
- 读写分离、分库分表
- 监控告警
连接云托管 MySQL 与本地没有区别,只是连接字符串不同:
const pool = await createPool({
uri: process.env.DATABASE_URL, // 云托管提供的连接字符串
})8. 性能优化
8.1 索引
import { mysqlTable, varchar, index } from 'drizzle-orm/mysql-core'
export const conversations = mysqlTable('conversations', {
id: varchar('id', { length: 36 }).primaryKey(),
userId: varchar('user_id', { length: 36 }).notNull(),
createdAt: timestamp('created_at').defaultNow(),
}, (table) => ({
userIdIdx: index('idx_user_id').on(table.userId),
createdAtIdx: index('idx_created_at').on(table.createdAt),
}))8.2 查询分析
EXPLAIN SELECT * FROM conversations
WHERE user_id = 'user-1'
ORDER BY created_at DESC
LIMIT 10;8.3 慢查询日志
MySQL 可以开启慢查询日志,记录执行时间超过阈值的查询:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 1 秒9. 适用场景
MySQL 适合以下场景:
- 团队熟悉 MySQL:降低学习成本
- 云服务商提供优质 MySQL 托管:阿里云、腾讯云
- 读多写少:MySQL 的读性能优秀
- 不需要复杂 JSON 查询或向量检索:这些场景 PostgreSQL 更合适
- 已有 MySQL 基础设施:避免引入新技术栈
如果项目需要向量检索、复杂 JSON 查询,或者团队没有 MySQL 经验,建议选 PostgreSQL。
总结
MySQL 是成熟稳定的关系型数据库,生态完善、云服务商支持好。
这一节涉及到的几个实践:
- 与 Hono 集成:Drizzle 或 Prisma,连接池管理
- 字符集:必须用 utf8mb4,避免中文和 emoji 问题
- 主键选择:自增主键性能好,UUID 安全性好
- JSON 类型:功能不如 PostgreSQL JSONB,频繁查询用生成列 + 索引
- 与 PostgreSQL 差异:语法、函数、类型有差异,ORM 可以屏蔽
- 云托管:生产环境常用 RDS、CDB 等托管服务
- 性能优化:索引、查询分析、慢查询日志
MySQL 适合团队熟悉、不需要复杂 JSON 或向量检索的项目。如果需要 pgvector 或强大的 JSON 查询,建议选 PostgreSQL。
下一篇看 SQLite 与轻量项目——SQLite 的特点、Cloudflare D1、适用场景。