为什么数据库启动时,开始的几行错误日志时间戳是UTC时区,而非本地时间?

一、问题

数据库参数

log_timestamps=system

这个参数控制写入错误日志/普通日志/满日志的时区,而当设置为system时,日志应该为本地系统时区。

但是数据库启动时,前几行日志的时间可能跟系统时间不一致。

二、错误日志

2019-01-30T04:48:31.838813Z 0 [Warning] option 'table_open_cache_instances': unsigned value 128 adjusted to 64
2019-01-30T04:48:31.841289Z 0 [Warning] Could not increase number of max_open_files to more than 10240 (request: 65535)
2019-01-30T04:48:31.841326Z 0 [Warning] Changed limits: table_open_cache: 2615 (requested 4096)
2019-01-30T12:48:36.787386+08:00 0 [Warning] 'NO_AUTO_CREATE_USER' sql mode was not set.
2019-01-30T12:48:36.787555+08:00 0 [Warning] Insecure configuration for --secure-file-priv: Current value does not restrict location of generated files. Consider setting it to a valid, non-empty path.
2019-01-30T12:48:36.787698+08:00 0 [Note] /data/mysql-server/sql/mysqld (mysqld 5.7.18-debug-log) starting as process 22960 ...

可以看出前三行日志的时间是UTC时间,而非根据参数设定的那样,遵照系统时间。

这其实是因为数据库启动时,要进行配置读取,参数校验,而这个过程是区分先后顺序的。mysqld定义了一系列需要提前进行初始化和赋值的参数,见vector<my_option> all_early_options。比如说控制performance_schema是否开启的参数performance_schema

// sys_var.cc
static Sys_var_mybool Sys_pfs_enabled(
       "performance_schema",
       "Enable the performance schema.",
       READ_ONLY GLOBAL_VAR(pfs_param.m_enabled),
       CMD_LINE(OPT_ARG), DEFAULT(TRUE),
       PFS_TRAILING_PROPERTIES);

然而,控制日志文件中时间时区的参数,log_timestamps不属于提前初始化的参数,这就导致了,如果在初始化比它靠前的参数时,需要打印日志(比如说配置了不合理的参数,如上日志内容),则使用的是UTC时间,并且此时可能还没有初始化错误日志文件,只存放在缓存中,等到初始化错误日志后,再输出到文件。下面是日志组件初始化的过程。

mysqld 在日志组件初始化之前的日志

比如说下面这个warn日志,来看下它是怎么被记录的

2019-01-30T04:48:31.838813Z 0 [Warning] option 'table_open_cache_instances': unsigned value 128 adjusted to 64

代码如下

if (error_log_buffering)
  {
    // Logfile not open yet, buffer messages for now.
    if (buffered_messages == NULL)
      buffered_messages= new (std::nothrow) std::string();
    std::ostringstream s;
    s << my_timestamp << " " << thread_id;
    if (level == ERROR_LEVEL)
      s << " [ERROR] ";
    else if (level == WARNING_LEVEL)
      s << " [Warning] ";
    else
      s << " [Note] ";
    s << buffer << std::endl;
    buffered_messages->append(s.str());
  }

将这些内容存放在static std::string *buffered_messages中。
my_timestamp是调用函数make_iso8601_timestamp(my_timestamp)来获取的

// 判断log_timestamps设置
  if (opt_log_timestamps == 0)
    gmtime_r(&seconds, &my_tm);
  else
  {
    localtime_r(&seconds, &my_tm);

此时log_timestamps是没有通过配置文件赋值的,还是默认值,所以获得的timestamp值,是UTC时间,而非配置中指定的log_timestamps=system.

mysqld 日志组件初始化过程

main
    mysqld_main(int, char**)
        init_server_components()
            open_error_log(char const*)
                flush_error_log_messages()
               

函数flush_error_log_messages()如下,将上面说的缓存的日志进行落盘。

void flush_error_log_messages()
{
  if (buffered_messages && !buffered_messages->empty())
  {
    fprintf(stderr, "%s", buffered_messages->c_str());
    fflush(stderr);
    delete buffered_messages;
    buffered_messages= NULL;
  }
  error_log_buffering= false;
}

三、修复办法

可以见mysql bug界面 https://bugs.mysql.com/bug.php?id=94139
已经提交了相关patch给官方。涉及到sql/sys_vars.ccsql/sys_vars.h这两个文件。

<think>我们正在处理一个嵌入式系统中的时间设置问题。根据用户的问题,我们需要解决两个主要部分: 1. 如何获取本地时间戳(即当前的本地时间,以时间戳形式表示) 2. 如何根据时间戳修改系统时间 注意:嵌入式系统通常运行Linux,因此我们将基于Linux环境来回答。 参考引用: [^1]:提到了在FAT32文件系统中处理时间的问题,以及系统时间与硬件时间的关系。其中提到系统时间强制加上8小时(可能是为了处理时区问题),硬件时间保持不变。这提示我们在设置时间时需要注意时区转换,以及系统时间和硬件时间的区别。 [^2]:展示了一些设置和显示时间的命令选项,如`date`命令的用法,包括设置时间、使用UTC、输出格式等。这为我们提供了在命令行操作时间的参考。 [^3]:讨论了时区设置,特别是将`/usr/share/zoneinfo/Asia/Shanghai`文件重命名为`localtime`并放到`/etc`目录下。这提示我们设置时区的方法。 步骤: 1. **获取本地时间戳** 在Linux中,时间戳通常指的是自1970年1月1日00:00:00 UTC(即Epoch时间)以来经过的秒数(或毫秒、微秒)。这个时间戳本身是UTC时间,没有时区概念。 但是,用户要求的是“本地时间戳”,这通常是指将当前时间转换为本地时区时间表示。然而,时间戳本身通常都是UTC的,要得到本地时间,我们需要在显示时进行转换。 如果用户需要的是当前本地时间对应的字符串,我们可以通过以下步骤: a. 获取当前时间UTC时间戳) b. 根据本地时区设置,转换为本地时间 但是,如果用户要求的时间戳是指本地时间的秒数(即从Epoch开始本地时间的秒数),请注意,这种表示是不标准的。因为时间戳都是基于UTC的,本地时间只是UTC时间加上时区偏移后的结果。 因此,我们通常的做法是: - 获取当前的UTC时间戳(例如,使用`time`函数) - 如果需要本地时间的字符串表示,则使用`localtime`函数将时间戳转换为本地时间的`struct tm`,然后用`strftime`格式化输出。 2. **根据时间戳修改系统时间** 修改系统时间通常需要root权限。我们可以使用`settimeofday`或`clock_settime`函数来设置系统时间。 注意:系统时间通常以UTC时间保存。因此,如果我们有一个本地时间时间戳,我们需要先减去时区偏移,转换为UTC时间戳,然后设置系统时间。 但是,这里有一个关键点:时间戳本身是UTC的,所以如果我们有一个表示UTC时间时间戳,我们可以直接设置;如果我们有一个本地时间时间戳(即该时间戳是已经加上了时区偏移的),那么我们需要减去时区偏移才能得到UTC时间戳。 然而,通常我们获得的时间戳都是UTC的。例如,从网络时间协议(NTP)获得的时间戳就是UTC。 具体步骤: 一、获取本地时间时间戳(这里我们理解为获取当前时间戳,然后转换为本地时间表示) - 使用`time`函数获取当前UTC时间戳(秒数) - 使用`localtime`函数将UTC时间戳转换为本地时间的结构体`struct tm` - 如果需要,可以将`struct tm`转换为字符串显示(使用`strftime`)或者如果需要本地时间时间戳(注意:这不是标准做法,因为时间戳通常都是UTC的),我们可以通过计算本地时间UTC时间的偏移来调整,但一般不这样做。 二、根据时间戳设置系统时间 - 假设我们有一个表示UTC时间时间戳(秒数),我们可以使用`settimeofday`或`clock_settime`来设置系统时间。 - 步骤: 1. 将时间戳转换为`timeval`或`timespec`结构。 2. 调用设置函数。 注意:设置系统时间后,通常还需要同步到硬件时钟(RTC),这样系统重启后时间不会丢失。可以使用`hwclock`命令或相关的系统调用来完成。 另外,关于时区设置: 时区信息通常存储在`/etc/localtime`文件中,该文件是`/usr/share/zoneinfo/`下某个时区文件的链接(或复制)。例如,设置时区为上海: ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 代码示例: 1. 获取当前时间戳UTC)并转换为本地时间字符串 ```c #include <stdio.h> #include <time.h> int main() { time_t rawtime; // UTC时间戳(秒) struct tm *local_time; char buffer[80]; // 获取当前UTC时间戳 time(&rawtime); // 转换为本地时间 local_time = localtime(&rawtime); // 格式化为字符串 strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S %Z", local_time); printf("Local time: %s\n", buffer); return 0; } ``` 2. 根据时间戳设置系统时间(需要root权限) ```c #include <stdio.h> #include <sys/time.h> #include <time.h> int main() { // 假设我们有一个UTC时间戳(例如,从网络获取) time_t new_time = 1609459200; // 2021-01-01 00:00:00 UTC // 设置系统时间 struct timeval tv; tv.tv_sec = new_time; // 秒 tv.tv_usec = 0; // 微秒 if (settimeofday(&tv, NULL) == -1) { perror("settimeofday"); return 1; } printf("System time set successfully.\n"); // 注意:设置系统时间后,通常需要将系统时间同步到硬件时钟 // 可以使用命令:hwclock --systohc 或者通过system函数调用 // system("hwclock --systohc"); return 0; } ``` 关于时区设置(非代码,系统配置): 在嵌入式系统中,我们可以通过以下方式设置时区: ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 注意:在设置时区后,使用`localtime`函数转换时间戳时会自动应用时区偏移。 重要:在设置系统时间时,我们设置的是UTC时间。系统会根据时区设置(/etc/localtime)在显示时转换为本地时间。 总结: - 获取本地时间:先获取UTC时间戳,再用localtime转换(需要正确设置时区)。 - 设置系统时间:使用UTC时间戳设置(settimeofday),同时确保系统时区设置正确。 参考引用[^1]中提到的系统时间强制加上8小时(可能是为了处理时区)的做法,实际上并不推荐。正确做法是: - 系统内部使用UTC时间。 - 设置正确的时区,这样在显示时间时自动加上时区偏移。 这样,在文件系统(如FAT32)中记录的时间戳(通常是UTC时间)在Windows上显示时,Windows会根据自己的时区设置转换为本地时间,从而正确显示。 因此,在嵌入式系统中,我们应: 1. 设置系统时区(如Asia/Shanghai)。 2. 系统时间保持为UTC时间。 3. 当需要显示时间时,转换为本地时间。 4. 修改系统时间时,使用UTC时间戳。 这样,与其它设备(如AV1、AV3)的时间同步也可以使用UTC时间,避免时区转换带来的混乱。 §§相关问题§§ 1. 如何在嵌入式系统中同步网络时间(NTP)? 2. 如何设置Linux系统的时区? 3. 系统时间和硬件时间有什么区别?如何同步? 4. 在文件系统中(如FAT32)存储的时间戳UTC时间还是本地时间?</think>### 嵌入式系统中时间戳操作指南 #### 一、获取本地时间戳 在嵌入式 Linux 系统中获取本地时间戳的核心步骤: 1. **获取 UTC 时间戳** ```c #include <time.h> time_t get_utc_timestamp() { time_t rawtime; time(&rawtime); // 获取UTC时间戳(自1970-01-01的秒数) return rawtime; } ``` 2. **转换为本地时间** ```c #include <time.h> #include <sys/time.h> struct tm* get_local_time(time_t timestamp) { // 设置时区(如东八区) setenv("TZ", "UTC-8", 1); // 参考[^3]的时区设置 tzset(); // 转换为本地时间结构体 return localtime(&timestamp); } ``` 3. **获取带时区时间戳** ```c long get_local_timestamp() { struct timeval tv; gettimeofday(&tv, NULL); // 获取微秒级时间戳 return tv.tv_sec + (8 * 3600); // UTC+8转换为本地时间戳[^1] } ``` #### 二、根据时间戳修改系统时间 需要 root 权限,使用 `clock_settime` 系统调用: ```c #include <time.h> #include <sys/time.h> void set_system_time(time_t new_timestamp) { struct timespec ts; ts.tv_sec = new_timestamp - (8 * 3600); // 本地时间UTC[^1] ts.tv_nsec = 0; if (clock_settime(CLOCK_REALTIME, &ts) == -1) { perror("Failed to set system time"); } // 同步到硬件时钟(可选) system("hwclock --systohc"); // 参考[^2] } ``` #### 三、关键配置步骤 1. **时区设置**(参考[^3]) ```bash # 嵌入式系统中配置时区 ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime echo "UTC-8" > /etc/timezone ``` 2. **时间同步验证** ```bash # 查看当前系统时间本地) date "+%Y-%m-%d %H:%M:%S %Z" # 查看硬件时钟(UTC) hwclock --utc --show ``` #### 四、时间处理流程图 ```mermaid graph TD A[获取UTC时间戳] --> B{时区转换} B -->|+8小时| C[本地时间戳] B -->|-8小时| D[UTC时间] C --> E[显示/日志] D --> F[设置系统时间] F --> G[同步到硬件时钟] ``` #### 五、注意事项 1. **权限要求**:修改系统时间需要 root 权限 2. **时区一致性**:确保所有设备使用相同时区配置(如 `Asia/Shanghai`) 3. **硬件时钟**:硬件时钟(RTC)通常存储 UTC 时间[^1] 4. **文件系统时间**:FAT32 使用本地时间戳[^1] 5. **网络时间同步**:建议使用 NTP 协议保持时间同步 > **最佳实践**:在时间敏感型应用中,统一使用 UTC 时间进行内部存储和计算,仅在显示时转换为本地时间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值