MySQL 触发器使用不同的 TIMEZONE
Posted
技术标签:
【中文标题】MySQL 触发器使用不同的 TIMEZONE【英文标题】:MySQL Triggers use different TIMEZONE 【发布时间】:2019-06-19 05:02:53 【问题描述】:我创建了一个触发器来为购买设置一个 uniq 名称 (ref),使用时区作为 create_date 字段。 它工作正常,直到我注意到重复的引用,其中一个引用的引用中的日期与 create_date 字段中的日期不匹配(差异的 1 天)?!!
BEGIN
SET NEW.reference := concat(
(SELECT name FROM provider WHERE id = NEW.provider_id),
date_format(NEW.create_date, '%Y%m%d'), '/',
(SELECT LPAD(IFNULL(MAX(SUBSTRING_INDEX(reference, '/', -1)) + 1, 0), 3, '0')
FROM purchase
WHERE date_format(NEW.create_date, '%Y%m%d') = date_format(create_date, '%Y%m%d')
AND NEW.provider_id = provider_id
)
);
END
有人知道发生了什么或更好的方法吗?
PS:create_date 字段中的日期是正确的,ref 中使用的 NEW.create_date 中的日期是错误的(可能是客户端时区的 b/c,因为我们将其作为字符串发送)
更新
表格结构:
购买:
CREATE TABLE purchase
(
id int auto_increment primary key,
provider_id int not null,
create_date timestamp null,
create_user int null,
change_date timestamp null,
change_user int null,
group_id int null,
reference varchar(45) null
);
CREATE INDEX purchase_reference_index ON purchase (reference);
CREATE INDEX purchase_provider_index ON purchase (provider_id);
提供者:
CREATE TABLE provider
(
id int auto_increment primary key,
name varchar(45) null,
constraint name_uniq unique (name)
);
查询示例:
INSERT INTO purchase (provider_id, create_date, create_user, group_id)
VALUE (4, '2019-01-30 02:36:58', 1, 3);
当我从使用服务器时区的会话中选择2019-01-29 23:36:58
时,2019-01-30 02:36:58
在数据库中保存为2019-01-29 23:36:58
。
我用来设置时区的函数:
function update_timezone($timezone = null)
if (is_null($timezone)) $timezone = __SERVER_TIMEZONE;
if (in_array($timezone, timezone_identifiers_list()))
date_default_timezone_set($timezone);
$tz = (new DateTime('now', new DateTimeZone(date_default_timezone_get())))->format('P');
$conn = Database::Connect();
Database::NonQuery("SET time_zone = '$tz';", $conn);
我的期望:
reference === 'provider_name20190129/00X'
我得到了什么:
reference === 'provider_name20190130/00Y'
如何重现问题:
CREATE DATABASE test;
CREATE TABLE purchase
(
id int auto_increment primary key,
provider_id int not null,
create_date timestamp null,
create_user int null,
change_date timestamp null,
change_user int null,
group_id int null,
reference varchar(45) null
);
CREATE INDEX purchase_reference_index ON purchase (reference);
CREATE INDEX purchase_provider_index ON purchase (provider_id);
CREATE TABLE provider
(
id int auto_increment primary key,
name varchar(45) null,
constraint name_uniq unique (name)
);
CREATE TRIGGER test.purchase_ref_insert
BEFORE INSERT
ON test.purchase
FOR EACH ROW
BEGIN
SET NEW.reference := concat(
(SELECT name FROM provider WHERE id = NEW.provider_id),
date_format(NEW.create_date, '%Y%m%d'), '/',
(SELECT LPAD(IFNULL(MAX(SUBSTRING_INDEX(reference, '/', -1)) + 1, 0), 3, '0')
FROM purchase
WHERE date_format(NEW.create_date, '%Y%m%d') = date_format(create_date, '%Y%m%d')
AND NEW.provider_id = provider_id
)
);
END
;
INSERT INTO provider (name) VALUE ('test');
SET time_zone = '+00:00';
INSERT INTO purchase (provider_id, create_date, create_user, group_id)
VALUE (1, '2019-01-30 02:36:58', 1, 3);
SET time_zone = '+05:00';
INSERT INTO purchase (provider_id, create_date, create_user, group_id)
VALUE (1, '2019-01-30 02:36:58', 1, 3);
SET time_zone = '-05:00';
INSERT INTO purchase (provider_id, create_date, create_user, group_id)
VALUE (1, '2019-01-30 02:36:58', 1, 3);
SET time_zone = '+00:00';
SELECT create_date, reference FROM purchase;
这是我得到的:
【问题讨论】:
表结构和数据在这里会有所帮助 考虑使用TIMESTAMP
而不是DATETIME
。并确保每台计算机都设置了适当的时区。
@RaymondNijland 我更新了问题,请再看看
@RickJames 我正在使用 DATATIME 并切换到 TIMESTAMP(当客户想要使用不同的时区时,我的问题就开始了)
【参考方案1】:
您可以在触发器中使用@@session.time_zone
将给定的时间戳CONVERT_TZ 转换为SYSTEM time_zone,然后再与表进行比较。
CREATE TRIGGER purchase_ref_insert
BEFORE INSERT ON purchase
FOR EACH ROW
BEGIN
SET @ts := CONVERT_TZ(NEW.create_date, @@session.time_zone, 'SYSTEM');
SET @providerName := (SELECT name FROM provider WHERE id = NEW.provider_id);
SET @refPostfix := (
SELECT
LPAD(IFNULL(MAX(SUBSTRING_INDEX(reference, '/', -1)) + 1, 0), 3, '0')
FROM purchase
WHERE NEW.provider_id = provider_id
AND CAST(CONVERT_TZ(create_date, @@session.time_zone, 'SYSTEM') AS DATE) = CAST(@ts AS DATE)
);
SET NEW.create_date_tz = @@session.time_zone;
SET NEW.reference := CONCAT(@providerName, DATE_FORMAT(@ts, '%Y%m%d'), '/', @refPostfix);
END;
您可以在表格中添加一个额外的列。 一个将包含插入记录时使用的会话时区。
create_date_tz varchar(6) not null default 'SYSTEM'
这样,您仍然可以在客户的时区中显示 create_date。
dbfiddle here
的测试例如:
SELECT
id, create_date, reference, create_date_tz,
CONVERT_TZ(create_date, 'SYSTEM', create_date_tz) as ts_at_TZ
FROM purchase;
返回:
id | create_date | reference | create_date_tz | ts_at_TZ
1 | 2019-01-30 07:30:01 | test20190130/000 | -05:00 | 2019-01-30 02:30:01
2 | 2019-01-30 02:30:02 | test20190130/001 | +00:00 | 2019-01-30 02:30:02
3 | 2019-01-29 21:30:03 | test20190129/000 | +05:00 | 2019-01-30 02:30:03
【讨论】:
使用CONVERT_TZ
似乎是一个干净的方法,您还移动了provider_id
条件以便更好地使用primary key
:) 只是为了让您知道,我们需要将create_date
转换为@ 987654322@ 也是为了避免重复条目。
我们真的不需要create_date_tz
,mysql将所有时区日期转换为UTC,当客户要求时,他使用会话时区将其转换回来
好吧,create_date_tz 更像是一个建议。它并不是真正需要的,但是当您想根据客户的当地时间进行查询时,它可以为您提供帮助。但我不明白您所说的“我们需要转换 create_date”是什么意思,因为 @ts
变量是 NEW.create_date,就在服务器的时区中。
我附上了 db<>fiddle 测试,看看,当添加另一个 +05:00 +10:00 测试时,我们得到了重复的 ref /000
为了防止这种情况发生,我使用了CAST(CONVERT_TZ(create_date, @@session.time_zone, 'SYSTEM') AS DATE) = CAST(@ts AS DATE)
【参考方案2】:
我找到了解决方案:D
我只需要在触发器SET time_zone = 'SYSTEM';
中设置时区
date_format 不遵守本地化时区
CREATE TRIGGER test.purchase_ref_insert
BEFORE INSERT
ON test.purchase
FOR EACH ROW
BEGIN
SET @time_zone_tmp := @@time_zone;
SET time_zone = 'SYSTEM';
SET NEW.reference := concat(
(SELECT name FROM provider WHERE id = NEW.provider_id),
date_format(NEW.create_date, '%Y%m%d'), '/',
(SELECT LPAD(IFNULL(MAX(SUBSTRING_INDEX(reference, '/', -1)) + 1, 0), 3, '0')
FROM purchase
WHERE date_format(NEW.create_date, '%Y%m%d') = date_format(create_date, '%Y%m%d')
AND NEW.provider_id = provider_id
)
);
SET time_zone = @time_zone_tmp;
END
;
【讨论】:
以上是关于MySQL 触发器使用不同的 TIMEZONE的主要内容,如果未能解决你的问题,请参考以下文章