访客日志高频写入需避免锁表和性能拖累:用InnoDB引擎、精简字段、轻量过滤、批量插入;UV统计用Redis HyperLogLog;分区表慎用DROP PARTITION,推荐分表或TRUNCATE PARTITION;分析类查询应导出至ClickHouse或SQLite处理。

直接写入日志表会锁表,用 INSERT DELAYED 已失效
MySQL 5.6 之后 INSERT DELAYED 被彻底移除,别在新项目里写这个。访客数据高频写入(比如每秒几十次页面访问),直接 INSERT INTO access_log 容易拖慢主业务,尤其当 access_log 表没建好索引或用了 MyISAM 引擎时,会触发表级锁。
实操建议:
- 引擎必须用
InnoDB,避免锁表; - 字段精简:只存必要字段,如
ip、ua、url、referer、created_at,别存完整HTTP headers; - 写入前做轻量过滤:比如用
WHERE NOT IN排除已知爬虫 UA(但别用子查询,改用临时内存表或应用层判断); - 批量写入比单条快 5–10 倍,应用层攒够 50–200 条再
INSERT ... VALUES (...), (...), (...)。
实时统计 UV/PV 不能靠 COUNT(DISTINCT ip),得用 HyperLogLog
每天百万级访客时,SELECT COUNT(DISTINCT ip) FROM access_log WHERE date = '2024-06-01' 会扫全表、内存爆涨、查询秒变分钟级。MySQL 原生不支持 HLL,但 5.7+ 可用 SET 类型 + 应用层布隆过滤做近似去重,更推荐的是用 Redis 的 PFCOUNT/PFADD 配合 MySQL 存原始明细。
典型流程:
- PHP/Node.js 收到请求后,先
PFADD uv:20240601 <ip>到 Redis; - 同时异步写 MySQL 明细表(不阻塞响应);
- 查 UV 直接
PFCOUNT uv:20240601,误差率约 0.81%; - MySQL 仅用于导出、审计、或按 URL/地区等维度做精确分组统计(这时加好复合索引就行)。
分区表不是万能的,按天分区要注意 DROP PARTITION 性能
想查“最近 7 天 PV”,建了按 created_at 的 RANGE 分区?没问题。但千万别定时用 ALTER TABLE access_log DROP PARTITION p_20240501 清理旧数据——InnoDB 分区表删分区本质是重建该分区对应的数据文件,大表可能卡住几秒到几分钟,期间所有对该表的 DML 都被阻塞。
更稳的做法:
- 用
TRUNCATE PARTITION p_20240501(比 DROP 快,且不记 binlog); - 或者干脆不用分区,改用按月分表(
access_log_202405、access_log_202406),清理就是DROP TABLE,原子又快; - 分区键必须是主键一部分,否则建表报错:
ERROR 1064 (42000): Constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed。
分析阶段别在生产库跑 GROUP BY,导出后再用 ClickHouse 或本地 SQLite
想看“每个 URL 的平均停留时长”,但 access_log 没存停留时间字段,只能靠前后两条记录的 created_at 差值算——这种分析 SQL 一跑就是十几分钟,还占满 CPU 和 I/O。生产库不是数仓,扛不住。
可行路径:
- 每天凌晨用
mysqldump --where="date(created_at) = '2024-06-01'"导出当天数据; - 导入本地
SQLite(轻量、支持窗口函数),或上传到ClickHouse(列式、高压缩、亚秒级聚合); - 如果非要在 MySQL 里查,至少加覆盖索引:
ALTER TABLE access_log ADD INDEX idx_url_time (url, created_at),避免回表。
真正难的不是怎么记,是怎么让记的动作不影响用户打开网页那一瞬间——所有优化都得从“请求响应链路”出发,而不是盯着 SELECT 语句本身。










