its-mieger/mysql-shared-locks

MySQL >= 5.7.5 使用的共享锁

1.1.0 2020-07-15 11:10 UTC

This package is auto-updated.

Last update: 2024-09-15 20:32:57 UTC


README

此包实现了基于MySQL的分布式应用的共享锁。

以下功能提供:

  • 无需预先注册的命名日志
  • 锁定获取的超时时间
  • 锁的TTL(存活时间)
  • 锁与数据库事务独立

实现的锁保证在持有锁的进程结束时释放(如果没有在之前显式释放)并且它们也保证即使在持有锁的进程挂起更长的时间,也不会阻塞超过它们的TTL。

实现细节

  • 使用数据库表(MyISAM引擎以忽略事务)管理锁
  • 通过数据库表的唯一索引防止两次尝试获取锁
  • 在表中插入锁后,使用MySQL的GET_LOCKRELEASE_LOCK函数来阻塞其他进程,直到锁再次空闲。
  • 锁表中的条目代表锁,而不是GET_LOCK对象。因此,任何仍持有当前锁的GET_LOCK对象的SQL会话将通过KILL终止。这也通知其他进程它不再拥有锁。
  • 在失败的获取尝试中清除过期的锁(TTL已超出)。

要求

  • PHP >= 5.6
  • MySQL >= 5.7.5(在5.7.5之前,每个会话只能获取一个命名锁)

数据库设置

运行以下SQL来创建锁表:

CREATE TABLE `shared_locks` (
  `name` varchar(64) NOT NULL,
  `connection_id` bigint(20) NOT NULL,
  `created` bigint(20) NOT NULL,
  `ttl` bigint(20) NOT NULL,
  `lock_acquired` tinyint(1) NOT NULL,
  UNIQUE KEY `shared_locks_ix1` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

示例用法

// configure database connection
SharedLock::configureConnection(DB_HOST, DB_DATABASE, DB_USER, DB_PASSWORD);

// Acquire lock (timeout = 5s, TTL = 10s)
$lock = SharedLock::lock($lockName, 5, 10);

// Release
$lock->release();

SQL标识符引号

默认情况下,使用MySQL的默认标识符引号'`'。但是,如果您使用ANSI_QUOTES引号'""',则可以调整引号样式。

SharedLock::setQuoteStyle(SharedLock::QUOTE_STYLE_ANSI);

注意一个进程很少会超过锁的TTL

想象一下,你有一个获取了锁的进程。当这个进程长时间挂起然后突然继续运行时,它获取的锁可能被另一个进程接管。这种情况很少发生,但仍然可能发生。

有两种策略来处理这种情况:

1. 使用相同的数据库连接进行锁和应用程序代码

您可以使用以下命令传递锁的数据库连接:

SharedLock::setConnection($pdoConnection);

然后使用事务,只有在锁未超时的情况下才提交数据。例如:

// start transaction
$pdoConnection->beginTransaction();

try {
	$lock = SharedLock::lock('namedLock', 5, 10);
	
	/*
	 * Your application code placed here
	 */

	// commit transaction
	$pdoConnection->commit();
			
}
catch (Exception $ex) {

	// rollback transaction
	$pdoConnection->rollBack();
	
	throw $ex;
}
finally {

	// release lock
	if (!empty($lock))
		$lock->release();
}

如果锁在此期间已释放,则SQL会话已经被杀死,因此您的交易将失败。

2. 使用assertTTL

如果使用相同的数据库连接进行锁和应用程序代码结合事务不是一种选择(或者您不使用锁来同步数据库操作),则可以使用assertTTL函数检查锁是否仍然持有,并且如果进程有足够的时间完成操作而不用担心在同时失去锁,则可以使用assertTTL函数。

try {
	$lock = SharedLock::lock('namedLock', 5, 10);
	
	/*
	 * Your application code placed here
	 */
	
	// check if still locked
	$lock->assertTTL(5);
	
	/*
	 * Your application code placed here
	 */
}
finally {

	// release lock
	if (!empty($lock))
		$lock->release();
}