目录
5.1.2.条件二——min_examined_row_limit
一. MySQL数据目录
常客了啊。这个数据目录是MySQL主要操作的最重要的目录,没有之一。
- 在Linux里面,数据目录默认是/var/lib/mysql/
- 在Windows里面,数据目录默认是C:\ProgramData\MySQL\MySQL Server 8.0\Data\
MySQL服务器的管理信息(比如说用户密码,用户授权,系统变量的默认值,用户创建的数据库和表等等)、业务数据(用户创建的这些库和表以及表中的数据)、⽇志⽂件(通用日志,慢查询日志,二进制日志等等)、磁盘缓冲⽂件(哪些需要和磁盘进行交互的,就会暂存到这里)等等文件默认存储在数据⽬录下:
事实上,这个数据目录可以通过选项datadir来修改,所以每一台机器的数据目录都是有可能在不同位置的,我们应该通过登陆mysql客户端里面查询这个数据目录的系统变量来查询这个数据目录在哪里。
show variables like 'datadir';
Linux下
进去看看有什么
Windows下
我们可以进入数据目录看看有什么
我们仔细观察里面的这些内容,我们大概记一下它们是干啥的就行了。
二.日志简介
mysql服务器在运行的时候会接受各种请求,执行各种SQL语句,记录各种数据,这些操作都会被记录到事件,以日志的形式保存到文件里面去。
MySQLServer有以下⼏种⽇志,可以记录服务器正在发⽣的活动。
日志类型 | 核心用途 | 默认状态 | 关键特性 |
---|---|---|---|
错误日志 | 记录mysqld在启动、运行或停止时的错误和警告 | Linux 开启,Windows 关闭 | 关键故障排查依据 |
一般查询日志 | 记录所有客户端连接和接收的SQL语句(含SELECT) | 关闭 | 可动态开启/关闭,支持写入文件或表 |
慢查询日志 | 执⾏时间超过 long_query_time 指定秒数的查询 | 关闭 | 需手动设置阈值,可动态开启/关闭 |
二进制日志 | 记录数据变更语句(主从复制的核心) | Linux 开启,Windows 关闭 | 支持主从复制、时间点恢复 |
中继日志 | 从主库接收的二进制日志(仅从库使用),这个来源于集群中的主服务器 | 从库开启 | 专用于主从复制从库 |
DDL日志 | 记录 DDL 操作(如 CREATE/ALTER/DROP) | 自动启用 | 元数据变更跟踪 |
回滚日志 (undo) | 支持事务回滚和多版本控制 (MVCC) | 自动启用 | 保障事务原子性 |
重做日志 (redo) | 服务器崩溃恢复时重放未落盘的数据修改,⽤于服务器崩溃恢复 | 自动启用 | 确保 ACID 持久性(InnoDB 特有) |
- 默认情况下,Windows下默认开启错误日志,其余任何日志都不开启。
- 默认情况下,Linux下默认开启错误日志和二进制 日志。
- 在服务器运行期间可以控制一般查询和慢查询日志的禁用与开启,也可以更改日志文件名
- 一般查询日志和慢查询日志记录可以写入日志表、日志文件或两者同时写入
- 默认情况下,所有启用的日志将写入数据目录,可以通过刷新日志强制服务器关闭并重新打开日志文件。
- 通过 FLUSH LOGS 语句刷新日志来强制服务器关闭并重新打开日志文件,也可以使用 mysqladmin的 flush-logs 或 refresh 参数,或mysqldump的 --flush-logs 或 --master-data 选项
- 中继日志仅用于主从复制过程中的从服务器。有关中继日志内容和配置的讨论在主从复制的章节讨 论。
三.一般查询日志和慢查询日志的输出形式
这个输出方式就是记录日志的方式。
如果启用一般查询日志和慢查询日志,日志的输出方式可以指定为日志文件或mysql系统库中的general_log(一般查询日志)和slow_log表(慢查询日志),也可以两者同时指定。
- 一般查询日志记录了所有的查询语句。这个我们一般是不会开启的。
- 慢查询日志只记录了执⾏时间超过 long_query_time 指定秒数的查询语句。
3.1.启动时的日志控制
log_output系统变量指定日志输出的形式,但并不会真正的启用日志。
log_output可以有 三个值,分别是:TABLE(表)、FILE(文件)、NONE(不输出),可以同时指定多个值,并用逗号隔开,未指定值时默认是FILE,如果列表中存在NONE则其他的不生效,也就是说NONE的优先级最高。就比如说TABLE,NONE表示不生效,FILE,NONE表示不生效,TABLE,FILE表示以表和文件的形式输出
通过设置general_log系统变量的值来控制一般查询日志的开启1与禁用0,如果要为日 志指定自定义的路径或文件名可以使用general_log_file系统变量,这个我们一般建议配置绝对路径
通过设置slow_query_log系统变量的值来控制慢查询日志的开启1与禁用0,如果要为 日志指定自定义的路径或文件名可以使用slow_query_log_file系统变量,这个我们一般建议配置绝对路径
示例,以选项文件中的配置为例:
我们先复习一下,选项文件是哪个?
- 在Linux里面,配置文件默认是/etc/mysql/my.cnf
- 在Windows里面,配置文件默认是C:\ProgramData\MySQL\MySQL Server 8.0\my.ini
- 将一般查询日志写入日志表和日志文件
[mysqld]
#日志写入表和文件
log_output=TABLE,FILE
#开启一般查询日志
general_log=1
注意啊:我们这个没有配置一般查询日志的路径时,使用默认的路径。那一般查询日志默认的路径是啥?我们可以查询一下
- 仅将一般查询日志和慢查询日志写入日志表
[mysqld]
log_output=TABLE #日志写入表
general_log=1 #开启一般查询日志
slow_query_log=1 #开启慢查询日志
我们这里也是没有指定慢查询日志的路径,这个也是使用默认路径。我们可以查询一下慢查询日志的路径
- 仅将慢查询日志写入日志文件
[mysqld]
log_output=FILE #日志文件
slow_query_log=1 #开启慢查询日志
- 将一般查询日志和慢查询日志写入日志文件,并指定自定义的日志路径(注意修改路径将涉及到权限的问题,该指定目录的用户和用户组必须是mysql)
[mysqld]
#日志文件
log_output=FILE
#开启一般查询日志
general_log=1
#指定自定义的文件名
general_log_file=/var/lib/mysql/general.log
#开启慢查询日志
slow_query_log=1
#指定自定义的文件名
slow_query_log_file=/var/lib/mysql/slow_query.log
接下来我们将示例一下
将一般查询日志和慢查询日志写入日志文件和表,并指定自定义的日志文件名
######################日志相关######################
[mysqld]
#日志文件
log_output=FILE,TABLE
#开启一般查询日志
general_log=1
#指定自定义的文件名
general_log_file=/var/lib/mysql/general.log
#开启慢查询日志
slow_query_log=1
#指定自定义的文件名
slow_query_log_file=/var/lib/mysql/slow_query.log
我们这里一般查询日志和慢查询日志的目录只设定为默认的数据目录即可。(这里避免了自定义目录的权限问题)我们给它们重新起了文件名。
注意:我们修改这个配置文件的时候,一定要将这个选项到官网去查查看:
我们打开上面这个官网,
我们按ctrl+f,出现下面这个情况
然后我们将要查询的对象放进去,按回车就能查询到
这个是很重要的。
我们保存退出,需要重启mysql服务
systemctl restart mysql
systemctl status mysql
很好,我们的这个就已经配置好了。
我们可以去mysql客户端看看
show variables like 'log_output';
show variables like 'slow_query_log';
show variables like 'slow_query_log_file';
show variables like 'general_log';
show variables like 'general_log_file';
嗯,我们是修改好了。
这里提醒一下大家:如果说我们定义的是自定义目录,但是这个目录的用户和用户组都不是mysql的话,那么我们在配置文件即使设置了general_log=1,那么我们mysql运行时还是会将general_log设置为0.
3.2 运行时的日志控制
在运行时修改 log_output 的值,以更改日志的输出形式,通过语句控制
语法:SET [GLOBAL|SESSION] variable_name=value
SET GLOBAL log_output=[FILE, TABLE, NONE]
- general_log[={0|1}] 和 slow_query_log[={0|1}] 可以表示启用和禁用一般查询日志和慢查询日志
- general_log_file 和 slow_query_log_file 表示通用查询日志和慢查询日志文件名称
- 只对当前会话禁用或启用一般查询日志记录,将 SESSION 作用域的 sql_log_off 变量设置为 ON 或 OFF
这个
sql_log_off
系统变量是 MySQL 中用来控制当前会话(Session)是否将执行的 SQL 语句记录到 通用查询日志(General Query Log) 的开关。它的核心作用和工作方式如下:
作用范围: 仅影响当前连接(Session)。它不会影响其他已有的连接或新的连接。
前提条件: 只有当 MySQL 服务器的全局通用查询日志本身是启用状态(通过配置
general_log=ON
或启动时指定--general-log
)时,这个变量才有效。如果全局通用查询日志是关闭的,设置sql_log_off
没有意义(因为没有日志可写)。取值与含义:
OFF
(默认值): 启用记录。当前会话执行的 SQL 语句会被写入通用查询日志文件。
ON
(文档中的ox
是笔误,应为ON
): 禁用记录。当前会话执行的 SQL 语句不会被写入通用查询日志文件。重要限制:
权限要求高: 设置会话级别的
sql_log_off
是一个受限制的操作。执行SET SESSION sql_log_off = ...
的用户必须拥有SYSTEM_VARIABLES_ADMIN
权限(或已弃用的SUPER
权限)。仅对当前会话有效: 它不会改变全局设置或其他会话的行为。
不控制错误日志/Slow Log: 它只影响通用查询日志(General Query Log),不影响错误日志(Error Log)或慢查询日志(Slow Query Log)。
设置方式
SET SESSION sql_log_off = ON; -- 禁用当前会话的日志记录 SET SESSION sql_log_off = OFF; -- 启用当前会话的日志记录(默认状态)
3.3 日志文件和日志表的区别
MySQL 的通用查询日志(General Query Log) 和慢查询日志(Slow Query Log) 都支持两种输出目的地:日志文件(Log File) 和 日志表(Log Table)(通常是 mysql.general_log
和 mysql.slow_log
)。
为什么在有了日志文件之后,还要提供日志表呢?主要是为了解决日志文件的一些固有缺点,并提供更灵活、更“数据库原生”的管理和分析方式:
以下是日志表存在的主要意义和优势:
更便捷的查询与分析:
痛点(文件): 查看和分析纯文本日志文件需要使用
grep
,awk
,sed
,less
等命令行工具,或者下载到本地用文本编辑器打开。对于复杂查询(比如查找特定用户、特定时间段、包含特定关键字的语句)非常麻烦且低效。优势(表): 日志存储在数据库表中后,你可以直接使用 SQL 语句 来查询和分析日志!就像查询任何其他业务数据表一样:
SELECT * FROM mysql.general_log WHERE event_time > '2025-07-04 10:00:00' AND user_host LIKE '%app_user%@%' AND argument LIKE '%SensitiveTable%';
这极大地提高了日志检索、过滤、聚合(如统计某类语句的执行次数)的效率。
集中化存储与管理:
痛点(文件): 日志文件通常分散存储在数据库服务器的文件系统上。管理(如轮转、归档、清理)需要额外的脚本或工具(如
logrotate
),并且可能涉及文件系统权限问题。优势(表): 日志表和其他数据库表存储在同一个 MySQL 实例(或集群)中。可以使用标准的数据库备份工具(如
mysqldump
,mysqlbackup
, 主从复制)来备份和恢复日志数据。管理(如清理旧日志)可以通过简单的 SQL 完成:DELETE FROM mysql.general_log WHERE event_time < DATE_SUB(NOW(), INTERVAL 30 DAY);
(注意:直接删除
general_log
或slow_log
表数据在高负载下可能影响性能,通常建议通过设置expire_logs_days
或定期TRUNCATE TABLE
来管理)。
访问控制和安全性(集成):
痛点(文件): 访问日志文件需要操作系统级别的文件读取权限。精细控制谁可以访问哪些日志内容比较困难,通常依赖于文件系统的用户/组权限,不够灵活。
优势(表): 数据库本身就有完善的权限系统。你可以使用标准的 MySQL
GRANT
语句精确控制哪些数据库用户或角色有权SELECT
、DELETE
日志表中的数据。例如:GRANT SELECT ON mysql.general_log TO 'auditor'@'%'; GRANT DELETE ON mysql.slow_log TO 'dba_admin'@'localhost';
这提供了更细粒度的安全控制。(注意:默认情况下,只有具有足够权限的用户如
root
或具有SUPER
权限的用户才能访问这些系统日志表)
避免文件系统 I/O 瓶颈(潜在优势):
痛点(文件): 在高并发写入场景下,频繁地写日志文件可能成为文件系统 I/O 的瓶颈,尤其是在机械硬盘上。
优势(表): 如果日志表使用的是事务性存储引擎(如 InnoDB,这是 MySQL 8.0 中这些表的默认引擎),并且数据库服务器的缓冲池(Buffer Pool)配置得当,写入日志表 可能 比直接写文件系统更高效,因为它可以利用数据库的缓冲机制。(但这并非绝对,取决于具体负载和配置,有时写表也可能引入额外开销)。
与监控/分析系统集成更简单:
痛点(文件): 将文件日志集成到 ELK Stack (Elasticsearch, Logstash, Kibana)、Prometheus/Grafana 或其他监控系统需要配置额外的日志收集器(如 Filebeat, Fluentd)和解析器。
优势(表): 可以直接通过数据库连接,使用 SQL 查询将日志数据抽取到分析系统或数据仓库中,或者编写存储过程/触发器进行实时分析(虽然通常不推荐在日志表上做太重的实时操作)。
总结对比:
特性 | 日志文件 (Log File) | 日志表 (Log Table) |
---|---|---|
存储形式 | 纯文本文件 (CSV 或其他格式) | MySQL 数据库表 (mysql.general_log , mysql.slow_log ) |
查询分析 | 困难,需命令行工具或下载编辑 | 容易,直接用 SQL 查询 |
管理 | 需文件系统工具 (logrotate ),脚本管理 |
可用 SQL 管理 (DELETE, TRUNCATE),集成数据库备份 |
访问控制 | 操作系统文件权限 | 精细的 MySQL 数据库权限控制 (GRANT/REVOKE) |
I/O 特点 | 直接写磁盘,可能受文件系统 I/O 限制 | 写入数据库缓冲池,可能 更高效 (依赖配置) |
集成 | 需额外日志收集器/解析器对接监控系统 | 更容易通过 SQL 接口对接分析系统 |
结论:
MySQL 提供日志表并不是要完全取代日志文件,而是为了提供另一种更符合数据库管理员和开发者习惯、更便于利用 SQL 强大能力进行查询分析、更容易集成到数据库自身管理生态的日志存储和管理方式。它是为了解决纯文本日志文件在查询便利性、管理集成度和访问控制粒度上的不足。
- 可以通过SQL语句的条件查询过滤日志内容,从而选择满足特定条件的日志记录。比如,某个客户端的日志;
- 可以通过客户端程序连接到服务器并查询表中的日志信息,无需登录服务器主机访问文件系统。
- 日志记录具有标准格式,可看日志表的结构,可以使用以下语句:
SHOW CREATE TABLE mysql.general_log; # 一般查询日志
SHOW CREATE TABLE mysql.slow_log; #慢查询日志
我们可以重新
四. 一般查询日志
核心功能:
记录连接/断开事件: 记录所有客户端与 MySQL 服务器建立连接 (
Connect
) 和断开连接 (Quit
/Disconnect
) 的信息。记录所有 SQL 语句: 记录客户端发送给服务器的 每一个 SQL 语句,无论其是否语法正确、是否执行成功、是否修改了数据、是否返回了结果。这包括
SELECT
,INSERT
,UPDATE
,DELETE
,SET
,SHOW
,BEGIN
,COMMIT
,ROLLBACK
等所有类型的 SQL 命令。
详细内容:
连接事件: 记录连接时间戳、连接使用的协议类型(见第3点)、连接来源的主机名/IP地址、连接使用的用户名。
SQL 语句事件: 记录语句执行的时间戳、执行该语句的用户@主机、具体的 SQL 语句文本。
断开事件: 记录断开的时间戳、断开对应的用户@主机。
资源消耗警告:
由于记录粒度极细(所有连接和所有语句),在活跃的生产环境中开启通用查询日志会产生海量日志数据。
频繁的磁盘 I/O(写入日志)会显著消耗服务器资源(CPU、磁盘 I/O、磁盘空间),可能影响数据库性能。
因此,默认情况下通用查询日志是关闭的 (
OFF
),仅在需要诊断特定问题(如连接问题、可疑查询来源)时才建议临时开启。
2. 启用方式与日志文件配置
启用方法:
启动选项 (推荐): 在 MySQL 服务器启动命令行或配置文件 (如
my.cnf
/my.ini
) 中使用--general_log[={0|1}]
。--general_log=1
或--general_log
:明确开启日志。--general_log=0
:明确关闭日志(默认状态)。
动态设置 (运行时):
SET GLOBAL general_log = 'ON'; -- 开启全局通用查询日志 SET GLOBAL general_log = 'OFF'; -- 关闭全局通用查询日志
注意:动态设置需要
SYSTEM_VARIABLES_ADMIN
或SUPER
权限。
日志文件位置与命名:
默认文件名:
host_name.log
。其中host_name
是运行 MySQL 服务器的主机名。修改文件名/路径:
启动选项:
--general_log_file=file_name
配置文件: 在
[mysqld]
部分添加general_log_file = /path/to/your_log_file.log
动态设置:
SET GLOBAL general_log_file = '/path/to/new_log_file.log';
重要提示:
确保 MySQL 运行用户 (
mysql
) 对指定的目录有写权限。修改路径后,如果日志已开启,新日志会立即写入新文件,旧文件不会自动轮转或删除。
日志输出目标: 除了文件,还可以输出到表
mysql.general_log
(需设置log_output = 'TABLE'
或'FILE,TABLE'
)。
3. 连接协议记录 (connection_type
)
通用查询日志中记录客户端连接事件的行,会明确包含一个 connection_type
字段,指示建立连接所使用的网络协议或机制。具体值及其含义如下:
connection_type 值 |
含义 | 适用平台 |
---|---|---|
TCP/IP |
客户端通过标准的、未加密的 TCP/IP 网络协议连接到 MySQL 服务器的端口(默认 3306)。 | 所有平台 (跨网络) |
SSL/TLS |
客户端通过 TCP/IP 协议连接,并且在传输层启用了 SSL/TLS 加密。这需要服务器配置 SSL 证书,客户端也支持 SSL 连接。 | 所有平台 (跨网络) |
Socket |
客户端与服务器位于同一台 Unix/Linux 主机上,使用操作系统提供的 Unix 域套接字文件 (通常是 /var/lib/mysql/mysql.sock 或 /tmp/mysql.sock ) 进行通信。这是本地连接最高效的方式,不经过网络协议栈。 |
Unix/Linux |
Named Pipe |
客户端与服务器位于同一台 Windows 主机上,使用 Windows 命名管道进行进程间通信 (IPC)。需要在服务器配置中启用命名管道支持 (--enable-named-pipe ) 并指定管道名 (--socket=<pipe_name> )。 |
Windows |
Shared Memory |
客户端与服务器位于同一台 Windows 主机上,使用 Windows 共享内存区域进行通信。需要在服务器配置中启用共享内存 (--shared-memory ) 并指定共享内存名 (--shared-memory-base-name=<name> )。 |
Windows |
日志中的体现示例:
2025-07-04T12:34:56.789012Z 10 Connect root@localhost on using TCP/IP 2025-07-04T12:34:57.123456Z 10 Connect app_user@192.168.1.100 on using SSL/TLS 2025-07-04T12:35:01.234567Z 11 Connect backup_user@localhost on using Socket
这里
10 Connect ... using TCP/IP
表示一个通过未加密 TCP/IP 建立的连接。10 Connect ... using SSL/TLS
表示一个通过加密的 TCP/IP (SSL/TLS) 建立的连接。11 Connect ... using Socket
表示一个本地 Unix 套接字连接。
Mysqld按照接收到SQL语句的顺序将语句写入查询日志,这个顺序可能与语句执行的顺序不同。
表结构如下:
这些字段啥意思?
event_time
类型:
timestamp(6)
含义: 记录事件发生的时间戳。这是日志条目被创建的确切时间点。
精度:
(6)
表示时间戳精确到微秒级别(百万分之一秒)。约束:
NOT NULL
- 该字段必须有值。默认值 & 更新行为:
DEFAULT CURRENT_TIMESTAMP(6)
- 如果插入时未指定值,则自动设置为当前时间戳(精确到微秒)。ON UPDATE CURRENT_TIMESTAMP(6)
- 如果该行被更新(理论上不应该发生,因为日志通常只追加不修改),时间戳会自动更新为当前时间(精确到微秒)。对于日志表来说,这个ON UPDATE
行为实际意义不大。
user_host
类型:
mediumtext
含义: 标识发起操作的用户和客户端主机。格式通常是
'username'@'hostname'
或'username'@'[ip_address]'
。示例:
'app_user'@'workstation-123'
,'root'@'localhost'
,'backup'@'192.168.1.100'
。约束:
NOT NULL
- 该字段必须有值。为什么是
mediumtext
? 用户名和主机名的组合长度可能较长(尤其是有域名的长主机名或IPv6地址),mediumtext
提供了足够的存储空间(最大约 16MB)。
thread_id
类型:
bigint unsigned
含义: 服务器内部线程的唯一标识符。每个客户端连接在服务器端都会被分配一个唯一的线程 ID。
作用:
用于关联
performance_schema.threads
表,获取该线程的更多详细信息(如关联的进程 ID)。在诊断问题时,可以帮助跟踪特定连接或会话的所有活动。
约束:
NOT NULL
- 每个事件必然关联到一个服务器线程。
server_id
类型:
int unsigned
含义: MySQL 服务器的唯一标识符。这是在 MySQL 服务器配置文件中(
my.cnf
/my.ini
)通过server_id
参数设置的。作用:
复制环境: 在 MySQL 主从复制架构中至关重要,用于区分事件是由哪个服务器(主库或特定的从库)产生的。这有助于追踪事件来源和解决复制问题。
非复制环境: 即使没有启用复制,该字段也会被填充,通常是默认值(如 1)。
约束:
NOT NULL
- 每个事件必然关联到一个服务器实例。
command_type
类型:
varchar(64)
含义: 记录的操作类型。这是通用查询日志记录的核心分类。
常见值:
Connect
: 表示一个客户端成功连接到服务器。Quit
: 表示一个客户端正常断开连接。Query
: 最重要的类型,表示客户端向服务器发送了一条 SQL 语句。所有SELECT
,INSERT
,UPDATE
,DELETE
,SET
,SHOW
,CREATE
,DROP
,BEGIN
,COMMIT
,ROLLBACK
等语句都会被记录为Query
。Init DB
: 表示客户端执行了USE database_name;
命令来切换当前数据库。Field List
: 表示客户端请求了某个表的字段列表(如某些客户端工具的行为)。Statistics
: 表示服务器内部发送了一些统计信息给客户端(较少见)。Binlog Dump
: 与主从复制相关,表示从库请求主库发送二进制日志事件。
约束:
NOT NULL
- 每个事件必须有一个类型。
argument
类型:
mediumblob
含义: 记录的具体内容。这个字段的内容完全取决于
command_type
。内容根据
command_type
变化:Query
: 这里存储的就是客户端发送的完整 SQL 语句文本。例如:"SELECT * FROM users WHERE id = 100;"
,"UPDATE orders SET status='shipped' WHERE order_id=1234;"
。Connect
: 通常存储的是连接建立时客户端尝试使用的数据库名。如果客户端连接时没有指定数据库(mysql -u user -p
),这里可能是空的。格式如"database_name"
。Quit
: 通常为空 (''
)。Init DB
: 存储的是客户端要切换到的数据库名。格式如"new_database_name"
。Binlog Dump
: 包含二进制日志位置等复制信息。
约束:
NOT NULL
- 即使内容为空(如Quit
),该字段也存在(值为空字符串)。为什么是
mediumblob
?SQL 语句 (
Query
) 可能非常长(复杂的查询、存储过程调用、大INSERT
语句等)。mediumblob
类型允许存储二进制大对象,最大容量约为 16MB,足以容纳绝大多数 SQL 语句。使用
blob
类型(而非text
)是因为理论上 SQL 语句可以包含任何二进制数据(尽管在实践中绝大多数是文本)。mediumblob
提供了更大的容量上限。
表级属性补充说明:
ENGINE=CSV
: 该表使用 CSV 存储引擎。这意味着:数据实际存储在服务器数据目录下的一个纯文本 CSV 文件(如
mysql/general_log.CSV
)中。优点:简单,可以直接用文本编辑器或
LOAD DATA INFILE
等工具查看和导入。缺点:
不支持索引: 查询效率非常低,尤其是在大型日志表中进行筛选(如按时间或用户查)。
写操作锁定: 写入操作(
INSERT
)会锁定整个表,在高并发写入日志时可能成为瓶颈。不支持事务。
空间可能浪费: CSV 文件是纯文本,不如某些二进制格式紧凑。
DEFAULT CHARSET=utf8mb3
: 表的默认字符集是utf8mb3
(即传统的 UTF-8,每个字符最多 3 字节)。这足以存储绝大多数语言的字符,但无法存储一些补充字符(如某些 emoji),因为utf8mb4
(4 字节)才是 MySQL 中真正的完整 UTF-8 支持。考虑到argument
是mediumblob
(二进制),字符集设置主要影响command_type
和user_host
字段的文本存储。COMMENT='General Log'
: 表的注释,说明这是通用查询日志表。
我们现在来看看这个具体的内容啊
select * from mysql.general_log;
我们发现太多了。
太多了,显示的不好,我们可以使用另外一种显示方式
select * from mysql.general_log\G
我们仔细看下面这个
这条日志记录表示:在 2025 年 7 月 4 日 13:00:58,用户
root
从本地主机 (localhost
) 执行了一条查询 (Query
),内容是查看 mysql.general_log
表的数据(SELECT * FROM mysql.general_log
)。
SELECT * FROM mysql.general_log是怎么来的?
我们发现argument后面怎么是这么一些数字啊,我根本看不懂。那咋办?
事实上这是十六进制字符串,解码十六进制字符串:
原始十六进制:`0x73656C656374202A2066726F6D206D7973716C2E67656E6572616C5F6C6F67` 去掉前缀`0x`,将每两个字符(一个字节)转换为对应的ASCII字符:
- 73 -> s
- 65 -> e
- 6C -> l
- 65 -> e
- 63 -> c
- 74 -> t
- 20 -> (空格)
- 2A -> *
- 20 -> (空格)
- 66 -> f
- 72 -> r
- 6F -> o
- 6D -> m
- 20 -> (空格)
- 6D -> m
- 79 -> y
- 73 -> s
- 71 -> q
- 6C -> l
- 2E -> .
- 67 -> g
- 65 -> e
- 6E -> n
- 65 -> e
- 72 -> r
- 61 -> a
- 6C -> l
- 5F -> _
- 6C -> l
- 6F -> o
- 67 -> g
连接起来:`select * from mysql.general_log`。
事实上,我们可以借助CAST函数来显示。
CAST()
是 MySQL 中用于数据类型转换的核心函数,它允许你将值从一种数据类型显式转换为另一种数据类型。在处理日志中的十六进制数据时,它特别有用。
CAST(expression AS type [CHARACTER SET charset_name])
expression:要转换的值或列名(如
argument
)type:目标数据类型
CHARACTER SET:可选项,指定字符集(如
utf8mb4
)
我们可以借助这个函数来转换那些字符
4.1 一般查询日志示例
- 查询表中的日志内容
SELECT DATE_FORMAT(event_time, '%Y-%m-%d %H:%i:%s') AS time,
user_host,
thread_id,
server_id,
command_type,
CAST(argument AS CHAR) AS query
FROM mysql.general_log\G
我们仔细看
- 查看⽂件中的⽇志内容
我们得去数据目录里面查找,就是/var/lib/mysql/
接下来我
vim general.log
五.慢查询日志
慢查询日志是 MySQL 性能调优的核心工具,它会记录执行时间过长或检查行数过多的 SQL 语句。
5.1.触发条件(必须同时满足)
5.1.1.条件一——long_query_time
long_query_time
用于定义 MySQL 中查询执行时间的阈值,超过该时间的查询会被记录到慢查询日志中,以便定位和优化性能问题。
5.1.2.条件二——min_examined_row_limit
min_examined_row_limit
指的是 MySQL 在决定是否将一个查询记录到慢查询日志中时,所考虑的“被检查的行数”的一个最低阈值。
理解这个参数的关键点:
“被检查的行数” (Examined Rows):
这是指 MySQL 服务器为了执行该查询,实际扫描、检查或处理了多少行数据。
这发生在查询的执行过程中,可能发生在存储引擎层(如 InnoDB 读取数据页)或服务器层(如排序、分组、连接处理)。
这个数字通常远大于最终返回给客户端的结果集行数。例如:
一个
SELECT * FROM big_table WHERE indexed_column = 'value'
可能只返回 1 行结果(如果只有一行匹配),但如果索引查找需要扫描多个索引项或访问主键回表,它可能检查了 10 行。一个没有索引的
SELECT * FROM big_table WHERE non_indexed_column = 'value'
可能需要全表扫描 100 万行才能找到 10 个匹配项,最终返回 10 行。这里“被检查的行数”是 100 万。
min_examined_row_limit
的作用:MySQL 有一个慢查询日志 (
slow_query_log
),用于记录执行时间超过指定阈值 (long_query_time
) 的查询。min_examined_row_limit
为慢查询日志记录增加了一个额外的条件:即使一个查询的执行时间超过了long_query_time
,但如果它检查的行数少于min_examined_row_limit
设置的值,这个查询也 不会 被记录到慢查询日志中。目的: 这个参数的主要作用是过滤掉那些虽然执行时间稍长(可能由于网络、锁等待或其他短暂因素),但实际上工作量(检查的数据量)很小的查询。避免慢查询日志被大量“伪慢查询”淹没,让你能更专注于那些真正处理了大量数据的慢查询(通常这些才是性能优化的重点)。
⚠️ 重要:只有同时满足这两个条件的查询才会被记录!
我们来看看
场景 1:默认配置(常见情况)
long_query_time = 10 # 10秒阈值
min_examined_row_limit = 0 # 无行数限制
✅ 触发情况:
只要执行时间 > 10秒的查询都会被记录(无论检查了多少行)
❌ 不触发情况:
执行时间 ≤ 10秒的查询(即使检查了百万行)
场景 2:严格配置(您疑惑的情况)
long_query_time = 5 # 5秒阈值
min_examined_row_limit = 1000 # 需检查超过1000行
✅ 触发情况(必须同时满足):
执行时间 > 5秒
检查行数 > 1000行
✅ 示例:
-- 执行时间 6秒,检查行数 1200行 → 记录
SELECT * FROM orders WHERE status = 'pending'
❌ 不触发情况(任一条件不满足):
-- 执行时间 6秒,检查行数 800行 → 不记录(行数不足)
SELECT * FROM users WHERE id = 100
-- 执行时间 4秒,检查行数 5000行 → 不记录(时间不足)
SELECT * FROM audit_log WHERE date > '2023-01-01'
被记录的慢查询需要进行优化,可以使用 mysqldumpslow 客户端程序对慢日志进行分析汇总。
获取初始锁的时间不计入执行时间,mysqld 在执行完 SQL 语句并释放所有锁后才将符合条件的语句写入慢速查询日志,因此日志顺序可能与执行顺序不同。
在mysql中,一个SQL语句执行经历的阶段如下:
- 执行线程首先需要获得锁
- 执行SQL语句并返回结果
- 释放锁
慢查询记录的真正的执行时间其实就是2,3步所用时间。
5.2.慢查询日志参数
- long_query_time 的默认值是10,最小值是0;
- 默认情况下,不记录管理语句(DDL语句),也不记录不使用索引的查询(全表扫描的查询)
- 慢查询日志默认为关闭(不开启),要启用慢查询日志可以使用:请使用 --slow_query_log[={0|1}].
- 默认日志文件名为 host_name-slow.log,可以使用 slow_query_log_file=file_name 修改;
- 使用 --log-short-format 选项,以简要格式记录慢查询日志
- 要记录管理语句,启用 log_slow_admin_statements 系统变量。管理语句包括 ALTER TABLE、 ANALYZE TABLE、 CHECK TABLE、 CREATE INDEX、 DROP INDEX、 OPTIMIZE TABLE 和 REPAIR TABLE。
- 要记录不使用索引的查询,启用 log_queries_not_using_indexes 系统变量。当记录不使用索引的查询时,日志会快速增长,通过设置系统变量 log_throttle_queries_not_using_indexes 限制每分钟写入慢查询日志同类查询的数量,默认值是0,表示无限制。
- 要启用log_queries_not_using_indexes,就一定需要设置log_throttle_queries_not_using_indexes。
5.3 慢查询日志内容
5.3.1 FILE格式
- 如果启用慢查询日志并将 FILE 作为输出目标,每条语句前面都用一行来表示日志的字段,该行以 # 字符开头并包含以下内容:
- Query_time: :SQL语句的执行时间,单位秒
- Lock_time: 获取锁的时间,单位秒
- Rows_sent: 发送到客户端的行数,也可以理解为返回的查询结果集的行数。
- Rows_examined: 服务器检查的行数(服务器扫描数据表中具体数据行的数量)
我们看个例子
这里一共有3个,我们一个一个看
第一条:UPDATE 操作(事务1)
# Time: 2023-10-22T03:34:57.593209Z
# User@Host: root[root] @ localhost [::1] Id: 10
# Query_time: 24.224840 Lock_time: 24.201250 Rows_sent: 0 Rows_examined: 1
use test_db;
SET timestamp=1697945673;
UPDATE ACCOUNT SET NAME='张三' where id = 1;
关键分析:
严重锁等待:
总耗时 24.22 秒,其中 24.20 秒(99.9%)在等待锁
实际执行时间仅 0.02359 秒(24.224840 - 24.201250)
操作细节:
更新
id=1
的行检查了 1 行(说明有主键索引)
影响 0 行(可能数据未变化)
问题根源:
有其他事务长时间持有该行的排他锁(X锁)
可能是未提交的事务或长时间运行的更新操作
第二条:SELECT 操作(事务2)
# Time: 2023-10-22T03:35:31.918024Z
# User@Host: root[root] @ localhost [::1] Id: 9
# Query_time: 19.012846 Lock_time: 19.012325 Rows_sent: 1 Rows_examined: 1
SET timestamp=1697945712;
select * from account where id < 2 FOR SHARE;
关键分析:
同样严重的锁等待:
总耗时 19.01 秒,19.01 秒(99.9%)在等待锁
实际执行时间仅 0.000521 秒
操作特殊性:
使用
FOR SHARE
(共享锁/S锁)查询
id<2
的记录(实际就是id=1
)返回 1 行结果
冲突原因:
尝试获取共享锁时被阻塞
说明有事务持有该行的排他锁(X锁)
可能与第一条UPDATE是同一个事务未提交
第三条:UPDATE 操作(事务3)
# Time: 2023-10-22T04:20:40.347153Z
# User@Host: root[root] @ localhost [::1] Id: 10
# Query_time: 50.412714 Lock_time: 50.411844 Rows_sent: 0 Rows_examined: 0
SET timestamp=1697948389;
UPDATE ACCOUNT SET NAME='张三' where id = 1;
关键分析:
极端锁等待:
总耗时 50.41 秒,50.41 秒(99.9%)在等待锁
实际执行时间仅 0.00087 秒
异常现象:
Rows_examined: 0
(检查0行)Rows_sent: 0
(影响0行)可能原因:
行已被删除
MVCC机制下快照不可见
锁等待超时后未重试
除了上面那些基本的信息之外,我们还可以添加额外字段
- 启用--log-slow-extra[={OFF|ON}]系统变量会将以下额外字段写入到FILE中,TABLE形式不受影响
- Thread_id: 线程标识符
- Errno: 错误码,没有发生错误则为 0
- Killed: 如果语句被终止,用错误码表示原因,如果语句正常终止则为 0。
- Bytes_received: 接收到SQL语句的Bytes值。
- Bytes_sent: 返回给客户端的Byte值。
- Read_first: 索引中第一个条目被读取的次数,如果这个值很高,表明服务器正在执行大量完整索引扫描
- Read_last: 读取索引中最后一个键的请求数,使用 ORDER BY 时关注Read_key: 基于索引读取一行数据的请求数。如果这个值很高,表明表为当前查询建立了正确的索引
- Read_next: 按索引排序读取下一行的请求数,查询具有范围约束的索引列,或者进行索引扫描,此值将递增。
- Read_prev: 按索引排序读取前一行的请求数。主要用于优化ORDER BY...DESC。
- Read_rnd: 基于固定位置读取一行的请求数。这个值很高表示,正在执行大量需要对结果进行排序的查询,可能有很多查询进行了全表扫描整,或者没有正确使用索引的连接。
- Read_rnd_next: 读取数据文件中下一行的请求数。如果进行大量的表扫描,这个值会很高。通常,表示表没有建立正确地索引,或者查询没有利用索引。
- Sort_merge_passes: 排序算法完成的归并次数,如果这个值很大,考虑增加sort_buffer_size系统变量的值。
- Sort_range_count: 使用范围进行排序的次数。
- Sort_rows: 排序的行数。
- Sort_scan_count: 通过扫描表完成的排序数。
- Created_tmp_disk_tables: 服务器在执行语句时创建内部磁盘临时表的数量。
- Created_tmp_tables: 服务器在执行语句时创建的内部临时表的数量。
- Start: 执行SQL语句开始时间
- End: 执行SQL语句结束时间
5.3.2.TABLE字段
SHOW CREATE TABLE mysql.slow_log;
那这些字段是啥意思呢?
start_time
类型:
timestamp(6)
含义:查询开始执行的时间戳(精确到微秒)
特殊属性:
默认值为当前时间戳(
CURRENT_TIMESTAMP(6)
)更新时自动刷新(虽然日志记录通常不会更新)
user_host
类型:
mediumtext
含义:客户端标识,格式为
'用户名'@'主机名' [连接信息]
示例:
'root'@'localhost [::1]'
query_time
类型:
time(6)
含义:查询总执行时间(精确到微秒)
重要说明:包含锁等待时间 + 实际执行时间
lock_time
类型:
time(6)
含义:等待锁的时间(精确到微秒)
关键点:仅包含等待锁的时间,不包含实际执行时间
rows_sent
类型:
int
含义:返回给客户端的行数
示例:
SELECT
查询返回的结果行数
rows_examined
类型:
int
含义:扫描检查的行数(性能关键指标)
优化依据:高值通常表示索引缺失或效率低下
db
类型:
varchar(512)
含义:查询所在的数据库名
示例:
test_db
last_insert_id
类型:
int
含义:该连接中最后生成的 AUTO_INCREMENT 值
适用场景:
INSERT
后使用LAST_INSERT_ID()
的值
insert_id
类型:
int
含义:本查询生成的 AUTO_INCREMENT 值
区别:
last_insert_id
是连接级,insert_id
是查询级
server_id
类型:
int unsigned
含义:MySQL 服务器的唯一标识符
作用:复制环境中区分主从服务器(在
my.cnf
中配置)
sql_text
类型:
mediumblob
含义:原始 SQL 语句(二进制存储)
查看方法:
SELECT CAST(sql_text AS CHAR CHARACTER SET utf8mb4) FROM mysql.slow_log;
thread_id
类型:
bigint unsigned
含义:服务器线程 ID(连接的唯一标识)
关联信息:可在
SHOW PROCESSLIST
中查看对应线程
在这里,我还是推荐使用日志文件的形式来记录慢查询!!!!