如何用mysql实现简单日志系统_mysql项目记录方案

来源:这里教程网 时间:2026-02-28 20:41:35 作者:

日志表设计要避开
TEXT
字段存关键字段

直接用

TEXT
level
service_name
trace_id
会导致查询慢、无法索引、排序失效。这些字段必须用定长或变长字符串类型,比如
VARCHAR(32)
VARCHAR(64)

典型错误设计:

CREATE TABLE logs (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  content TEXT,          -- ✅ 日志正文可用 TEXT
  level TEXT,            -- ❌ 错误:level 应为 VARCHAR(16)
  service_name TEXT,     -- ❌ 错误:应为 VARCHAR(64)
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

推荐结构要点:

level
VARCHAR(16)
(值如
'INFO'
'ERROR'
)并加索引
service_name
trace_id
同样用
VARCHAR
,长度按实际最长值 +20% 预留
created_at
必须建索引,复合查询常搭配
level
,建议建联合索引:
INDEX idx_level_time (level, created_at)
避免在日志表里存二进制或 Base64 内容;真有需要,单独拆到
log_attachments

批量写入要用
INSERT ... VALUES (...), (...), (...)

单条

INSERT
插一条日志,在高并发下会迅速成为瓶颈,QPS 上不去,连接还容易被占满。MySQL 原生支持一次插入多行,性能提升明显(实测 5~10 倍),且事务开销更小。

示例(一次写 3 条):

INSERT INTO logs (level, service_name, trace_id, content, created_at) 
VALUES 
('ERROR', 'user-service', 'trc-9a8b7c', 'DB connection timeout', NOW()),
('WARN',  'order-service', 'trc-9a8b7c', 'retry limit reached', NOW()),
('INFO',  'gateway',       'trc-9a8b7c', 'request forwarded', NOW());

注意事项:

单次最多插多少行?取决于
max_allowed_packet
(默认 4MB),建议单批 ≤ 500 行,每行内容平均 ≤ 2KB
应用层做批量缓冲时,别等太久——超 500ms 或积满 100 条就发一次,避免日志延迟过高 别用
REPLACE INTO
INSERT IGNORE
写日志:它们会触发唯一键检查,纯属浪费

查最近 1 小时 ERROR 日志,
WHERE
条件顺序影响执行计划

哪怕加了索引,

WHERE level = 'ERROR' AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)
和反过来写,执行效率可能差一个数量级。MySQL 优化器倾向先过滤高区分度、范围小的条件。

因为

level
只有少数几个值('INFO'/'WARN'/'ERROR'),而时间范围是连续区间,所以
level
必须放前面:

SELECT * FROM logs 
WHERE level = 'ERROR' 
  AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)
ORDER BY created_at DESC 
LIMIT 100;

如果没走索引,用

EXPLAIN
key
列是否显示你建的联合索引名。常见掉坑点:

写了
WHERE created_at > ... AND level = 'ERROR'
→ 优化器可能弃用联合索引
用了函数包裹字段,如
WHERE DATE(created_at) = '2024-06-01'
→ 索引完全失效
查询带
LIKE '%xxx'
content
字段上 → 没法走索引,只能全表扫;真要模糊查,考虑导出到 Elasticsearch

归档旧日志别用
DELETE
大表,用
RENAME + DROP

线上日志表跑一个月后动辄千万行,直接

DELETE FROM logs WHERE created_at  会锁表、打满 I/O、拖慢写入,甚至触发主从延迟爆炸。

安全做法是按月分表 + 交换归档:

每月初新建表
logs_202405
,原表改名为
logs_202404
应用配置指向新表,旧表留着只读或导出后
DROP
建表语句保持一致,但可对旧表删掉不必要的索引(比如只留
created_at
)来减小体积

脚本化示例(MySQL 8.0+ 支持原子重命名):

-- 假设当前是 2024-06-01,把老表归档
RENAME TABLE logs TO logs_202405;
CREATE TABLE logs LIKE logs_202405;
-- (可选)给新表加写入优化:关闭 autocommit 批量插入时更稳

注意:归档不是一劳永逸。如果业务要求保留 90 天,就得写定时任务自动清理

logs_202403
及更早的表,而不是留一堆“已归档但没删”的表占空间。

相关推荐