本文目录导读:

在 PHP 项目中,合理选用共享锁(读锁)和排他锁(写锁)的核心原则是根据并发访问中数据一致性与性能的平衡需求。
简单记忆:读多写少用共享锁(提高并发),写多或写操作依赖读结果用排他锁(保证安全)。
下面从场景、代码示例、性能权衡三个维度详细说明。
核心区别回顾
| 锁类型 | 英文 | 别称 | 特点 | 能否并发读 | 能否并发写 |
|---|---|---|---|---|---|
| 共享锁 | Shared Lock (S) | 读锁 | 允许多个进程同时读取 | 是 | 否 |
| 排他锁 | Exclusive Lock (X) | 写锁 | 只允许一个进程独占 | 否 | 否 |
场景选用指南
场景 1:数据缓存重建(高频、典型)
背景:缓存过期后,多个请求同时回源数据库查询并写入缓存,如果不加锁,会导致“缓存击穿”或“雪崩”。
推荐:排他锁(写锁)
- 原因:只允许一个进程去查数据库并写缓存,其他进程等待或直接使用旧缓存。
- 反例:如果只用共享锁,多个进程会同时读到“缓存不存在”的状态,然后都去查询数据库,造成数据库压力暴增。
// 伪代码:使用文件锁实现 $lockFile = '/tmp/cache_lock_user_123.lock'; $fp = fopen($lockFile, 'w+'); flock($fp, LOCK_EX); // 排他锁:只允许一个进程进入 $data = getFromDatabase(); // 耗时操作 setToCache($key, $data, 3600); flock($fp, LOCK_UN); fclose($fp);
场景 2:高并发读取、低频率写入(如配置信息)
背景:一个秒杀活动的配置(如开始时间、限购数量),大部分时间被读取,偶尔由后台管理员修改。
推荐:共享锁(读锁)
- 原因:允许数百个请求同时读取配置,互不阻塞,性能极高。
- 注意:修改时必须先加排他锁,或改为只从主库/写库读取(如 MySQL 读写分离)。
// 读取配置时 $fp = fopen($lockFile, 'r'); flock($fp, LOCK_SH); // 共享锁:所有读进程不互斥 $config = readFromFile($fp); flock($fp, LOCK_UN); fclose($fp);
什么时候共享锁会出问题? 如果你在读取配置的过程中,依赖配置数据做后续写操作(如“读取余额→计算→写入新余额”),共享锁会导致读到脏数据,此时必须用排他锁。
场景 3:库存扣减 / 计数器(高一致性要求)
背景:秒杀场景下,商品库存数从 100 减为 0,多个请求同时读写。
推荐:排他锁(写锁)
- 原因:这是一个经典的“读-修改-写”原子操作,即使是共享锁,也无法阻止多个进程同时读到库存=10,然后都扣减,导致库存为负数。
- 更优方案:使用 Redis 原子操作(
DECR)或数据库行锁,避免文件锁的复杂性。
// 使用 Redis 原子操作(推荐替代排他锁)
$stock = $redis->decr('stock:item_123');
if ($stock < 0) {
$redis->incr('stock:item_123'); // 回滚
echo '库存不足';
}
场景 4:日志写入 / 异步队列(写多读少)
背景:多个 worker 进程同时向一个日志文件追加内容。
推荐:排他锁(写锁)
- 原因:一次只能有一个进程写入,防止数据交错乱行。
- 优化:写锁本身会串行化,但日志场景天然需要顺序,如果性能不足,可改用 Redis list 或 Kafka 收集后再落盘。
$fp = fopen('/var/log/app.log', 'a');
flock($fp, LOCK_EX); // 排他锁
fwrite($fp, date('Y-m-d H:i:s') . " - " . $message . PHP_EOL);
flock($fp, LOCK_UN);
fclose($fp);
合理选用的决策流(场景决策树)
这个操作是读还是写?
├── 纯读(无后续写) → 共享锁(允许并发读)
└── 写或读+写 → 进入2
2. 是否需要基于读取的结果来写?(如:读到旧值,修改后写回)
├── 是 → 必须用排他锁(保证事务隔离)
└── 否 → 进入3
3. 写操作频繁吗?
├── 非常频繁(每秒多次)→ 排他锁(并发写必然冲突)
└── 非常低(偶尔一次)→ 可用排他锁,或用“写时拷贝” + 共享锁(安全性较高,如配置文件)
常见误区与最佳实践
| 误区 | 正确做法 |
|---|---|
| 为了性能,所有读都用共享锁 | 但如果读后要写(如库存扣减),共享锁会导致超卖,这类场景必须用排他锁。 |
| 共享锁 = 完全不加锁 | 共享锁依然会阻止其他进程的写操作,适用于“读多写少”场景。 |
文件锁 (flock) 可以用于高并发数据库 |
文件锁受限于单机磁盘 I/O,高并发下推荐用 Redis、MySQL 行锁或分布式锁。 |
| 排他锁就是加锁,越短越好 | 排他锁持有期间会阻塞所有其他读/写,要尽量缩短加锁时间,不要在里面执行慢 SQL 或 sleep。 |
性能对比(文件锁为例)
| 场景 | 进程数 | 共享锁耗时 | 排他锁耗时 | |
|---|---|---|---|---|
| 100 个进程同时读 | 100 | 极快(几乎同时完成) | 很慢(串行执行) | 纯读场景,共享锁快数十倍 |
| 100 个进程同时写 | 100 | 无法使用(写只能排他) | 较慢(需排队) | 无选择,必须排他 |
| 50 读 + 50 写 | 100 | 读快写慢,但写时读也等待 | 所有都等待 | 此时需要读写锁(或 PHP 中的 flock 无法区分,只能整体串行)→ 建议改用数据库或 Redis 实现读写锁 |
总结一句话
- 如果并发场景是“多个线程/进程读取同一份数据,几乎不写” → 用共享锁(性能高)。
- 如果并发场景是“任何写操作(包括读后写)” → 用排他锁(保证数据一致性)。
- 如果同时有大量读和写(1:1 比例) → 不建议用文件锁,改用 Redis 读写锁或 MySQL 乐观锁。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播