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