You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

高频调用下PHP写入JSON文本文件损坏问题及解决方案咨询

问题分析与解决方案

你的问题根源在于并发写入的竞态条件,而非文件大小本身。每秒1-8次的请求同时操作同一个文件时,你代码里的ftruncate和fwrite并非原子操作——当多个PHP进程同时打开文件、读取文件大小、截断末尾、写入新内容时,它们会互相干扰,最终破坏JSON数组的结构。

举个直观的例子:进程A刚截断了文件的最后一个],还没来得及写入新内容,进程B就已经读取了被A修改后的文件大小,再次执行截断操作,之后两个进程先后写入内容,就会出现多余逗号、缺失括号的情况,直接导致文件损坏。

下面是几个可行的解决方案,按推荐程度排序:

方案1:使用文件锁保证操作原子性

在文件操作前后加上排他锁,确保同一时间只有一个进程能修改文件。修改后的代码如下:

<?php
$file = '../backtests/' . $_POST['name'] . '.txt';
// 初始化文件结构(首次写入)
if (!file_exists($file)) {
    $initial_content = '[' . $_POST['_data'] . ']';
    file_put_contents($file, $initial_content, LOCK_EX);
} else {
    // 以读写模式打开文件并加排他锁
    $fh = fopen($file, 'r+') or die("can't open file");
    if (flock($fh, LOCK_EX)) { // 获取独占锁,阻塞其他进程直到锁释放
        // 将指针移到文件末尾前1位(跳过最后的])
        fseek($fh, -1, SEEK_END);
        // 写入新内容并补全闭合括号
        fwrite($fh, ',' . $_POST['_data'] . ']');
        // 释放锁
        flock($fh, LOCK_UN);
    }
    fclose($fh);
}
?>

这里用r+模式替代原有的a模式,结合flock(LOCK_EX)确保独占访问,同时用fseek替代fstat+ftruncate的组合,操作更高效且降低出错概率。

方案2:改用行分隔的JSON格式(强烈推荐)

放弃维护一个大的JSON数组,改成每行存储一个独立的JSON对象。这样每次写入只需要追加一行,不需要修改已有内容,而追加操作在大多数操作系统中是原子的,从根本上避免了竞态问题。

写入代码:

<?php
$file = '../backtests/' . $_POST['name'] . '.txt';
// 直接追加一行JSON字符串,加LOCK_EX确保安全
file_put_contents($file, $_POST['_data'] . PHP_EOL, FILE_APPEND | LOCK_EX);
?>

后续读取时,只需逐行解析即可:

<?php
$file = '../backtests/' . $_POST['name'] . '.txt';
$lines = file($file, FILE_IGNORE_NEW_LINES);
$data = array_map('json_decode', $lines);
// $data 就是所有事件组成的数组
?>

这种方式不仅解决了并发问题,还让文件读写更高效,即使文件体积持续增长也不会影响写入性能。

方案3:改用数据库存储

如果数据量会持续增长,或者后续需要频繁查询、筛选这些事件数据,使用数据库(比如SQLite、MySQL)是更合适的选择。数据库本身具备完善的并发控制机制,能安全处理高频写入请求,而且数据管理和查询会比文本文件灵活得多。

比如SQLite的示例代码:

<?php
// 初始化数据库连接
$db = new SQLite3('../backtests/events.db');
// 首次运行时创建表
$db->exec('CREATE TABLE IF NOT EXISTS events (name TEXT, data JSON, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)');
// 插入事件数据
$stmt = $db->prepare('INSERT INTO events (name, data) VALUES (:name, :data)');
$stmt->bindValue(':name', $_POST['name'], SQLITE3_TEXT);
$stmt->bindValue(':data', $_POST['_data'], SQLITE3_TEXT);
$stmt->execute();
$db->close();
?>

内容的提问来源于stack exchange,提问作者user3119384

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:00:38