MySQL迁移PostgreSQL实战指南:从评估到上线全流程解析
最近半年我连续做了两个从MySQL迁移到PostgreSQL的项目。一个是偏静态的报表库全量迁移一个是核心交易系统的部分拆表同步。两个项目做完最大的感受是迁移本身不难难的是提前把方言差异和业务SQL的老毛病全部筛出来。这篇指南就按我实际操作顺序来写从评估值不值、盘点存量、选择搬运工具到Schema差异、SQL改造、上线验证和调优每一步都给出可以直接照抄的做法和参数。先声明一下适用范围文章针对的是常规OLTP业务库数据仓库级的迁移路线略有不同但底下的检查清单也能覆盖七八成。适合谁看正在评估要不要迁的架构师以及已经接到任务、需要快速落地的开发或DBA。MySQL和PostgreSQL我都不站队但后面的内容会真实反映我踩过的坑以及后来沉淀下来的稳定方案。1. 先说结论MySQL迁移PostgreSQL到底值不值很多人做技术选型容易陷入一个误区因为PostgreSQL功能更强所以就应该迁移。但实际上迁移是有成本的而且这个成本往往被低估。我给两个项目做评估时是把风险和收益分开列出来的这里直接分享这套判断框架。1.1 什么情况下迁移能带来明确收益我整理了一个简单的加分项清单命中越多迁移越值得现有SQL里大量使用复杂分析函数、多层子查询PostgreSQL的CTE和窗口函数支持明显更顺手优化器在处理复杂关联时也更稳定。需要把非结构化数据比如JSON和业务结构化数据放在同一个库里PostgreSQL的JSONB支持相当成熟甚至能做GIN索引。对数据完整性要求高需要复杂CHECK约束、排他约束、或者跨库的断言逻辑PG这方面的完整度比MySQL高。索引模型需要更丰富的手段比如部分索引、表达式索引、BRIN这类面向大表的索引MySQL在这块选择面窄很多。想要开源许可更自由、社区治理更开放避免商业授权的不确定性。我做的第一个项目就是这么回事原业务在MySQL里跑了一堆复杂报表每个季度都在超时边缘迁到PG后最深的那个嵌套查询从秒级降到了几十毫秒级别。这种提升不是靠调MySQL参数能追回来的。1.2 什么情况下千万别冲动迁移反过来也有几类明确的反向信号业务正在大版本迭代或者代码里MySQL专属写法特别多比如大量REPLACE INTO、IGNORE、GROUP_CONCAT嵌套迁移会变成一场SQL重构马拉松。系统可用性要求极高而团队里没人熟悉PostgreSQL的运维模型。PG的连接进程模型、vacuum机制跟MySQL完全是两套玩法上线后出问题没人能接手比不迁移的风险大得多。现有MySQL规模很小查询根本没什么压力迁移收益几乎为零。这种场景纯粹属于折腾。我当时给某交易系统的核心库做评估时就是因为双写风险太大改成了先把分析查询切到PGOLTP主库暂时不动。这个折中方案既拿到了PG的查询能力又没有把核心交易链路推到重来的境地。2. 迁移前的保留项目方言审计与兼容性盘点很多人拿到迁移任务就急着用工具导数据这是最容易翻车的做法。数据导过去只需要几小时但应用起来报错、报表跑不通、字段查出来对不上这些事可能折磨你几周。所以我习惯把迁移前的工作拆成三件事摸清驱动和连接方式、审计SQL方言、盘点数据类型的边界情况。2.1 驱动、连接池和账号权限的差异清单这一层不解决应用连上数据库之后寸步难行。我先列一个最常被忽视的清单项目MySQL习惯PostgreSQL习惯迁移时要注意的坑JDBC驱动com.mysql.cj.jdbc.Driverorg.postgresql.Driver驱动包、类名、URL全部要换JDBC URLjdbc:mysql://host:3306/dbjdbc:postgresql://host:5432/db端口默认值不同连接参数含义也不同连接池HikariCP通用HikariCP通用需要调connectionInitSql等参数Python生态pymysql / mysqlclientpsycopg2 / psycopgSQLAlchemy的dialect名称不同账号体系CREATE USER GRANTCREATE ROLE GRANTPG的角色即用户权限层级更细账号权限这块很容易踩坑。MySQL建账号一般是一句话搞定GRANT ALL ON db.* TO userhost。PG不一样你要先CREATE ROLE再GRANT CONNECT ON DATABASE还要给SCHEMA的USAGE权限再给表的SELECT/INSERT权限。很多应用连上PG后报permission denied for schema就是因为只建了角色没有给schema权限。我后来养成的习惯是写一个权限初始化脚本统一给应用账号分配某个schema下的全部表权限避免漏配。2.2 方言审计找出所有依赖MySQL的写法这一步的核心是让看不见的坑提前现形。我的做法分两条线一条是静态扫描。把应用代码里所有SQL文件拉出来用脚本搜MySQL独有关键字包括但不限于IFNULL、GROUP_CONCAT、DATE_FORMAT、REPLACE INTO、ON DUPLICATE KEY UPDATE、LIMIT offset, count反引号包裹的表名和列名ENGINEInnoDB这类建表语句特有的语法TINYINT(1)当布尔用的旧写法另一条是动态抓取。正式迁移前我在MySQL上开通用日志或慢日志把业务高峰期实际执行过的SQL全部收集起来跑一个静态规则检测工具。这个动作特别重要因为很多老系统里有大量历史遗留SQL平时藏在存储过程、定时任务和报表引擎里光靠翻代码根本翻不完。动态抓取帮我抓出过一个很典型的例子报表模块里有个LIMIT 100000, 20这种深度分页写法MySQL和PG的LIMIT语义虽然整体一致但配合ORDER BY的时候排序稳定性不同迁移后翻页偶尔会出现重复数据。这种问题只有靠真实SQL才能发现。2.3 数据类型边界盘点特别是字符串和布尔数据类型映射是迁移里最容易产生垃圾数据的环节。先给一个我用过的标准映射表注意不是所有MySQL类型都能无脑一一对应MySQL类型PostgreSQL类型注意事项INT / BIGINTINTEGER / BIGINT一般直接对应VARCHAR(n)VARCHAR(n)PG按字符数但要注意与排序规则配合CHAR(n)CHAR(n)PG的CHAR会补空格建议业务上尽量避免TINYINT(1)BOOLEAN应用层取值方式要改DATETIMETIMESTAMP WITHOUT TIME ZONE注意时区语义差异TIMESTAMPTIMESTAMP WITH TIME ZONEMySQL的TIMESTAMP有时区转换PG要明确用哪种ENUM自定义类型或TEXT CHECK直接导入可能会变成自定义类型后续维护麻烦TEXTTEXT基本一致BLOBBYTEA驱动读写方式不同JSONJSONBPG的JSONB功能更强GEOMETRYPostGIS需要额外安装不是内置能力必须提前装扩展字符串这块有个常见的认知盲区MySQL的utf8mb4和PG的UTF8虽然都能存中文但排序规则collation的机制完全不同。MySQL按配置的排序规则比较PG默认依赖系统locale两者的排序结果可能不一样尤其是在中文环境下。如果业务里有依赖特定排序的列表迁移前要提前确认PG的lc_collate设置否则list页面的顺序会变得很奇怪。布尔类型是另一个高频坑点MySQL的TINYINT(1)在驱动层通常表现得像数字0和1但PG的BOOLEAN在JDBC、psycopg里读出来的是true/false。如果你的代码有if (row.getInt(is_active) 1)这种写法迁移后要么报类型转换错误要么永远判断不相等。这种坑我在SQL改造时见过太多次了。3. 三套数据迁移路线实测对比pgloader、mysqldump改造与双写同步数据怎么搬过去是很多人最关心的部分。市面上方案很多但真正到了生产环境我觉得只有三条路值得认真考虑pgloader、mysqldump加脚本手工改造、双写加实时同步。我把三者的实测感受放在一起对比。3.1 pgloader大部分场景的首选方案pgloader是我默认推荐的开源工具它做了一步非常关键的事自动映射数据列把MySQL的表结构读取后转换成PG的DDL同时搬数据。不需要自己先去PG里建表它会自动生成并执行CREATE TABLE虽然你最好还是提前检查生成的DDL是否符合预期。最简单的用法是直接写命令行参数pgloader mysql://app_user:pass127.0.0.1:3306/old_db postgresql://app_user:pass127.0.0.1:5432/new_db但如果表数量多、数据量大我建议用命令行文件方式方便重复执行和调整参数。下面是一个实际在生产用过的配置LOAD DATABASE FROM mysql://app_user:pass127.0.0.1:3306/old_db INTO postgresql://app_user:pass127.0.0.1:5432/new_db WITH create tables, create indexes, reset sequences, on error stop SET PostgreSQL PARAMETERS maintenance_work_mem 512MB, work_mem 64MB ;关键在于这几个参数create indexes让索引在数据导入后再建速度比边导边建快得多reset sequences确保导入后自增主键不会撞上重复值maintenance_work_mem调大一些能显著加快索引创建。pgloader默认会为每个表开启多个并发加载器小表基本是秒导。pgloader不是没有缺点。它生成的DDL默认会把MySQL的TINYINT(1)转成SMALLINT而不是BOOLEAN把DATETIME转成TIMESTAMP但未必带上时区语义也就是说类型映射还是要人工过一遍。另外遇到没有主键的表pgloader会直接拒绝导入或者生成无主键的表这是它比较严格的地方。我的习惯是先小规模试跑一个schema查看生成的报告和日志确认映射没有大问题后再放全量。3.2 手工路线mysqldump加脚本改造pgloader没法用的场景我遇到过几次比如目标机器上装不了额外的软件或者公司网络环境限制对外下载。这时候手工路线就是保底方案流程大致是mysqldump --skip-comments --skip-add-drop-table --no-data导出纯结构。用脚本把建表语句里MySQL特有的语法替换成PG风格。在PG里执行清洗后的DDL先建好所有表。用mysqldump --no-create-info --complete-insert --skip-extended-insert --hex-blob导出纯数据。把INSERT语句里的反引号去掉把字符串里的\转义规则改掉再灌进PG。--skip-extended-insert是我强烈建议加上的一项它让每条INSERT只包含一行数据方便出错时定位具体行。代价是文件巨大导入慢。如果数据量大可以拆成多份并行导入或者导入前临时把目标表上的索引全部删掉导完再重建。手工路线的最大问题是体力活多尤其是类型映射和转义规则。PG的字符串里反斜杠默认不转义标准SQL行为而MySQL默认把反斜杠当转义符。导数据时最怕出现含特殊字符的文本字段一个处理不当就截断或报错。所以这个方案我只建议在数据量不大、表结构简单的前提下使用。3.3 双写加实时同步停机窗口很短时的硬核方案有些系统允许的停机时间不是几小时而是几分钟。这时候全量导入再用追加数据的方式做增量就非常吃力。我第二次做迁移用的就是这个思路先在PG建好完整schema评估好新旧两边的类型映射。用同步工具把MySQL的binlog事件流实时转发到PG让两边数据持续保持一致。可以基于开源CDC组件构建也可以自己解析binlog事件。关键在于要处理MySQL和PG在数据类型上的差异比如把MySQL的INSERT事件翻译成PG可接受的SQL参数。应用层改造为双写模式同一个SQL同时发往MySQL和PG但PG失败不影响主流程只记日志。观察一段时间确认PG侧数据一致且应用日志没有异常再把读流量逐步切到PG。最后把写流量整体切到PGMySQL降级为只读保留用于快速回退。双写方案的风险在于两个库的事务语义不一样。MySQL默认的事务隔离级别是REPEATABLE READ而PG默认是READ COMMITTED如果应用依赖了可重复读特性切换后可能看到不同的数据快照。我在这个项目里是把PG的事务隔离级别显式调成了REPEATABLE READ才真正做到行为一致。下面这个表是我做技术选型时整理的对比后来写进项目文档里了方案实施难度停机要求数据一致性保障适用场景pgloader低小时级迁移时有停写大部分OLTP迁移mysqldump 脚本中高小时级全量后需手工校验受限环境、小数据量双写 实时同步高分钟级高但需自建监控核心系统、停机敏感从我个人经验看80%的迁移用pgloader就能解决没必要一上来就上双写。双写的成本不只是开发还有后续的binlog理解成本和运维负担除非业务真的无法接受长时间停写否则我一般劝退。4. Schema迁移的核心差异建表、约束、序列与索引数据倒腾过去只是第一步真正的细节在DDL和约束层面。同一个业务表在MySQL和PG里建出来语义可能差一截。这一章我把最容易出问题的几个点单独拉出来展开。4.1 建表语法和自增主键从AUTO_INCREMENT到IDENTITYMySQL时代最典型的写法是CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT, amount DECIMAL(10,2) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;PG对应的写法是CREATE TABLE orders ( id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, amount NUMERIC(10,2) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP );先把表名和列名上的反引号去掉再把AUTO_INCREMENT换成GENERATED BY DEFAULT AS IDENTITY。这里我特别强调用BY DEFAULT而不是BY ALWAYSBY ALWAYS拒绝应用显式指定主键值如果有历史数据要按原ID插入或者业务有指定ID的场景会直接报错。BY DEFAULT则允许两者共存应用给值时用给定值不给时走自增迁移期最省心。另外serial是一种更老的写法ID BIGSERIAL PRIMARY KEY也能用但它本质是INT配一个独立序列和标准IDENTITY相比在权限管理、TRUNCATE重置行为上有细微差别。新项目我推荐直接用IDENTITY。4.2 约束和默认值PG更严格也更接近标准MySQL的DATETIME DEFAULT CURRENT_TIMESTAMP可以配合ON UPDATE CURRENT_TIMESTAMP让行更新时自动改时间戳。这个语法非常常见我在迁移时发现PG没有直接等价的关键字DEFAULT CURRENT_TIMESTAMP只保证插入时生效行更新不会自动改。要模拟这个行为需要写一个触发器CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at CURRENT_TIMESTAMP; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_orders_updated_at BEFORE UPDATE ON orders FOR EACH ROW EXECUTE FUNCTION set_updated_at();触发器逻辑不复杂但需要为每一张要自动更新时间戳的表都建一个批量处理时容易遗漏。我的建议是写个脚本从information_schema查出所有包含updated_at列的表自动生成触发器语句避免漏表。PG的约束体系比MySQL完整比如CHECK约束可以直接引用多列甚至可以用函数作为条件EXCLUDE约束可以做时间段重叠校验这在MySQL里没有等价物。迁移时如果原MySQL表里有CHECK约束PG会忠实地保留并强制执行这一点比MySQL更好但也意味着如果有历史脏数据迁移后写入可能开始报错。上线前记得跑一遍全表数据完整性校验。4.3 索引定义方式完全不同MySQL习惯在建表语句里带索引定义比如KEY idx_status (status)。PG的索引是用独立的CREATE INDEX语句创建的所以pgloader导入后索引是单独建出来的。这不只是写法问题而是优化器行为、维护方式都不一样。有几个MySQL老索引在PG里需要重新思考MySQL的前缀索引比如KEY idx_name (name(10))PG没有直接等价物但可以用表达式索引模拟CREATE INDEX idx_name_prefix ON users (left(name, 10));效果接近但要确认查询写法能匹配上。如果原表有大量联合索引PG里要考虑索引列的顺序差别因为PG的索引扫描在部分场景下无法像MySQL那样灵活利用索引前面的列。PG的BRIN索引非常适合时间序列数据如果有一张巨大的流水表按时间范围查询可以试试CREATE INDEX idx_orders_created_at ON orders USING BRIN(created_at);体积小很多扫描大范围数据时效果明显。还需要注意创建索引的并发问题。MySQL在早期版本里ADD INDEX会锁表PG的CREATE INDEX默认不阻塞DML但大表上长时间建索引会拉高I/O。迁移建索引用CONCURRENTLY更稳妥CREATE INDEX CONCURRENTLY idx_orders_created_at ON orders (created_at);用CONCURRENTLY的时候不能放在事务块里而且建索引过程中如果遇到错误会留下无效索引需要手动清理。生产环境建索引我习惯加这个关键字虽然慢一点但不怕阻塞业务。5. SQL方言适配从函数替换到ORM改造数据搬完约束建好下一步就是让应用层真正跑起来。这一层的适配琐碎且量很大如果不系统化处理会陷入改一个SQL发现另一个报错的循环里。5.1 高频函数替换对照我整理了一张高频函数替换表基本覆盖了我在实际项目里遇到过的90%情况MySQL写法PostgreSQL写法说明IFNULL(a, b)COALESCE(a, b)语义几乎一样IF(cond, a, b)CASE WHEN cond THEN a ELSE b END没有直接等价函数GROUP_CONCAT(col)STRING_AGG(col, ,)要指定分隔符默认不带DATE_FORMAT(d, %Y-%m-%d)TO_CHAR(d, YYYY-MM-DD)格式模板不同NOW()NOW() / CURRENT_TIMESTAMP都能用CURDATE()CURRENT_DATE直接对应REPLACE INTO t VALUES (...)INSERT ... ON CONFLICT (pk) DO UPDATE ...REPLACE INTO语义是删旧插新PG用ON CONFLICT显式表达INSERT IGNOREINSERT ... ON CONFLICT DO NOTHING效果类似但要先有唯一约束LIMIT offset, countLIMIT count OFFSET offset顺序反过来了容易写错REPLACE INTO值得单独说一句它不是简单的存在就更新而是先删除旧行再插入新行副作用是会把没参与插入的列重置成默认值还会触发外键和级联操作。迁移到PG没有这个语法必须改写成ON CONFLICT而且这个改写会影响业务逻辑不能机械替换要逐条确认语义。5.2 分组、排序和布尔表达式的行为差异GROUP BY是另一个容易出问题的地方。MySQL在非严格模式下允许SELECT出不在GROUP BY里的列结果随机取一行PG严格要求GROUP BY后面的列必须和查询列一致否则直接报错。迁移前我的建议是把MySQL的sql_mode临时调成ONLY_FULL_GROUP_BY让应用在迁移前就把所有不规范的GROUP BY暴露出来。这个动作能省去上线后一大半的SQL报错。布尔表达式同样要小心。旧代码里如果写WHERE is_active 1PG会报错因为BOOLEAN不能直接和整数比较。要改成WHERE is_active TRUE或者更简洁的WHERE is_active反过来如果应用代码里用占位符传0或1要统一改成传true或false或者把列类型定成SMALLINT并从应用层做映射。这个必须在前期类型盘点时定下来不然会出现一大堆列类型不匹配的错误。5.3 ORM和框架层的改造要点现在业务系统很少会裸写JDBC基本都是走ORM。这块的改造虽然不用逐条改SQL但有个从底层就开始的迁移动作。MyBatis体系下配置里要改driver和jdbcUrl同时建议启用databaseIdProvider这样可以在Mapper里区分MySQL和PG的方言SQL。早期版本如果不配置databaseId出现兼容性差异时会很痛苦。Hibernate的做法则是切换dialect为PostgreSQL方言JPA会把分页、序列、布尔类型自动转成PG风格省很多事。Python技术栈用SQLAlchemy连接串从mysqlpymysql://user:passhost/db改成postgresqlpsycopg2://user:passhost/db然后逐个核对TypeDecorator。最典型的是BOOLEAN类型ORM模型里的Boolean在MySQL下映射到TINYINT(1)切到PG后是真正的BOOLEAN如果模型定义用的是SmallInteger也要改。字段校验和序列化逻辑都可能跟着变。ORM改造最大的坑不在代码本身而在测试覆盖不全。很多团队只做了主流程的冒烟测试结果漏掉了一些只有特定入口才会触发的SQL上线后才会炸。我现在的习惯是切库前把接口层的自动化用例完整跑一遍同时拿一版生产环境的脱敏数据在PG上做并发回归。6. 割接当天数据校验、回滚预案与上线后的首要调优迁移项目的成败除了功能跑通还看能不能保证数据一致。这一章是我每次割接前必须检查的内容也是容易被临时赶工漏掉的环节。6.1 数据校验脚本不能只对数先对表数量在源库和目标库分别执行information_schema.tables统计核对表名清单。这一步通常能发现pgloader漏导的表。再对行数每张表分别SELECT count(*)但大表全表count在MySQL里并不慢在PG里因为MVCC机制需要扫描全表反而可能很慢。所以大表我建议用抽样校验取主键最大值、最小值和几组关键列的和值做对比。我实际用的校验SQL大概是这样的-- 源库 MySQL 侧 SELECT COUNT(*) AS row_count, COALESCE(SUM(amount), 0) AS amount_sum, COALESCE(MIN(id), 0) AS min_id, COALESCE(MAX(id), 0) AS max_id FROM orders; -- 目标库 PG 侧 SELECT COUNT(*) AS row_count, COALESCE(SUM(amount), 0) AS amount_sum, COALESCE(MIN(id), 0) AS min_id, COALESCE(MAX(id), 0) AS max_id FROM orders;两边结果完全一致才算通过第一关。之后再随机抽几条样本行全字段比较一次值是否一致防止某列因类型映射产生隐式转换差错。自增序列状态也要检查。pgloader的reset sequences参数会自动重置但如果你是手工导入的必须手动执行SELECT setval(pg_get_serial_sequence(orders, id), (SELECT MAX(id) FROM orders));不做这一步后续插入的主键会从1开始立刻撞主键。6.2 回滚预案关键不是怎么写而是什么时候触发回滚方案不能等到割接失败才想。我习惯把回滚条件提前写清楚比如核心交易接口在PG上错误率超过0.5%持续5分钟数据校验差值超过某个阈值应用日志出现批量未处理异常只要满足任何一个条件就立即把流量切回MySQL。因为MySQL在迁移期间一直保留为只读或被双写写入数据并没有停机回滚不用回补数据。这也是我为什么一直建议迁移后的MySQL实例至少保留两周只读状态的原因。很多人割接成功后立刻把老库删了一旦发现问题就只能从备份恢复代价天差地别。6.3 上线后第一件事调PG参数和抓慢查询很多MySQL DBA直接拿默认配置跑PG然后抱怨性能差。其实PG默认配置是针对低配机器的生产环境必须调。我上线后第一组要改的参数是shared_buffers 4GB effective_cache_size 12GB work_mem 16MB maintenance_work_mem 512MB max_connections 200 wal_level replica参数值要根据机器内存和连接数来定不能照抄。shared_buffers通常设为内存的25%effective_cache_size是操作系统缓存加shared_buffers的估算值会影响优化器的索引选择倾向work_mem不能开太大否则大量并发排序直接吃光内存我见过有人把work_mem调到1GB然后数据库OOM的。自动清理autovacuum千万不要关。PG的MVCC机制依赖vacuum回收死元组关闭后会无限膨胀性能直线下降。迁移初期数据量变动大更需要保证autovacuum正常触发。慢查询分析方面我建议把pg_stat_statements扩展打开CREATE EXTENSION IF NOT EXISTS pg_stat_statements;然后在配置里加上shared_preload_libraries pg_stat_statements改这个要重启数据库所以最好在割接前就配好。之后通过pg_stat_statements查平均耗时最高的SQL按照调用次数加权排序逐个排查。迁移后出现性能问题的SQL一部分是因为统计信息没更新跑一次ANALYZE就能好很多另一部分是真的SQL写法不适配PG优化器需要改写。7. 迁移后最容易翻车的三个坑大小写、时间戳与旧查询最后把这些年在项目里反复遇到的三个经典坑单独拎出来说。它们都不是什么高深问题但几乎每个迁移项目都会碰到。7.1 大小写敏感一个引号引发的空表问题MySQL在Linux默认的表名大小写敏感但列名不敏感而PG把所有未加引号的标识符折叠成小写。这就产生了一个很尴尬的场景原表叫OrderInfo用pgloader导到PG后如果表名没加引号建出来的表实际叫orderinfo而应用层SQL查询写的是OrderInfoPG会按照带引号的精确大小写去找结果找不到表。更麻烦的是应用代码里的表名全是驼峰迁移过来后你可能会看到一张名为OrderInfo带引号创建和一张orderinfo不带引号创建同时存在的诡异局面。我的建议是迁移前在MySQL侧就把表名统一改成小写或者在PG侧建表时统一用CREATE TABLE order_info这种全小写带下划线的风格应用SQL也统一不加引号。这样最省心。7.2 时间戳时区看似一样实则差八小时MySQL的DATETIME不存时区信息你存进去什么就取出什么。PG的TIMESTAMP WITHOUT TIME ZONE简称timestamp也不存时区行为和MySQL一致但很多迁移工具会默认把MySQL的DATETIME转成PG的TIMESTAMP WITH TIME ZONEtimestamptz。一旦转成timestamptzPG会把客户端会话的时区考虑进去再做转换如果你的应用服务器时区是UTC入库后查出来会比东八区少8小时。这个问题特别隐蔽因为小数据量测试时可能用例太少看不出异常一上生产看凌晨的数据就全部对不上。我的建议是如果原业务完全不关心时区转换目标列类型直接统一用TIMESTAMP WITHOUT TIME ZONE如果确实需要全球多时区那就要在应用层和驱动层把时区语义理清楚不能指望数据库自动转换。7.3 历史遗留SQL改完应用代码别忘了定时任务和后台脚本很多迁移项目把精力花在主业务接口上忽略了一大批隐藏的SQL使用者报表定时任务、消息队列消费者、深夜跑批脚本、管理后台的数据导出功能。这些代码往往不在主干上测试时也很少被覆盖但一到凌晨就跑出大量报错日志。我经历过一次典型事故迁移后的第三天凌晨批处理任务报错排查后发现脚本里用了一个MySQL特有的REPLACE INTO迁移时漏改了。这类问题没有捷径只能在迁移前把定时任务清单和后台脚本全部拉出来按计划任务逐个过一遍SQL而不是等项目验收时才想起来。迁移这件事做到最后往往发现工作量的70%都在前期的SQL梳理和类型映射上真正执行迁移的时间反而不多。如果你手里也有这样的任务先把本文第2章的审计清单做扎实后面会顺很多。

相关新闻

锂电池参数辨识实战:FFRLS+卡尔曼滤波+遗传算法,Matlab代码全解

锂电池参数辨识实战:FFRLS+卡尔曼滤波+遗传算法,Matlab代码全解

搞电池的人,尤其是做BMS算法或者电芯测试这一行的,手里估计都囤着好几个电池模型。模型建好了,参数怎么来?靠厂商给一份常温下的标称值,仿真一跑,低温或者大倍率工况下电压偏差能到几十毫伏甚至更多&#x…

2026/10/17 12:44:02 阅读更多 →
PyQt5+YOLOv5工业级检测界面:非阻塞推理与模型热加载方案

PyQt5+YOLOv5工业级检测界面:非阻塞推理与模型热加载方案

简介:这是一套面向PyQt5与YOLOv5初学者的GUI目标检测实战项目,专为刚接触GUI开发和目标检测算法的学习者设计,帮助其快速搭建可运行、可调试的端到端检测界面。资源共112个文件,包含26个Python源码(含主程序、逻辑模块…

2026/10/17 12:43:02 阅读更多 →
友盟分享SDK 6.4.6 定制版:Android 12+ 与 targetSdkVersion=33 兼容修复方案

友盟分享SDK 6.4.6 定制版:Android 12+ 与 targetSdkVersion=33 兼容修复方案

简介:本资源是面向Android应用开发者的友盟社会化分享SDK集成包,专为快速接入微信、QQ、微博及Facebook、Twitter等国内外主流社交平台分享功能而设计,适用于中高级移动开发者在项目中实现标准化、高兼容性的社交传播能力。压缩包为ZIP格式&a…

2026/10/17 12:43:02 阅读更多 →

最新新闻

具身智能进阶开发:从感知到控制的完整技术链路与工程实践

具身智能进阶开发:从感知到控制的完整技术链路与工程实践

做具身智能这几年,我最大的感受是:这个方向不缺热度,缺的是能把系统跑通的人。很多人一上来就追大模型、追VLA,结果连ROS2的坐标变换都没搞明白,仿真环境里物体抓得稳稳的,一上真机就到处乱撞。我之前在团队…

2026/10/17 14:40:24 阅读更多 →
C#实现梯形分幅图幅编号:国标GB/T 13989-2012的生产级转换引擎

C#实现梯形分幅图幅编号:国标GB/T 13989-2012的生产级转换引擎

简介:本资源是面向地理信息系统开发与测绘类竞赛选手的实战型算法实现资料,聚焦梯形分幅法下多级比例尺地图图幅编号的正向计算与反算功能。基于湖北省省赛真题,完整呈现C#窗体程序的设计逻辑、核心算法推导(含1:100万至1:1万共7级…

2026/10/17 14:40:24 阅读更多 →
jsPDF配合jspdf-autotable导出PDF中文乱码的完整解决方案

jsPDF配合jspdf-autotable导出PDF中文乱码的完整解决方案

做导出功能遇到中文乱码,我猜你已经搜了不少文章,代码复制了一堆,结果要么还是乱码,要么直接白屏。这个问题在我这儿折腾了一整个下午,最后发现根子不在代码,而在PDF文件本身的字体机制。今天的主题就是把使…

2026/10/17 14:40:24 阅读更多 →
大模型分布式优化实战:从DDP到ZeRO-3的底层调优路径

大模型分布式优化实战:从DDP到ZeRO-3的底层调优路径

1. 项目概述:这不是“调参”,而是重构大模型训练的底层逻辑“大模型分布式优化技术(一)”这个标题,乍看像一篇论文小节,但实际是当前工业界最硬核、也最容易被误解的一类实操课题。我带过多个千卡级训练项目…

2026/10/17 14:40:24 阅读更多 →
ComfyUI自定义节点实战:数值转字符串转换器开发与类型映射解析

ComfyUI自定义节点实战:数值转字符串转换器开发与类型映射解析

1. 为什么值得动手写这个节点最近在折腾ComfyUI工作流的时候,我发现一个很实际的问题:工作流里经常需要把数字类型的数据(比如自定义步数、种子值、某个节点的评分输出)转成字符串,用作文件名后缀、提示词拼装或者API参…

2026/10/17 14:40:24 阅读更多 →
input按钮onclick事件的可靠性设计与实战避坑指南

input按钮onclick事件的可靠性设计与实战避坑指南

1. 这个看似简单的onclick到底在解决什么问题?“input按钮中的onclick事件”——光看标题,很多人第一反应是:“这有什么好讲的?不就是点一下触发个函数吗?”但我在一线带过几十个前端新人、参与过十几个中大型系统重构…

2026/10/17 14:39:23 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →