前言
MySQL 实现分布式锁,常见的方案有以下几种:
- 基于唯一索引(INSERT 操作)实现
- 基于排他锁(SELECT FOR UPDATE)实现
- 基于乐观锁(CAS)实现
基于 MySQL 实现分布式锁,需要注意的实现细节
- 锁是否可重入
- 是否会引发死锁
- TTL 机制,即超时自动释放锁
- 锁的公平性(公平锁、非公平锁)
- 规定只能释放自己的锁,不能误释放别人的锁。
实现方案一
基于唯一索引(INSERT 操作),通过使用表(InnoDB)中的行级锁和事务来实现分布式锁。
创建锁表
- 创建一个专门用于锁的表,使用 InnoDB 存储引擎,并设置锁名称所在的列为主键(唯一)。
- 为了防止死锁,需要设置锁的过期时间。在创建锁表时,添加一个带有过期时间的列,并在获取锁时更新这一列。
- 为了确保每个线程只能释放自己加的锁,在创建锁表时,需要添加一个唯一标识符(如线程 ID)。这样每个线程在获取锁时会记录自己的 ID,只有拥有相同 ID 的线程才能释放该锁。
1 2 3 4 5 6 7
| CREATE TABLE distributed_locks ( lock_name VARCHAR(255) NOT NULL PRIMARY KEY, owner_id VARCHAR(255) NOT NULL, locked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL ) ENGINE=InnoDB;
|
获取锁
- 在获取锁时,可以使用
INSERT
语句尝试插入一条记录。如果记录插入失败(记录已经存在),则获取锁失败。 - 使用
INSERT IGNORE
或者 INSERT ... ON DUPLICATE KEY UPDATE
可以避免重复插入数据导致的错误。 - 在实现分布式锁时,使用
ROW_COUNT()
可以检查获取锁的操作是否成功。ROW_COUNT()
是一个 MySQL 函数,用于返回上一个执行的 INSERT
、UPDATE
或 DELETE
语句影响的行数。 - 通过下述的
ON DUPLICATE KEY UPDATE
部分,如果锁已经存在且没有过期,则不会更新锁的过期时间和锁的唯一标识符。如果锁已经存在且已经过期,则会更新锁的过期时间和锁的唯一标识符,并重新获取锁。
1 2 3 4 5 6 7 8 9
| INSERT INTO distributed_locks (lock_name, owner_id, expires_at) VALUES ('your_lock_name', 'your_thread_id', DATE_ADD(NOW(), INTERVAL 10 SECOND)) ON DUPLICATE KEY UPDATE owner_id = IF(expires_at < NOW(), VALUES(owner_id), owner_id), expires_at = IF(expires_at < NOW(), VALUES(expires_at), expires_at);
SELECT ROW_COUNT();
|
释放锁
- 通过删除对应的记录来释放锁。
- 确保只有持有该锁的线程才能释放锁,不能释放其他线程持有的锁。
1 2
| DELETE FROM distributed_locks WHERE lock_name = 'your_lock_name' AND owner_id = 'your_thread_id';
|
检测锁是否过期(可选)
- 在应用层可以定期检查锁的状态,并在锁过期时删除过期的锁记录。
1 2
| DELETE FROM distributed_locks WHERE expires_at < NOW();
|
参考资料