关于mysql坑爹的timestamp

本文详细探讨了MySQL中datetime和timestamp字段在不同时区和Java、JDBC、Python环境下存取的时间行为差异,揭示了实际应用中的时间存储策略建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大家都听说过mysql的datetime有时区问题,timestamp没有时区问题,但是我在公司码坑的时候却发现并不是这句话这么简单。比如每次写入的timestamp的字段总是存的serverTimezone时区的时间。所以准备彻底试一下。

1.mysql的官方解释

2. 开始测试

2.1 表结构

CREATE TABLE `study_test`.`Untitled`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `descript` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `datetime_field` datetime(0) NULL DEFAULT NULL,
  `timestamp_field` timestamp(0) NULL DEFAULT NULL,
  -- 用个长整型时间戳精确存储的时间
  `number_timestamp` bigint(20) NULL DEFAULT NULL,
  -- 虚拟列,用来展示长整型时间戳
  `number_timestamp_view` datetime(0) GENERATED ALWAYS AS (date_format(from_unixtime((`number_timestamp` / 1000)),'%Y-%m-%d %H:%i:%S')) VIRTUAL NULL,
  PRIMARY KEY (`id`) USING BTREE
) ;

数据库时区:
在这里插入图片描述

2.2 纯mysql的存取

-- 在0区状态下写入0点
set time_zone = '+0:00';
insert into time_test(descript, datetime_field,timestamp_field,number_timestamp) 
values ('mysql原生写入-0时区','2022/11/11 00:00:00','2022/11/11 00:00:00',1668124800000);

-- 在8区状态下写入8点
set time_zone = '+8:00';
insert into time_test(descript, datetime_field,timestamp_field,number_timestamp) 
values ('mysql原生写入-8时区','2022/11/11 08:00:00','2022/11/11 08:00:00',1668124800000);

康康结果,符合mysql官方的描述,datetime不会根据连接的时区切换而切换,timestamp则会计算
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 java jpa的写入

因为公司使用jpa,所以优先测试jpa,随便搭个小的测试jpa项目

// entity
@Getter
@Setter
@Entity
@Table(name = "time_test")
public class TimeTest {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "descript")
    private String descript;

    @Column(name = "datetime_field")
    private Date datetimeField;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "timestamp_field")
    private Date timestampField;

    @Column(name = "number_timestamp")
    private Long numberTimestamp;

    public Date getNumberTimestampView() {
        return numberTimestamp == null ? null : new Date(numberTimestamp);
    }
}
// repository
@Repository
public interface TimeTestRepository extends JpaRepository<TimeTest, Long>, JpaSpecificationExecutor<TimeTest> {
}
// test
@SpringBootTest(classes = {JpaStudyApplication.class})
class TimeTestRepositoryTest {
    @Autowired
    private TimeTestRepository timeTestRepository;
    @Autowired
    private DataSource dataSource;
	/**
	 * 修改java的连接时区时,修改serverTimezone属性
	 */
    @Test
    @Rollback(false)
    void testSaveZone() {
        Date dateTime = new Date(1668124800000L);
        TimeTest timeTest = new TimeTest();
        timeTest.setDescript("java jpa写入-0区连接-0区数据库");
        timeTest.setDatetimeField(dateTime);
        timeTest.setTimestampField(dateTime);
        timeTest.setNumberTimestamp(dateTime.getTime());
        timeTestRepository.save(timeTest);
    }
}

写入的结果,当serverTimezone=Asia/Shanghai的时候,timestamp写入了一个错误的值:
在这里插入图片描述
康康java的数据库链接time_zone

	@Test
    void testJdbcTemplate() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("show variables like '%time_zone%'");
        for (Map<String, Object> map : maps) {
            System.out.println(map);
        }
    }

结果是,没有变化:

{Variable_name=system_time_zone, Value=}
{Variable_name=time_zone, Value=+00:00}

2.4 jdbc的写入

@Test
    @Rollback(false)
    void testJdbcInsert() throws Exception {
        long stamp = 1668124800000L;
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(
                "insert into time_test(descript, datetime_field,timestamp_field,number_timestamp) " +
                        "values (?,?,?,?)");
        statement.setString(1, "java jdbc写入-0区连接-0区数据库");
        statement.setDate(2, new java.sql.Date(stamp));
        statement.setDate(3, new java.sql.Date(stamp));
        statement.setLong(4, stamp);
        statement.execute();
    }

似乎timestamp和datetime的表现都一样
在这里插入图片描述

2.5 试试pymysql

因为公司的python服务也比较多,所以也测一下

import pymysql
import datetime

with pymysql.connect(user='root', password='123456', host='localhost', database='study_test', port=3307,
                     cursorclass=pymysql.cursors.DictCursor) as conn:
    with conn.cursor() as cursor:
        # 设置连接时区
        cursor.execute("set time_zone='+0:00'")
        timestamp = 1668124800000
        dt = datetime.datetime.fromtimestamp(timestamp / 1000)
        cursor.execute("insert into time_test(descript, datetime_field,timestamp_field,number_timestamp) "
                       "values (%(descript)s,%(datetime_field)s,%(timestamp_field)s,%(number_timestamp)s);",
                       dict(descript='python pymysql写入-0区连接-0区数据库', datetime_field=dt, timestamp_field=dt,
                            number_timestamp=timestamp))
    conn.commit()

python 比较直接,把当地时间字符串直接发送给了mysql程序,然后mysql程序根据时区配置计算时间,并存储起来
在这里插入图片描述

3.原理分析

// todo

4.总结

如果项目有跨时区以及跨语言应用,一定要用bigint作为时间字段。
如果只是java,用datetime和timestamp区别不大,java存取没有什么问题。

### 如何在MySQL中存储Timestamp类型的数据 在MySQL中,`TIMESTAMP` 数据类型用于表示日期和时间。当创建表时可以定义 `TIMESTAMP` 列来自动记录行的插入时间和更新时间。 #### 创建带有 Timestamp 的表格 为了确保每次插入新纪录时都能正确设置当前的时间戳,在创建表结构的时候应该指定默认值为 `CURRENT_TIMESTAMP`: ```sql CREATE TABLE example ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` 如果还需要追踪最后一次修改的时间,则可以在同一张表里增加另一个字段并为其设定 `ON UPDATE CURRENT_TIMESTAMP` 属性[^1]。 然而有时候可能不希望每当记录被更改时都触发这个行为,这时可以通过移除或调整该属性实现目的。例如要删除现有的 on update 设置可如下操作: ```sql ALTER TABLE example MODIFY COLUMN updated_at TIMESTAMP; ``` 这会将原有的具有 ON UPDATE 特性的 timestamp 字段转换成普通的 timestamp 类型而不再随数据变动自动生成新的时间戳。 对于那些已经存在的含有此特性的列来说,也可以通过更改为其他形式如 DATETIME 来达到相同的效果[^3]。 另外一种方法是在应用层面上处理时间戳而不是依赖数据库层面的功能;比如使用整数保存 Unix 时间戳再利用函数将其转回标准格式显示给用户[^2]: ```sql SELECT FROM_UNIXTIME(unix_timestamp_column) AS formatted_date FROM table_name; ``` 以上就是关于如何在 MySQL 中管理和储存 timestamp 类型的一些基本概念和技术细节。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值