MySQL锁机制详解:共享锁和排他锁是MySQL中最基础的两种锁类型。共享锁允许多个事务同时读取数据,但不能修改;排他锁则不允许其他事务读写被锁定的数据。在PHP中,使用InnoDB引擎时,可以通过SELECT ... FOR UPDATE来获取排他锁,防止幻读和不可重复读。代码示例:$pdo->exec("BEGIN;"); $stmt = $pdo->prepare("SELECT amount FROM accounts WHERE id = ? FOR UPDATE"); $stmt->execute([$accountId]); $row = $stmt->fetch(); // 修改余额 $pdo->exec("COMMIT;"); 这在转账场景中非常实用,确保余额不会被并发修改。
行锁的应用
行锁是InnoDB的默认锁粒度,只锁定单行数据。PHP中处理库存扣减时,使用行锁避免超卖:BEGIN; SELECT stock FROM products WHERE id=1 FOR UPDATE; 如果stock>0,则UPDATE products SET stock=stock-1 WHERE id=1; COMMIT; 这比表锁高效,因为只锁当前商品行,其他商品不受影响。
表锁的场景
表锁适用于小表或批量操作,如MyISAM引擎。PHP代码:LOCK TABLES users READ; // 读取操作 UNLOCK TABLES; 在报表生成时用读锁,防止写操作干扰,但大表慎用,会阻塞所有事务。
间隙锁和Next-Key锁
InnoDB的间隙锁防止幻读,在范围查询时生效。比如SELECT * FROM orders WHERE status='pending' FOR UPDATE; 会锁住status范围内的间隙。PHP电商订单处理中,这确保新订单不会插入已锁范围,避免并发冲突。
死锁检测与避免
死锁常见于多表更新,按固定顺序加锁可避免:总是先锁表A再锁表B。PHP中用try-catch捕获死锁错误:try { $pdo->exec("START TRANSACTION"); // 操作 COMMIT; } catch (PDOException $e) { if (strpos($e->getMessage(), 'Deadlock') !== false) { $pdo->exec("ROLLBACK"); // 重试逻辑 } }
乐观锁实现
乐观锁不用数据库锁,通过version字段检查:SELECT amount, version FROM accounts WHERE id=?; if ($newVersion != $oldVersion) { 冲突,重试; } else { UPDATE accounts SET amount=?, version=version+1 WHERE id=? AND version=?; } PHP中用CAS模式,高并发读多写少场景首选。
Redis分布式锁辅助
跨数据库锁用Redis:$redis->setnx("lock:key", time() + 30); if ($redis->get("lock:key") == $val) { // 执行业务 $redis->del("lock:key"); } 防止PHP多进程并发问题,超时自动释放。
FAQ
Q: 什么时候用共享锁?
A: 多个事务只读同一数据时,如查询用户列表,用SELECT ... LOCK IN SHARE MODE;。
Q: 如何处理死锁?
A: 捕获错误后回滚并指数退避重试,或统一加锁顺序。
Q: 表锁和行锁哪个好?
A: 行锁并发高,用InnoDB;表锁简单但阻塞多,用小表。
Q: PHP中锁超时怎么设?
A: innodb_lock_wait_timeout设置等待秒数,默认50秒。