一、基于数据库表的方案
- 悲观锁实现(行锁)
sql
-- 1. 创建锁表
CREATE TABLE `distributed_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`lock_key` varchar(64) NOT NULL COMMENT '锁标识',
`business_id` varchar(255) DEFAULT NULL COMMENT '业务标识',
`expire_time` datetime DEFAULT NULL COMMENT '过期时间',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_lock_key` (`lock_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2. 获取锁
BEGIN;
SELECT * FROM distributed_lock
WHERE lock_key = 'order_lock_123'
FOR UPDATE;
-- 检查锁是否过期
-- 如果无记录或已过期,插入或更新
INSERT INTO distributed_lock(lock_key, business_id, expire_time)
VALUES ('order_lock_123', 'order_001', DATE_ADD(NOW(), INTERVAL 30 SECOND))
ON DUPLICATE KEY UPDATE
business_id = VALUES(business_id),
expire_time = VALUES(expire_time);
COMMIT;
-- 3. 释放锁
DELETE FROM distributed_lock
WHERE lock_key = 'order_lock_123'
AND business_id = 'order_001';
- 乐观锁实现(版本号)
sql
-- 创建带版本号的锁表
CREATE TABLE `optimistic_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`resource_name` varchar(64) NOT NULL COMMENT '资源名',
`version` int(11) NOT NULL DEFAULT '0' COMMENT '版本号',
`owner` varchar(64) DEFAULT NULL COMMENT '持有者',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_resource` (`resource_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 获取锁(CAS操作)
UPDATE optimistic_lock
SET version = version + 1,
owner = 'service_A'
WHERE resource_name = 'resource_1'
AND version = #{oldVersion};
二、基于GET_LOCK()函数的方案
sql
-- 1. 获取锁(会话级)
SELECT GET_LOCK('my_lock', 10); -- 等待10秒
-- 2. 检查是否获取成功
SELECT IS_FREE_LOCK('my_lock');
SELECT IS_USED_LOCK('my_lock');
-- 3. 释放锁
SELECT RELEASE_LOCK('my_lock');
-- 4. 释放所有锁
SELECT RELEASE_ALL_LOCKS();
三、完整实现示例
Java 实现类
java
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
public class MySQLDistributedLock {
private DataSource dataSource;
private String lockKey;
private long expireMillis = 30000;
private String ownerId;
public MySQLDistributedLock(DataSource dataSource, String lockKey) {
this.dataSource = dataSource;
this.lockKey = lockKey;
this.ownerId = generateOwnerId();
}
public boolean tryLock(long waitTime, TimeUnit unit) throws SQLException {
long start = System.currentTimeMillis();
long timeout = unit.toMillis(waitTime);
while (System.currentTimeMillis() - start < timeout) {
if (acquireLock()) {
return true;
}
try {
Thread.sleep(100); // 重试间隔
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
private boolean acquireLock() throws SQLException {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
// 使用行级锁
String checkSql = "SELECT id, expire_time FROM distributed_lock " +
"WHERE lock_key = ? FOR UPDATE";
try (PreparedStatement ps = conn.prepareStatement(checkSql)) {
ps.setString(1, lockKey);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
// 检查是否过期
java.sql.Timestamp expireTime = rs.getTimestamp("expire_time");
if (expireTime != null && expireTime.after(new java.util.Date())) {
// 锁被其他进程持有且未过期
conn.rollback();
return false;
}
}
// 获取或更新锁
String upsertSql = "INSERT INTO distributed_lock " +
"(lock_key, owner_id, expire_time) VALUES (?, ?, ?) " +
"ON DUPLICATE KEY UPDATE " +
"owner_id = VALUES(owner_id), " +
"expire_time = VALUES(expire_time)";
try (PreparedStatement upsertPs = conn.prepareStatement(upsertSql)) {
upsertPs.setString(1, lockKey);
upsertPs.setString(2, ownerId);
upsertPs.setTimestamp(3, new java.sql.Timestamp(
System.currentTimeMillis() + expireMillis));
int rows = upsertPs.executeUpdate();
conn.commit();
return rows > 0;
}
}
} catch (SQLException e) {
if (conn != null) {
conn.rollback();
}
throw e;
} finally {
if (conn != null) {
conn.setAutoCommit(true);
conn.close();
}
}
}
public void unlock() throws SQLException {
String sql = "DELETE FROM distributed_lock " +
"WHERE lock_key = ? AND owner_id = ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, lockKey);
ps.setString(2, ownerId);
ps.executeUpdate();
}
}
private String generateOwnerId() {
return Thread.currentThread().getName() +
"_" + System.currentTimeMillis() +
"_" + Math.random();
}
}
Spring Boot 集成
java
@Component
public class DistributedLockService {
@Autowired
private JdbcTemplate jdbcTemplate;
public <T> T executeWithLock(String lockKey, long waitTime,
TimeUnit unit, Supplier<T> supplier) {
MySQLDistributedLock lock = new MySQLDistributedLock(
jdbcTemplate.getDataSource(), lockKey);
try {
if (lock.tryLock(waitTime, unit)) {
try {
return supplier.get();
} finally {
lock.unlock();
}
} else {
throw new RuntimeException("获取分布式锁失败: " + lockKey);
}
} catch (SQLException e) {
throw new RuntimeException("分布式锁异常", e);
}
}
// 使用示例
public void processOrder(String orderId) {
String lockKey = "order_lock_" + orderId;
executeWithLock(lockKey, 5, TimeUnit.SECONDS, () -> {
// 业务逻辑
updateOrderStatus(orderId);
return null;
});
}
}
四、进阶优化方案
- 可重入锁实现
sql
-- 支持可重入的锁表结构
CREATE TABLE `reentrant_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`lock_key` varchar(64) NOT NULL,
`owner_id` varchar(128) NOT NULL,
`count` int(11) NOT NULL DEFAULT '0',
`expire_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_lock_key` (`lock_key`)
);
- 锁续期机制
java
public class LockRenewalTask implements Runnable {
private MySQLDistributedLock lock;
private volatile boolean running = true;
@Override
public void run() {
while (running) {
try {
Thread.sleep(10000); // 每10秒续期一次
renewLock();
} catch (Exception e) {
// 处理异常
}
}
}
private void renewLock() throws SQLException {
String sql = "UPDATE distributed_lock " +
"SET expire_time = DATE_ADD(NOW(), INTERVAL 30 SECOND) " +
"WHERE lock_key = ? AND owner_id = ?";
// 执行更新
}
}
五、各方案对比
方案 优点 缺点 适用场景
基于行锁 实现简单,强一致 性能较低,可能死锁 低并发,强一致性要求
乐观锁 并发性好 需要重试机制 并发冲突少的场景
GET_LOCK() MySQL原生支持 会话级别,连接断开自动释放 简单同步需求
六、最佳实践建议
- 锁粒度控制:尽量缩小锁的范围,使用细粒度锁
- 超时设置:必须设置锁超时时间,防止死锁
- 锁清理:定期清理过期锁记录
- 监控报警:监控锁等待时间和死锁情况
- 降级方案:考虑锁获取失败时的降级策略
七、注意事项
- 数据库性能:高并发场景下,数据库锁可能成为瓶颈
- 死锁风险:注意事务执行顺序,避免死锁
- 连接池配置:确保数据库连接足够
- 时钟同步:多节点服务器时间需要同步
- 错误处理:妥善处理网络异常和数据库异常
对于高性能要求的场景,建议考虑 Redis 或 ZooKeeper 作为分布式锁的实现方案。MySQL 分布式锁更适合于:
· 已有 MySQL 基础设施
· 并发量不高(QPS < 1000)
· 需要强一致性保证
· 系统简单,不希望引入额外中间件