簡體   English   中英

如何使用 convert_tz 或任何其他函數優化數據庫時區

[英]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)

對於您的問題,我有兩種解決方案,希望其中一種對您有用

解決方案1

在 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

解決方案2

您只能為當前會話設置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 )。 但它需要在后台(或前台,如果你不想更臟的話……)進行一些計算。

這里的骯臟技巧是您只會對結果進行時間轉換,而不是對數據庫中存在的所有數據進行時間轉換。


首先,您必須計算(並保持)之間的最大時間間隔:

  • 公司時區
  • cakePHP 服務器時區
  • 數據庫服務器時區

  • UTC 時區。

在您的情況下,它可能是: 4H

然后

1-將 4H 添加到您的目標end_date ,然后將此日期轉換為end_timestamp ,保留您的start_date - 不做任何修改 - 並將此日期轉換為start_timestamp

2- 使用start_timestampend_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM