MySQL 分布式锁实现方案

一、基于数据库表的方案

  1. 悲观锁实现(行锁)
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';
  1. 乐观锁实现(版本号)
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;
        });
    }
}

四、进阶优化方案

  1. 可重入锁实现
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`)
);
  1. 锁续期机制
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原生支持 会话级别,连接断开自动释放 简单同步需求

六、最佳实践建议

  1. 锁粒度控制:尽量缩小锁的范围,使用细粒度锁
  2. 超时设置:必须设置锁超时时间,防止死锁
  3. 锁清理:定期清理过期锁记录
  4. 监控报警:监控锁等待时间和死锁情况
  5. 降级方案:考虑锁获取失败时的降级策略

七、注意事项

  1. 数据库性能:高并发场景下,数据库锁可能成为瓶颈
  2. 死锁风险:注意事务执行顺序,避免死锁
  3. 连接池配置:确保数据库连接足够
  4. 时钟同步:多节点服务器时间需要同步
  5. 错误处理:妥善处理网络异常和数据库异常

对于高性能要求的场景,建议考虑 Redis 或 ZooKeeper 作为分布式锁的实现方案。MySQL 分布式锁更适合于:

· 已有 MySQL 基础设施

· 并发量不高(QPS < 1000)

· 需要强一致性保证

· 系统简单,不希望引入额外中间件

相关推荐
努力学编程呀(๑•ี_เ•ี๑)1 小时前
【405】Not Allowed
java·vue.js·nginx·node.js
未既2 小时前
docker & docker-compose离线部署步骤
java·docker
Zachery Pole2 小时前
JAVA_04_判断与循环
java·开发语言
Volunteer Technology2 小时前
LangGraph的WorkFlow(一)
java·服务器·windows
懒惰成性的2 小时前
11.Java的String类
java·开发语言
FoldWinCard2 小时前
Python 第三次作业
java·服务器·python
傻啦嘿哟2 小时前
Python列表排序:用key参数掌控排序规则
java·开发语言
大尚来也2 小时前
解决 IDEA 运行 Spring Boot 测试时“命令行过长”错误的终极方案
java·spring boot·intellij-idea
云姜.2 小时前
如何在idea上使用数据库
java·数据库·intellij-idea