示例服务开发中的全局锁
1 简介 全局锁 (Global Lock)
全局锁 是对整个数据库服务器的加锁,通常是使用 FLUSH TABLES WITH READ LOCK 来实现的。
全局锁会锁住整个数据库,使得其他事务不能访问数据库,直到锁被释放。
用途:全局锁通常用于备份场景,但由于它会锁住整个数据库,因此很容易引起性能瓶颈和死锁。在避免死锁时,尽量避免使用全局锁,或仅在必要时使用。
2 全局锁(Global Lock)详解
-
- 全局锁的设计模式
(1) 什么是全局锁?
全局锁(Global Lock)是一种作用于整个 MySQL 实例(Server)的锁,它会锁住整个数据库服务器,使所有的表不可写入。通常,全局锁是通过以下 SQL 语句实现的:
FLUSH TABLES WITH READ LOCK;
这条命令会使整个数据库进入只读模式,所有写操作(INSERT、UPDATE、DELETE、DROP 等)都会被阻塞,但读操作仍然可以进行。
(2) 设计目的
确保数据库在某个时刻的数据一致性,防止数据发生变化。
适用于备份或迁移数据库的场景,防止数据在导出过程中发生修改。
(3) 主要特性
作用范围广
影响整个 MySQL 实例,而不仅仅是某个数据库或某张表。
只允许读,不允许写
所有事务的写操作都会被阻塞,但 SELECT 查询仍然可以执行。
可能阻塞其他事务
如果长时间持有全局锁,其他写入操作会被阻塞,影响数据库正常业务。
(4) 释放全局锁
当完成数据库备份后,可以通过以下命令释放全局锁:
UNLOCK TABLES;
这样数据库就可以恢复正常读写了。
3 全局锁的使用场景
(1) 数据库备份
物理备份(如 mysqldump)需要在某个时间点冻结数据,防止数据写入不一致。
例如,在 MyISAM 存储引擎下,FLUSH TABLES WITH READ LOCK 可以确保数据一致性。
(2) 数据迁移
当需要从一个 MySQL 服务器迁移数据到另一台服务器时,可能会使用全局锁确保迁移期间数据一致。
(3) 防止临时修改
例如,在维护或者数据统计时,可以短时间锁住数据库,以防止数据被修改。
4. MySQL 中全局锁的使用示例
(1) 备份数据库
FLUSH TABLES WITH READ LOCK;
– 现在可以执行 mysqldump 进行备份
– 例如:mysqldump -u root -p --all-databases > backup.sql
UNLOCK TABLES;
解释:
FLUSH TABLES WITH READ LOCK; —— 锁住所有表,防止写入。
使用 mysqldump 进行数据备份。
UNLOCK TABLES; —— 释放锁,恢复数据库正常写入。
(2) 数据库迁移
FLUSH TABLES WITH READ LOCK;
– 迁移数据,例如拷贝 binlog 文件或数据文件
UNLOCK TABLES;
解释:
先锁住数据库,确保数据不会变化。
执行数据迁移(比如将 MySQL 数据目录拷贝到新服务器)。
迁移完成后,解除全局锁,恢复正常运行。
5 在 Gin 服务中使用全局锁
在框架的 Web API 中,我们可以结合 MySQL 全局锁,确保数据一致性,比如在数据库备份 API 中使用全局锁。
示例:API 触发数据库备份
(1) 依赖
使用 os/exec 调用 mysqldump 进行备份。
(2) 代码
var db *sql.DB
func init() {
var err error
dsn := "root:password@tcp(127.0.0.1:3306)/testdb?parseTime=true"
db, err = sql.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
}
// 触发数据库备份
func backupDatabase(c *gin.Context) {
// 1. 加全局锁
_, err := db.Exec("FLUSH TABLES WITH READ LOCK;")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "无法锁定数据库"})
return
}
// 2. 备份数据库
cmd := exec.Command("sh", "-c", "mysqldump -u root -pYOUR_PASSWORD testdb > backup.sql")
err = cmd.Run()
if err != nil {
// 释放锁
db.Exec("UNLOCK TABLES;")
c.JSON(http.StatusInternalServerError, gin.H{"error": "备份失败"})
return
}
// 3. 释放全局锁
_, err = db.Exec("UNLOCK TABLES;")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "无法解锁数据库"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "数据库备份成功"})
}
func main() {
r := gin.Default()
r.GET("/backup", backupDatabase)
r.Run(":8080")
}
6 全局锁的影响
(1) 优势
保证数据一致性
适用于备份、迁移等场景,防止数据在操作期间发生变更。
操作简单
只需 FLUSH TABLES WITH READ LOCK; 即可锁定所有表。
(2) 潜在问题
阻塞写入
所有写操作都会被阻塞,如果某个事务长时间持有全局锁,其他事务会处于等待状态,影响数据库性能。
可能导致死锁
如果某个进程在持有全局锁时崩溃或未释放锁,可能会导致死锁问题。
不能在 InnoDB 事务中使用
InnoDB 存储引擎提供多版本并发控制(MVCC),通常不需要全局锁进行备份,而是可以使用 一致性快照(Transaction Snapshot) 进行数据备份:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
-- 备份操作
COMMIT;
7 结论
特点 全局锁
影响范围 整个数据库实例
作用 只读锁,阻止写操作
适用场景 备份、迁移
影响 阻塞所有写入,可能影响业务
替代方案 InnoDB 一致性快照
全局锁适用于短时间冻结数据库,以保证数据一致性,但不适用于高并发场景。
在 API 服务 中,可以通过 FLUSH TABLES WITH READ LOCK 来进行数据库备份,但要注意及时释放锁,避免影响业务运行。
这种方式在数据库备份和迁移时非常实用,但在高并发应用中需要谨慎使用,以防止长时间阻塞写操作影响业务。
- 点赞
- 收藏
- 关注作者
评论(0)