我刚从一套老旧的Oracle EBS系统往MySQL 8.0迁移报表库,第一周就被时间函数狠狠教育了一顿。迁移之前我以为"取当前日期时间"这种最基础的功能,两个数据库顶多函数名不同,改个名字就行。结果翻车现场极其惨烈:Oracle里跑得好好的SYSDATE相关逻辑,在MySQL里有的差8小时,有的差一整天,有的从"带时分秒"悄悄变成"只有日期",最离谱的是同一段日期格式化代码,在两边跑出来的字符串格式完全南辕北辙。Oracle和MySQL读取当前日期时间,表面上都是"给我当前时间"这么一句话,底层却藏着时区、类型、精度、格式四套完全不同的世界观。
这篇不是给你背文档的,是我把两边的核心时间函数逐个拆开、对照着踩坑之后的整理。适合正在做Oracle迁MySQL、或者平时在这两库之间写工具脚本的同学,看完能少走至少一周弯路。
1. 迁移项目里最先爆出来的雷:Oracle和MySQL取时间根本不是一个粒度
1.1 为什么写这篇——一次从Oracle迁到MySQL的翻车记录
背景是这样的:客户那边核心库是Oracle 11g,报表层原来直接连核心库跑一堆统计SQL。为了分担核心库压力,我们把报表库迁到MySQL 8.0,用ETL同步数据。表面上看只是"换个地方查数",实际上大部分SQL要重写。
第一个雷爆在"当日数据统计"上。原SQL大致长这样:
-- Oracle SELECT COUNT(*) FROM orders WHERE order_date >= TRUNC(SYSDATE) AND order_date < TRUNC(SYSDATE) + 1;这段SQL在Oracle的意思是"从今天零点到明天零点之前",TRUNC(SYSDATE)把SYSDATE的时分秒砍掉,只留当天日期。到了MySQL,我条件反射地改成了:
-- MySQL 第一次改错 SELECT COUNT(*) FROM orders WHERE order_date >= CURDATE() AND order_date < CURDATE() + INTERVAL 1 DAY;语法上没问题,跑起来也不报错,但细细一核对,统计口径错了。为什么?MySQL的CURDATE()确实返回当前日期,但它是基于会话时区的。如果应用连接的time_zone参数和服务器本地时区不一致,或者连接串里没显式指定时区,CURDATE()得到的"今天"跟Oracle里SYSDATE的"今天"可能根本不是同一天。
这只是个开始。后面还遇到了NOW()和SYSTIMESTAMP的精度差、TO_DATE和STR_TO_DATE的格式串互相不认、HH24和%H的写法差异等等一堆问题。我才意识到:取当前日期时间这个动作,等于同时牵涉了"取什么时间"、"按什么时区取"、"取回来是什么类型"、"显示成什么格式"四件事,两边没有一件是默认相同的。
1.2 两份代码对照:看起来很像,结果完全不同
我把两边最常用的"取当前日期时间"函数先摆在一起看:
| 意图 | Oracle | MySQL |
|---|---|---|
| 当前日期时间(数据库所在操作系统) | SYSDATE | 无完全对应物,最接近的是NOW()但语义不同 |
| 当前日期时间(会话时区) | CURRENT_DATE/CURRENT_TIMESTAMP | NOW()/CURRENT_TIMESTAMP() |
| 当前日期(会话时区) | TRUNC(SYSDATE)/CURRENT_DATE | CURDATE() |
| 当前UTC时间 | SYS_EXTRACT_UTC(SYSTIMESTAMP) | UTC_TIMESTAMP() |
| 带时区信息的时间戳 | SYSTIMESTAMP返回TIMESTAMP WITH TIME ZONE | CURRENT_TIMESTAMP()返回DATETIME,本身不带时区 |
看第一行就该警惕了:Oracle的SYSDATE在MySQL里没有完全对应物。NOW()虽然长得像,但SYSDATE取的是数据库服务器所在操作系统的时间,而NOW()取的是会话时区下的当前时间。如果数据库服务器时区设的是Asia/Shanghai,而客户端连接会话时区被设成了UTC,那么SYSDATE和NOW()会差8个小时。
更坑的是Oracle的CURRENT_DATE也不等于SYSDATE。前者是会话时区的当前日期,后者是数据库服务器本地的当前日期。很多人用了十年Oracle都没注意过这个区别,因为绝大多数场景下DBA会把数据库时区设成操作系统时区,会话时区也不乱改,两者恰好相等。一旦你通过ALTER SESSION SET TIME_ZONE改了会话时区,CURRENT_DATE会跟着变,SYSDATE纹丝不动。
1.3 这两类时间函数的本质差异预览
表面是函数名不一样,本质上是两边的时间模型不一样。
Oracle的日期时间体系是"两条腿走路":DATE类型和TIMESTAMP系列类型并存。DATE精度到秒,TIMESTAMP可以到纳秒。SYSDATE是DATE族,SYSTIMESTAMP是TIMESTAMP WITH TIME ZONE族。你用SYSDATE还是SYSTIMESTAMP,不只是精度不同,连"带不带时区语义"都不同。
MySQL则是"一个类型走天下":核心是DATETIME和TIMESTAMP。MySQL的TIMESTAMP虽然名字里有"时区"两个字,但它的行为很特别——内部存储的是UTC值,显示的时候按会话时区转换。DATETIME则完全没有时区概念,存进去是什么就是什么。而NOW()返回的是DATETIME,它本身不携带时区信息,只是在生成那一刻用了会话时区做了换算。
一句话总结两边的差异:Oracle的时区语义分散在函数和数据类型的组合里,MySQL的时区语义集中在会话参数里。后面所有踩坑,几乎都是围绕这两套模型展开的。
2. 逐个拆函数:SYSDATE、SYSTIMESTAMP、CURRENT_DATE 和 NOW()、CURDATE()、UTC_TIMESTAMP() 的真实身份
2.1 Oracle家族:SYSDATE 与 CURRENT_DATE 的时区分歧
先看Oracle。Oracle里的"当前日期时间"主要有四个函数,很多人其实只熟悉前两个。
SYSDATE返回DATE类型,精度到秒。它取的是数据库实例所在操作系统的时间。注意,是关键:Oracle文档明确说SYSDATE返回的是"database server operating system"的当前时间,不是数据库内部自己维护的时间。如果你在Linux服务器上date -s改了系统时间,然后去查SYSDATE,它大概率也跟着变。
SYSTIMESTAMP返回TIMESTAMP WITH TIME ZONE类型,精度可以到小数秒(默认6位,最少0位,最多9位)。它同样取操作系统时间,但返回结果里带时区信息,比如15-MAR-25 10.30.00.123456 +08:00。这个"带时区"不是装饰,后面的算术运算、比较操作都会考虑时区。
CURRENT_DATE返回DATE类型,精度到秒,但它取的是会话时区的当前日期时间。它跟SYSDATE的区别在于时区的基准点不同:SYSDATE以服务器操作系统为准,CURRENT_DATE以ALTER SESSION SET TIME_ZONE设定的会话时区为准。如果会话时区刚好等于数据库服务器所在时区,两者相同;一旦会话时区被改,两者就分道扬镳。
CURRENT_TIMESTAMP返回TIMESTAMP WITH TIME ZONE,同样基于会话时区。它跟SYSTIMESTAMP的区别跟上面一模一样,只是时区基准不同。
看一个实际对照:
ALTER SESSION SET TIME_ZONE = 'UTC'; SELECT SYSDATE, CURRENT_DATE, SYSTIMESTAMP, CURRENT_TIMESTAMP FROM DUAL; -- 假设服务器本地时间是 15-MAR-25 10:30:00 +08:00 -- SYSDATE : 15-MAR-25 10:30:00 -- CURRENT_DATE : 15-MAR-25 02:30:00 -- 差8小时 -- SYSTIMESTAMP : 15-MAR-25 10.30.00.123456 +08:00 -- CURRENT_TIMESTAMP : 15-MAR-25 02.30.00.123456 +00:00CURRENT_DATE显示为02:30:00是因为它按UTC时区重新换算了一次。SYSDATE还是10:30:00,因为它死抱着操作系统时间不放。Oracle的TRUNC(SYSDATE)也是基于操作系统日期的零点截断。
2.2 MySQL家族:NOW()、CURDATE()、UTC_TIMESTAMP() 的分类
MySQL的核心函数比Oracle简洁,但坑在语义。
NOW()或CURRENT_TIMESTAMP()返回DATETIME,基于会话时区。MySQL启动时会读取time_zone系统变量,默认值是SYSTEM,此时会话时区等于服务器系统时区。如果再给连接串加个connectionTimeZone=UTC之类的参数,会话时区就变成UTC了,NOW()的结果也会整体偏移。
CURDATE()返回DATE,基于会话时区。CURDATE()等于DATE(NOW()),但注意它不是一个"截断"操作,它是从时区换算之后的时间取出日期部分。如果会话时区是UTC而服务器本地是北京时间,UTC的日期可能比北京时间慢一天。
UTC_TIMESTAMP()返回DATETIME,它是唯一一个"明牌"表示UTC时间的函数。注意它在MySQL里也有对应的UTC_DATE()和UTC_TIME()。这三个函数不受会话时区影响,始终返回UTC值。
还要注意UNIX_TIMESTAMP()。它返回的是Unix时间戳(秒),但转换基准也是会话时区。MySQL文档里写得很清楚:UNIX_TIMESTAMP()在当前会话时区下,把传入的DATETIME转换成Unix秒。同一个时间值,在UTC会话和+08:00会话下取出来的秒数不一样。这在跨时区数据对账的时候经常让人吃一惊。
-- 假设会话时区 +08:00,当前时间是 2025-03-15 10:30:00 SET time_zone = '+08:00'; SELECT NOW(), CURDATE(), UTC_TIMESTAMP(), UNIX_TIMESTAMP(); -- NOW() : 2025-03-15 10:30:00 -- CURDATE() : 2025-03-15 -- UTC_TIMESTAMP() : 2025-03-15 02:30:00 -- UNIX_TIMESTAMP() : 1742016600如果把会话时区切到+00:00,NOW()变成02:30:00,UNIX_TIMESTAMP()还是1742016600,因为Unix秒对应的绝对时刻没变。
2.3 对应关系表与实际返回值对比
既然要迁移,最关心的就是Oracle语句改成MySQL语句,到底怎么对应。我的结论是:不能按函数名对应,要按"你想要的绝对时刻+时区口径"来对应。
| 你想要的语义 | Oracle写法 | MySQL写法 |
|---|---|---|
| 取服务器操作系统本地时间 | SYSDATE | 没有直接对应。若会话时区=SYSTEM,可用NOW()近似 |
| 取会话时区的日期时间 | CURRENT_DATE或CURRENT_TIMESTAMP | NOW()或CURRENT_TIMESTAMP() |
| 取会话时区的日期(零点) | TRUNC(SYSDATE) | CURDATE() |
| 取UTC现在的日期时间 | SYS_EXTRACT_UTC(SYSTIMESTAMP) | UTC_TIMESTAMP() |
| 取带时区信息的完整时间戳 | SYSTIMESTAMP | 无直接对应。MySQL不提供"带时区偏移量"的时间戳类型 |
第二行特别值得强调:Oracle的CURRENT_DATE和MySQL的NOW()在时区语义上是同一类——都基于会话时区,但因为OracleCURRENT_DATE返回DATE类型、MySQLNOW()返回DATETIME类型,两者在显示精度上又不一样,Oracle的CURRENT_DATE没有小数秒,MySQL的NOW()默认没有小数秒但可以指定精度NOW(6)。
很多迁移手册建议"把SYSDATE全改成NOW()",这句话只有在你确保"数据库服务器时区=应用服务器时区=会话时区"三者在同一时区时才成立。分布式部署、云数据库、跨区域连接,任何一个环节时区不一致,这个改法就是埋雷。
3. 三种核心差异:时区语义、返回类型、精度,为什么迁移后SQL会"悄悄变正确"
3.1 时区语义:数据库时区 vs 会话时区
这是最隐蔽也最致命的一层差异。Oracle和MySQL都有"数据库时区"和"会话时区"两个概念,但具体行为完全不同。
Oracle有两个关键参数:DBTIMEZONE和SESSIONTIMEZONE。DBTIMEZONE是数据库的时区设置,通常建库时固定,改了很麻烦。SESSIONTIMEZONE是会话的时区,可以通过ALTER SESSION SET TIME_ZONE = ...随意改。Oracle里SYSDATE不受这两个参数影响,它直接读操作系统;而CURRENT_DATE、CURRENT_TIMESTAMP受SESSIONTIMEZONE影响。Oracle的DBTIMEZONE目前主要影响TIMESTAMP WITH LOCAL TIME ZONE类型的显示,对SYSDATE都不起作用。
MySQL这边,核心参数是全局变量time_zone和会话变量time_zone。会话time_zone的默认值是SYSTEM,表示跟随系统时区。你可以用SET time_zone = '+08:00'或者SET time_zone = 'Asia/Shanghai'来改变(后者需要加载时区表)。MySQL没有"数据库时区"这样一个专门的、固定的概念,所有的NOW()、CURDATE()都是跟着会话time_zone走。MySQL的TIMESTAMP类型内部存UTC值,显示按会话时区转换;DATETIME完全不按时区换算。
举一个我在生产环境真实遇到的场景:Oracle源库服务器在华东,SYSDATE返回北京时间。MySQL目标库跑在阿里云上,ECS系统时区设置成了UTC(有些基础镜像默认就是UTC)。ETL同步程序用的连接串没有指定时区参数,JDBC驱动默认把会话时区设成了系统时区UTC。结果就是:Oracle那边TRUNC(SYSDATE)得到北京时间的当天零点,MySQL这边NOW()得到UTC时间,比北京时间慢8小时。我写"取昨天数据"的条件时,明明改了函数名,口径还是错了8小时。
这个问题的排查方法很简单:一进连接就执行SELECT NOW(), UTC_TIMESTAMP(), @@session.time_zone;,看看会话时区到底是不是你预期的。如果NOW()和UTC_TIMESTAMP()相等,说明会话时区是UTC;如果NOW()比UTC快8小时,会话时区多半是+08:00。
3.2 返回类型:DATE 与 DATETIME/TIMESTAMP 的装死问题
Oracle的DATE和MySQL的DATETIME看起来都能存"年月日时分秒",但它们的"自我修养"完全不同。
Oracle的DATE类型必然包含时分秒,即使你INSERT INTO t VALUES (DATE '2025-03-15'),存进去的也是2025-03-15 00:00:00。Oracle的DATE '2025-03-15'字面量是合法的,会自动补齐零点。查询时如果NLS设置把时分秒隐藏了,你会以为它只有日期,其实它一直带着时分秒在跑。
MySQL的DATE类型真的只有日期,时分秒连存都不让你存。MySQL的DATETIME和TIMESTAMP才会带时分秒。所以在MySQL里你不能直接写CURDATE() + 1这种算术——日期加整数1在MySQL会被当作"加一天"吗?并不会,你试试就知道,SELECT CURDATE() + 1会得到一个奇怪的数字比如20250316,因为MySQL把日期转成了整数20250315再加1,结果203...不对,实际上是20250316这个数字,而不是日期。这种写法在Oracle里是合法的(SYSDATE + 1表示加一天),迁移时如果没注意,会出现不加报错但结果完全错误的情况。
正确写法是:
-- Oracle TRUNC(SYSDATE) + 1 -- 明天零点 TRUNC(SYSDATE) + 3/24 -- 今天凌晨3点 -- MySQL CURDATE() + INTERVAL 1 DAY -- 明天零点 CURDATE() + INTERVAL 3 HOUR -- 今天凌晨3点MySQL还有一种坑:TIMESTAMP类型有2038年问题。TIMESTAMP在MySQL里的有效范围是1970-01-01 00:00:01到2038-01-19 03:14:07UTC。存业务单据没问题,但你要存"远期排产计划"之类的日期,建议直接用DATETIME。DATETIME的范围大到1000-01-01到9999-12-31,基本不用操心。Oracle的DATE范围是公元前4712年到公元9999年,也不存在2038问题。所以从Oracle迁MySQL时,凡是原来用DATE或TIMESTAMP存的字段,迁移目标表里我倾向于直接用DATETIME,避开时区换算和2038双重麻烦。
3.3 精度:秒、毫秒、微秒与小数位
Oracle的DATE精度是秒,TIMESTAMP默认精确到小数点后6位(微秒),可以指定到9位(纳秒)。SYSDATE返回DATE,所以你不管怎么查,它都只有秒。SYSTIMESTAMP返回TIMESTAMP WITH TIME ZONE,默认带6位小数秒。
MySQL的NOW()默认不带小数秒,但你可以用NOW(3)、NOW(6)指定,最高6位(微秒)。CURDATE()没有小数秒。CURRENT_TIMESTAMP(6)也可以取微秒。
这个精度差异会带来一个很实际的迁移问题:Oracle里用SYSTIMESTAMP生成的流水号、比对值、审计字段,到了MySQL如果NOW()不带精度,同一秒内的多条记录时间完全一样,排序稳定性会变差。解决办法是统一用NOW(6)读微秒。
反过来也有坑。Oracle的TIMESTAMP和DATE做比较,Oracle会先隐式把DATE提升为TIMESTAMP,再按时间比较,这个行为比较自然。MySQL里DATETIME和TIMESTAMP类型比较也还行,但要小心字符串类型字段和时间字段比较时的隐式转换规则。比如WHERE order_date = '2025-03-15 10:30:00',MySQL允许字符串隐式转日期再比较,但如果order_date是DATETIME(6)而你给的字符串只到秒,实际上比较的是2025-03-15 10:30:00.000000,没问题;如果order_date里有微秒值,字符串没带微秒,那就永远不等。
精度还影响"同一天"的判断。Oracle里比较某条记录的创建时间和TRUNC(SYSDATE),只要忽略时分秒就行;MySQL里如果直接用DATE(order_date) = CURDATE(),等于对每行做一次函数转换,索引会失效。正确做法是范围比较:
WHERE order_date >= CURDATE() AND order_date < CURDATE() + INTERVAL 1 DAY这样既能命中索引,又不受时分秒精度影响。
4. 格式化与字符串转换:TO_CHAR / TO_DATE 对 DATE_FORMAT / STR_TO_DATE 的九种错法
4.1 格式串的语法差异:占位符完全不是一套体系
取到时间之后,最常干的事就是格式化输出。Oracle和MySQL的格式符差异,是迁移时最容易批量报错的地方。
Oracle用一套"字母缩写+数字位数"的体系:
| 含义 | Oracle格式符 | 示例输出 |
|---|---|---|
| 年份(4位) | YYYY | 2025 |
| 月份 | MM | 03 |
| 日 | DD | 15 |
| 24小时制小时 | HH24 | 14 |
| 12小时制小时 | HH或HH12 | 02 |
| 分钟 | MI | 30 |
| 秒 | SS | 45 |
| 毫秒/微秒 | FF3/FF6 | 123 / 123456 |
MySQL用另一套%前缀的占位符:
| 含义 | MySQL格式符 | 示例输出 |
|---|---|---|
| 年份(4位) | %Y | 2025 |
| 月份 | %m | 03 |
| 日 | %d | 15 |
| 24小时制小时 | %H | 14 |
| 12小时制小时 | %h | 02 |
| 分钟 | %i | 30 |
| 秒 | %s | 45 |
| 微秒 | %f | 123456 |
最大的错法是:把Oracle的MI(分钟)当成MySQL的%m(月份)。Oracle里TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS')是经典写法,其中MI是分钟。有人改成MySQL时顺手写成了DATE_FORMAT(NOW(), '%Y-%m-%d %H:%m:%s'),用%m代替分钟位置,结果分钟位置输出了月份,比如2025-03-15 14:03:45——你永远查不出到底哪分钟出的问题。
血的教训,MySQL的分钟占位符是%i,不是%m。
4.2 12小时制与24小时制的坑
Oracle里用HH或HH12表示12小时制,HH24表示24小时制。如果你写下TO_CHAR(SYSDATE, 'YYYY-MM-DD HH:MI:SS'),下午2点会输出02,不会输出14。而且Oracle的12小时制显示结果受"上午/下午"标记影响,如果你同时没有输出AM或PM,很难判断到底是凌晨还是下午。
MySQL里%h表示12小时制,%H表示24小时制。如果从Oracle迁移时保留了HH思路,写成DATE_FORMAT(NOW(), '%Y-%m-%d %h:%i:%s'),下午2点同样输出02。
自查建议:只要涉及跨小时、跨时区的统计报表,一律用24小时制格式串。前端的"下午2点"交给展示层去干,数据库出来的字符串统一24小时制。
还有一个更隐蔽的:Oracle的MI跟HH24搭配没问题,但如果你在Oracle里写HH:MI且数据落在中午12点,HH输出12;MySQL里%h对中午12点也输出12,下午1点输出01。两边行为一致,但"12点到底是凌晨还是中午"这个问题,在字符串比较排序时会出现玄学:字符串'04'会排在'12'后面,但按时间04:00应该排在12:00前面?不对,凌晨4点在中午12点之前,字符串序04 < 12,所以字符串排序恰好是对的;但如果用12小时制,凌晨1点是01,中午1点也是01,字符串无法区分。所以涉及排序、区间判断,千万别用12小时制字符串。
4.3 隐式转换靠不靠谱
Oracle在客户端工具里查询时,DATE类型默认按NLS_DATE_FORMAT显示,默认值通常是DD-MON-RR这种格式(比如15-MAR-25)。这意味着你在Oracle里直接SELECT SYSDATE FROM DUAL;看不到时分秒,但不代表没有,NLS只控制显示。这个"显示陷阱"在迁移调研期间最坑:业务方拿Oracle客户端看到日期字段只有年月日,误以为数据里没有时分秒,设计MySQL目标表时建成了DATE类型,结果同步后时分秒全被截断,数据对不上。
MySQL这边隐式转换比Oracle宽松得多,也危险得多。MySQL对字符串转日期非常"宽容":'2025-03-15 10:30:45'、'2025/03/15'、'20250315'它都能解析。但宽容意味着意外:如果字符串格式不规范,MySQL有时不报错,而是返回一个"零值日期"0000-00-00,或者干脆截断。你在Oracle里用TO_DATE('15-03-2025', 'DD-MM-YYYY')是严格的、有报错的;在MySQL里STR_TO_DATE('15-03-2025', '%d-%m-%Y')也能用,但如果你直接把Oracle写法抄过来,格式串不识别,结果可能就是NULL或错误值。
另一个最经典的隐式转换差异:字符串比较。Oracle里如果你拿一个VARCHAR2字段和DATE字段比较,Oracle会优先把字符串隐式转成DATE,按时间语义比较。MySQL里不同,字符串和DATETIME比较时,MySQL先尝试把字符串转成数字或时间,但转换规则经常跟你的直觉不一致。最典型的例子是WHERE date_col = '2025-03-15':在Oracle里,'2025-03-15'会隐式转成2025-03-15 00:00:00,跟DATE字段比较没问题;在MySQL里,如果date_col是DATETIME,字符串会被转成2025-03-15 00:00:00再比吗?实测MySQL默认允许这种比较,但前提是字符串能被完整解析。一旦date_col的时分秒不是零点,=永远不成立。所以还是那句:范围比较才是稳妥解。
5. 实战建议与踩坑记录复盘
5.1 迁移项目里最值得遵守的五条时间处理规范
这五条是我这次迁移之后沉淀下来、写进团队开发规范里的内容,每一条背后都有一次真实的事故。
一、应用和数据库的会话时区必须显式统一。连接MySQL的连接串里,建议直接指定时区参数,不要依赖服务器默认值。以JDBC为例,serverTimezone=Asia/Shanghai要写清楚;Python的pymysql可以在连接参数里指定init_command="SET time_zone = '+08:00'"。Oracle连接同样建议统一ALTER SESSION SET TIME_ZONE。两边不一致,后面所有时间逻辑都是废的。
二、新增代码里禁用SYSDATE/NOW()裸奔。要取当前时间就明确意图:要取UTC就用UTC_TIMESTAMP();要取本地业务时间就先约定会话时区,再统一用NOW()。不要在SQL里混用CURRENT_TIMESTAMP、LOCALTIMESTAMP、UNIX_TIMESTAMP,很容易乱。
三、"今天"一律用范围比较,不要用函数套字段。无论Oracle还是MySQL,WHERE TRUNC(date_col) = TRUNC(SYSDATE)或WHERE DATE(date_col) = CURDATE()这种写法,在数据量大时都会让索引失效。正确写法是用>= 今天零点 AND < 明天零点的范围。
四、所有跨系统交互的时间字符串,统一走ISO 8601格式。即YYYY-MM-DDTHH:MM:SS,Java、Python、各数据库都认识,按字典序排序就是时间序。别用DD-MON-YYYY这种区域格式,别说Oracle默认NLS那套,连MySQL里都容易踩转换坑。
五、表结构迁移时,OracleDATE字段不是直接对应MySQLDATE。要看业务里是否真的只用日期,如果有时分秒的比较或统计,目标类型用DATETIME。OracleTIMESTAMP系列字段,MySQL里我用DATETIME(6)承接微秒,避免精度丢失。
5.2 一个低峰期数据核对小脚本的写法
在迁移过程中,我写过一个快速核对两库时间口径的小脚本,思路是同时在Oracle和MySQL执行一组时间查询,把结果并排对比。这个脚本低峰期跑一下,能提前暴露90%的时区问题。
-- Oracle SELECT SYSDATE AS db_local_time, CURRENT_DATE AS session_date, SYSTIMESTAMP AS systimestamp_val, SYS_EXTRACT_UTC(SYSTIMESTAMP) AS utc_val, DBTIMEZONE, SESSIONTIMEZONE FROM DUAL;-- MySQL SELECT NOW() AS session_now, CURDATE() AS session_curdate, UTC_TIMESTAMP() AS utc_val, @@global.time_zone AS global_tz, @@session.time_zone AS session_tz, NOW(6) AS session_now_us对比思路:
- 看Oracle的
SYSDATE和CURRENT_DATE是否相同。不同,说明会话时区被改过。 - 看MySQL的
NOW()和UTC_TIMESTAMP()的差值,判断会话时区偏移量。 - 把MySQL的
NOW()换算到UTC,再跟Oracle的SYS_EXTRACT_UTC(SYSTIMESTAMP)比对,差值在秒级之内才算通过。 - 记录两边的
SESSIONTIMEZONE和time_zone参数,放在迁移文档里存档。
我在两个环境各跑一次,把上面的结果贴到同一张Excel里,一眼就能看出两边差在哪。凡是查出有偏差的环境,都是连接参数或数据库初始化的锅,改完重跑一遍即可。
5.3 最容易被忽略的"日期字面量"差异
最后再单独说一个我这次差点忽略的点:日期字面量的标准写法不一致。
Oracle的标准写法是DATE '2025-03-15',注意中间DATE关键字是ANSI SQL标准语法,Oracle支持。它等价于TO_DATE('2025-03-15', 'YYYY-MM-DD')。
MySQL的日期字面量写法没有DATE关键字,直接写字符串'2025-03-15'。SELECT '2025-03-15' + INTERVAL 1 DAY在MySQL里是合法的,但你如果照搬Oracle的DATE '2025-03-15'到MySQL,会直接语法报错。
反过来,Oracle里直接'2025-03-15' + 1会报错还是隐式转?实际上是尝试隐式转换的,但数字加日期如果顺序不对会出问题。为了避免这种混乱,我的建议是:Oracle统一用DATE 'YYYY-MM-DD'或TO_DATE;MySQL统一用CAST('2025-03-15' AS DATE)或直接字符串 +INTERVAL。两边都不要依赖隐式转换。
写到这里,我已经把Oracle和MySQL读当前日期时间的主要差异翻了个底朝天。最后集中回答一下开头那个最朴素的问题:如果你现在就要把一段Oracle SQL翻译成MySQL,最省心的做法是先问自己三个问题——你取的这个时间,客户想要的是他所在时区的当前时间,还是服务器所在时区的当前时间?这个时间要不要精确到秒以下?后面拿它做什么(比较、分组、排序、格式化)?三个问题一答,对应到本文第二、三、四章的函数和写法,基本就不会出错了。但我说实话,光看完不练没意义,建议你直接在自己的环境里把第二章那个核对脚本跑一遍,亲眼看看两边输出,比读十篇文章都管用。