ARTICLE DETAIL

资讯详情

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

MySQL日期时间类型选型与避坑指南:从DATETIME到TIMESTAMP

MySQL日期时间类型选型与避坑指南:从DATETIME到TIMESTAMP 上个月线上项目突然报警用户表里一个birthday字段默认值设成了0000-00-00后端用 Java 8 的LocalDateTime读取时直接崩了一片异常。排查了一整天最后发现根子不在业务代码而是藏在 MySQL 日期时间类型的底层存储设计里。这种问题并不罕见MySQL 的日期时间类型看着简单真正用起来全是细节类型怎么选、默认值怎么设、时区怎么处理、字符串怎么转换、存储过程和触发器里怎么把日期变量和谐地塞进去。这篇文章我就把这些年踩过的坑、查过的官方文档、跑过的测试都整理出来希望能帮大家少走弯路。文章主要面向三类读者刚入门 MySQL、建表时还在犹豫用datetime还是timestamp的新手已经在项目里被日期时间字段折腾过、想知道“为什么”的中级开发者以及准备面试、需要把日期时间考点一次理清的求职者。我会从类型本质、选型逻辑、实操 SQL、存储过程与触发器、问题排查五个维度展开尽量把每个“为什么”都讲透。1. 五种原生日期时间类型先搞清楚它们本质上是什么1.1 DATE、TIME、DATETIME三种基础类型MySQL 官方文档里的日期时间类型一共有五个DATE、TIME、DATETIME、TIMESTAMP、YEAR。先看三种最基础的。DATE只存日期不存时间格式是YYYY-MM-DD业务上常见的生日、入职日期、账单日都是它的主场。它占用 3 个字节支持的范围是1000-01-01到9999-12-31。很多人没注意的是DATE在底层其实是以整数形式存储的计算时会被拆成年、月、日三个部分所以对DATE字段做范围判断时直接用字符串比较通常没问题但如果你在查询条件里写DATE()函数包住字段索引就失效了这一点在第 5 章我会专门展开。TIME只存时间格式是HH:MM:SS也可以带微秒部分比如838:59:59.000000。这里有个非常冷门的知识点TIME类型的取值范围不是 0 到 23 点而是-838:59:59到838:59:59。为什么因为 MySQL 允许把TIME当作“时间段”使用可以用来表示两个事件之间的间隔比如某个任务跑了 800 个小时你完全可以直接存进TIME字段里而不需要换算成天。这一点在面试中偶尔会被问到属于加分项。DATETIME则是日期和时间的组合格式YYYY-MM-DD HH:MM:SS占用 8 个字节范围同样到9999-12-31。它是业务系统里最常用的类型因为不依赖时区存进去是什么就是什么。比如用户下单时间、文章发布时间、课程开课时间只要你想保留“真实发生时刻”的绝对表示DATETIME是默认选项。1.2 TIMESTAMP 的底层秘密与 2038 年问题TIMESTAMP是最容易让人迷糊的类型。它跟DATETIME外表长得一样都是YYYY-MM-DD HH:MM:SS但内部原理完全不同。TIMESTAMP占用 4 个字节存储的是从1970-01-01 00:00:00 UTC到某个时刻的秒数也就是 Unix 时间戳。为什么只有 4 个字节因为 32 位整数在 2038 年会溢出这就是著名的“2038 年问题”。所以TIMESTAMP的可表示范围只到2038-01-19 03:14:07 UTC再往后就存不进去了。在 64 位系统上这个问题被弱化了很多但你在设计表结构时仍然要记住如果业务预期要持续几十年或者要存类似于百年校庆时间这种“远未来”日期TIMESTAMP并不合适老老实实用DATETIME。TIMESTAMP的另一个特性是跟时区绑定。它在写入时会从当前会话时区转换到 UTC 存储读取时再从 UTC 转换回当前会话时区。这带来的好处是如果服务器迁移到不同时区或者业务面向多个地区的用户用TIMESTAMP存“事件发生时刻”可以自动换算成查看者本地时间。坏处是如果你只想存一个“墙上时间”而不管时区TIMESTAMP反而会造成迷惑因为你看到的数值可能和写入时不一致。1.3 YEAR 类型和它的存储技巧YEAR类型可能是被忽略次数最多的一个。它只存年份占用 1 个字节范围是1901到2155也支持0000。这个类型看起来没什么用但在某些场景里很香比如存会员等级年份、统计报表按年分表、或者查“近几年”的数据YEAR比从DATE字段里YEAR(field)提取更快因为存储和索引层面都已经“定死”了不需要每次查询时做函数转换。有一个容易踩的坑写入YEAR时如果你用两位数比如98MySQL 会理解为1998但20会被理解为2020吗不一定规则是00-69映射到2000-206970-99映射到1970-1999。这种模糊规则很容易让代码的“可读性”变差所以我的习惯是项目里统一用四位数字坚决不用两位缩写连配置文件里也不允许出现这类“捷径”。如果你在维护老项目时看到YEAR(2)这种写法那是旧版本的遗留语法MySQL 8.0 里已经不再支持显示宽度你只需要知道它存在过就行。2. 从业务出发选类型DATETIME 与 TIMESTAMP 到底怎么取舍2.1 时区敏感场景TIMESTAMP 天然帮你解决选DATETIME还是TIMESTAMP本质上是“绝对时间点”和“本地墙上时间”之间的取舍。举个例子你在做一个跨境电商后台订单创建时间如果存DATETIME写入的是“订单发生那一刻的中国时间”。一个美国用户在网页上看到的订单时间是北京时间还是他本地时间架构层面如果全靠应用层转换每个接口都要写时区换算逻辑一旦某处没转对就会出现“明明是刚刚下的单怎么显示成凌晨”的诡异现象。这种情况用TIMESTAMP会更省心存的时候统一转成 UTC 秒数查询时 MySQL 根据每个会话的时区设置自动转成对应的地方时间。你只需要在建立连接时告诉 MySQL“当前用户在东八区”剩下的转换它自己就干了。但要注意TIMESTAMP自动转换只发生在“会话时区”正确配置的前提下。如果你在 JDBC 连接串里没有显式指定serverTimezoneAsia/Shanghai应用服务器和 MySQL 服务器时区又不一致那读写之间就会莫名其妙差 8 小时。这是 MySQL 项目里最常见的“时区 bug”我遇到过不止一次。2.2 业务时间与系统时间DATETIME TIMESTAMP 的组合写法实践里我见过很多规范的表结构会把两类时间分开业务上的“业务发生时间”用DATETIME存因为它的语义是业务单据上的真实时间比如订单的“付款时间”就是用户在 14 点 30 分点击付款那一刻墙上的时间而“记录创建/更新时间”这类系统时间用TIMESTAMP存因为它们本质上就是“数据被写入数据库的操作时间”用TIMESTAMP的自动时区转换和自动更新特性再合适不过。建表时可以这样写CREATE TABLE order_info ( order_id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, pay_time DATETIME NOT NULL COMMENT 业务付款时间不随会话时区改变, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 记录创建时间, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 记录更新时间, PRIMARY KEY (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;CURRENT_TIMESTAMP是 MySQL 里一个很实用的功能后面第 3 章会详细讲。这里先提一个点updated_at配合ON UPDATE CURRENT_TIMESTAMP之后一行数据只要被任何UPDATE语句改动MySQL 就会自动刷新它的值。这比在应用层手动set updated_at now()要可靠得多因为所有入口包括存储过程、扳机、临时脚本、甚至直接手改数据的运维操作都会自动维护这个字段不会漏。2.3 NULL 与零值日期一个历史包袱带来的大坑NULL和0000-00-00是日期时间字段最容易出幺蛾子的地方。老项目里常见这样建表birthday DATE NOT NULL DEFAULT 0000-00-000000-00-00这个“零日期”在 MySQL 里被允许存在是历史原因早期版本里它作为“默认值 非空”的妥协方案用来表示“这个字段还没填”同时满足NOT NULL约束。到了 MySQL 5.7 以后官方引入了NO_ZERO_DATE和NO_ZERO_IN_DATE这两个 SQL 模式默认是开启的。也就是说你在 MySQL 8.0 里想建一个默认值为0000-00-00的表会直接报错或者想插入2024-02-30这种不存在的日期也会被拒绝。这个变化对老系统的迁移冲击非常大。我自己就经历过一次从 MySQL 5.6 迁到 8.0所有默认零日期的表全部报错不得不先写脚本把所有0000-00-00替换成NULL再把字段改成可空。所以如果你正在设计新表我的建议是日期字段的“未填”状态一律用NULL表达不要用零日期Java 侧读取也直接用LocalDate而不是Date配合 MyBatis 的类型处理器能少很多心累。如果确实要兼容老系统也可以在 JDBC 连接串上临时把zeroDateTimeBehavior设为convertToNull但这只是权宜之计不是好方案。3. 实操环节日期时间操作的完整 SQL 示例3.1 设置默认值 CURRENT_TIMESTAMP 以及“默认值 0”的来龙去脉给日期时间字段设置默认值不同版本差异很大。MySQL 5.6 之前DATETIME不能直接用函数作为默认值只能用TIMESTAMP才能配合CURRENT_TIMESTAMP。所以老项目里到处是TIMESTAMP字段不是因为设计者真的想用而是被版本逼的。MySQL 5.6 之后DATETIME也支持DEFAULT CURRENT_TIMESTAMP了新项目没必要再因为这个原因选TIMESTAMP。在 MySQL 8.0 里创建一个“创建时间带默认值、更新时间自动刷新”的表可以这样写CREATE TABLE article ( id INT NOT NULL AUTO_INCREMENT, title VARCHAR(200) NOT NULL, publish_time DATETIME DEFAULT NULL COMMENT 发布时间允许为空, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT文章表;这里要注意一个细节DEFAULT CURRENT_TIMESTAMP只能表示“行写入那一刻的完整时间戳”如果你只想默认一个固定的日期比如2025-01-01MySQL 8.0 也支持直接写字面量start_date DATE DEFAULT 2025-01-01如果避免不了和老系统打交道让你非要把某个字段默认值设置成 0 或者0000-00-00那在建表前先看一下当前 SQL 模式SELECT sql_mode;只要结果里有NO_ZERO_DATE或NO_ZERO_IN_DATE你设默认值 0 一定会报错。你可以临时去改全局配置但这不是好实践我更推荐直接改表结构。3.2 字符串转日期STR_TO_DATE 与 CAST 的细节业务里大量日期是以字符串形式进来的比如2025/06/18 14:30、20250618、18-06-2025。直接往日期时间字段里塞MySQL 会做隐式转换成功与否取决于字符串是否严格符合格式以及 SQL 模式是否严格。隐式转换不可控所以我一般建议显式转换。最常用的是STR_TO_DATE-- 把 2025/06/18 14:30 转成 DATETIME SELECT STR_TO_DATE(2025/06/18 14:30, %Y/%m/%d %H:%i); -- 把 20250618 转成 DATE SELECT STR_TO_DATE(20250618, %Y%m%d); -- 把 18-06-2025 转成 DATE注意是日在前所以要写成 %d-%m-%Y SELECT STR_TO_DATE(18-06-2025, %d-%m-%Y);STR_TO_DATE的格式符非常关键%Y是四位年份%y是两位年份%m是两位月份%c是不带前导零的月份%d是两位日%e是不带前导零的日%H是 24 小时制%h是 12 小时制%i是分钟%s是秒。凡是 12 小时制后面必须配合%pAM/PM否则时间会算错。很多人不知道的是STR_TO_DATE在匹配失败时会返回NULL而不是报错。比如SELECT STR_TO_DATE(2025-02-30, %Y-%m-%d);结果就是NULL。如果你的插入语句没做非空校验这条数据就会带着NULL静默插进去等你查出来才发现漏了。比较稳妥的做法是在插入前先用WHERE或者应用层代码校验一次。另外CAST(2025-06-18 AS DATE)只适合转标准格式遇到自定义分隔符就无能为力了这也是我优先推荐STR_TO_DATE的原因。3.3 日期格式化、计算及和排序相关的常见写法格式化输出是报表系统里的高频需求。MySQL 的DATE_FORMAT和STR_TO_DATE是一对“镜像操作”SELECT DATE_FORMAT(NOW(), %Y-%m-%d %H:%i:%s); -- 2025-06-18 14:30:45如果要做日期加减直接用DATE_ADD、DATE_SUB、或者INTERVAL表达式-- 当前时间加 7 天 SELECT NOW() INTERVAL 7 DAY; -- 当前时间减 3 小时 SELECT NOW() - INTERVAL 3 HOUR; -- 查询最近 30 天内的数据 SELECT * FROM order_info WHERE pay_time NOW() - INTERVAL 30 DAY;有一个很隐蔽的坑NOW()返回的是语句开始执行的时间同一个语句里多次调用NOW()结果都一样而SYSDATE()返回的是它被调用的那一刻时间。在长事务或者存储过程里NOW()和SYSDATE()可能出现不一致。如果业务上要求“同一批数据所有行都使用同一个时间戳”就用NOW()如果要求“每一行看到当前实时时间”才考虑SYSDATE()。排序方面日期时间类型的字段默认就是按时间先后排序的直接ORDER BY pay_time DESC即可。但有个常见误区如果字段是VARCHAR里面存的又是2025/06/18这种非标准格式排序结果是字典序不是时间序。这种表结构设计本身就是错的修根的办法是改字段类型而不是靠排序时转换。如果实在改不动排序时用ORDER BY STR_TO_DATE(pay_time_str, %Y/%m/%d)但这会让索引失效只适合数据量小的表。4. 存储过程与触发器里的日期时间4.1 存储过程中声明变量与动态日期拼接存储过程里用日期时间变量最大的困惑往往不是函数不熟而是变量声明和作用域。声明格式是DECLARE 变量名 类型 [DEFAULT 默认值]类型可以是DATE、DATETIME、TIMESTAMP等。写一个简单的示例根据传入的订单编号把订单的“最近提醒时间”改成当前时间之后 3 天。DELIMITER // CREATE PROCEDURE update_remind_time(IN order_no VARCHAR(32)) BEGIN DECLARE new_remind_time DATETIME; SET new_remind_time NOW() INTERVAL 3 DAY; UPDATE order_info SET remind_time new_remind_time WHERE order_no order_no; END// DELIMITER ;这里第一眼看过去好像没有歧义但实际上order_no order_no是经典的“变量和列名重名”陷阱。在这个UPDATE语句里两个order_no都会被解析成列名结果就是永远返回 TRUE整表更新。解决方式有两个一是参数名加前缀比如p_order_no二是使用SELECT ... INTO先取出主键再用主键更新。我自己习惯统一在参数前加p_前缀这个规则在团队规范里写得清清楚楚。存储过程里另一个常见需求是动态拼日期范围比如按月归档CREATE PROCEDURE archive_data_by_month(IN target_month VARCHAR(7)) BEGIN DECLARE start_time DATETIME; DECLARE end_time DATETIME; SET start_time CONCAT(target_month, -01 00:00:00); SET end_time DATE_ADD(CONCAT(target_month, -01 00:00:00), INTERVAL 1 MONTH); INSERT INTO order_archive SELECT * FROM order_info WHERE pay_time start_time AND pay_time end_time; END//这种写法比用LIKE 2025-06%高效很多因为范围查找在pay_time有索引时可以走索引而LIKE前缀匹配加上前导通配符直接让索引废掉。4.2 触发器中用日期字段做校验以及分隔符问题触发器里做日期校验是很多人会掉进去的深坑。最典型的需求新插入一条订单时如果pay_time晚于当前时间 5 分钟就视为异常订单拒绝插入或者把它标记成非法状态。DELIMITER // CREATE TRIGGER validate_pay_time BEFORE INSERT ON order_info FOR EACH ROW BEGIN IF NEW.pay_time NOW() INTERVAL 5 MINUTE THEN -- 直接报错终止插入 SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT pay_time is invalid; END IF; END// DELIMITER ;触发器里NEW.字段名是插入或更新后的新值OLD.字段名是更新前的旧值这跟存储过程的DECLARE不是一回事。要特别注意的是在BEFORE UPDATE触发器里给NEW.updated_at赋值是常见做法但我前面说过字段定义里已经有了ON UPDATE CURRENT_TIMESTAMP的话你再在触发器里赋值会互相干扰。触发器里写日期格式串最容易出错的地方是NOW()和CURDATE()的返回值类型不同比较时 MySQL 会做隐式类型转换。举个例子CURDATE()返回DATE类型和2025-06-18 14:30:00这种字符串比较时MySQL 会把字符串转成日期时间部分被丢弃。你本来想判断当天 14 点这个时刻结果被简化成了当天零点条件就变宽了。所以如果要比较“今天某个时刻”别偷懒直接用NOW()。还要强调一下DELIMITER //的必要性。MySQL 的客户端默认用分号作为语句分隔符如果触发器或存储过程里有多条用分号结尾的语句不加DELIMITER客户端就会在第一个分号处把语句切断然后报语法错误。把分隔符临时改成//触发器整体才会作为一个完整的对象提交给服务端。很多新手在网上复制触发器代码执行报错一部分原因就是没注意DELIMITER。4.3 把远程库的某张表同步到本地时日期字段如何处理这是一个很常见的运维需求把远程库的一张业务表定期同步到本地分析库。如果直接SELECT *然后INSERT日期时间字段会有两个隐性风险。第一个是时区问题远程库TIMESTAMP存的是 UTC 秒数本地库会话时区如果不是同一个同步后读出来的“墙上时间”会偏移。第二个是精度问题老版本 MySQL 里DATETIME精确到秒但业务系统可能已经用了DATETIME(3)存毫秒级时间同步到DATETIME字段会被四舍五入或者截断造成同一批订单的时间序列出现错乱。稳妥的同步脚本步骤如下先在本地库建一张结构一致的表并显式指定日期时间字段的精度比如pay_time DATETIME(3)。在你的导入SELECT语句里用CONVERT_TZ()把远程时间转换成目标时区或者统一转成字符串再插入。日期字段不要省掉时区信息如果字段类型是TIMESTAMP导入前先SET time_zone 08:00保证会话时区一致。插入时使用INSERT IGNORE或者基于主键的REPLACE避免重复同步时产生重复数据。SET time_zone 08:00; INSERT INTO local_order_info (order_id, pay_time, create_time) SELECT order_id, pay_time, create_time FROM remote_order_info WHERE pay_time last_sync_time;如果你的数据量非常大建议每次同步都以“时间增量”为条件比如同步最近 10 分钟的数据而不是全量导出。我见过一个项目因为同步任务没有增量条件每次全量跑 40 多分钟日期字段里的毫秒还被截断结果业务方用时间排序时发现了大量“同秒重复单”排查起来特别痛苦。5. 常见问题排查与性能优化5.1 日期比较别让索引失效函数包裹字段是大忌日期时间字段的索引优化核心就一句话别在 WHERE 条件的字段上套函数。反例一-- 全表扫描DATE() 套在字段上索引失效 SELECT * FROM order_info WHERE DATE(pay_time) 2025-06-18;反例二-- 全表扫描字段参与了运算索引失效 SELECT * FROM order_info WHERE pay_time INTERVAL 1 DAY NOW();这两个写法在数据量小时看不出问题一旦表数据过千万直接拖垮数据库。正确写法是让字段“裸奔”把计算放在常量的那一边-- 走索引范围查询 SELECT * FROM order_info WHERE pay_time 2025-06-18 00:00:00 AND pay_time 2025-06-19 00:00:00;用这种“左闭右开”的范围写法不仅索引能正常走语义上也更严谨不会漏掉当天的最后一秒。如果想查“最近 30 天”同样写成pay_time NOW() - INTERVAL 30 DAY函数是在NOW()上是常量表达式对索引没有影响。顺便说一句update语句如果牵涉到日期范围也要小心间隙锁。在 RR可重复读隔离级别下UPDATE ... WHERE pay_time BETWEEN ...这种条件不是精确匹配时除了行锁还会加公共间隙锁。并发写入时这个范围会被锁住容易引起Lock wait timeout exceeded。如果你要做大批量的日期区间更新建议先查主键范围再按主键分批更新一次只更新几百行降低锁的粒度。5.2 连接报错其实和日期无关但排查思路值得记很多新手在写日期时间查询时会遇到 MySQL 客户端连不上的情况。最典型的是这样的报错ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock (2)这个错误和日期时间类型没有直接关系但它的排查思路对日常运维很重要。出现这个报错的常见原因是MySQL 服务没启动、socket 文件路径不对、或者用 socket 连接时权限不足。排查顺序一般是确认服务是否在运行systemctl status mysqld或者ps -ef | grep mysql。确认 socket 文件路径cat /etc/my.cnf | grep socket。确认 SELinux 或防火墙是否挡住了/tmp/mysql.sock的访问。临时用 TCP 连接绕开 socket 问题比如mysql -h127.0.0.1 -P3306 -uroot -p。我提这个例子是想说明日常运维里很多问题看似在某个业务点上报错实际原因可能非常“外围”。日期时间字段的排查也一样先看数据、再看时区、最后看 SQL 模式不要一上来就怀疑 MySQL bug大多数情况是自己配置文件或者表结构设计的问题。5.3 高频面试题速查表日期时间类型怎么答整理一下面试里最常见的几个日期时间相关问题直接背结论是不够的关键要说出“为什么”。问题核心答案加分点DATETIME 和 TIMESTAMP 的区别DATETIME 8 字节存范围到 9999TIMESTAMP 4 字节存 2038 年且 TIMESTAMP 受时区影响提到底层存储是 Unix 时间戳面试官会眼前一亮函数索引为什么失效在索引列上应用函数后索引树中的排序就不再满足查询条件举例WHERE DATE(pay_time)...如何高效查某一天的数据用 00:00:00 AND 次日 00:00:00提到左闭右开避免23:59:59漏数据怎么存毫秒级时间用DATETIME(3)或TIMESTAMP(3)提到NOW(3)是另一个函数时区表怎么处理建一张时区映射表业务侧统一用 UTC 存储结合CONVERT_TZ举例默认值如何设置DEFAULT CURRENT_TIMESTAMP配合ON UPDATE提 MySQL 5.6 前后的版本差异面试时聊到日期时间尽量往“我从业务出发怎么设计表”这个方向带比如“订单表我用 DATETIME 存业务时间TIMESTAMP 存系统创建时间”这比单纯背语法要有说服力得多。5.4 小技巧把应用层与数据库层的时间基准统一最后分享一个项目级的小技巧。线上很多疑难杂症其实都源于应用服务器和数据库服务器的系统时间不一致或者 JDBC 连接串里时区参数没写对。无论你用的是DATETIME还是TIMESTAMP只要应用层和数据库层时间基准不一致日期时间就会变成一把“失控的尺子”。我现在的团队约定是所有服务器统一使用 UTC8 作为系统时区JDBC 连接串统一写成jdbc:mysql://localhost:3306/app_db?serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrueserverTimezone这个参数让驱动知道后端 MySQL 的时区不是让数据库去转换数据而是让连接的读写解释保持一致。很多人只写了characterEncodingutf8结果时间字段怎么读都差 8 小时就是这个参数缺失。还有一个跟日期时间紧密相关的坑应用层的定时任务。如果你用CronTrigger指定每天凌晨 2 点执行归档任务但应用服务器时钟走偏了哪怕偏 1 分钟都会导致日期范围处理错位。所以我建议在定时任务里不要依赖系统时间而是先查数据库的SELECT NOW()作为基准时间再计算任务的时间窗口。这样即使服务器时钟有问题至少任务逻辑不会乱。时间基准统一这件事看起来是个小配置但真的能避免大量“隔一段时间抽风一次”的疑难 bug。我自己在维护多个项目的过程中遇到过无数看似莫名其妙的时间错乱最终定位下来基本都是时区参数不一致导致的。把这条规则写进团队规范比事后排查要划算太多。人在项目里待得越久越会发现 MySQL 的日期时间类型根本不是“会个NOW()就行”的事。字符集错了是乱码日期时间选错了是逻辑错误后者比前者更难发现。我这里总结的每一段经验几乎都是从真实的报警和加班里换来的。如果你读完对自己项目里的字段类型起了疑心建议立刻拉出所有建表语句检查一遍把DATETIME、TIMESTAMP、精度、默认值、时区参数这几项全部过一遍。改一条不规范的表远比上线之后处理一批错乱数据要轻松得多。
返回列表