本文目录导读:

我来详细讲解PHP中悲观锁死锁的检测和处理方法。
什么是悲观锁死锁
基本概念
// 悲观锁:假设会发生冲突,在整个数据处理过程中将数据锁定 // 死锁:两个或多个事务互相等待对方释放锁,导致永久阻塞
死锁示例场景
// 事务A: 锁定资源1,等待资源2 // 事务B: 锁定资源2,等待资源1 // 结果:互相等待,形成死锁
数据库层面的死锁检测
MySQL死锁检测配置
// 开启死锁检测(默认开启) SET GLOBAL innodb_deadlock_detect = ON; // 设置锁等待超时时间(秒) SET GLOBAL innodb_lock_wait_timeout = 5;
死锁日志查看
-- 查看最近的死锁信息 SHOW ENGINE INNODB STATUS\G; -- 查看当前事务 SELECT * FROM information_schema.INNODB_TRX; -- 查看锁信息 SELECT * FROM information_schema.INNODB_LOCKS;
PHP实现死锁检测
1 基础死锁检测类
<?php
class DeadlockDetector
{
private $pdo;
private $maxRetry = 3;
private $retryDelay = 100; // 毫秒
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* 执行带死锁检测的事务
*/
public function executeWithDeadlockDetection(callable $callback)
{
$attempt = 0;
while ($attempt < $this->maxRetry) {
try {
$this->pdo->beginTransaction();
$result = $callback($this->pdo);
$this->pdo->commit();
return $result;
} catch (PDOException $e) {
$this->pdo->rollBack();
// 检查是否是死锁错误
if ($this->isDeadlockError($e)) {
$attempt++;
echo "检测到死锁,重试第 {$attempt} 次\n";
if ($attempt >= $this->maxRetry) {
throw new Exception('多次重试仍无法解决死锁问题');
}
// 随机延时,避免再次形成死锁
usleep($this->retryDelay * 1000 * random_int(1, 3));
continue;
}
throw $e;
}
}
}
/**
* 判断是否死锁错误
*/
private function isDeadlockError(PDOException $e)
{
// MySQL错误码:1213 死锁,1205 锁等待超时
$deadlockCodes = ['1213', '1205', '40001'];
$sqlState = $e->getCode();
$message = $e->getMessage();
// 检查错误码
foreach ($deadlockCodes as $code) {
if ($sqlState == $code || strpos($message, $code) !== false) {
return true;
}
}
// 检查错误信息
return (strpos($message, 'Deadlock found') !== false ||
strpos($message, 'Lock wait timeout') !== false);
}
}
2 乐观锁与死锁检测对比
<?php
/**
* 乐观锁实现(解决并发冲突的另一种方式)
*/
class OptimisticLockHandler
{
private $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* 使用版本号实现乐观锁
*/
public function updateWithVersion($table, $id, $data)
{
$sql = "UPDATE {$table} SET
data = :data,
version = version + 1
WHERE id = :id AND version = :version";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([
':data' => json_encode($data),
':id' => $id,
':version' => $data['version']
]);
if ($stmt->rowCount() === 0) {
throw new Exception('数据已被其他用户修改,请刷新后重试');
}
}
}
3 死锁预防策略
<?php
class DeadlockPrevention
{
private $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* 统一的锁获取顺序
*/
public function lockMultipleResources(array $resources)
{
// 排序,保证所有事务按相同顺序获取锁
sort($resources);
$this->pdo->beginTransaction();
foreach ($resources as $resource) {
$sql = "SELECT * FROM resources
WHERE resource_id = :id
FOR UPDATE";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $resource]);
if ($stmt->rowCount() === 0) {
$this->pdo->rollBack();
throw new Exception("资源 {$resource} 不存在");
}
}
}
/**
* 设置合理的锁等待时间
*/
public function setLockTimeout($seconds)
{
$this->pdo->exec("SET innodb_lock_wait_timeout = {$seconds}");
}
/**
* 使用GET_LOCK实现应用级锁
*/
public function acquireAppLock($lockName, $timeout = 5)
{
$sql = "SELECT GET_LOCK(:lock_name, :timeout)";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([
':lock_name' => $lockName,
':timeout' => $timeout
]);
return (bool)$stmt->fetchColumn();
}
/**
* 释放应用级锁
*/
public function releaseAppLock($lockName)
{
$sql = "SELECT RELEASE_LOCK(:lock_name)";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':lock_name' => $lockName]);
}
}
4 实时死锁监控类
<?php
class DeadlockMonitor
{
private $pdo;
private $logFile;
public function __construct(PDO $pdo, $logFile = '/tmp/deadlock.log')
{
$this->pdo = $pdo;
$this->logFile = $logFile;
}
/**
* 主动检测当前死锁状态
*/
public function detectActiveDeadlocks()
{
// 查询当前有等待的事务
$sql = "
SELECT
trx_id,
trx_state,
trx_started,
trx_wait_started,
trx_mysql_thread_id,
trx_query
FROM information_schema.INNODB_TRX
WHERE trx_state = 'LOCK WAIT'
";
$stmt = $this->pdo->query($sql);
$waitingTransactions = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($waitingTransactions) > 0) {
$this->logDeadlockSituation($waitingTransactions);
return $this->analyzeDeadlockPotential($waitingTransactions);
}
return false;
}
/**
* 分析死锁可能性
*/
private function analyzeDeadlockPotential($transactions)
{
// 简化分析:如果多个事务都在等待,很可能发生死锁
$waitMap = [];
foreach ($transactions as $trx) {
$waitMap[$trx['trx_mysql_thread_id']] = [
'query' => $trx['trx_query'],
'started' => $trx['trx_started']
];
}
// 如果等待关系形成环,则存在死锁
return $this->hasCircularWait($waitMap);
}
/**
* 检查是否有循环等待
*/
private function hasCircularWait($waitMap)
{
// 简化实现,实际需要构建等待图并检测环
// 这里可以根据具体业务逻辑实现更复杂的检测
return count($waitMap) > 1;
}
/**
* 记录死锁日志
*/
private function logDeadlockSituation($transactions)
{
$logData = [
'time' => date('Y-m-d H:i:s'),
'transactions' => $transactions
];
file_put_contents(
$this->logFile,
json_encode($logData, JSON_PRETTY_PRINT) . "\n",
FILE_APPEND
);
}
/**
* 获取数据库死锁状态
*/
public function getInnoDBStatus()
{
$stmt = $this->pdo->query("SHOW ENGINE INNODB STATUS");
$result = $stmt->fetch(PDO::FETCH_ASSOC);
// 解析死锁部分
if (preg_match('/LATEST DETECTED DEADLOCK(.*?)----------------------/s',
$result['Status'], $matches)) {
return $matches[1];
}
return null;
}
}
5 完整使用示例
<?php
// 数据库连接
$pdo = new PDO(
'mysql:host=localhost;dbname=test;charset=utf8mb4',
'username',
'password',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5 // 连接超时
]
);
// 死锁检测器
$detector = new DeadlockDetector($pdo);
try {
// 执行带死锁检测的业务操作
$result = $detector->executeWithDeadlockDetection(function ($pdo) {
// 业务逻辑
$pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
return true;
});
echo "操作成功";
} catch (Exception $e) {
echo "操作失败: " . $e->getMessage();
}
// 死锁监控
$monitor = new DeadlockMonitor($pdo);
$hasDeadlock = $monitor->detectActiveDeadlocks();
if ($hasDeadlock) {
// 处理死锁情况
echo "检测到可能的死锁";
}
// 查看数据库死锁状态
$status = $monitor->getInnoDBStatus();
if ($status) {
echo "最近死锁详情:\n" . $status;
}
死锁处理的最佳实践
1 预防措施
// 1. 保持事务简短
$pdo->beginTransaction();
// 快速执行必要的操作
$pdo->commit();
// 2. 按固定顺序访问资源
function updateUsers($pdo, $userId1, $userId2) {
$ids = [$userId1, $userId2];
sort($ids); // 保证顺序
$pdo->beginTransaction();
foreach ($ids as $id) {
$pdo->exec("SELECT * FROM users WHERE id = {$id} FOR UPDATE");
}
// 继续业务逻辑
$pdo->commit();
}
// 3. 使用索引减少锁的范围
CREATE INDEX idx_user_id ON accounts(user_id) FOR UPDATE;
2 监控和告警
// 定时任务脚本
while (true) {
$monitor = new DeadlockMonitor($pdo);
if ($monitor->detectActiveDeadlocks()) {
// 发送告警
sendAlert('检测到数据库死锁');
// 记录日志
logDeadlock($monitor->getInnoDBStatus());
}
sleep(30); // 每30秒检查一次
}
推荐的死锁处理策略
- 设置合理的超时:避免无限等待
- 使用重试机制:自动重试失败的事务
- 死锁监控:实时监控并告警
- 优化查询:减少锁的范围和持有时间
- 统一锁顺序:避免循环等待
- 使用隔离级别:根据需求选择合适的隔离级别
通过以上方法,可以有效检测和处理PHP中的悲观锁死锁问题。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播