[英]How to optimize database timezones with convert_tz or any other function
我试图在处理时区时优化我的 mySQL 查询。 我的数据库 (mySQL) 设置为 EET 时间 (+02:00)(我很快将转向使用 UTC 的 AWS),但无论如何,我们的 Cakephp 实现有一个设置,可以将记录检索为 UTC。 我们的timestamp
列是一个timestamp
类型。
因此,在我们的 mySQL (+2) 中发现的2019-12-19 12:44:27
实际上是 CakePHP 实现中的2019-12-19 10:44:27
(UTC)。
问题是我需要根据公司的时区而不是根据服务器/数据库显示日期范围之间的行,例如今天的结果。
考虑到+04:00
时区,我创建了以下查询。
$company_timezone ='+04:00';
SELECT company_id, COUNT( timestamp ) AS views, url
FROM behaviour
WHERE company_id = 1
AND CONVERT_TZ(timestamp,'+00:00','{$company_timezone}') >= DATE(CONVERT_TZ(NOW(),'+00:00','{$company_timezone}'))
GROUP BY URL
ORDER BY views
DESC LIMIT 20
然而,这在性能方面是非常需要的。 大约需要 4-5 秒。 如果没有convert_tz
它需要的时间不超过 0.5 秒。
我的问题是如何优化它? 当然,我们的timestamp
列被索引,即使它在特定查询中没有任何意义,因为我将它与convert_tz
一起使用。
处理日期范围的新查询(今天 - 昨天)
我相信下面的例子更强调我的情况:通过这个查询,我根据公司的时区显示结果。 例如,如果他们公司的当地时间是 00:01,那么这对他们来说是新的一天,无论服务器/mysql 的时间如何。
SELECT COUNT(hash) as how_many
FROM table
WHERE company_id = 1
AND CONVERT_TZ(last_visit,'+00:00','{$company_timezone}') >= DATE(CONVERT_TZ(NOW(),'+00:00','{$company_timezone}') - INTERVAL 1 DAY)
GROUP BY date(last_visit)
ORDER BY last_visit DESC
谢谢
从关于TIMESTAMP
类型的 MySQL文档中:
MySQL 将
TIMESTAMP
值从当前时区转换为 UTC 进行存储,然后从 UTC 转换回当前时区以进行检索。 (这不会发生在其他类型,例如DATETIME
。)默认情况下,每个连接的当前时区是服务器的时间。 可以在每个连接的基础上设置时区。
因此,您实际上是在保存基于 UTC 的值。 您可能认为您已经存储了 UTC+2 值,但这只是因为查询时的默认时区(会话时区)与服务器的默认时区相同。
此外,像NOW()
这样的函数也使用会话时区。 因此,由于会话时区在双方相同,因此无需进行时区转换。 你只能说:
AND timestamp >= NOW()
这有一个额外的好处(正如symcbean 的回答所指出的那样),允许 DBMS 使用索引 - 换句话说,查询变得sargable 。
您可能还想阅读 MySql 文档中的优秀文章, 来自 TIMESTAMP 列的索引查找,它解释了会话时区如何影响索引查询和非索引查询。
在查询时将时区显式设置为 UTC 也可能是值得的:
SET time_zone = 'UTC';
无论哪种方式,您都将获得相同的结果,但由于 DBMS 现在要执行的时区转换更少,因此效率更高。
对于编辑中的第二个查询,您仍然在表达式的左侧显示转换。 同样,您希望它只是字段,以便可以使用索引。 由于该字段是TIMESTAMP
类型,因此您只需计算要检索的最早时间戳。
如果您想内联您的查询,转换次数会使其有点复杂:
SELECT COUNT(hash) as how_many
FROM table
WHERE company_id = 1
AND last_visit >= CONVERT_TZ(DATE(CONVERT_TZ(NOW(), @@session.time_zone, '{$company_timezone}')) - INTERVAL 1 DAY, '{$company_timezone}', @@session.time_zone)
GROUP BY DATE(CONVERT_TZ(last_visit, @@session.time_zone, '{$company_timezone}'))
ORDER BY DATE(CONVERT_TZ(last_visit, @@session.time_zone, '{$company_timezone}'))
出于这个原因,在查询之前简单地更改会话时区要容易得多,以便它统一应用于所有操作:
SET time_zone = '{$company_timezone}';
SELECT COUNT(hash) as how_many
FROM table
WHERE company_id = 1
AND last_visit >= DATE(NOW()) - INTERVAL 1 DAY
GROUP BY DATE(last_visit)
ORDER BY DATE(last_visit)
对于您的问题,我有两种解决方案,希望其中一种对您有用
在 cakePHP 中更改 datetime 的 timeZone 然后执行查询。
public static function convertDate($datetime, $companyTimeZone, $dbTimeZone)
{
$newDate = new DateTime($datetime, new DateTimeZone($companyTimeZone));
$newDate->setTimezone(new DateTimeZone($dbTimeZone));
return $newDate->format('Y-m-d H:i:s');
}
$datetime = self::convertDate($datetime, $companyTimeZone, $dbTimeZone);
SELECT company_id, COUNT( timestamp ) AS views, url
FROM behaviour
WHERE company_id = 1
AND timestamp >= $datetime
GROUP BY URL
ORDER BY views
DESC LIMIT 20
您只能为当前会话设置mysql的时区
##$company_timezone ='+04:00';
#NOTE this will set timZone only for the current session
SET time_zone= $company_timezone;
SELECT company_id, COUNT( timestamp ) AS views, url
FROM behaviour
WHERE company_id = 1
AND timestamp >= NOW()
GROUP BY URL
ORDER BY views
DESC LIMIT 20
注意:我的应用程序的数据库是 UTC 并且用户来自世界各地,所以我已经实施了解决方案 1,并且它自 2 年以来一直运行良好。
您正在对谓词的两侧应用相同的转换 - 所有这些都使 DBMS 无法使用索引。 直接比较值:
AND timestamp >= NOW()
如果您将一个时区中的数据与不同时区中的挂钟进行比较,这会变得有点复杂 - 但您只需要确保仅将转换应用于仅包含文字而不是属性引用的谓词一侧.
我建议将时间戳转换为(纪元转换)长值并将其保存到临时列,然后将长值转换为时区特定的时间戳。
我会建议你一个真正的 DIRTY 方法!
这种方法不会改变你的数据库,并且会避免对数据库进行任何大的计算(不会使用convert_tz
)。 但它需要在后台(或前台,如果你不想更脏的话……)进行一些计算。
这里的肮脏技巧是您只会对结果进行时间转换,而不是对数据库中存在的所有数据进行时间转换。
首先,您必须计算(并保持)之间的最大时间间隔:
和
在您的情况下,它可能是: 4H 。
然后
1-将 4H 添加到您的目标end_date
,然后将此日期转换为end_timestamp
,保留您的start_date
- 不做任何修改 - 并将此日期转换为start_timestamp
。
2- 使用start_timestamp
和end_timestamp
查询。 [这避免了查询中的任何时区和时间转换]
请注意,您将在数据库查询结果中获得太多数据(您将获得更多的“垃圾”数据,尤其是在start_timestamp
附近)...
2- 在您的后台:过滤坏元素(这意味着您必须删除前一个start_timestamp
和同一个start_timestamp
+4H之间的所有数据)以删除所有垃圾数据。
3- 在您的后台:最后将剩余的结果转换为合适的时区(在您的情况下为 UTC+4)
4- 将数据发送到前台。
由于该列已经是timezone
类型,因此您不需要使用convert_tz
。 MySQL 将始终使用当前设置的时区(这是与列类型datetime
的区别之一)。
因此,您可以预先设置时区,然后在没有convert_tz
情况下进行查询。 应该使用所有索引。
SET timezone = '{$company_timezone}';
SELECT COUNT(hash) as how_many
FROM table
WHERE company_id = 1
AND last_visit >= DATE(NOW() - INTERVAL 1 DAY)
GROUP BY DATE(last_visit);
ORDER BY last_visit DESC
您可以直接使用正确的时区,例如“美国/东部”。 有了这个,您将轻松应对夏季和冬季。 以下是设置方法: https : //dev.mysql.com/doc/refman/8.0/en/time-zone-support.html
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.