本文目录导读:

- 数据库场景(最核心)
- 文件系统场景(文件读写)
- Session 与 Cookie(会话数据)
- 缓存系统(Redis/Memcached)
- 终极避坑指南(“读己之写” 失败的三大元凶)
- 面试/进阶考点:如何测试“读己之写”的一致性?
在 PHP 中,“读己之写”通常指的是读取自己刚刚写入的数据,这个概念在并发编程、缓存一致性、数据库隔离级别中至关重要。
针对不同的存储介质和场景,PHP 的实现方式截然不同,以下分四种最常见的情况详解:
数据库场景(最核心)
这是最常被问到的,PHP 向 MySQL 写入一条数据后,立刻查询它。
核心问题:事务隔离,在默认的 REPEATABLE READ 隔离级别下,如果写入和读取在同一个事务里,你一定能读到(因为快照在第一次读时建立),但如果是跨请求(异步),就需要考虑提交时机。
最佳实践(必读代码):
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$pdo->beginTransaction();
try {
// 1. 写入
$stmt = $pdo->prepare("INSERT INTO users (name, age) VALUES (?, ?)");
$stmt->execute(['John', 25]);
// 2. 获取自增ID(关键:用 lastInsertId 而不是再次查库)
$newId = $pdo->lastInsertId();
// 3. 读己之写(在同一事务内,绝对一致)
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$newId]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$pdo->commit();
print_r($user); // 一定能看到刚写入的数据
} catch (Exception $e) {
$pdo->rollBack();
}
重要陷阱:
- 不要依赖
SELECT的普通查询去判断是否写入成功(因为可能读到旧缓存),要基于rowCount()或lastInsertId()。 - 如果写入后关闭了连接,再开新连接读取,如果数据库主从分离(写主库、读从库),会有主从延迟,此时读不到是正常的,需要使用半同步复制或等待机制。
文件系统场景(文件读写)
PHP 写入文件后立刻读取,核心问题是缓冲区(Buffer)。
反例(读不到):
<?php
file_put_contents('test.txt', 'Hello World');
echo file_get_contents('test.txt'); // 注意:这里偶尔会失败,因为输出缓冲未刷新到磁盘
正解:使用 fopen + fwrite + fflush 强制刷新缓冲区。
<?php
$file = fopen('test.txt', 'w');
fwrite($file, 'Hello World');
fflush($file); // 强制将缓冲区数据写入磁盘
rewind($file); // 将文件指针移回开头
echo stream_get_contents($file); // 输出: Hello World
fclose($file);
针对大文件的特殊技巧:如果文件很大,不要 rewind,直接 fopen 一个新的读取流。
$handle = fopen('large_file.txt', 'a+');
fwrite($handle, 'append data');
// 关闭写入流,立即打开读取流
fclose($handle);
echo file_get_contents('large_file.txt'); // 此时必定能读到
Session 与 Cookie(会话数据)
PHP 的 Session 机制天然支持“读己之写”。
<?php session_start(); $_SESSION['username'] = 'Alice'; // 写入 // 注意:Session 数据尚未落盘,但在本次请求内你直接读取是 OK 的 echo $_SESSION['username']; // 输出: Alice // 如果要在另外一个请求读取,必须等 session_write_close() 执行 session_write_close();
陷阱:如果你在写入 Session 后,立即在同一个请求中做复杂的重定向或长耗时操作,请显式调用 session_write_close() 释放锁,避免其他请求阻塞。
缓存系统(Redis/Memcached)
核心问题:Redis 是单线程的,写后立即读必定成功,但需要注意序列化和过期时间。
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 写
$redis->set('user:100', json_encode(['name' => 'Bob']));
// 读(立即读,必然有值)
$data = $redis->get('user:100');
echo $data; // 输出: {"name":"Bob"}
特别注意:如果你设置了 EX 过期时间极短(如 1秒),建议在读之前用 EXISTS 检查,避免读到 null 导致报错。
终极避坑指南(“读己之写” 失败的三大元凶)
- 主从分离延迟:写主库,读从库,解决方案:读请求强制走主库(在 Laravel 中设置
DB::connection()->setReadWriteType('write')或使用延迟阈值)。 - PHP 输出缓冲:在写文件时忘了
fflush或ob_flush。 - 事务未提交:写入后
rollback,但代码逻辑执行了读取逻辑(需检查异常处理流程)。
面试/进阶考点:如何测试“读己之写”的一致性?
如果面试官问“你怎么验证你的代码做到了读己之写?”,标准答案是:
“我会使用事务确保读写原子性,并利用
lastInsertId作为读取的唯一键,对于高并发场景,我会引入版本号(乐观锁),在读取时校验updated_at或version字段,若匹配则读取成功,否则重试。”
如果你需要我针对某个具体框架(如 Laravel、ThinkPHP) 给出读取刚写入数据的专属写法,可以继续告诉我。