PlanetScale Schema 变更 #
PlanetScale 的非阻塞 Schema 变更是其核心功能之一,本章将深入介绍其工作原理和使用方法。
传统 DDL 的问题 #
MySQL DDL 的痛点 #
text
┌─────────────────────────────────────────────────────────────┐
│ 传统 DDL 问题 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 场景:给大表添加列 │
│ │
│ ALTER TABLE users ADD COLUMN phone VARCHAR(20); │
│ │
│ 问题: │
│ │
│ 1. 表锁定 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ ████████████████████████████████████████████ │ │
│ │ 表被锁定,所有读写操作等待 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 2. 长时间阻塞 │
│ - 1000 万行数据:约 10 分钟 │
│ - 1 亿行数据:约 2 小时 │
│ - 10 亿行数据:约 20 小时 │
│ │
│ 3. 影响生产 │
│ - 服务响应变慢 │
│ - 连接池耗尽 │
│ - 可能导致服务中断 │
│ │
│ 4. 维护窗口 │
│ - 需要安排停机时间 │
│ - 需要夜间/周末操作 │
│ - 团队需要待命 │
│ │
└─────────────────────────────────────────────────────────────┘
传统解决方案 #
text
┌─────────────────────────────────────────────────────────────┐
│ 传统解决方案 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. pt-online-schema-change (Percona) │
│ ├── 创建新表 │
│ ├── 复制数据 │
│ ├── 使用触发器同步增量 │
│ └── 最后切换表名 │
│ │
│ 2. gh-ost (GitHub) │
│ ├── 无触发器设计 │
│ ├── 通过 binlog 同步 │
│ ├── 更安全可靠 │
│ └── 需要自行部署管理 │
│ │
│ 3. 手动迁移 │
│ ├── 创建新表 │
│ ├── 编写迁移脚本 │
│ ├── 双写期间同步 │
│ └── 切换应用 │
│ │
│ 问题: │
│ ❌ 需要自己运维工具 │
│ ❌ 配置复杂 │
│ ❌ 需要专业知识 │
│ ❌ 出错风险高 │
│ │
└─────────────────────────────────────────────────────────────┘
PlanetScale 的解决方案 #
在线 Schema 变更 #
text
┌─────────────────────────────────────────────────────────────┐
│ PlanetScale OSC │
├─────────────────────────────────────────────────────────────┤
│ │
│ 核心优势: │
│ │
│ ✅ 非阻塞 │
│ - 不锁表 │
│ - 读写正常进行 │
│ - 用户无感知 │
│ │
│ ✅ 自动化 │
│ - 无需手动操作 │
│ - 自动处理同步 │
│ - 自动切换 │
│ │
│ ✅ 可回滚 │
│ - 变更前自动备份 │
│ - 支持回滚操作 │
│ - 安全可靠 │
│ │
│ ✅ 可追踪 │
│ - 变更历史记录 │
│ - 审核流程 │
│ - 团队协作 │
│ │
└─────────────────────────────────────────────────────────────┘
工作原理 #
text
┌─────────────────────────────────────────────────────────────┐
│ VReplication 原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 阶段 1:创建影子表 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 原表 users │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ id, name, email, created_at │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 影子表 _users_new │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ id, name, email, phone, created_at │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 阶段 2:复制数据 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ users ──────复制──────> _users_new │ │
│ │ │ │
│ │ 同时:应用正常读写 users 表 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 阶段 3:持续同步 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ users ──────VReplication──────> _users_new │ │
│ │ (实时同步增量变更) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 阶段 4:原子切换 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ RENAME TABLE users TO _users_old, │ │
│ │ _users_new TO users; │ │
│ │ │ │
│ │ 切换在毫秒级完成,不影响应用 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
执行 Schema 变更 #
基本流程 #
text
┌─────────────────────────────────────────────────────────────┐
│ 变更流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 步骤 1:创建开发分支 │
│ ┌─────────────┐ │
│ │ main │ │
│ └──────┬──────┘ │
│ │ │
│ │ pscale branch create db add-phone │
│ ▼ │
│ ┌─────────────┐ │
│ │ add-phone │ │
│ └─────────────┘ │
│ │
│ 步骤 2:执行 DDL │
│ pscale shell db add-phone │
│ mysql> ALTER TABLE users ADD COLUMN phone VARCHAR(20); │
│ │
│ 步骤 3:创建 Deploy Request │
│ pscale deploy-request create db add-phone │
│ │
│ 步骤 4:审核并部署 │
│ pscale deploy-request deploy db 1 │
│ │
│ 步骤 5:验证变更 │
│ pscale shell db main │
│ mysql> DESCRIBE users; │
│ │
└─────────────────────────────────────────────────────────────┘
CLI 操作详解 #
bash
# 1. 创建开发分支
pscale branch create my-database add-phone-column
# 2. 等待分支就绪
pscale branch show my-database add-phone-column
# 3. 连接到开发分支
pscale shell my-database add-phone-column
# 4. 执行 Schema 变更
mysql> ALTER TABLE users ADD COLUMN phone VARCHAR(20);
Query OK, 0 rows affected (0.05 sec)
# 5. 验证变更
mysql> DESCRIBE users;
+------------+--------------+------+-----+-------------------+
| Field | Type | Null | Key | Default |
+------------+--------------+------+-----+-------------------+
| id | int | NO | PRI | NULL |
| name | varchar(100) | NO | | NULL |
| email | varchar(255) | NO | UNI | NULL |
| phone | varchar(20) | YES | | NULL |
| created_at | timestamp | YES | | CURRENT_TIMESTAMP |
+------------+--------------+------+-----+-------------------+
# 6. 退出 shell
mysql> exit
# 7. 创建 Deploy Request
pscale deploy-request create my-database add-phone-column
# 8. 查看变更
pscale deploy-request diff my-database 1
# 9. 部署变更
pscale deploy-request deploy my-database 1
支持的 Schema 变更 #
添加列 #
sql
-- 添加单列
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- 添加多列
ALTER TABLE users
ADD COLUMN phone VARCHAR(20),
ADD COLUMN address TEXT,
ADD COLUMN birth_date DATE;
-- 添加带默认值的列
ALTER TABLE users
ADD COLUMN status VARCHAR(20) DEFAULT 'active';
-- 添加带约束的列
ALTER TABLE users
ADD COLUMN age INT NOT NULL DEFAULT 0;
删除列 #
sql
-- 删除单列
ALTER TABLE users DROP COLUMN deprecated_field;
-- 删除多列
ALTER TABLE users
DROP COLUMN old_field1,
DROP COLUMN old_field2;
注意:删除列会永久丢失数据!
修改列 #
sql
-- 修改列类型
ALTER TABLE users MODIFY COLUMN name VARCHAR(200);
-- 修改列名
ALTER TABLE users CHANGE COLUMN old_name new_name VARCHAR(100);
-- 修改列默认值
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'inactive';
-- 删除默认值
ALTER TABLE users ALTER COLUMN status DROP DEFAULT;
索引操作 #
sql
-- 添加索引
CREATE INDEX idx_email ON users(email);
CREATE INDEX idx_name_email ON users(name, email);
-- 添加唯一索引
CREATE UNIQUE INDEX idx_unique_email ON users(email);
-- 添加全文索引
CREATE FULLTEXT INDEX idx_content ON posts(content);
-- 删除索引
DROP INDEX idx_email ON users;
-- 添加主键(如果不存在)
ALTER TABLE users ADD PRIMARY KEY (id);
-- 删除主键
ALTER TABLE users DROP PRIMARY KEY;
表操作 #
sql
-- 创建新表
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
total DECIMAL(10, 2) NOT NULL,
status ENUM('pending', 'paid', 'shipped', 'completed') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_status (status)
);
-- 删除表
DROP TABLE old_table;
-- 重命名表
RENAME TABLE old_table TO new_table;
-- 修改表名
ALTER TABLE old_table RENAME TO new_table;
不支持的变更 #
外键约束 #
text
┌─────────────────────────────────────────────────────────────┐
│ 外键限制说明 │
├─────────────────────────────────────────────────────────────┤
│ │
│ PlanetScale 不支持外键约束: │
│ │
│ ❌ 不支持的操作 │
│ ALTER TABLE orders │
│ ADD CONSTRAINT fk_user │
│ FOREIGN KEY (user_id) REFERENCES users(id); │
│ │
│ 原因: │
│ - 影响分片能力 │
│ - 降低写入性能 │
│ - 增加复杂度 │
│ │
│ 替代方案: │
│ - 应用层验证关系 │
│ - 使用事务保证一致性 │
│ - ORM 的关联功能 │
│ │
└─────────────────────────────────────────────────────────────┘
存储过程和触发器 #
text
┌─────────────────────────────────────────────────────────────┐
│ 存储过程限制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ 不支持 │
│ - 存储过程 (Stored Procedures) │
│ - 触发器 (Triggers) │
│ - 自定义函数 (User-defined Functions) │
│ - 事件调度器 (Event Scheduler) │
│ │
│ 替代方案: │
│ - 将逻辑移到应用代码 │
│ - 使用云函数 │
│ - 使用消息队列 │
│ - 使用 CDC 监听变更 │
│ │
└─────────────────────────────────────────────────────────────┘
变更最佳实践 #
变更前检查 #
text
┌─────────────────────────────────────────────────────────────┐
│ 变更前检查清单 │
├─────────────────────────────────────────────────────────────┤
│ │
│ □ 理解变更影响 │
│ - 影响哪些表 │
│ - 影响哪些查询 │
│ - 是否需要应用代码配合 │
│ │
│ □ 备份重要数据 │
│ - 导出关键数据 │
│ - 记录当前 Schema │
│ │
│ □ 测试变更 │
│ - 在开发分支测试 │
│ - 验证变更正确性 │
│ - 测试应用兼容性 │
│ │
│ □ 通知团队 │
│ - 告知变更内容 │
│ - 协调部署时间 │
│ - 准备回滚方案 │
│ │
└─────────────────────────────────────────────────────────────┘
安全变更模式 #
sql
-- 1. 添加可空列(安全)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- 2. 添加带默认值的列(安全)
ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active';
-- 3. 扩大列长度(安全)
ALTER TABLE users MODIFY COLUMN name VARCHAR(200);
-- 4. 添加索引(安全,但可能影响写入性能)
CREATE INDEX idx_email ON users(email);
-- 5. 删除列(危险!需要先更新应用)
-- 步骤:
-- a. 确认应用不再使用该列
-- b. 执行删除
ALTER TABLE users DROP COLUMN deprecated_field;
-- 6. 重命名列(危险!需要同步更新应用)
-- 步骤:
-- a. 添加新列
-- b. 更新应用使用新列
-- c. 迁移数据
-- d. 删除旧列
大表变更策略 #
text
┌─────────────────────────────────────────────────────────────┐
│ 大表变更建议 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 对于大表(> 1000 万行): │
│ │
│ 1. 分批变更 │
│ - 避免一次性大量变更 │
│ - 每次变更少量列 │
│ │
│ 2. 监控进度 │
│ - 查看变更状态 │
│ - 关注系统负载 │
│ │
│ 3. 选择低峰期 │
│ - 避开流量高峰 │
│ - 减少影响范围 │
│ │
│ 4. 准备回滚 │
│ - 记录变更内容 │
│ - 准备回滚脚本 │
│ │
│ 注意:PlanetScale 会自动处理大表变更, │
│ 但变更时间会更长。 │
│ │
└─────────────────────────────────────────────────────────────┘
变更回滚 #
自动回滚 #
text
┌─────────────────────────────────────────────────────────────┐
│ 自动备份 │
├─────────────────────────────────────────────────────────────┤
│ │
│ PlanetScale 在部署前自动创建备份: │
│ │
│ 1. 部署前 │
│ ┌─────────────┐ │
│ │ main │ │
│ │ (当前状态) │ │
│ └──────┬──────┘ │
│ │ │
│ │ 自动备份 │
│ ▼ │
│ ┌─────────────┐ │
│ │ 备份点 │ │
│ └─────────────┘ │
│ │
│ 2. 部署变更 │
│ ┌─────────────┐ │
│ │ main │ │
│ │ (新 Schema)│ │
│ └─────────────┘ │
│ │
│ 3. 如需回滚 │
│ 联系支持恢复到备份点 │
│ │
└─────────────────────────────────────────────────────────────┘
手动回滚 #
bash
# 创建回滚分支
pscale branch create my-database rollback-phone
# 执行反向变更
pscale shell my-database rollback-phone
mysql> ALTER TABLE users DROP COLUMN phone;
# 创建 Deploy Request
pscale deploy-request create my-database rollback-phone
# 部署回滚
pscale deploy-request deploy my-database 2
监控变更进度 #
查看变更状态 #
bash
# 查看 Deploy Request 状态
pscale deploy-request show my-database 1
# 输出示例
ID: 1
Branch: add-phone
State: in_progress
Created: 2024-01-15 12:00:00
Updated: 2024-01-15 12:05:00
Web 控制台监控 #
text
┌─────────────────────────────────────────────────────────────┐
│ 部署进度 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Deploy Request #1 │
│ Branch: add-phone → main │
│ │
│ Status: In progress │
│ ████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 45% │
│ │
│ Steps: │
│ ✅ Schema validation │
│ ✅ Create shadow table │
│ 🔄 Copy data (45%) │
│ ⏳ Synchronize changes │
│ ⏳ Cut over │
│ ⏳ Cleanup │
│ │
│ Estimated time remaining: 5 minutes │
│ │
└─────────────────────────────────────────────────────────────┘
常见问题 #
变更卡住 #
text
问题:Deploy Request 状态一直是 in_progress
原因:
- 大表变更需要时间
- 系统负载高
- 网络问题
解决:
1. 查看详细进度
2. 等待变更完成
3. 如果超时,联系支持
变更失败 #
text
问题:Deploy Request 显示 error 状态
原因:
- Schema 不兼容
- 数据类型问题
- 系统错误
解决:
1. 查看错误信息
2. 修复问题
3. 创建新的 Deploy Request
应用兼容性 #
text
问题:Schema 变更后应用报错
原因:
- 应用代码未更新
- 列名不匹配
- 数据类型不兼容
解决:
1. 更新应用代码
2. 使用向后兼容的变更策略
3. 必要时回滚 Schema
下一步 #
现在你已经掌握了 Schema 变更,接下来学习 数据库连接,了解如何从应用连接 PlanetScale!
最后更新:2026-03-29