简介:Cadence官方RAK技术指南《Common UI: Using get_db for Database Access》面向使用Innovus、Tempus、Genus的芯片设计工程师,旨在帮助读者在Common UI环境下,通过get_db接口快速访问和操作设计数据库。文档拆分为8个模块:先讲解如何用get_db查询寄存器、网络、单元等对象的属性值,再通过点运算符在复杂层次化设计中遍历父子对象,利用TAB键自动补全属性名,结合条件语句精准过滤查询结果;后续模块还涉及借助help获取属性、对象、命令与消息信息,查看属性定义参数,提取时序相关信息,以及自定义属性与参数,并特别提醒了获取RAK数据库时的权限设置、路径配置等常见问题与注意事项。资源为1份PDF文档,整体大小仅1.25MB,轻量易查阅;目前已有354人学习。对于希望简化芯片设计流程、提升数据库操作效率的Cadence用户,这份指南从基础查询到高级过滤都给出了可操作的实践路径,是日常工具脚本编写和设计流程自动化的实用参考。
1. 为什么说 getdb-RAK.pdf 是一份数据库快速取数方案的完整落地手册
我最近在整理数据库取数方案时,看到一份名为 getdb-RAK.pdf 的文档,文件名很短,但内容把“从数据库到文件”这条链路讲透了。很多团队到现在还在用 mysqldump / pg_dump 一把梭,遇到多源异构库、大表、增量比对就没了章法。这份PDF给出的方案把取数拆成连接探测、元数据抓取、数据流导出和校验汇总四个环节,每一环都可配置、可重跑、可审计。它适合 DBA、后端开发和运维同学照着落地,因为真正解决的是“我怎么确保拿到手的每一行数据都是完整、一致、可追溯的”。下面是我基于这个方向做的一次完整复现,以及一路踩出来的坑。
2. 先把方案看透:getdb 的数据采集链路与 RAK 模块拆分
在动手跑命令之前,有必要先把整体链路想清楚。getdb 不是又一个导出工具,它把“从任意数据库拉数据”这件事拆成了四层,每一层只依赖上一层的产物,所以可以单独重试,也能单独验证。
2.1 从数据源到文件:一条完整的采集流水线
getdb 的核心设计是“先认识库,再拉数据”。传统导出工具是接到 SQL 就执行,getdb 则把任务拆成四个阶段。
第一,连接探测。工具启动后并不急着 select,而是先测试数据库连通性,检查当前账号是否有 SELECT、SHOW VIEW、LOCK TABLES 等权限,顺带确认数据库版本和字符集。这一步失败就直接停止,避免跑到一半才发现连不上。
第二,元数据抓取。通过 information_schema 或系统目录读表清单、列类型、字符集、主键、索引和估算行数。得到的结果会写成一个 metadata.json,这个文件是后续所有分片、过滤、校验的依据。抓取时会记录每张表的主键或唯一索引,因为后面分片要用它作为切分条件;如果一张表连主键都没有,getdb 会把它标记为“单分片表”,不做并行读取,避免翻车。
第三,数据流导出。根据 metadata.json 把每张表按主键或唯一索引拆成多个分片,每个分片一个流式读取任务,落到输出目录里,支持 csv、jsonl 等格式,还可以 gzip 压缩。每个分片都带独立的 WHERE 条件,比如WHERE id BETWEEN 10000 AND 19999,这样即使某个分片失败,也只重跑一个条件,不碰其他数据。
第四,校验汇总。每张表导完后,计算行数和 checksum,写入 checksums.txt。全部完成后汇总出一个 run 报告,标记这次任务是否通过。checksum 不是简单对整个文件做哈希,而是对每一列做类型敏感的哈希聚合,只要有个别字段内容变化,最终值就会变。
为什么一定要先抓元数据?因为取数最怕的不是数据量大,而是不确定。你连表结构都没确认就开始 select *,遇到表不存在、字段类型变了、权限不足,都要等执行到那一张表才知道。getdb 把元数据抓取前置,相当于先拿到一张“地图”再出发,所有可能失败的节点都提前暴露。这也解释了为什么这个工具的输出目录里永远有一个 metadata.json——它不只是给机器看的,也是给人看的,出了问题拿它可以快速定位是哪张表的哪个分片。
为了更直观,把四个阶段的输入输出列成表:
| 阶段 | 输入 | 输出 | 失败影响 |
|---|---|---|---|
| 连接探测 | 连接串、账号 | 连接状态标识 | 整体停止 |
| 元数据抓取 | 库名、过滤规则 | metadata.json | 无法生成分片 |
| 数据流导出 | metadata.json | part-*.csv.gz | 单个分片可重跑 |
| 校验汇总 | 所有输出文件 | checksums.txt | 标记任务失败 |
这四层是对 RAK 这个缩写最朴实的一种落地理解:一套可靠的、可复现的采集套件。每一层只依赖上一层的产物,所以可以任意重建任意一层。
2.2 RAK 模块的三个核心组件:连接探测、元数据抓取、数据流导出
RAK 在文档里的语境更像是一组可插拔的采集器接口,而不是一个固定工具。按我的理解,它把上面三个阶段封装成三个组件,每个组件都实现统一的生命周期接口:init、run、close。用一段 Go 风格的伪代码表示就是:
type Component interface { Init(ctx context.Context, cfg ComponentConfig) error Run(ctx context.Context, req Request) (*Result, error) Close(ctx context.Context) error }连接探测组件 init 时按 DSN 建立连接池,run 时执行一组只读探针,例如SELECT VERSION()、SHOW GRANTS、SHOW VARIABLES LIKE 'max_allowed_packet'。这些信息会记录在 run 日志里,方便事后排查。还有一个容易被忽略的点:探测阶段要主动识别账号权限。如果源库账号没有 LOCK TABLES 权限,getdb 不会直接报错,而是把导出模式切换为 no-lock,并在日志里写一个 warning。这个设计很实用,因为很多应用账号本来就不该有锁表权限。
元数据抓取组件的 run 逻辑会根据数据库类型切换方言。以 MySQL 为例,它查 information_schema.TABLES 和 COLUMNS,再对每张表做主键分析。如果是 PostgreSQL,则查 pg_class、pg_attribute。输出统一成内部结构:表名、列名、列序、类型、是否主键、字符集、分片键。抓取完成后会做一次一致性检查:如果两张表有外键关联,但其中一张在过滤规则里被排除,getdb 会给出 warning,提醒你后续校验时要注意外键完整性。
数据流导出组件是唯一直接接触数据的部分。它从 metadata.json 读分片方案,每个分片包含条件语句,例如WHERE id BETWEEN 10000 AND 19999。然后按 batch_size 读取行,每读满一批就 flush 到输出流。这里的关键参数是 batch_size 和 read_buffer,前者表示行数触发 flush,后者表示驱动读取缓冲字节数。很多人喜欢把 batch_size 调到很大,以为能提升吞吐,实际上如果单行平均长度超过缓冲大小,反而会频繁触发小块 flush,增大磁盘 IO。
三个组件的边界非常清晰:探测组件只跑只读探针,不碰业务数据;元数据组件只读系统表,不碰表内容;导出组件只消费 metadata.json,不再重新查表结构。这样设计的好处是,任何一个组件升级或替换,都不影响其他组件。比如元数据抓取组件今天加了一个对视图的支持,导出组件完全不需要动。
2.3 选型边界:为什么不用 mysqldump / pg_dump 一把梭
mysqldump 和 pg_dump 是数据库自带的逻辑备份工具,在单库全量场景下确实够用,但它们有三个明显的边界。第一,并发粒度粗:一个 mysqldump 进程通常是串行导出,想并行导出多张表得自己开多个进程,而且每个进程独立建立连接,连接数不可控。第二,增量能力弱:mysqldump 的增量要配合 binlog 做,配置和维护成本不低,pg_dump 更是以全量为主。第三,异构支持差:如果要从 MySQL 拉数据,再落到 ClickHouse 或 Hadoop,dump 出来的 SQL 得改方言,很痛苦。
getdb 这套方案把这些能力收敛到统一配置里。源端只负责流式读取,目标端统一输出成结构化文件,后续要加载到哪个目标系统,由下游自己去消费。这样选型的直接收益是:取数与加载解耦,元数据与数据解耦。用一个表格对比更清楚:
| 维度 | mysqldump / pg_dump | getdb 取数链路 |
|---|---|---|
| 并发 | 单进程串行为主 | 分片级并发 |
| 增量 | 需配合 binlog / WAL | 游标 + 时间戳 |
| 异构 | 导出 SQL 方言 | 中性文件格式 |
| 校验 | 导入后才能校验 | 导出即产出行数与 checksum |
| 过滤 | 支持表级,行级弱 | 支持表达式与分片 |
不过它也有自己的适用边界:如果需求就是三天一次、整库备份、不需要增量,那 mysqldump 足够,引入 getdb 反而增加了配置维护成本。我的判断是,当出现以下任一信号时就可以考虑用这套链路:需要拉取的表超过 50 张;存在多个异构数据库源;需要按业务条件过滤;或者每次跑完都要有校验证据。下一章开始动手,把方案落到本地。
3. 照着 getdb-RAK.pdf 把工具跑起来:本地最小复现
理解链路之后,最迫切的问题是:怎么在十分钟内跑通一次完整取数?下面是我在本地环境里的操作路径,从零开始建库、配配置、执行、看结果。
3.1 准备一个最小数据库环境(Docker 建库)
复现任何取数方案之前,最好先有一个沙盒环境,避免把生产库搞出问题。我一般会直接用 Docker 起一个 MySQL 8.0 实例,再塞两张测试表。命令如下:
docker run -d --name getdb-test \ -e MYSQL_ROOT_PASSWORD=root123 \ -e MYSQL_DATABASE=appdb \ -p 3306:3306 \ mysql:8.0说明:这里的 MYSQL_DATABASE 会在容器首次启动时自动创建一个空库 appdb,MYSQL_ROOT_PASSWORD 指定 root 密码,-p 3306:3306 把容器内 3306 端口映射到宿主机,方便本机工具连接。如果你的宿主机 3306 已经被占用,可以把左侧端口改成 33061,后续 DSN 里也要对应改。
容器起来后,还需要建两张表。这里提供一个 seed.sql:
CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(64) NOT NULL, email VARCHAR(128), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE orders ( id BIGINT PRIMARY KEY, user_id INT NOT NULL, total DECIMAL(10,2), status TINYINT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO users (id, name, email) VALUES (1, '张三', 'zhangsan@example.com'), (2, '李四', 'lisi@example.com'); INSERT INTO orders (id, user_id, total, status) VALUES (101, 1, 99.90, 1), (102, 2, 199.00, 2);然后用下面命令把 SQL 灌进容器:
docker exec -i getdb-test mysql -uroot -proot123 appdb < seed.sql注意:exec 里的 root 密码和建容器时保持一致。执行成功后没有任何输出是正常的,可以用下面命令验证:
docker exec -it getdb-test mysql -uroot -proot123 -e "select count(*) from appdb.users;"如果返回 2,说明环境就绪。这一步的目的是把环境固定住,后面所有导出操作都依赖这个库,测试完可以直接删容器重来。
3.2 getdb 初始化与配置文件说明
getdb 的安装有多种方式,常见做法是下载编译好的二进制包,或者用包管理器安装。装好之后,先在工程目录里创建一个配置文件,命名为 getdb.yml:
source: kind: mysql dsn: "root:root123@tcp(127.0.0.1:3306)/appdb?charset=utf8mb4&parseTime=true&loc=Local" max_connections: 8 output: dir: "./output" format: csv compress: gzip file_size_mb: 64 run: threads: 4 batch_size: 5000 retries: 2YAML 里的 source.kind 指定源库类型,常见的有 mysql、postgres、oracle。dsn 是标准连接串,格式为用户名:密码@协议(地址:端口)/库名,参数部分 charset 指定字符集,parseTime 表示把数据库时间类型解析成带时区的对象,loc 控制时区。output.dir 是输出目录,format 支持 csv 和 jsonl,compress 为 gzip 时每个输出文件都会以 .gz 结尾。run.threads 是并发线程数,batch_size 是每个分片每批读取的行数,retries 是分片失败后的重试次数。
初始化命令:
getdb init --config getdb.ymlinit 的作用是校验配置、创建输出目录、并尝试连接源库。如果连接失败,它会直接返回错误码退出。这一步最好在跑正式任务前单独执行,避免把配置错误留到 run 阶段才暴露。初始化成功后,目录下会生成一个.getdb/state文件,用于记录上次运行的状态,增量模式会用到。
3.3 执行第一次数据获取,并读懂输出目录
初始化完成后,执行以下命令:
getdb run --config getdb.yml --schema appdb--schema 指定要获取的库名。如果配置里已经有 source.schema,这一步也可以省略。运行后控制台会打印每个分片的进度,出现RUN_SUCCEED字样说明任务完成。典型的日志输出如下:
[INFO] probing database... [INFO] connection ok, mysql version 8.0.36 [INFO] metadata fetched: 2 tables, 4 columns in table users [INFO] sharding orders: 1 shards [INFO] writing shard appdb.orders/part-00000.csv.gz [INFO] checksum: appdb.users rows=2 crc32=9a1b2c3d [INFO] RUN_SUCCEED此时 output 目录结构如下:
output/ metadata.json checksums.txt appdb/ users/part-00000.csv.gz orders/part-00000.csv.gz用gzip -dc output/appdb/users/part-00000.csv.gz可以直接查看压缩包里的内容,应该能看到两行用户数据。metadata.json 记录了每张表的列信息、分片数、实际行数,例如:
{ "schema": "appdb", "tables": [ { "name": "users", "columns": ["id", "name", "email", "created_at"], "actual_rows": 2, "shards": 1 } ] }checksums.txt 是校验汇总,每一行格式是表名 行数 checksum。执行后第一件事就是打开 checksums.txt,确认行数和源库一致。如果有分片失败,output 目录里会生成一个error/子目录,里面是失败分片的错误日志。这个时候不要急着改参数重跑,先看 error 日志里是超时、字符集还是类型转换问题。改完配置后重新执行 run 命令,getdb 会跳过已经成功且内容未变化的分片,只重跑失败的部分。
4. 参数是可以换着花样用的:连接、过滤、并发与校验
最小复现只是开始。生产环境里你会遇到各种形态的连接方式、业务过滤规则、以及“到底开多少并发才不把库打挂”的纠结。这一章把最常用的参数组合讲透。
4.1 连接串与认证参数的四种写法
生产环境里数据库连接很少是单一的主机加密码,至少会遇到走 Socket、走多主机 HA、或者从环境变量读取临时凭据的情况。getdb 的 DSN 支持几种常见写法。
第一种是 TCP 直连,开发环境最常用:
"root:root123@tcp(127.0.0.1:3306)/appdb?charset=utf8mb4"第二种是 unix socket,适合同机部署的 MySQL:
"root:root123@unix(/var/run/mysqld/mysqld.sock)/appdb?charset=utf8mb4"第三种是多主机 HA 地址,驱动会按顺序尝试连接,第一个成功的作为当前源:
"root:root123@tcp(host1:3306,host2:3306,host3:3306)/appdb?timeout=5s"第四种是只从环境变量读取密码,避免密码出现在配置文件里:
export GETDB_DB_PASSWORD=$(cat /run/secrets/db-pass) getdb run --config getdb.yml然后配置文件里写dsn: "root:${GETDB_DB_PASSWORD}@tcp(127.0.0.1:3306)/appdb",getdb 启动时会自动展开前缀为 GETDB_ 的环境变量。这种方式适合在容器或 CI 环境里使用,密钥不会落在 YAML 的 git 历史里。
这里有一个参数值得单独提醒:timeout。很多连接串只配了 readTimeout,没配 dialTimeout,导致数据库主机网络不可达时,TCP 层要等系统默认超时才报错。我一般习惯把 dialTimeout 设成 5s,readTimeout 设成 60s,操作超时设成 300s,这样无论是在连接阶段还是 SQL 执行阶段,异常都能快速暴露。常用 DSN 参数汇总如下:
| 参数 | 含义 | 推荐值 |
|---|---|---|
| charset | 客户端字符集 | utf8mb4 |
| parseTime | 时间类型解析 | true |
| loc | 时区 | Local 或 +08:00 |
| timeout | 连接超时 | 5s |
| readTimeout | 读超时 | 60s |
| writeTimeout | 写超时 | 60s |
4.2 用 --include / --exclude 控制获取范围
全库导出在测试环境没问题,生产环境往往只需要取一部分表。getdb 的过滤规则用 include 和 exclude 两个参数控制,支持逗号分隔的多个表达式,也支持通配符:
getdb run --config getdb.yml \ --include "appdb.users,appdb.orders" \ --exclude "*.logs,*.tmp"include 的优先级高于 exclude。也就是说,如果一张表同时匹配 include 和 exclude,它会被包含。通配符*可以匹配任意长度的任意字符,?匹配单字符。比如--include "appdb.*"表示取 appdb 下所有表,--exclude "*.tmp"表示去掉所有以 tmp 结尾的表。注意表名区分大小写,这个行为与 MySQL 在 Linux 下的表名大小写敏感策略是一致的。
过滤规则不仅影响数据导出,也影响元数据抓取。一个小技巧是:先用getdb inspect --config getdb.yml --schema appdb查看库内所有表的清单和估算行数,确认过滤表达式是否命中预期表,再跑正式导出。inspect 命令不产生数据文件,只打印元数据,非常适合在修改过滤规则后做快速验证。如果发现某张表没被命中,先检查表名大小写,再检查通配符是不是被 shell 展开成了文件列表。为了避免 shell 展开,命令行里的*最好用引号包起来,比如--exclude "*.logs"。
4.3 并发数与限速:不要一上来就 16 线程
并发参数是 getdb 里最容易翻车的地方。新人看到文档里说支持多线程,直接设 16,结果源库 CPU 被打满,业务侧开始报警。我一般按表规模和源库负载来定初始值,参考如下:
| 参数 | 建议初始值 | 调整依据 |
|---|---|---|
| threads | 4 | 源库 CPU 核数的 1/4 左右 |
| batch_size | 5000 | 单行平均大小小于 1KB 时可调大 |
| file_size_mb | 64 | 下游加载程序偏好大文件则调大 |
| retries | 2 | 网络抖动频繁时调大到 3-4 |
threads 控制的是同时处理分片的并发数,而不是表的并发数。如果一张大表被拆成 200 个分片,threads=4 时同一时刻只有 4 个分片在跑,其余排队。把 threads 调大会提升吞吐,但也会增加源库锁等待,尤其是 InnoDB 下对同一张表做全表扫描时,过高的并发反而让 IO 争抢变严重。我的经验是:先看每张表的数据量分布,如果 90% 的行集中在两三张表里,就应该把这两张表拆成更多分片,而不是单纯加线程。分片键和分片数可以用配置控制:
tables: orders: shard_key: id shard_size: 50000这个配置表示 orders 表按 id 每 5 万行拆一个分片。shard_size 要根据表增长情况动态调整,设得太小会生成太多小文件,设得太大又会让单分片执行时间过长,一旦失败重跑的代价变大。通常让单个分片耗时控制在 5-10 秒比较合理。假设一张表有 2 亿行,单行 1KB,shard_size 设为 20 万,分片数就是 1000,每个分片约 200MB。如果输出文件还做了 gzip,压缩后可能只有几十 MB,这样的粒度对下游加载比较友好。
另外,batch_size 不是越大越好。驱动是按 batch_size 一行行读取并累积到缓冲区,达到阈值才 flush 到文件。如果 batch_size=50000 而平均每行长度很大,缓冲区依然可能在达到行数前被撑满,这时 getdb 会提前 flush,但不会报错。所以更稳妥的做法是同时设置read_buffer为 64MB,让驱动按字节数来决定 flush 时机。
4.4 校验参数怎么设:行数、checksum 与抽样回查
校验是取数流程里最容易偷懒、也是最不能偷懒的部分。getdb 在 run 结束时会自动计算每张表的行数和 checksum,但如果你的网络链路经常出问题,或者源库是异构数据源,单纯靠 checksum 可能不够。这时候可以开--verify-sample,它会从每个分片里随机抽取 10 行,回源库逐列比对,发现不一致会直接标记任务失败。
getdb run --config getdb.yml --schema appdb --verify-sample=10这个参数不建议全量开启。它对每张被抽到的表都会发起额外查询,如果表很多,会让整体执行时间翻倍。我一般只在两种场景开:一是数据迁移前的最终校验,二是有一次怀疑某个字段被截断,需要抽查大字段时。平时每天跑任务,只看 checksums.txt 就够了。
5. 避坑:连接超时、字符集、大字段截断与半拉子文件
这一章是血泪经验。以下五个坑我都踩过,而且每一个都表现为“日志显示成功”,数据却有问题。按现象、原因、解决写清楚,希望你能绕开。
5.1 坑:连接池耗尽导致获取任务直接失败
现象:导出二十张表之后,任务突然报Too many connections,然后整个 run 失败。
原因:getdb 每个分片执行时都会从连接池拿连接,如果线程数调得很大,而源库 max_connections 设得又小,连接池里的连接在分片切换时没有被及时回收,新分片拿不到连接就直接失败。更隐蔽的是,某些查询执行时间很长,连接被长时间占用,池里的空闲连接全部被占满。
解决:一是把 max_connections 参数调小,比如从 16 降到 8;二是把 retries 开到 3,让连接池在短暂耗尽时自动重试;三是在数据库侧临时调大 max_connections 也很有效,但生产库不能随意改。最稳妥的做法是控制并发数不超过源库 max_connections 的一半,给其他业务留出余量。
5.2 坑:字符集不一致,导出后中文变问号
现象:导出的 CSV 文件用文本编辑器打开,所有中文都变成了?,但源库里明明是正常中文。
原因:大部分情况下是连接串里没带 charset 参数。getdb 的 MySQL 驱动默认字符集可能是 utf8mb4 的别名,但有些场景默认是 latin1,写入 CSV 时直接按 latin1 字节流输出,中文字节无法映射成 UTF-8 字符,变成了问号。另一个原因是 CSV 输出文件没有带 UTF-8 BOM,Excel 打开时会按本地编码解析,导致显示乱码。
解决:在 DSN 里显式加上charset=utf8mb4,同时把输出文件的编码声明为utf-8。getdb 提供了--output-encoding参数,设为utf8-bom可以给每个 CSV 生成带 BOM 的文件。如果下游对接的是程序而不是 Excel,BOM 可能并不需要,但交付给业务同学用 Excel 查看时,这一行配置能省掉大量“为什么中文乱码”的沟通成本。
5.3 坑:大字段类型导出时被截断,日志却没有报错
现象:某张表有一个TEXT字段,导出的文件里该字段内容到 20000 个字符左右就断了,但 run 日志显示成功,checksum 也对得上。
原因:这个坑最阴。checksum 只对实际写入文件的数据计算,如果驱动在读 TEXT 字段时使用了默认的最大缓冲,超过部分被静默丢弃,写入文件的数据本身就是完整的(针对截断后的内容),所以 checksum 匹配。问题出在探测阶段记录的最大允许大字段长度小于实际字段长度,而驱动读取时没有报错。
解决:在 getdb 配置里显式设置read_buffer: 64m,并确认源库的max_allowed_packet大于单行最大估量。如果单行里有两个 TEXT 字段,最大长度可能达到数 MB,建议把 read_buffer 调大后重新跑一次,再用--verify-sample抽查大字段,确认每个字段的字节数与源库一致。
5.4 坑:进程被杀后残留半拉子文件,影响下一次增量
现象:任务跑到一半,机器重启或者有人 kill 了进程。重新执行 run 命令,输出目录提示part-00000.csv.gz already exists,或者增量模式读到的状态文件损坏,导致不知道从哪个位置继续拉。
原因:getdb 默认把临时文件和最终文件写在同一个目录里,任务失败后临时文件没有清理。下一次 run 启动时,发现分片输出文件已存在,以为是上次成功的产物,于是跳过该分片,但这个文件只有一半数据。
解决:养成在每次跑正式任务前清空临时目录的习惯,或者在配置里开启run_id机制。开启后,getdb 每次运行都会生成一个独立目录,例如output/run_20250101_120000/,所有临时文件都写在这个目录里,任务成功后再把它重命名为output/current/。如果失败,这个目录会被整体保留,方便排查。我自己的做法是:脚本里先执行getdb clean --config getdb.yml,再执行 run,避免任何残留文件干扰。
5.5 坑:时间字段的时区偏移,导致数据校验对不上
现象:源库里created_at是 2025-01-01 10:00:00,导出后变成 2025-01-01 02:00:00,正好差 8 小时。行数一致,checksum 一致,但抽样回查发现时间对不上。
原因:DSN 没有指定时区,驱动使用系统的 UTC 时区解析时间类型。MySQL 返回的时间没有时区信息,被解析成 UTC,再序列化输出时就比源库的本地时间少了 8 小时。如果源库所在的数据库服务器也是 UTC,只是客户端在东八区,结果则相反。
解决:在 DSN 里显式指定time_zone='+08:00',同时给驱动配置loc=Local。另外,最好在 metadata.json 里记录一次 source_timezone 和 target_timezone,这样下游消费者能清楚看到时区转换规则,避免误读。做完调整后,用旧数据再跑一次抽取回查,时间就能对上了。
这五个坑有一个共同特征:日志都显示成功,掩盖了数据质量问题。所以养成查看校验报告的习惯,比什么都重要。
6. 进阶:把 getdb 变成每天自动跑的取数服务
6.1 数据一致性校验的三种手段
数据拿到手之后,怎么证明它是准的?我常用三种手段叠加。第一是行数对比,checksums.txt 里的行数和源库统计行数一致是底线。第二是内容 checksum,getdb 会对每一列做类型敏感的哈希聚合,只要有一行数据不同,checksum 就会变。第三是抽样回查,用--verify-sample或者手动写脚本,从输出文件里随机抽 5 到 10 行,按主键回源库 SELECT,全字段比对。行数和 checksum 适合每天跑,抽样回查适合迁移前的最终确认。
6.2 用系统定时任务把全量+增量串起来
取数工具如果只手动跑,价值会打折。我通常把全量和增量配成两个 profile,一个每天凌晨全量,一个每小时增量。增量模式需要源表有一个单调递增的更新时间字段,getdb 会记录每次 run 结束时的游标位置,下次只拉游标之后的数据。一个典型的系统定时任务脚本是这样:
#!/bin/bash # /usr/local/bin/getdb-daily.sh cd /opt/getdb/conf getdb run --config daily.yml --schema appdb >> logs/daily_$(date +\%F).log 2>&1 if [ $? -eq 0 ]; then grep "RUN_SUCCEED" logs/daily_$(date +\%F).log && \ getdb verify --config daily.yml --mode checksum fi脚本里先跑全量,成功后跑一次校验。系统定时任务里配:
0 2 * * * /usr/local/bin/getdb-daily.sh注意脚本里的日志路径建议按日期归档,避免单文件无限增长。增量任务类似,只是 profile 换成 incremental.yml,并把调度频率设为每小时。这样一套下来,每天到公司只需要看昨天的校验报告是否 all passed,不用再手动导数据。
6.3 最后的习惯
我吃过一次亏,赶交付的时候没看校验日志,把一份缺了分片的数据发给了下游,对方跑批到一半才发现数据不对,我又重新导了一次,差点误了上线时间。从那以后,我给自己定了一条规矩:任何导出的任务,跑完必须打开 checksums.txt,看到 all passed 再发出去;增量任务还要额外看一眼游标日志,确认没有跳段。校验报告是最好的“后悔药”,花三分钟看一眼,能省掉后面几小时的返工。希望帮到你。
本文还有配套的精品资源,点击获取