ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Oracle与MySQL取当前时间全对比:SYSDATE、NOW()、时区与格式化避坑指南

Oracle与MySQL取当前时间全对比:SYSDATE、NOW()、时区与格式化避坑指南 1. 为什么要把Oracle和MySQL的时间读取放在一起说最近好几个朋友都在折腾数据库迁移从Oracle换到MySQL或者两套库并存。聊下来发现大家最先踩坑的往往不是那些复杂的SQL优化反而是最基础的取当前时间这种小操作。明明都是写一行SQLOracle里跑得好好的到了MySQL直接报错或者结果不对时间差八小时更是家常便饭。Oracle和MySQL是当前企业里最常见的两套关系型数据库一个偏重企业级事务处理一个在互联网场景里遍地开花。它们的SQL语法大体相通但一到日期时间这种东西差异就藏在细节里。日期函数看似简单实则牵连着时区、格式、事务行为、存储精度、甚至触发器和存储过程的执行语义一旦用错轻则时间不准重则数据错乱、报表对不上。这篇文章不光把函数列出来对比我还会结合这些年实际工作中踩过的坑把函数背后的机制、时区模型、事务中的表现差异、格式化处理都掰开揉碎了讲清楚。不管你是做迁移的DBA、写业务代码的Java开发还是维护报表的运维都能从中找到直接能用的东西。2. 逐个拆解Oracle读取当前时间的函数Oracle里读取当前日期时间表面上就几个函数实际用起来门道不少。先列最常用的几个SYSDATE、SYSTIMESTAMP、CURRENT_DATE、CURRENT_TIMESTAMP、LOCALTIMESTAMP。2.1 SYSDATE与SYSTIMESTAMP最核心的两个说Oracle的时间函数SYSDATE永远是绕不开的那个。SYSDATE返回的是数据库服务器所在操作系统的当前日期和时间精度是秒类型是DATE。比如在X86 Linux服务器上跑一个最简单的查询SELECT SYSDATE FROM DUAL;输出的样子取决于会话的NLS设置默认格式通常是DD-MON-YY比如05-FEB-25。这里有个新手很容易疑惑的点为什么SYSDATE看起来没有时分秒不是函数没返回时分秒是默认显示格式把它藏起来了。用TO_CHAR显式格式化就能看到完整时间SELECT TO_CHAR(SYSDATE, YYYY-MM-DD HH24:MI:SS) FROM DUAL;这样返回的就是类似2025-02-05 14:30:25的完整字符串。SYSTIMESTAMP和SYSDATE不同它返回的是TIMESTAMP WITH TIME ZONE类型精度到纳秒在多数平台上实际能精度到位数取决于操作系统而且自带数据库服务器的时区信息。举个直观的例子SELECT SYSTIMESTAMP FROM DUAL; -- 输出类似05-FEB-25 02.30.25.123456 PM 08:00如果你只需要日期时间本身不需要时区信息用SYSDATE就够了如果你在分布式系统里需要记录带时区的精确时间戳SYSTIMESTAMP是更稳的选择。2.2 CURRENT_DATE与CURRENT_TIMESTAMP会话时区的参与者CURRENT_DATE返回的是基于当前会话时区的日期时间类型是DATE。而SYSDATE基于的是数据库服务器时区。这两者在绝大多数单机部署场景下结果一样但一旦你改了会话时区差异立刻显现。ALTER SESSION SET TIME_ZONE America/New_York; SELECT SYSDATE FROM DUAL; -- 服务器本地时间 SELECT CURRENT_DATE FROM DUAL; -- 纽约时区的当前日期时间如果你的应用服务器和数据库服务器不在同一个时区或者客户端设置了非默认时区CURRENT_DATE得到的可能和SYSDATE相差好几个小时。CURRENT_TIMESTAMP和CURRENT_DATE类似但它返回的是TIMESTAMP WITH TIME ZONE类型等价于SYSTIMESTAMP的会话时区版本。而LOCALTIMESTAMP返回的是TIMESTAMP类型不带时区同样基于会话时区。我把这几个函数整理成一个表方便对照函数返回类型基准时区精度SYSDATEDATE数据库服务器秒SYSTIMESTAMPTIMESTAMP WITH TIME ZONE数据库服务器纳秒级CURRENT_DATEDATE会话时区秒CURRENT_TIMESTAMPTIMESTAMP WITH TIME ZONE会话时区纳秒级LOCALTIMESTAMPTIMESTAMP会话时区纳秒级2.3 DUAL这张伪表是绕不开的坑Oracle查询时间必须带FROM DUAL这是和MySQL最大的体验差异之一。DUAL是Oracle里的一张特殊单行单列表专门用来执行不带表的查询。MySQL里你直接写SELECT NOW();就行了完全不需要DUAL。这个习惯在从Oracle迁到MySQL时特别容易犯。我见过不少人迁移SQL第一行还是SELECT SYSDATE FROM DUAL;切到MySQL直接报错——MySQL默认其实也支持FROM DUAL所以这个坑还好不算致命。但要反过来MySQL的语句到了Oracle不带FROM DUAL就一定挂。另一个Oracle的隐藏特性在存储过程里面取时间SYSDATE是一个语句级的表达式每次调用都会重新求值这一点和后面要讲的MySQL的NOW()在存储过程里的表现完全不同这里先埋个伏笔。3. MySQL这边怎么读取当前时间MySQL读取当前时间的函数数量不比Oracle少但命名习惯和Oracle差异很大MySQL更喜欢NOW()、CURDATE()、CURTIME()这种直白的名字而且支持的时间精度档位很灵活。3.1 NOW()、CURDATE()、CURTIME()的基本功NOW()返回当前日期和时间格式就是标准的YYYY-MM-DD HH:MM:SS类型是DATETIME。这是MySQL里用得最多的一个。SELECT NOW(); -- 输出2025-02-05 14:30:25CURDATE()返回当前日期类型是DATE约等于Oracle里只取日期的效果SELECT CURDATE(); -- 输出2025-02-05CURTIME()返回当前时间类型是TIMESELECT CURTIME(); -- 输出14:30:25这三个函数还各自有带括号的等价写法CURRENT_TIMESTAMP()、CURRENT_DATE()、CURRENT_TIME()。在MySQL里NOW()和CURRENT_TIMESTAMP完完全全是同义词用哪个都行。MySQL从5.6.4版本开始支持小数秒NOW(3)表示精确到毫秒NOW(6)精确到微秒这个特性在做性能埋点、日志记录时非常实用SELECT NOW(3); -- 输出2025-02-05 14:30:25.1233.2 CURRENT_TIMESTAMP和SYSDATE()的区别这条坑最深MySQL里有个非常容易混淆的组合NOW()、CURRENT_TIMESTAMP和SYSDATE()。很多老手都会在这里翻车。NOW()以及CURRENT_TIMESTAMP取的是语句开始执行的时间。也就是说一条SQL无论执行多久这条语句里所有地方出现的NOW()都返回同一个值。这在存储过程、触发器和批量更新里尤其关键。而SYSDATE()取的是函数被执行那一刻的时间。如果在一条长SQL里、一个事务里反复调用SYSDATE()每次可能拿到不同的值。看一个经典的对比场景。如果你在一个存储过程里这样写CREATE PROCEDURE p_test() BEGIN SELECT NOW(), SLEEP(3), NOW(); SELECT SYSDATE(), SLEEP(3), SYSDATE(); END;执行一下观察结果第一行两次NOW()的时间完全相同第二行两次SYSDATE()的时间相差3秒。这个差异在生产环境会引发很隐蔽的问题。比如你在存储过程里做循环插入数据用NOW()给时间字段赋值那这一批数据的时间完全一致如果用SYSDATE()则每条记录的时间都不同。如果你靠时间字段来做数据分片或者在事务里判断时间范围这两种行为会带来完全不同的结果。从Oracle迁移过来的同学尤其要注意Oracle的SYSDATE是每次执行都重新取值的语义上更接近MySQL的SYSDATE()而不是NOW()。如果迁移时没注意这个区别存储过程和触发器里的时间行为会跟原来完全不同。3.3 MySQL的DATETIME与TIMESTAMP存储层面的另一个差异虽然这篇文章主要在说读取当前时间但MySQL里DATETIME和TIMESTAMP的差异直接影响你存下来的时间是不是你想要的。DATETIME范围从1000-01-01到9999-12-31存储时不带时区你写入什么就存什么。TIMESTAMP范围从1970-01-01到2038-01-19存储时会把时间转成UTC读取时再按会话时区转回来。从读取当前时间的角度看如果你用CURRENT_TIMESTAMP给TIMESTAMP字段赋值实际存进去的值会受时区影响。而给DATETIME字段赋值存的就是字面上的时间。这个问题在跨时区部署、多地域业务里尤其刺眼。4. 时区处理是真正的分水岭前面提了好几次时区但这个话题值得单独拉出来说透彻。Oracle和MySQL在时区模型上的设计思路完全不同不理解这一点时间差八小时这类问题就会反复来找你。4.1 Oracle的时区模型数据库时区和会话时区两套Oracle的时区体系分两层第一层是数据库时区DBTIMEZONE在创建数据库时指定一般用于TIMESTAMP WITH LOCAL TIME ZONE类型的存储基准。查看方式SELECT DBTIMEZONE FROM DUAL;第二层是会话时区SESSIONTIMEZONE开启一个会话后可以由客户端或者ALTER SESSION指定。它决定了CURRENT_DATE、CURRENT_TIMESTAMP、LOCALTIMESTAMP这些函数返回结果所用的时区SELECT SESSIONTIMEZONE FROM DUAL;关键在于SYSDATE和SYSTIMESTAMP两大金刚不受会话时区控制永远返回数据库服务器操作系统的本地时间。这意味着如果你的数据库服务器设在东八区客户端在上海没设置时区SYSDATE和CURRENT_DATE的结果一致改掉会话时区为东京时间东九区后CURRENT_DATE会比SYSDATE快一个小时。这给应用带来的现实问题应用端拿到的时间到底是什么基准如果代码里混用了SYSDATE和CURRENT_TIMESTAMP同一个请求里可能会看到两个相差数小时的时间。最稳的做法是全公司约定一个统一的时间基准函数不要今天用这个明天用那个。4.2 MySQL的时区模型全局一个会话一个但要看清楚细节MySQL的时区设置有两个关键参数system_time_zone和time_zone。system_time_zone表示操作系统层面的时区MySQL启动时继承过来的一般只读。time_zone可以理解为当前会话的时区也支持全局设置SELECT global.time_zone, session.time_zone, system_time_zone;如果time_zone的值是SYSTEM默认就是它那MySQL就跟着操作系统时区走。很多人在云服务器上遇到的日期时间正常但加八小时问题根源就是服务器系统时区设成了UTC而time_zone还是SYSTEM。修改方式如下SET GLOBAL time_zone 08:00; SET SESSION time_zone 08:00;更推荐在配置文件my.cnf的[mysqld]段里写死[mysqld] default-time-zone 08:00这样重启后仍然生效不会因为应用重连导致时区漂移。有一个容易忽略的细节MySQL的时区设置影响的是FROM_UNIXTIME()、NOW()、CURTIME()等函数对时间戳的解释但并不会改变已经写入DATETIME字段里的字面值。换句话说你改时区只能影响读取和转换不能自动纠正已经错乱的历史数据。4.3 实操场景Java应用连接Oracle和MySQL同时遇到时区问题我做Java开发的朋友经常抱怨同一个应用一边连Oracle一边连MySQL时间不对的表现还不一样。连Oracle时JDBC驱动在读取DATE和TIMESTAMP类型时用的是JVM默认时区来转换。如果JVM时区和数据库服务器时区不一致取出来的时间就会出现过偏移。解决方式一般是在连接串里加参数jdbc:oracle:thin://host:1521/service?oracle.net.tns_admin...或者在代码里设置JVM时区并让数据库服务器的时区统一。连MySQL时问题往往出在连接串的serverTimezone参数没设置或者设成了UTC。很多Initializer模板默认jdbc:mysql://host:3306/db?serverTimezoneAsia/Shanghai如果漏了或者设错PreparedStatement写入TIMESTAMP时驱动会按照错误的时区做转换。最常见的结果就是数据库里存的时间比实际快八小时或者读出来比实际慢八小时。这种问题排查起来特别尴尬因为直接在数据库客户端用SELECT NOW()看时间是对的但应用读出来的就是不对。我建议排查时先看三处操作系统时区、MySQL的time_zone参数、JDBC连接串的serverTimezone逐一比对。三条对齐了八小时问题基本消失。5. 格式化与类型转换的差异日期时间读出来了下一步往往就是格式化。Oracle和MySQL的格式化函数从名字到语法完全是两个路子。如果你两套库都在用脑子里得有两条模板千万别记混。5.1 TO_CHAR vs DATE_FORMAT熟悉却又容易写错的语法Oracle里格式化日期用的是TO_CHAR格式符是YYYY、MM、DD、HH24、MI、SS这一套SELECT TO_CHAR(SYSDATE, YYYY-MM-DD HH24:MI:SS) FROM DUAL; -- 输出2025-02-05 14:30:25MySQL里格式化用的是DATE_FORMAT格式符是%Y、%m、%d、%H、%i、%s这一套SELECT DATE_FORMAT(NOW(), %Y-%m-%d %H:%i:%s); -- 输出2025-02-05 14:30:25两个格式符长得像但完全不同。最坑的是%i代表分钟、%s代表秒而%m代表月份、%M代表英文月份名。新手经常把MySQL里分钟写成%m结果把月份当成分钟输出来时间完全错乱。我把常用的对应关系列成一张速查表两套系统对比着看含义Oracle格式符MySQL格式符年份四位YYYY%Y年份两位YY%y月份MM%m日DD%d时24小时制HH24%H时12小时制HH%h分MI%i秒SS%s如果你在写兼容两套数据库的代码建议写一个统一的时间格式化工具类由底层根据数据库类型选择对应的格式串避免在SQL里到处硬编码。5.2 字符串转日期也有差异有反向需求时Oracle用TO_DATEMySQL用STR_TO_DATE-- Oracle SELECT TO_DATE(2025-02-05 14:30:25, YYYY-MM-DD HH24:MI:SS) FROM DUAL; -- MySQL SELECT STR_TO_DATE(2025-02-05 14:30:25, %Y-%m-%d %H:%i:%s);这里有个MySQL的隐性行为要特别警惕MySQL对非法日期的容忍度比Oracle高很多。比如STR_TO_DATE(2025-02-30, %Y-%m-%d)在Oracle的TO_DATE里会直接报ORA-01839错误而MySQL会返回NULL或者警告甚至在某些模式下会自动修正为2025-02-28之类。如果你在迁移数据清洗逻辑这种宽松反而会掩盖脏数据导致数据入库时悄悄变了样。我的习惯是在MySQL里手动做一次严格校验或者依赖STRICT_TRANS_TABLES这种严格SQL模式别让脏时间混过去。5.3 默认显示格式的差异两家数据库对日期时间的默认展示风格完全不同。Oracle的systimestamp、current_timestamp返回结果带着毫秒微秒和时区格式偏向05-FEB-25 02.30.25.123456 PM 08:00这种Oracle特色的写法。而MySQL的NOW()直接给标准YYYY-MM-DD HH:MM:SS人类友好度高很多。这个差异导致从Oracle迁移到MySQL时很多报表、导出文件、日志字符串的格式全部要对一遍。原来依赖OracleDD-MON-YY默认格式的代码到MySQL必须显式格式化否则输出会完全变样。6. 事务中的时间表现一个容易彻底理解错的地方日期时间函数在事务里的行为是最能体现两家数据库设计哲学差异的地方也是很多人写代码时根本没考虑过的盲区。6.1 Oracle的SYSDATE不受事务控制Oracle的SYSDATE、SYSTIMESTAMP返回的是语句实际执行那一刻的服务器时间不受事务开始时间影响。也就是说一个事务在上午9点开始中间跑了好几个小时事务里最后一条SQL执行到SELECT SYSDATE FROM DUAL时返回的是执行当时的时间而不是事务开始那个时刻。MySQL的NOW()则完全不同在同一个事务内部所有调用NOW()的地方返回的事务开始时刻的时间准确点说跟语句起点又有些关系但事务内表现基本是稳定值。这其实是MySQL为binlog复制和一致性考虑的设计保证主从库的NOW()行为一致。这个设计各有取舍。Oracle适合那种下单时记录真实时间的强实时诉求MySQL的语义更偏向事务内时间统一保证数据的一致性视图。6.2 实际场景订单时间、流水时间到底该用哪个一个最典型的场景电商下单后应用代码里开了事务插入订单主表循环插入订单明细最后更新库存。这时候要给每条数据填创建时间。如果你用的是Oracle无论你在存储过程里调用SYSDATE多少次取的始终是当前真实时间明细插入有先后顺序时间可能精确到秒级但仍然有细微差别。如果你用的是MySQL并且所有地方都用NOW()整个事务内的所有插入语句拿到的都是同一个时间点。看起来反直觉但这在数据一致性上反而是有优势的——后续对账、分页、归档时一个订单的所有子数据时间相同不会出现主子表时间对不上的诡异数据。如果你想要Oracle那种每条记录都是独立时间的效果MySQL里要用SYSDATE()替代NOW()。但我要提醒一句比较激进地使用SYSDATE()会影响MySQL的索引优化因为它打破了NOW()的语句级稳定性MySQL可能没法把表达式当作常量来优化。排查手段用可以生产环境还是建议统一用NOW()。6.3 触发器和存储过程中的时间陷阱触发器是另一个高频踩坑点。在MySQL里如果你在BEFORE INSERT触发器里用SET NEW.create_time NOW()这条NOW()返回的是触发语句开始执行的时间不是行级触发瞬间的时间。这在批量插入时特别明显一次INSERT ... SELECT插入一万行触发器里的NOW()对每一行都是同一个值所有行的create_time一模一样。Oracle的触发器里如果调用SYSDATE每一行触发的瞬间取到的都是最新时间一万行可能就会有一万种微妙的时间差异虽然很多行会落在同一秒内。这提醒我们做数据迁移时不能只看函数名得观察触发器和存储过程在批量场景下的时间行为是否符合业务预期。特别当你需要靠时间字段去重、排序或做增量抽取时这两种行为会直接决定抽取结果的质量。7. 常见问题与排查技巧实录这部分整理一些我在实际项目中反复遇到的、跟时间读取直接相关的问题每一类都给出排查思路和解决方案。7.1 时间差八小时先从三层时区查起不管是Oracle还是MySQL只要出现当前时间比真实时间差八小时优先按下面的顺序排查。第一层操作系统时区。直接用date命令看服务器如果显示UTC、GMT那就不是北京时间。第二层数据库参数。MySQL看system_time_zone和session.time_zoneOracle看DBTIMEZONE和SESSIONTIMEZONE。第三层连接层。Java的JDBC连接串是否指定了serverTimezonePython的pymysql、cx_Oracle是否有时区处理这些都会影响最终呈现在应用层的时间。这里有个特别常见的乌龙数据库服务器系统时区是UTCMySQL的time_zone是SYSTEM你直连MySQL执行SELECT NOW()结果UTC时间没错到应用里读出来连接串又用了serverTimezoneAsia/Shanghai两个都是正确的配置但拼接在一起就错位了。所以排查时一定要把服务器时区、数据库时区参数、连接串时区三件套对齐。7.2 Oracle里能查到但程序里取不到当前时间有些开发在Oracle存储过程里用SELECT SYSDATE INTO v_time FROM DUAL运行正常。但把代码改成v_time : SYSDATE;后突然发现某些工具或语法检查报错。其实这是因为Oracle的PL/SQL里SYSDATE可以直接赋值给DATE变量不用走SELECT INTO。而如果写成v_time : CURRENT_DATE;在旧版Oracle里有时会提示表达式不能出现在赋值语句中属于语法兼容性问题。统一使用SYSDATE在存储过程里面就不会有这种麻烦。7.3 MySQL的字段自动更新为什么我没传时间时间也变了这个问题跟读取当前时间看起来相反但根源相通。MySQL的TIMESTAMP或者DATETIME字段如果设置了DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP那么插入或更新时MySQL会自动写入或刷新当前时间。CREATE TABLE t_order ( id INT PRIMARY KEY, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );很多人刚接触时很困惑我没给update_time赋值它怎么自己变了其实这正是自动更新在起作用。这特性在生产环境很实用但也有风险如果你用一个批量更新语句把大量行的某个无关字段改了update_time会跟着刷新让下游同步任务误以为这些数据变更过。所以我一般建议有明确业务含义的时间字段比如订单支付时间、发货时间不要设置ON UPDATE CURRENT_TIMESTAMP只让系统级的审计字段自动更新。7.4 数据迁移时时间格式悄悄变化从Oracle导数据到MySQL时时间字段最容易出问题。Oracle的DATE类型本身隐含时分秒如果不显式TO_CHAR导出很多工具导出后只保留日期部分。到了MySQL端再导入时间就丢了。最稳妥的做法是导出时统一用字符串格式把时间字段全量输出SELECT TO_CHAR(create_time, YYYY-MM-DD HH24:MI:SS) FROM old_table;导入MySQL之后再转成DATETIMEUPDATE new_table SET create_time STR_TO_DATE(2025-02-05 14:30:25, %Y-%m-%d %H:%i:%s);迁移完成后的数据校验也一样抽样对比几条记录的时间别光看日期和秒毫秒、微秒、时区这些信息要单独核。7.5 毫秒和微秒精度丢失Oracle的TIMESTAMP支持纳秒级精度如果你在Oracle里存了一个微秒级时间戳迁到MySQL的DATETIME(6)精度上可以接上但MySQL的NOW(6)和Oracle的SYSTIMESTAMP在取当前时间时的实际精度下限都受操作系统影响。Windows上的精度通常低于Linux做高精度埋点时要注意平台差异。如果你用DATE类型接收SYSTIMESTAMPOracle会自动截断到秒级微秒直接丢掉。这一点在写接口代码时特别容易忽略接口层返回的是毫秒字符串落到库里却发现全是.000结尾。7.6 函数索引与排序的隐性问题还有一类问题不直接报错但会让性能变差。在Oracle里经常用WHERE TRUNC(create_time) TRUNC(SYSDATE)来查当天数据这种写法在Oracle里如果用函数索引还好如果没建函数索引常规索引会失效。MySQL同样WHERE DATE_FORMAT(create_time, %Y-%m-%d) CURDATE()这类写法也无法高效利用索引。正确姿势是用范围查询-- Oracle WHERE create_time TRUNC(SYSDATE) AND create_time TRUNC(SYSDATE) 1; -- MySQL WHERE create_time CURDATE() AND create_time CURDATE() INTERVAL 1 DAY;这算是读取当前时间连带着的经典优化点。很多新手把数据库时间函数用得飞起却没意识到对字段做函数包装会直接劝退索引。8. 一些关于踩坑之后的使用心得写了这么多对比和案例最后分享几条我自己的使用习惯。第一在一个项目里明确统一取时间的入口。Oracle项目里定死用SYSDATEMySQL项目里定死用NOW()。除非有特殊时区需求否则别让团队里有的人用CURRENT_DATE、有的人用SYSTIMESTAMP排查问题时真的会疯掉。第二时间字段的精度和类型在建表初期就要想清楚。要记录毫秒甚至更细粒度的时间MySQL就建DATETIME(3)或DATETIME(6)Oracle就建TIMESTAMP(3)。不要等到数据量大、应用上线后再去改字段类型改类型的锁表时间和数据转换成本会让你怀疑人生。第三任何跨数据库迁移时间函数和时区配置必须放在改造清单的前三页。很多人迁移时只盯着存储过程、分页语法、字符串拼接这些大头把时间函数当小case结果往往是这些小case在生产环境第一个爆发。建议迁移前先跑一遍全文检索把SYSDATE、NOW、CURRENT_TIMESTAMP、TO_CHAR、TO_DATE、DATE_FORMAT全部揪出来逐个改写并做好回归验证。第四养成用范围查询代替函数比较的习惯。不管在Oracle还是MySQL对时间字段的直接函数处理都容易挡住索引。很多时候报错还好最怕的是能跑但性能很慢慢到超时这种问题排查起来费时费力。早点把时间范围写法练成肌肉记忆能省掉不少夜里的电话。数据库的时间读取看起来是个小知识点实际上牵连了SQL语法、时区模型、存储引擎、事务隔离、JDBC行为好几个层面。把Oracle和MySQL的差异吃透不光是迁移项目的刚需更是日常开发里少踩坑的关键。希望这篇梳理能帮你在两套数据库之间切换时不再被时间问题折磨。
返回列表