这条路径解决“会写 SQL,但不敢碰生产库”的问题:先建立关系模型和查询反馈的直觉,再练慢查询、锁、复制、备份、连接池和迁移。适合后端开发、SRE 和需要独立维护数据库的运维工程师。
前置知识:会用 Linux 命令行,写过基本 SQL,能在本机跑 Docker。四个阶段按依赖排序,每阶段建议 3–4 小时;没有现成数据库的,用单容器 MySQL 8 和 PostgreSQL 16 起步。
所有破坏性实验都在一次性容器或测试实例里做。生产库上禁止直接演练 KILL、pg_terminate_backend、主从切换和全量恢复;这些操作必须先有审批、备份和回滚路径。
目标:能设计三张有关联的表,写出可解释执行计划的查询,并说清 MySQL 与 PostgreSQL 的差异边界。
阅读顺序:
EXPLAIN 和扩展。动手任务:在本机 Docker 起 MySQL 8 和 PostgreSQL 16,各建一张订单表,插入数据并对比 EXPLAIN。环境是本机 Docker,端口只绑定 127.0.0.1;密码是实验用的临时密码。风险是容器会占内存和磁盘,结束后删除容器和卷。
mkdir -p /tmp/db-lab
docker run -d --name lab-mysql -e MYSQL_ROOT_PASSWORD=labpass -p 127.0.0.1:3307:3306 mysql:8.0
docker run -d --name lab-pg -e POSTGRES_PASSWORD=labpass -p 127.0.0.1:5433:5432 postgres:16
sleep 15
docker exec -i lab-mysql mysql -uroot -plabpass <<'SQL'
CREATE DATABASE shop;
USE shop;
CREATE TABLE orders (id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, amount DECIMAL(10,2), created_at DATETIME, KEY idx_user_created (user_id, created_at));
INSERT INTO orders VALUES (1, 10, 99.00, NOW());
EXPLAIN SELECT * FROM orders WHERE user_id = 10 ORDER BY created_at DESC;
SQL
docker exec -i lab-pg psql -U postgres -c 'CREATE DATABASE shop;'
docker exec -i lab-pg psql -U postgres -d shop -c 'CREATE TABLE orders (id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, amount NUMERIC(10,2), created_at TIMESTAMPTZ);'
docker exec -i lab-pg psql -U postgres -d shop -c 'EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 10;'
过关标准:
EXPLAIN 中的扫描方式、行数估算和是否命中索引。目标:看到慢、卡、连接满时,能从执行计划、锁等待和连接状态定位到具体 SQL 或事务。
阅读顺序:
EXPLAIN 和索引选择。pg_hba、监听地址和连接数。动手任务:先按 lab-mysql-deadlock 在一次性 MySQL 容器里复现死锁,再在 PostgreSQL 容器里制造一次锁等待并读阻塞链。环境是隔离容器;风险是锁会阻塞其他会话,绝不能在共享库执行,事后回滚事务并确认没有残留锁。
docker exec lab-mysql mysql -uroot -plabpass -e 'SHOW ENGINE INNODB STATUS\G' | sed -n '/LATEST DETECTED DEADLOCK/,/TRANSACTIONS/p'
docker exec -i lab-pg psql -U postgres -d shop <<'SQL'
CREATE TABLE accounts (id INT PRIMARY KEY, balance INT NOT NULL);
INSERT INTO accounts VALUES (1,100),(2,100);
SQL
docker exec -i lab-pg psql -U postgres -d shop -c "BEGIN; UPDATE accounts SET balance=balance-10 WHERE id=1; SELECT pg_sleep(15); COMMIT;" &
sleep 3
docker exec -i lab-pg psql -U postgres -d shop -c "SELECT pid, state, wait_event_type, wait_event, left(query,60) FROM pg_stat_activity WHERE state <> 'idle';"
docker exec -i lab-pg psql -U postgres -d shop -c "SELECT * FROM pg_locks WHERE NOT granted;"
wait
过关标准:
pg_stat_activity 和 pg_locks 指出阻塞链的源头会话。目标:能搭一主一从,判断复制延迟原因,并完成一次经过验证的恢复。
阅读顺序:
replay_lag、复制槽和从库回放。动手任务:在 PostgreSQL 容器里做一次逻辑备份,恢复到新库并核对行数;如果已按 PITR 页面搭好归档,再验证 pg_stat_replication 和备份清单。环境是一次性容器,备份文件只写到 /tmp。风险是备份包含业务数据,必须限制文件权限;恢复只能写到新库,不能覆盖原库。
docker exec lab-pg pg_dump -U postgres -Fc shop > /tmp/db-lab/shop.dump
docker exec -i lab-pg createdb -U postgres shop_restore
docker exec -i lab-pg pg_restore -U postgres -d shop_restore < /tmp/db-lab/shop.dump
docker exec -i lab-pg psql -U postgres -d shop_restore -c 'SELECT count(*) FROM orders;'
docker exec -i lab-pg psql -U postgres -c 'SELECT client_addr, state, sent_lsn, replay_lsn, replay_lag FROM pg_stat_replication;'
过关标准:
目标:能选择连接池位置和大小,设计一次可回滚的迁移,并按访问模式而不是流行度选型。
阅读顺序:
pg_upgrade 与逻辑复制。动手任务:对订单表做一次“只导结构”的迁移预检,列出 MySQL 专有语法和类型,再在 PostgreSQL 中核对版本与兼容项。环境是一次性容器;风险是结构导出本身安全,但不要把预检脚本直接当迁移脚本执行,更不能在生产库上跑。
docker exec -i lab-mysql mysqldump -uroot -plabpass --no-data --skip-comments shop > /tmp/db-lab/shop-schema.sql
grep -nE 'AUTO_INCREMENT|ENGINE=|`' /tmp/db-lab/shop-schema.sql | head -20
docker exec lab-mysql mysql -uroot -plabpass -e "SHOW VARIABLES LIKE 'character_set_server';"
docker exec -i lab-pg psql -U postgres -d shop -c 'SELECT version();'
docker exec -i lab-pg psql -U postgres -d shop -c '\d+ orders'
过关标准:
合上页面,先回答,再展开对照。
replay_lag 和复制线程状态,结合大事务、从库 CPU/IO、复制槽和网络 RTT 判断;只看 Seconds_Behind_Master 容易误判。