python对比不同数据库的数据

用Python做数据对比,对比最麻烦的不同往往不是逻辑本身,而是数据数据不同数据库之间的“方言”差异。上周帮朋友处理一个对账需求,对比MySQL里存着订单流水,不同PostgreSQL里是数据数据结算记录,两边表结构看着差不多,对比跑出来的不同结果却对不上。问题出在时间字段:MySQL的数据数据DATETIME默认不带时区,PostgreSQL的对比TIMESTAMP WITH TIME ZONE直接按UTC存。Python的不同pymysql读出来是朴素datetime对象,psycopg2读出来是数据数据带时区的。直接比较?对比等于拿苹果和削了皮的苹果比。

很多人第一反应是不同写个脚本,把两张表都SELECT *出来,数据数据然后用pandas的merge或者compare。数据量小的时候没问题,超过十万行就开始卡。更糟的是,不同数据库的NULL处理、字符串排序规则、浮点数精度,全都在暗处等着咬你一口。我见过一个案例:SQLite里NULL和空字符串是两回事,Oracle里空字符串就是NULL。Python读出来一个None一个'',df.equals()直接返回False,查了半天才发现是数据库语义差异。

比较靠谱的做法是让数据库自己先做一轮“标准化”。比如统一用字符串格式输出时间,或者把两边都转成UTC时间戳。Python这边用SQLAlchemy的create_engine连接不同数据库,写一个中间层把结果集转成同一种结构。我习惯用records库或者pandas.read_sql,但记得指定dtype,别让pandas自己猜。猜错了就是object和int64打架,NaN和None混淆。

具体到代码,可以这样干:先分别从两个库拉数据,存成两个DataFrame,列名对齐。然后给每行算一个哈希值,用hashlib.md5把关键字段拼起来。这样比较的是哈希,不是原始值,能绕开类型差异。但要注意,浮点数不能直接拼字符串,得先round到固定小数位。字符串要strip,因为MySQL的CHAR会补空格,PostgreSQL不会。这些细节不写出来,对账永远对不平。

另一个坑是主键顺序。merge的时候如果没指定how='outer'和indicator=True,你根本不知道哪边多了哪边少了。我一般会加一列_merge,然后value_counts()看一眼。如果left_only和right_only都很多,说明主键匹配逻辑有问题,可能是大小写敏感或者前后空格。这时候别急着改代码,先SELECT几条样本出来肉眼看看。数据库客户端和Python读出来的可能不一样,比如MySQL的utf8mb4排序规则在Python里就是普通字符串比较。

性能方面,别把几百万行全拉进内存。用chunksize分块读,或者直接在数据库层面做EXCEPT和INTERSECT。但不同数据库的EXCEPT语法有差异,MySQL 8.0之前根本不支持。折中方案是用Python生成SQL的WHERE条件,把主键列表分批传进去查。比如先从一个库拿主键集合,切成1000个一批,去另一个库查对应的行。这样内存占用小,速度也快。

最后说个真实教训。有次对比MongoDB和MySQL的数据,MongoDB里数字是int32,MySQL里是BIGINT,Python读出来都是int,没问题。但MongoDB的ObjectId转成字符串后,和MySQL的VARCHAR主键比较,大小写敏感。MongoDB的ObjectId是十六进制,包含大写字母,MySQL里存的是小写。merge的时候死活匹配不上,最后str.lower()一下才解决。这种问题,文档里不会写,只有踩过才知道。

所以,Python对比不同数据库的数据,核心不是Python多厉害,而是你得多了解每个数据库的脾气。类型、时区、排序、NULL、精度,挨个对齐了,剩下的才是写循环和判断。工具选SQLAlchemy加pandas够用,但别指望一行代码搞定所有事。对账这种事,慢一点,稳一点,比快更重要。