定时执行相应的sql任务,是工作中常遇到的场景。通常用在统计相关的任务,这些sql语句中,通常都含有相对的时间函数,如now()等, 如:
select count(*) from tb_cinfo where create_time>=UNIX_TIMESTAMP(DATE_FORMAT(date_sub(now(), interval 1 HOUR), '%Y-%m-%d %H')) and create_time<UNIX_TIMESTAMP(DATE_FORMAT(now(), '%Y-%m-%d %H'))
上面这个sql语句的的结果为 上一个小时内新增的数据量,但如该sql语句18点执行失败,19点进行手工重试时,就会有问题了,因为18点执行时统计的是17点的数据,而若19点进行重试,重新执行该语句,则统计的是18点的数据。
方案
自定义业务场景常用的时间函数,然后对伪sql解析为具体的可执行sql, 并将该可执行sql记录下来,之后重试则执行该记录下来的sql。
举例说明:
{@date}: 表示当前日期,e.g. 20200831, ,支持加减,如前一天:{@date-1} => 20200830,后一天:{@date+1} => 20200901
{@date_timestamp}: 表示当前时间戳, e.g. 1598864224, 支持加减,如前10分钟:{@date_timestamp-600},后1个小时:{@date_timestamp+3600}
{@date_minute_timestamp}: 表示当前分钟时间戳, 如2020-08-31 16:50:55按照2020-08-31 16:50:00,转换为1598863800, 同样支持加减,如前一分钟: {@date_minute_timestamp-1},后一分钟: {@date_minute_timestamp+1}
{@date_hour_timestamp}: 表示当前小时时间戳,同样支持加减,如前一小时: {@date_hour_timestamp-1},后一小时: {@date_hour_timestamp+1}
ps: 此处仅部分示例,可根据实际业务场景,自定义需要的时间函数
则之前的sql, 就可以改写为:
select count(*) from tb_cinfo where create_time>={@date_hour_timestamp-1} and create_time<{@date_hour_timestamp}
对该sql进行解析, 得到解析后的sql:
select count(*) from audit_cinfo where create_time>=1598860800 and create_time<1598864400
此时,若该sql执行失败,无论什么时间,都可以直接重试即可,因为该sql语句并不包含时间函数,而是具体的时间数值。
附sql解析语句示例(php版本):
/** * sql解析 * * @param $sql * @return mixed */ public function parseQuery($sql) { $sql = str_replace("\n", ' ', trim($sql, ';')); $sql = preg_replace("/\s(?=\s)/", "\\1", trim($sql)); preg_match_all('/\{.*?\}/', $sql, $matches); $matches = $matches[0]; $replace = []; foreach ($matches as $val) { $val = str_replace(' ', '', strtolower($val)); preg_match('/@\w+[+|\-|\}]/', $val, $match1); preg_match('/[+|\-]\d+\}/', $val, $match2); $match1 = empty($match1[0]) ? '' : substr($match1[0], 1, -1); $func = empty($match2[0]) ? '' : substr($match2[0], 0, 1); $match2 = empty($match2[0]) ? '' : substr($match2[0], 1, -1); $currTime = time(); $res = $match1; switch ($match1) { case 'date': $res = $this->calculation(strtotime(date("Y-m-d 00:00:00", $currTime)), $func, $match2, 86400); $res = date("Ymd", $res); break; case 'date_timestamp': $res = $this->calculation($currTime, $func, $match2, 1); break; case 'date_minute_timestamp': $res = $this->calculation(strtotime(date("Y-m-d H:i:00", $currTime)), $func, $match2, 60); $res = strtotime(date("Y-m-d H:i:00", $res)); break; case 'date_hour_timestamp': $res = $this->calculation(strtotime(date("Y-m-d H:00:00", $currTime)), $func, $match2, 3600); $res = strtotime(date("Y-m-d H:00:00", $res)); break; default: break; } $replace[] = $res; } return str_replace($matches, $replace, $sql); } private function calculation($base, $func, $param, $step = 1) { $res = $base; if (empty($func) || empty($param) || empty($step)) { return $res; } switch ($func) { case '+': $res = $base + $param * $step; break; case '-': $res = $base - $param * $step; break; default: break; } return $res; }