首页 > 数据库 >MySQL timestamp(3)问题

MySQL timestamp(3)问题

时间:2022-10-30 10:03:11浏览次数:68  
标签:execlog end timestamp SQL 问题 耗时 MySQL date


背景

最近在负责开发维护的一款数据平台,有一个功能是把数据从某个源头数据源(如常规的JDBC数据源,MySQL,Oracle等)推到目地数据源(还包括企微,MQ等)。一次推送数据就是一个任务,当然需要记录此次推送任务的执行情况,如任务的开始时间,结束时间,任务名称,任务执行状态,任务日志(失败原因),执行人,执行方式(手动执行还是定时触发)。另外,从来源数据源取数,怎么取数是通过SQL指定的,那我们还可以记录一下SQL查询耗时,以及SQL查询条数。总耗时就是任务执行结束时间减去任务开始时间,总耗时肯定大于SQL查询耗时。

统计SQL查询耗时时,还需要考虑到SQL取数的数据量,假如SQL查询量为1000w,程序不可能一次查询全部数据,然后加工处理并发送到下游目地数据源,故而需要设置分批。假设批次为50w,则SQL查询耗时为20次查询的耗时之和。

扯远一句,项目是接手维护的,一开始的设计开发者是用秒来记录SQL查询耗时,这样一看就不严谨,因为很多SQL查询耗时根本不需要1秒。事实上就算查询耗时超过1s,1.6s和1.7s也是有区别的,故而需要带毫秒来优化记录SQL查询耗时。最后在前端展示时,用小数点来表示毫秒。

很常规,看起来也没有任何问题的数据表设计:

create table execlog (
id bigint(11) auto_increment primary key,
total_sql_time bigint null comment 'SQL执行耗时,单位豪秒',
start_date timestamp default CURRENT_TIMESTAMP not null comment '执行开始时间',
end_date timestamp null comment '执行结束时间',
);

注:​​total_sql_time​​注释单位毫秒,以及程序记录单位是后来优化调整的。

某次任务执行记录截图如下,发现总耗时为0秒,而查询耗时为146毫秒。这显然不符合逻辑。

MySQL timestamp(3)问题_mysql

优化

优化思路也不难,就是任务开始时间需要记录到毫秒级别,改进后的表结构为:

create table execlog (
id bigint(11) auto_increment primary key,
total_sql_time bigint null comment 'SQL执行耗时,单位豪秒',
start_date timestamp(3) default CURRENT_TIMESTAMP(3) not null comment '执行开始时间',
end_date timestamp(3) null comment '执行结束时间',
);

值得注意的是,MySQL直到版本5.6(不太确定)才支持,如何知道自己使用的MySQL Server版本是否支持​​timestamp(3)​​​,执行语句即可验证,没有报错并且返回毫秒数表示支持:​​select now(3);​

优化上面截图中的日志记录问题分为两个步骤,即日志记录和日志显示。

日志记录

增加一个取当时时间精确到毫秒的静态方法:

public static final String COMMON_DATE_WITH_MILLI_SECOND = "yyyy-MM-dd HH:mm:ss:sss";

public static String getNowWithMilliSecond() {
SimpleDateFormat sdf = new SimpleDateFormat(Constant.COMMON_DATE_WITH_MILLI_SECOND);
return sdf.format(new Date());
}

表结构如上改进后,发现​​start_date​​​记录没有问题,带3位小数点。但​​end_date​​记录不到小数点,即未记录到毫秒,于是怀疑表结构不对。

某次任务执行肯定会有​​start_date​​​,故而设置为​​not null​​​,但会因发布,或调试中断等各种原因导致记录不到​​end_date​​​时间点,因此字段不能设置为​​not null​​。

落不到数据,怀疑需要给一个默认值,于是如下改表结构

create table execlog (
id bigint(11) auto_increment primary key,
start_date timestamp(3) default CURRENT_TIMESTAMP(3) not null comment '执行开始时间',
end_date timestamp(3) default CURRENT_TIMESTAMP(3) null comment '执行结束时间'
);

但是还是记录不到结束时间的毫秒数。

此时只能好好看代码,​​end_date​​​是在任务结束(不管是正常结束还是异常结束,异常结束在finally语句块)时​​updateExecLog​​更新:

<update id="updateExecLog" parameterType="com.xy.cloudiview.common.po.ExecLog">
UPDATE execlog t
<set>
<if test="errorLog != null">
t.error_log = #{errorLog},
</if>
t.end_date = now(),
</set>
WHERE t.id = #{id}
</update>

此处已经指定​​end_date​​​取数为​​now()​​​,肯定不会记录到毫秒数的。需改成​​t.end_date = now(3),​​。

再看另外一个​​updateExecLog​​更新语句:

<update id="updateByPrimaryKeySelective" parameterType="com.xy.cloudiview.common.po.ExecLogWithBlobs">
update execlog
<set>
<if test="endDate != null">
end_date = #{endDate,jdbcType=TIMESTAMP},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>

貌似找到一点问题解决思路,开启MySQL日志打印功能:

mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

一个很无厘头的尝试:

<if test="endDate != null">
end_date = #{endDate,jdbcType=TIMESTAMP(3)},
</if>

报错:

MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error resolving JdbcType. Cause: java.lang.IllegalArgumentException: No enum constant org.apache.ibatis.type.JdbcType.TIMESTAMP(3)
at java.lang.Enum.valueOf(Enum.java:238)

那就把​​jdbcType​​去掉:

<if test="endDate != null">
end_date = #{endDate},
</if>

打印日志如下:

JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7ef24deb] will not be managed by Spring
==> Preparing: update execlog SET end_date = ? where id = ?
==> Parameters: 2022-10-12 17:08:13:013(String), 31542157(Long)
nested exception is com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Incorrect datetime value: '2022-10-12 16:05:47:047' for column 'end_date' at row 1

也就是说,当MyBatis遇到MySQL ​​timestamp(3)​​时,MyBatis上面这种写法支持不了。

于是只能换一种写法:

<update id="updateByPrimaryKeySelective" parameterType="com.xy.cloudiview.common.po.ExecLogWithBlobs">
update execlog
<set>
<if test="modelId != null">
model_id = #{modelId,jdbcType=BIGINT},
</if>
end_date = now(3),
</set>
where id = #{id,jdbcType=BIGINT}
</update>

解决问题。也不需要在Java代码层设置带毫秒数的当前时间。

此时,回头看看第一个截图,里面有一个日志类型的字段,文章开头没有描述。这里解释一下,因为平台有多种类型的任务,涉及多个maven Module模块和多个表,于是有多个execlog的insert和update语句。

再来看一个insertExecLog语句,省略无关字段,这种方法是MyBatis插件generator自动生成的,也没有任何问题,就是看起来非常冗余,两个​​<trim>​​​语句块,再加上每个​​<trim>​​​语句块里面每个字段都有​​<if>​​语句块条件判断语句:

<insert id="insertSelective" parameterType="com.xy.cloudiview.common.po.ExecLogWithBlobs">
<selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
insert into execlog
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="startDate != null">
start_date,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="startDate != null">
#{startDate,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>

维护项目的一个默认的潜规则,就是除非没有大的问题,尽可能不要大面积改动代码,大面积改动还不如重构,重构的前提是对项目非常了解。

当然这个地方只是一个MyBatis方法而已,还达不到重构那个深度。但是我也是想着尽可能不要改动太多,于是改动如下:

<insert id="insertSelective" parameterType="com.xy.cloudiview.common.po.ExecLogWithBlobs">
<selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
insert into execlog
<trim prefix="(" suffix=")" suffixOverrides=",">
start_date,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
now(),
</trim>
</insert>

报错信息就不贴出来了,原因还是当MyBatis遇到MySQL ​​timestamp(3)​​时,MyBatis上面这种写法不支持。

改动方法:

  1. 删除​​start_date​​​对应的​​if​​​判断语句块,让MySQL的​​default CURRENT_TIMESTAMP(3)​​来使其生效
  2. 使用如下方法:
<insert id="saveExecLog" parameterType="com.xy.cloudiview.common.po.ExecLogWithBlobs">
INSERT INTO execlog(start_date) VALUES (now(3))
</insert>

日志查询

改进前的执行日志列表页查询语句为:

select 
DATE_FORMAT(t.start_date,'%Y-%m-%d %H:%i:%s') as startDate,
TO_SECONDS(t.end_date)-TO_SECONDS(t.start_date) as totalTime
from execlog

改进后的:

select
SUBSTRING(DATE_FORMAT(t.end_date, '%Y-%m-%d %H:%i:%s.%f'), 1, 23) AS endDate,
TIMESTAMPDIFF(MICROSECOND, t.start_date, t.end_date) / (1000 * 1000) AS totalTime
from execlog

最后实现的效果是:

MySQL timestamp(3)问题_sql查询_02

参考


标签:execlog,end,timestamp,SQL,问题,耗时,MySQL,date
From: https://blog.51cto.com/u_15851118/5807200

相关文章

  • FastJson序列化和反序列化问题记录
    序列化与反序列化问题调用外部接口,http://100.111.55.67:9999/cmdb/v0.2.0/departments?page_size=1000,返回数据格式如下:{"code":0,"content":[{......
  • SQL Server推送数据到MQ字段丢失的生产问题排查
    背景从SQLServer推数据到MQ。SQL查询语句(省略若干查询字段):selectCallStartDt,SeqNum,CallId,Service_Id,UMIDfromCallDetailwhereCallInsertDt>=CONVERT(varchar(10),......
  • 记一次MyBatis驼峰与下划线转化问题
    最近在做一个项目优化与重构,测试同学在回归功能测试时,指出一个页面功能有Bug,并扔出日志:NPE嘛,这还不简单,根据日志记录的行数,定位到代码行数,都几乎不需要执行接口测试单步调......
  • MySQL(零)
    MySQL前置1.MySQL介绍MySQL是一个开放源代码的关系型数据库管理系统.MySQL支持大型的数据库,可以处理拥有上千万条记录的大型数据库.MySQL使用标准的sql数据语言形式.......
  • 解决windows下环境变量太长而无法添加的问题
    (39条消息)解决windows下环境变量太大的问题(终极!!可用)_河流儿的博客-CSDN博客_环境变量太大笔者电脑上装了一大堆软件,有一大堆的环境变量需要配置,而且有些软件,笔者也喜欢......
  • 解决jupyter无法导入jieba,pyLDAvis,gensim包的问题(转载)
    一、无法导入jieba使用pipInstalljieba已经安装成功,piplist也可以看到jieba,但是在导入时却出错一、在官网下载jieba包官网地址:https://pypi.org/project/jieba/#file......
  • pip安装模块出现pip is configured with locations that require TLS/SSL问题
    pip安装模块出现pipisconfiguredwithlocationsthatrequireTLS/SSL问题原因有可能你的python解释器是编译安装的,因为编译安装比较纯净,在安装的时候没有指定ssl这个......
  • Python 多重继承时metaclass conflict问题解决与原理探究
    背景最近有一个需求需要自定义一个多继承abc.ABC与django.contrib.admin.ModelAdmin两个父类的抽象子类,方便不同模块复用大部分代码,同时强制必须实现所有抽象方法,没想按想......
  • JavaWeb-MySQL基础
    JavaWeb-MySQL基础1,数据库相关概念1.1数据库存储和管理数据的仓库,数据是有组织的进行存储。数据库英文名是DataBase,简称DB。数据库就是将数据存储在硬盘上,可......
  • MySQL库表操作小结(未完成)
    MySQL库表操作小结(以下cmd都是以管理员身份运行):一、启动MySQL1、配置好环境的情况下:(注意此处的myslq80需要看自己电脑上的MySQL服务名称:可通过cmd中输入serv......