PHP 怎么读己之写

wen PHP项目 2

本文目录导读:

PHP 怎么读己之写

  1. 数据库场景(最核心)
  2. 文件系统场景(文件读写)
  3. Session 与 Cookie(会话数据)
  4. 缓存系统(Redis/Memcached)
  5. 终极避坑指南(“读己之写” 失败的三大元凶)
  6. 面试/进阶考点:如何测试“读己之写”的一致性?

在 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 导致报错。


终极避坑指南(“读己之写” 失败的三大元凶)

  1. 主从分离延迟:写主库,读从库,解决方案:读请求强制走主库(在 Laravel 中设置 DB::connection()->setReadWriteType('write') 或使用延迟阈值)。
  2. PHP 输出缓冲:在写文件时忘了 fflushob_flush
  3. 事务未提交:写入后 rollback,但代码逻辑执行了读取逻辑(需检查异常处理流程)。

面试/进阶考点:如何测试“读己之写”的一致性?

如果面试官问“你怎么验证你的代码做到了读己之写?”,标准答案是:

“我会使用事务确保读写原子性,并利用 lastInsertId 作为读取的唯一键,对于高并发场景,我会引入版本号(乐观锁),在读取时校验 updated_atversion 字段,若匹配则读取成功,否则重试。”

如果你需要我针对某个具体框架(如 Laravel、ThinkPHP) 给出读取刚写入数据的专属写法,可以继续告诉我。

抱歉,评论功能暂时关闭!