一位开发者为了清理日志数据库,亲手往MySQL里灌了100万条记录,然后发现官方命令慢得离谱。他写了个替代方案,现在被下载了1000次——这个数字背后藏着一个关于"删除"的经典陷阱。

从个人痛点到千次安装

打开网易新闻 查看精彩图片

事情始于作者对Laravel Telescope的不满。这个官方调试工具会吞下海量日志,而清理命令telescope:clear执行起来像蜗牛爬。他先写了个小工具包laravel-telescope-flusher扔到Packagist上,最近安装量破了1000。

但作者觉得光说"快"不够,得拿数据说话。于是他搭了个干净环境:MySQL 8.0跑在Docker里,默认配置,零调优。种子数据精确到100万条telescope_entries,每条塞了约2KB的JSON内容;300万条telescope_entries_tags,外键带级联删除;再加50条监控记录。

初始状态完全一致:

• telescope_entries:100万行,逻辑大小2.33 GB,.ibd文件2.4 GB
• telescope_entries_tags:300万行,逻辑大小672 MB,.ibd文件688 MB
• telescope_monitoring:50行,忽略不计
• 总计:约400万行数据,3.1 GB物理文件

测试对象三个:官方telescope:cleartelescope:prune --hours=24,以及他的telescope:flush

官方命令的致命循环

作者扒了源码,找到性能杀手的第一层——一个朴素的do-while循环:

public function clear()do {$deleted = $this->table('telescope_entries')->take($this->chunkSize)->delete();} while ($deleted !== 0);// 对telescope_monitoring重复同样操作

chunkSize硬编码为1000。100万行数据,意味着1000次往返数据库的DELETE语句。每次删除都要写redo log、undo log、double-write buffer,InnoDB的事务开销被放大千倍。

更隐蔽的是第二层杀手,作者承认第一次写文章时完全漏掉了:telescope_entries_tags表的外键约束。

这张表在entry_uuid上有外键,且声明了ON DELETE CASCADE。平均每条entry带3个tag,所以每删1条父记录,MySQL默默追加3条子记录删除。100万次父删除,触发300万次额外的级联删除——而那个循环对此一无所知,继续按部就班地 chunk、chunk、chunk。

telescope:prune --hours=24本质是同个循环加了WHERE时间过滤。作者没跑完测试,因为"形状已经清楚了":有WHERE还是循环,有循环就有千次往返,有外键就有级联爆炸。

磁盘空间的幻觉

执行完telescope:clear后,information_schema.tables显示数据长度接近零。看起来清干净了?作者用ls -lah戳穿真相:

.ibd文件纹丝不动,还是2.4 GB和688 MB。

这是InnoDB的古老行为:DELETE只标记页为可复用,不会把空间还给操作系统。要真正缩文件,得跑OPTIMIZE TABLE(重建表)或者ALTER TABLE ... ENGINE=InnoDBtelescope:clear两个都不做,于是开发者的硬盘持续肿胀。

作者把测试脚本开源在bench/目录,"你自己跑一遍"。

数字说话:7400倍的差距

结果对比触目惊心。作者没给具体秒数,但留下了关键比例:约7400倍更快,且3 GB磁盘空间真正归还

两边跑完,information_schema报告的大小都一样——这就是陷阱。只有直接看.ibd文件才能知道谁真的释放了空间。

作者顺带提了一句:prune --hours=0clear几乎 identical,他没跑完。言下之意,想靠prune清光数据的人,踩的是同一个坑。

技术选择的缩影

这个案例像一面镜子,照出框架工具的设计取舍。Telescope的清理命令优先考虑"安全":小批量删除避免锁表、级联外键保证引用完整、不碰表结构以防意外。这些选择在生产环境有其道理,但在开发环境就成了负担——尤其是当开发者只想快速重置状态、回收磁盘时。

作者的替代方案telescope:flush走了另一条路:直接操作表结构,用TRUNCATE或DROP+重建绕过循环删除,自然也就没有级联删除的开销,同时让InnoDB真正释放文件空间。代价是更激进的锁和短暂不可用,但对开发环境完全可接受。

1000次安装说明这不是一个人的怪癖。Laravel生态里大量开发者被Telescope的日志膨胀困扰过,而官方命令的"慢"和"假清理"足够反直觉,以至于需要一个社区方案来填补。

更深层的问题是:为什么框架不内置两种模式?--force--dev开关并不复杂。或许Telescope团队认为日志清理属于边缘场景,不值得增加复杂度;或许他们低估了开发环境下数据量的膨胀速度。无论哪种,社区用1000次下载投了票。

MySQL的InnoDB存储引擎在这里也扮演了沉默的共谋者。它的空间回收机制对新手足够晦涩——DELETE后表"看起来"空了,文件却占着地盘。作者特意强调ls -lahinformation_schema的落差,就是在戳这个认知黑洞。很多开发者可能跑了无数次telescope:clear,从未发现硬盘被悄悄吃空。

这个测试的严谨性也值得注意:Docker环境保证可复现,百万级数据量贴近真实压力场景,外键级联还原了生产表结构。作者甚至开源了bench脚本,把验证权交给读者。在技术写作普遍靠"感觉"和"应该"的行业里,这种用数字和代码说话的风格本身就在建立信任。

7400倍不是魔法,是算法复杂度的胜利:O(n)的循环删除对O(1)的表级操作,加上对存储引擎行为的准确理解。这个差距在数据量小时不明显,百万级开始狰狞,千万级将彻底不可用。Telescope的设计或许从未预期单表百万条调试日志,但现代应用的观测数据膨胀速度,显然跑在了框架前面。