最近我把团队内部跑了一年多的代码工厂(Code Factory)重新翻修了一遍,核心服务用 Go 重写,底层存储全部落到 PostgreSQL。这一轮改造不是小打小闹——原来靠各自的本地脚本、Shell 拼装生成的代码,现在统一收敛到一个平台:模板管理中心、AI 辅助生成、任务编排、产物归档,全部一体化。为什么要动这个项目?因为代码生成这事情一旦多了,模板版本、参数组合、生成日志就全是脏数据,得有一个正经的数据库来兜底。
如果你也在搞类似的东西——不管是代码生成器、脚手架平台,还是自动化任务编排系统,这篇文章应该能帮你少踩几个坑。我会把整个优化过程拆开讲:从数据模型怎么设计、Go 那边连接池和批量写入怎么调,到查询索引怎么建、PG 版本到底选哪个,以及实际部署和排障中遇到的那些问题。内容偏实战,直接照着做就行。
1. 项目背景与整体架构设计
1.1 代码工厂到底解决了什么问题
先说清楚“代码工厂”是个什么东西。它不是那种命令行里跑一下的 scaffold 工具,而是一个持续运行的平台。团队里不同业务线的同学需要生成 API 服务、消息消费者、CRUD 页面、数据迁移脚本等各类代码,传统做法是人人本地装个 generator,各自维护模板,生成的代码风格不统一,模板更新了也没法追溯谁用的是老版本。
代码工厂要做的,就是把“代码生成”这一动作变成平台化的服务。核心流程是这样的:用户在前端选择一个模板,填写参数,系统把参数注入模板引擎,渲染出代码,再打包下载或者直接推送到代码仓库。整个过程涉及模板管理、参数校验、任务调度、产物存储、日志审计,任何一个环节出了问题,都得能查、能回滚、能重跑。
Go 在这套系统里承担的是核心服务的角色——HTTP API、任务队列消费、与模板引擎和外部命令的交互。选 Go 的原因很直接:编译产物单文件、并发模型适合大批量生成任务、部署简单,尤其在服务器上不需要预装一堆运行环境,拷贝二进制就能跑。这一点在后面的部署章节还会提到。
1.2 为什么最终选了 PostgreSQL 而不是 MySQL
这个选择我犹豫过一阵。MySQL 我们团队用得很多,运维经验也足,但这次我坚持用了 PostgreSQL,理由有三条。
第一,模板参数是典型的多态结构——不同模板的参数列表完全不一样,有的模板要数据库连接串,有的要消息队列 topic,还有要一大堆 JSON 配置的。PostgreSQL 的 JSONB 字段可以直接存这类半结构化数据,还能建 GIN 索引做内部查询,MySQL 的 JSON 类型虽然也能用,但功能和性能跟 PG 比还是有差距。
第二,代码工厂需要做全文检索——搜索模板、搜索生成日志、搜索产物包里的关键字。PostgreSQL 内置了全文检索和 tsvector,配上中文分词插件,基本不用引入额外的 Elasticsearch。MySQL 的全文检索在 InnoDB 上的表现,尤其是在中文场景下,确实不够顺手。
第三,PostgreSQL 的物化视图、窗口函数、递归查询、partial index 这些特性,在我们做统计报表和任务依赖分析时帮了大忙,MySQL 8.0 虽然补了不少功能,但 PG 在这块本来就做得更扎实。
1.3 整体技术栈与模块划分
改造后的系统分成了下面几个模块:
- API 网关层:Gin 框架提供 REST API,处理模板查询、生成请求、任务状态查询。
- 任务调度模块:基于 PG 中的任务表做状态机流转,配合 Go 的 goroutine 池执行生成任务。
- 模板引擎模块:支持 Go template、Jinja2 语法(通过外部命令调用 Python 渲染),后期接入了 opencode 的 AI 辅助生成能力。
- 产物存储模块:代码产物打成 zip 包,元数据存 PG,zip 二进制存对象存储或本地磁盘,PG 里只保留路径索引。
- 统计与审计模块:记录每次生成的参数快照、耗时、生成结果,按天/按模板维度统计。
这套模块划分是经过一次大的重构才定下来的。最早版本把所有逻辑都写在 API handler 里,模板渲染、任务状态管理、产物打包全挤在一起,代码混乱不说,并发稍高一点就各种锁等待。拆开之后各模块独立演进,任务调度单独跑worker,API 只做接收和查询,数据库压力小了很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据模型:围绕模板与任务设计存储
2.1 核心表结构与字段选型
我花了最多心思的其实是表结构设计,因为代码工厂的数据模型跟普通业务系统差异挺大。最终沉淀下来几张核心表,结构大致是这样。
模板表的核心字段:
sql复制CREATE TABLE template (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(128) NOT NULL,
slug VARCHAR(128) NOT NULL UNIQUE,
version INT NOT NULL DEFAULT 1,
category VARCHAR(64),
content TEXT NOT NULL, -- 模板原始内容
config JSONB NOT NULL DEFAULT '{}', -- 模板参数定义
engine VARCHAR(16) NOT NULL DEFAULT 'gotemplate',
status SMALLINT NOT NULL DEFAULT 1,
created_by VARCHAR(64),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
任务表的字段设计是另一个重点。生成任务是代码工厂里最核心的实体,状态机流转复杂,字段必须覆盖整个生命周期:
sql复制CREATE TABLE gen_task (
id BIGSERIAL PRIMARY KEY,
task_no VARCHAR(64) NOT NULL UNIQUE,
template_id BIGINT NOT NULL REFERENCES template(id),
template_version INT NOT NULL,
params JSONB NOT NULL DEFAULT '{}',
status SMALLINT NOT NULL DEFAULT 0, -- 0待执行 1执行中 2成功 3失败 4超时 5已取消
priority SMALLINT NOT NULL DEFAULT 5,
worker_id VARCHAR(64),
error_msg TEXT,
output_path TEXT,
attempt_count INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ
);
CREATE INDEX idx_gen_task_status ON gen_task(status, priority DESC, id ASC);
CREATE INDEX idx_gen_task_template ON gen_task(template_id, created_at DESC);
两个关键点:一是 task_no 用独立的业务编号而不是直接用自增 id,方便对外暴露和排查问题时快速定位;二是状态字段用 SMALLINT 而不是字符串,省空间且比较快,但代价是代码里需要维护枚举映射,各有利弊看团队习惯。
2.2 JSONB 字段怎么用才不踩坑
JSONB 是 PostgreSQL 的杀手锏,但用不好就成了灾难。我总结了几条实战经验。
第一,JSONB 字段一定给默认值。空对象就是 '{}'::jsonb,空数组就是 '[]'::jsonb,避免 NULL 导致的 ->> 运算符返回 NULL 而引发的逻辑 bug。
第二,频繁查询的 JSONB 内部字段要建 GIN 索引。比如任务表里经常要按 params->>'template_name' 过滤,这个查询建了索引之后性能天差地别:
sql复制CREATE INDEX idx_gen_task_params ON gen_task USING GIN (params);
第三,别把 JSONB 当万能药。如果某个字段查询频率极高,就把它的值冗余成独立列,加普通 B-tree 索引,查询更快、索引更小。我在模板表里就是 category 单独成列、config 里细粒度参数走 JSONB,两者配合。
2.3 模板版本管理与历史回溯
模板的版本管理是代码工厂区别于普通脚手架的关键功能。一个模板被修改后,之前生成的代码必须能够追溯——用户填的参数是什么、模板内容是哪个版本、生成结果是什么。
我在设计时把模板内容和模板定义分开存:template 表只存当前有效版本,template_history 表存每次变更的快照。生成任务里记录 template_version 字段,这样即使模板后续被修改,历史任务依然能还原出当时的生成逻辑。
版本管理的坑主要在两个地方。一个是并发修改:两个用户同时改同一模板,后提交的可能会覆盖先提交的。解决方案是加版本号乐观锁,更新时带上 version 条件。另一个是历史清理:生成任务爆炸增长后,历史快照占空间很大,我最后是写了个定时任务,把超过半年的历史任务归档到独立的分区表,应用查询只访问热数据。
3. Go 侧连接层与并发模型优化
3.1 pgx 还是 database/sql 加 lib/pq
Go 操作 PostgreSQL,老牌的方案是 database/sql + lib/pq,现在更推荐的是直接上 jackc/pgx。pgx 既是 driver 又是高级 API,纯 Go 实现,支持 PostgreSQL 特有的协议特性,性能和功能都比 lib/pq 强太多。
我最终选的是 pgx 的 pgxpool 连接池模式,它在 database/sql 之上提供了更细的控制。核心原因是代码工厂有大量的批量写入场景,pgx 的 CopyFrom 能直接走 PostgreSQL 的 COPY 协议,比逐条 INSERT 快好几倍。这个在后面的批量章节细说。
连接池的初始化代码基本是固定的套路:
go复制config, err := pgxpool.ParseConfig(dsn)
if err != nil {
log.Fatal(err)
}
config.MaxConns = 50
config.MinConns = 10
config.MaxConnLifetime = time.Hour
config.MaxConnIdleTime = 5 * time.Minute
config.HealthCheckPeriod = time.Minute
pool, err := pgxpool.NewWithConfig(ctx, config)
3.2 连接池参数到底怎么调
连接池参数是新手最容易忽略但又影响最大的地方。我见过不少项目直接抄别人的配置,完全不思考自己的业务形态。代码工厂的任务特点是突发性强——用户可能一口气提交上百个生成任务,瞬间数据库连接需求暴涨,但平时只有零星请求。
这种情况下 MaxConns 不能设得太小,我试过 10 个连接,任务一多请求全部排队,前端直接超时。也不能设得太大,连接数超过 PostgreSQL 端的 max_connections(默认 100),数据库直接拒绝连接。我最后调到了 50,留出余量给其他服务和运维工具。
MaxConnLifetime 这个参数我要特别提醒。PostgreSQL 在处理旧连接时会有一些状态残留问题,比如 prepared statement 失效、临时表残留等。把连接生命周期限制在 1 小时以内,可以有效规避这类玄学问题。实测下来,加上这个参数之后,线上偶发的 cached plan must not change result type 错误再没出现过。
3.3 批量入库:从 INSERT 到 COPY 协议
代码工厂有个场景:一次提交生成 500 个任务,每个任务完成后要写一条产物记录。最早实现是循环里逐条 INSERT,任务一多耗时蹭蹭往上涨。后来发现 pgx 支持 CopyFrom,直接通过 COPY 协议批量写入,性能提升非常明显。
go复制rows := [][]interface{}{
{"TASK001", 1, 2, `{"name":"order-service"}`, "success", "/data/artifacts/task001.zip"},
{"TASK002", 2, 1, `{"name":"user-service"}`, "failed", ""},
// ...
}
_, err := pool.CopyFrom(ctx,
pgx.Identifier{"artifact"},
[]string{"task_no", "template_id", "template_version", "params", "status", "output_path"},
pgx.CopyFromRows(rows),
)
实话说,从逐条 INSERT 改成 CopyFrom,500 条数据的写入耗时从 3 秒多降到了 200 毫秒出头,前提是要把批量大小控制在合理范围。我测试过 1000 条一批和 5000 条一批,差距不大,但超过 10000 条之后内存占用涨得厉害,所以最终定为 2000 条一批。
3.4 并发任务与事务隔离级别
代码工厂的任务执行涉及多步操作:更新任务状态为执行中、调用模板引擎渲染、写入产物、再次更新任务状态为成功。这四步如果在多个事务里独立执行,一旦中途崩溃,任务就会卡在“执行中”状态永远下不来。
我的做法是:每个任务一个事务,事务里完成状态流转和结果写入。事务隔离级别用默认的 Read Committed,这个级别下不需要担心脏读,而代码工厂的业务对一致性要求没那么苛刻,不需要 Serializable。
并发控制上有个细节容易被忽略。多个 worker 同时领取任务时,可能存在两个 worker 拿到同一个任务的情况。解决方法是在更新任务状态时加上条件:
sql复制UPDATE gen_task
SET status = 1, worker_id = $2, started_at = now()
WHERE task_no = $1 AND status = 0;
如果影响行数为 0,说明任务已经被别人领走了。这种乐观锁的设计比 SELECT ... FOR UPDATE 更轻量,在高并发下也不会产生锁等待。
4. 查询优化与慢 SQL 实战
4.1 慢查询日志与 EXPLAIN ANALYZE 的正确姿势
代码工厂上线一段时间后,后台统计页面开始变慢,特别是按模板维度查生成趋势的时候,一个接口要好几秒才返回。我先是打开了 PostgreSQL 的慢查询日志,定位到具体的 SQL,再用 EXPLAIN ANALYZE 分析执行计划。
慢查询日志的开启方式不复杂:
sql复制SET log_min_duration_statement = 500;
SET log_statement = 'ddl';
不过只开日志不够,更重要的是学会看 EXPLAIN ANALYZE 的输出。很多人在这一步就开始糊涂了,其实核心就三件事:看有没有走索引(Seq Scan 大概率有问题)、看每个节点的实际耗时(哪个节点最贵就是瓶颈)、看预估行数和实际行数的差异(统计信息是否过时)。
那次排查的结果是统计页面上的一个查询用了 COUNT(*) 之后 GROUP BY created_at,PG 在数据量大了之后选择了 Seq Scan。处理方式是建了正确的索引之后,顺带刷新了下统计信息,查询时间从 6 秒降到了 300 毫秒左右。
4.2 索引选择:B-tree、GIN 与覆盖索引
PostgreSQL 的索引类型很多,代码工厂用到了三种。
最常见的是 B-tree,用在等值查询和范围查询上。比如任务表的 created_at 字段、模板表的 slug 字段,都建了 B-tree 索引。
JSONB 字段上的查询用 GIN 索引,这个前面说过了。需要注意 GIN 索引更新代价比 B-tree 高,频繁更新的 JSONB 字段不适合加 GIN 索引,所以我把 params 字段设计成只读——任务是提交后参数不会再变,这样 GIN 索引的维护成本可控。
覆盖索引是另一个容易被忽略的性能利器。PostgreSQL 从 11 版本开始支持 INCLUDE 语法,可以在索引里冗余一些字段,避免回表查询:
sql复制CREATE INDEX idx_gen_task_created ON gen_task(created_at) INCLUDE (task_no, status);
这个索引在统计“某天生成了多少任务”之类的查询时,直接从索引里拿数据,不用访问表数据,速度自然快。
4.3 窗口函数替代 GROUP BY 的典型场景
代码工厂的统计页面需要展示每个模板的累计生成次数、最近一次生成时间,还有生成总数的环比变化。最初用 GROUP BY 写,SQL 长且慢,后来改成窗口函数,清爽高效。
一个具体例子:查询每个模板最近 5 次生成记录的耗时趋势:
sql复制SELECT
task_no,
template_id,
finished_at,
duration_ms,
ROW_NUMBER() OVER (PARTITION BY template_id ORDER BY created_at DESC) AS rn
FROM gen_task
WHERE status = 2
ORDER BY template_id, created_at DESC;
然后在外面包一层,过滤 rn <= 5。这种写法在 PG 里性能很好,而且逻辑清晰。窗口函数的优势是可以在同一行里同时访问聚合结果和明细数据,GROUP BY 做不到这一点。
4.4 分区表与归档策略
代码工厂跑了几个月之后,gen_task 表到了千万级,虽然索引都有,但数据量大了之后统计类查询明显变慢。我没有选择直接加更多的索引,而是做了分区表改造。
分区键选择了 created_at,按月分区。改造方式比较温和:建一张分区主表,然后把旧数据迁移进去,新数据自动路由到对应分区。
sql复制CREATE TABLE gen_task_part (
LIKE gen_task INCLUDING ALL
) PARTITION BY RANGE (created_at);
CREATE TABLE gen_task_part_202501 PARTITION OF gen_task_part
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
分区之后,统计查询只扫描当月分区而不是全表,响应时间大幅下降。而且数据归档也方便,直接把超过半年的分区 DETACH 掉,不会影响现有查询。
5. PostgreSQL 版本选择与部署实录
5.1 16 还是 17:版本差异与选型建议
内容规划到这里,有个实际问题绕不开:到底用哪个版本的 PostgreSQL?我一开始想直接上最新的 PostgreSQL 17,后来评估了一番还是选了 16。
PostgreSQL 17 确实带来了一些不错的改进:Vacuum 内存管理优化、MERGE 命令增强、逻辑复制性能提升等。但对于代码工厂这种业务来说,这些功能和性能优化并没有带来质的改变。反而是 16 版本经过了更长时间的线上验证,各种周边工具(监控插件、备份工具、ORM 驱动)兼容性做得更稳。
还有一个实际的考虑是热词里频繁出现的“postgresql 16便携版”。有些部署场景——比如临时环境、开发机——需要快速起一个实例,16 版本的 Docker 镜像和便携包生态更成熟,坑也都被踩平了。如果你有明确的需求用到 17 的新特性,再上 17 也不迟,否则 16 是稳妥的选择。
5.2 Docker 部署与数据目录挂载
代码工厂的服务端部署在 Linux 上,数据库的部署方式直接用了 Docker。这种方式最大的好处是升级省事——换一个镜像标签就能完成版本切换,数据通过 volume 持久化,不会因为容器删除而丢失。
我的 docker-compose 配置大概长这样:
yaml复制services:
postgres:
image: postgres:16-alpine
container_name: codefactory-pg
restart: always
environment:
POSTGRES_USER: codefactory
POSTGRES_PASSWORD: ${PG_PASSWORD}
POSTGRES_DB: codefactory
TZ: Asia/Shanghai
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
command:
- "postgres"
- "-c"
- "max_connections=200"
- "-c"
- "shared_buffers=2GB"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U codefactory"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pg_data:
有几个细节想说一下。alpine 镜像体积小,但某些扩展可能没有预编译的包,如果要用到一些特殊插件建议换 postgres:16 完整版。初始化脚本放在 docker-entrypoint-initdb.d 目录里,第一次启动时会按文件名顺序执行,适合建表、建索引、初始数据。数据目录单独用 volume 挂载,这是必须的,不要依赖容器层的写存储。
5.3 Linux 离线安装与升级实践
内网环境经常遇到没法用 Docker Hub 的情况,只能离线安装。PostgreSQL 的离线安装比想象中简单,关键是准备好事先下载好的 RPM 包或源码包。
以 CentOS 7.9 为例,离线安装 PostgreSQL 16 的步骤大概是这样:
- 在一台有网的机器上,从 PostgreSQL 官方 Yum 仓库下载所需的 RPM 包,包括
postgresql16、postgresql16-server、postgresql16-libs及其依赖。 - 把 RPM 包拷贝到目标机器,用
rpm -ivh *.rpm安装。 - 初始化数据库:
/usr/pgsql-16/bin/postgresql-16-setup initdb。 - 启动服务:
systemctl start postgresql-16,设置开机自启。
离线升级的情况更敏感。比如原环境是 PostgreSQL 14,要升到 16,不能直接替换二进制。正确路径是先用 pg_upgrade 工具做数据文件升级,先停掉旧实例、再跑升级命令、最后启动新实例。这里必须强调:升级前一定要完整备份数据。pg_dumpall 是做逻辑备份的常用手段,虽然慢一点,但在升级场景里最保险,不要盲目自信直接升。
6. 常见问题与排查技巧
6.1 连接数耗尽:诡异的 “FATAL: sorry, too many clients”
代码工厂上线的头一周,我就遇到了一次连接数耗尽的问题。现象是前端提交生成任务后 API 直接 502,查看服务端日志发现报错:FATAL: sorry, too many clients already。
排查思路是先确认 PG 端的限制:SHOW max_connections;,默认是 100。然后查当前连接数:SELECT count(*) FROM pg_stat_activity;,发现已经占满了。
真正的原因是 Go 侧连接池的 MaxConns 配了 150,而 PG 端 max_connections 只有 100,连接池里的连接全挤到了数据库上,其他服务排不上队。解决方法是双向协调:数据库端调 max_connections=200,Go 端连接池 MaxConns 降到 50,再留些余量给运维工具和管理员连接。
这个坑提醒我一个道理:连接池参数不是独立存在的,要跟数据库端配置联动考虑。
6.2 死锁:批量任务相互等待
死锁在代码工厂里出现过一次,症状是部分任务一直卡在“执行中”状态,不成功也不失败。查看 PG 日志发现了死锁信息。
场景是这样的:批量生成 100 个任务,每个任务在事务里更新任务状态,同时要往 artifact 表插入产物记录。不同 worker 执行的顺序不同,有的先更新 task A 再插入 artifact A,有的先插入 artifact B 再更新 task B,在极端情况下形成了环路等待。
解决死锁的思路有两个层面。第一是规范事务内的操作顺序,所有任务先更新任务表、再写产物表,保证多事务加锁顺序一致。第二是在批量任务场景下,引入一个短暂随机延迟,打散并发节奏,降低死锁概率。
6.3 PostgreSQL 参数调优清单
代码工厂在不同阶段的性能表现,跟数据库参数的关系非常直接。经过多轮调整,目前线上在用的参数配置如下:
| 参数 | 配置值 | 调整理由 |
|---|---|---|
max_connections |
200 | 满足 Go 连接池与 MinConns 需求,留运维余量 |
shared_buffers |
2GB | 内存充裕时,约等于总内存的 1/4,是 PG 的默认建议 |
effective_cache_size |
6GB | 告知 PG 可用系统缓存总量,供查询计划评估 |
work_mem |
32MB | 排序、哈希操作的可用内存,调大减少临时文件落盘 |
maintenance_work_mem |
256MB | 建索引、vacuum 等维护操作使用,调大加速 |
wal_buffers |
64MB | 写入缓冲,避免频繁 flush WAL |
checkpoint_completion_target |
0.9 | 延长 checkpoint 窗口,减少 IO 峰值 |
random_page_cost |
1.1 | SSD 上比默认更小,鼓励优化器走索引 |
log_min_duration_statement |
500 | 记录慢查询,方便后续优化 |
需要注意这些参数不是越大越好。work_mem 调得过大,在高并发连接下总内存会爆掉。实际调整时,先参考 PG 自带的 pg_settings 说明,再结合 EXPLAIN ANALYZE 的结果修正。
6.4 缓存外部化:别把数据库当缓存用
代码工厂有个接口是查询模板列表,前端每次打开页面都会调用。数据量不大,但请求频率高,一开始这个查询直接打 PG,虽然走了索引,仍然导致数据库 CPU 偶尔飙高。
方案是加了一层基于 Go map 的进程内缓存,TTL 30 秒,代码不到 20 行,就把模板列表接口的数据库查询从每秒几十次降到了几乎为零。注意缓存与数据库的一致性——模板变更后主动失效对应的缓存 key。对代码工厂这种低频变更的元数据来说,这种冷热分离是最实用的优化,比砸钱上 Redis 实在得多。
这件事给我的启示是:优化数据库性能,很多时候先别急着调 SQL 和加索引,先看看哪些查询是可以完全不查数据库的。能用缓存兜住的,就别让数据库多干活。
7. 一段真实的经验总结
这一轮把代码工厂从工程泥潭里拉出来的过程,我一直记着几个关键节点。
第一,代码生成类系统的数据模型,一开始就应该按照“模板、任务、产物”三条线分清,别让它们混在一起。混在一起导致的后果是后期每次加功能都要先解耦,成本极高。第二,Go 和 PostgreSQL 的组合在代码生成这种 IO 密集、批量操作多的场景下相当趁手,但前提是连接池、批量写入、索引这老三样得打磨到位,这一块没有捷径。
最意外的是 AI 辅助生成能力的接入,让“代码工厂”这个名字真正有了想象空间。opencode 这类 AI 工具可以在模板引擎渲染的基础上,对生成的代码做进一步的质量检查和优化建议,相当于给流水线加了一个质检员。实际接入过程中发现,AI 的回复质量依赖上下文的组织方式,强行把大段模板内容塞给 AI 效果反而差,更合理的做法是提取关键结构片段送进去。
做这类项目的另一个体会是,不要指望一套方案打天下。代码工厂里大量用了 PostgreSQL 的高级特性,但我依然保留了 MySQL 团队那套心智模型——先把数据模型理顺,再去考虑特性。好的技术选型,永远是解决业务问题的副产品,而不是炫技的借口。
最后说一个小经验:数据库优化的优先级,应该先看数据模型是不是合理,再看查询是不是有索引,最后才轮到参数调优。数据模型错了,索引和参数再怎么调都是事倍功半。这一轮改造之所以顺利,正是因为先把表结构拆清楚了,后面所有优化都是水到渠成。
