PHP的Session默认采用文件存储方式,当一个请求调用session_start()时,会锁定对应的Session文件,直到请求执行结束才会释放锁。如果同一个用户同时发起多个请求,后续请求会因为无法获取Session文件锁而进入阻塞状态,这就是Session并发锁问题。这类问题会导致页面加载缓慢,甚至引发功能异常,需要开发者重点关注。

问题产生原因
PHP默认的Session存储处理器是files,其工作流程如下:
- 调用session_start()时,PHP会打开对应的Session文件并加独占锁
- 脚本执行过程中,所有Session读写操作都基于这个已加锁的文件
- 脚本执行结束后,Session文件才会关闭并释放锁
如果同一个用户的两个请求几乎同时到达,第一个请求持有Session锁,第二个请求就会一直等待锁释放,直到第一个请求执行完成。如果第一个请求执行时间较长,第二个请求的等待时间也会相应变长,表现为用户操作卡顿。
问题诊断方法
1. 观察请求响应时间
可以通过浏览器的开发者工具或者服务端日志,查看同一个用户多个并发请求的响应时间。如果部分请求的响应时间明显长于其他请求,且这些请求都涉及Session操作,就可能是并发锁问题。
2. 代码排查
检查项目中所有调用session_start()的位置,看是否存在长时间持有Session锁的情况,比如:
- session_start()之后执行了耗时操作,比如调用外部接口、大文件读写
- 脚本中没有及时关闭Session就继续执行其他逻辑
3. 模拟并发请求测试
可以编写简单的测试脚本,模拟同一个用户的多个并发请求,观察请求的执行情况。测试脚本示例如下:
<?php // 测试脚本 test_session.php session_start(); $_SESSION['test'] = time(); // 模拟耗时操作,睡眠3秒 sleep(3); echo "Request finished at " . time(); ?>
使用ab工具模拟两个并发请求:
ab -n 2 -c 2 http://127.0.0.1/test_session.php
如果第二个请求的完成时间比第一个晚至少3秒,说明存在Session并发锁问题。
解决方案
1. 及时释放Session锁
如果脚本中不需要再操作Session,可以尽早调用session_write_close()释放锁,后续逻辑可以继续执行,不会阻塞其他请求。示例代码如下:
<?php session_start(); // 读取或写入必要的Session数据 $user_id = $_SESSION['user_id'] ?? 0; // 不再需要操作Session,及时释放锁 session_write_close(); // 后续执行耗时操作,不会影响其他请求的Session操作 sleep(3); echo "User ID: " . $user_id; ?>
2. 使用自定义Session存储 handler
默认的files存储方式依赖文件锁,可以替换为其他存储方式,比如Redis、Memcached,这些存储方式通常没有文件锁的问题,并发性能更好。以Redis为例,配置方式如下:
<?php
// 连接Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 设置自定义Session处理器
session_set_save_handler(
function ($savePath, $sessionName) use ($redis) { return true; },
function () use ($redis) { return true; },
function ($sessionId) use ($redis) { return $redis->get("session:" . $sessionId); },
function ($sessionId, $data) use ($redis) { return $redis->set("session:" . $sessionId, $data, 3600); },
function ($sessionId) use ($redis) { return $redis->del("session:" . $sessionId); },
function ($maxLifetime) use ($redis) { return true; }
);
session_start();
?>
3. 调整Session配置
可以通过修改php.ini配置,调整Session的相关参数,比如:
- session.save_handler:改为redis或者memcached,使用非文件存储
- session.gc_maxlifetime:调整Session的垃圾回收时间,避免无用Session占用资源
- session.save_path:如果使用文件存储,可以将路径设置为有更好IO性能的目录,或者使用分层目录存储减少文件锁冲突
4. 避免不必要的Session操作
如果某些请求不需要使用Session,可以不调用session_start(),或者在不需要Session的页面中关闭Session自动启动。对于只读Session的场景,可以尝试在读取完成后立即释放锁,减少锁持有时间。
注意事项
在使用session_write_close()之后,不能再对Session进行写入操作,否则写入的数据不会生效。如果后续逻辑还需要读取Session数据,是可以正常读取的,因为Session数据已经加载到内存中。另外,更换Session存储 handler时,需要确保存储服务的可用性和性能,避免引入新的问题。
PHP_Session并发锁session_startfile_put_contentsflock修改时间:2026-06-13 07:36:15