php - 如何使用 convert_tz 或任何其他函数优化数据库时区
问题描述
我正在尝试在处理时区时优化我的 mySQL 查询。我的数据库(mySQL)设置为 EET 时间(+02:00)(我将很快转移到我将使用 UTC 的 AWS 上),但无论如何,我们的 Cakephp 实现具有将记录检索为 UTC 的设置。我们的timestamp
专栏是他的一个timestamp
类型。
因此,2019-12-19 12:44:27
在我们的 mySQL (+2) 中找到的 a 实际上是2019-12-19 10:44:27
CakePHP 实现中的 (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
谢谢
解决方案
MySQL 将
TIMESTAMP
值从当前时区转换为 UTC 进行存储,然后从 UTC 转换回当前时区进行检索。(这不会发生在其他类型,例如DATETIME
.)默认情况下,每个连接的当前时区是服务器的时间。可以基于每个连接设置时区。
因此,您实际上是在保存基于 UTC 的值。您可能认为您已经存储了 UTC+2 值,但这只是因为查询时的默认时区(会话时区)与服务器的默认时区相同。
此外,类似的功能NOW()
也使用会话时区。因此,由于双方的会话时区相同,因此无需进行时区转换。你可以说:
AND timestamp >= NOW()
这有额外的好处(正如symcbean 的回答所指出的),允许 DBMS 使用索引 - 换句话说,查询变得sargable。
您可能还想阅读 MySql 文档中的优秀文章Indexed Lookups from TIMESTAMP Columns,它解释了会话时区如何影响索引查询和非索引查询。
在查询时将时区显式设置为 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)
推荐阅读
- python - Python:如果 XML 标记不存在,我需要打印“空白”以及输出
- swift - 如何计算保存的对象数量
- git - 如何通过最新推送找到特定文件名的用户的电子邮件 ID?
- php - 如何使用 HTAccess 重写某些文件夹,但不是全部?
- javascript - Chrome Web 检查器打开时双击不起作用
- html - 样式化除了在 CSS 中选择的所有类实例
- acumatica - Acumatica Excel 查询 SSO
- performance - 如何在 cAdvisor 中将 CPU 负载聚合减少到 1 秒而不是 10 秒?
- excel - 自动更新等级功能的范围
- mongodb - 使用位置运算符更新嵌套数组对象