fix: 添加只读链接 读写分离

This commit is contained in:
imeepos 2025-07-15 18:51:09 +08:00
parent 8d8e98188d
commit 0ea1b2cd38
2 changed files with 138 additions and 33 deletions

View File

@ -74,29 +74,52 @@ impl ModelRepository {
}
/// 根据ID获取模特
/// 使用读写分离避免嵌套锁问题
pub fn get_by_id(&self, id: &str) -> Result<Option<Model>> {
let conn = self.database.get_connection();
let conn = conn.lock().unwrap();
println!("get_by_id 开始执行model_id: {}", id);
let mut stmt = conn.prepare(
"SELECT id, name, stage_name, gender, age, height, weight, measurements,
description, tags, avatar_path, contact_info, social_media,
status, rating, created_at, updated_at, is_active
FROM models WHERE id = ?1"
)?;
// 首先获取模特基本信息(使用只读连接)
let model = {
match self.database.try_get_read_connection() {
Some(conn) => {
println!("get_by_id 使用只读连接获取基本信息");
let model_iter = stmt.query_map([id], |row| {
self.row_to_model(row)
})?;
let mut stmt = conn.prepare(
"SELECT id, name, stage_name, gender, age, height, weight, measurements,
description, tags, avatar_path, contact_info, social_media,
status, rating, created_at, updated_at, is_active
FROM models WHERE id = ?1"
)?;
for model in model_iter {
let mut model = model?;
// 加载照片信息
let model_iter = stmt.query_map([id], |row| {
self.row_to_model(row)
})?;
let mut result = None;
for model in model_iter {
result = Some(model?);
break;
}
result
},
None => {
println!("get_by_id 只读连接被占用,使用基本方法");
// 如果只读连接被占用,使用不加载照片的基本方法
return self.get_basic_by_id(id);
}
}
};
// 如果找到模特,加载照片信息(此时已释放了基本信息查询的锁)
if let Some(mut model) = model {
println!("get_by_id 开始加载照片信息");
model.photos = self.get_photos(&model.id)?;
return Ok(Some(model));
println!("get_by_id 执行完成");
Ok(Some(model))
} else {
println!("get_by_id 未找到模特");
Ok(None)
}
Ok(None)
}
/// 根据ID获取模特基本信息不加载照片避免死锁
@ -269,12 +292,25 @@ impl ModelRepository {
self.row_to_model(row)
})?;
// 先收集所有模特基本信息
let mut models = Vec::new();
for model in model_iter {
let mut model = model?;
// 加载照片信息
model.photos = self.get_photos(&model.id)?;
models.push(model);
models.push(model?);
}
// 释放连接锁
drop(stmt);
drop(conn);
// 为每个模特加载照片信息(避免嵌套锁)
for model in &mut models {
model.photos = match self.get_photos(&model.id) {
Ok(photos) => photos,
Err(e) => {
println!("search 加载模特 {} 的照片失败: {},跳过照片加载", model.id, e);
Vec::new()
}
};
}
Ok(models)
@ -373,22 +409,19 @@ impl ModelRepository {
}
/// 获取模特照片
/// 使用连接池优化的数据库访问模式,避免锁竞争
/// 使用专用只读连接,避免与写操作的锁竞争
pub fn get_photos(&self, model_id: &str) -> Result<Vec<ModelPhoto>> {
println!("get_photos 开始执行model_id: {}", model_id);
// 优先使用连接池,如果不可用则回退到单连接模式
match self.database.get_best_connection() {
// 使用专用的只读连接,避免与写操作竞争
match self.database.get_best_read_connection() {
Ok(conn) => {
println!("get_photos 成功获取数据库连接({}模式)",
if self.database.has_pool() { "连接池" } else { "单连接" });
let photos = self.execute_photo_query(&conn, model_id)?;
println!("get_photos 执行完成,返回 {} 张照片", photos.len());
Ok(photos)
},
Err(e) => {
println!("get_photos 无法获取数据库连接: {}", e);
println!("get_photos 无法获取只读数据库连接: {}", e);
// 如果所有连接都被占用返回空结果避免阻塞UI
Ok(Vec::new())
}

View File

@ -36,11 +36,13 @@ impl<'a> DerefMut for ConnectionHandle<'a> {
/// 数据库管理器
/// 遵循 Tauri 开发规范的数据库设计模式
/// 支持连接池以提高并发性能
/// 支持连接池以提高并发性能,实现读写分离
pub struct Database {
// 保留原有的单连接模式以兼容现有代码
// 主连接(用于写操作和复杂事务)
connection: Arc<Mutex<Connection>>,
// 新增连接池支持
// 只读连接(专门用于读操作,避免锁竞争)
read_connection: Arc<Mutex<Connection>>,
// 连接池支持
pool: Option<Arc<ConnectionPool>>,
}
@ -183,8 +185,23 @@ impl Database {
None
};
// 创建专用的只读连接
let read_connection = Connection::open(&db_path)?;
read_connection.pragma_update(None, "secure_delete", "ON")?;
read_connection.pragma_update(None, "temp_store", "MEMORY")?;
read_connection.pragma_update(None, "foreign_keys", "ON")?;
read_connection.pragma_update(None, "journal_mode", "WAL")?;
read_connection.pragma_update(None, "synchronous", "NORMAL")?;
read_connection.pragma_update(None, "cache_size", "10000")?;
read_connection.pragma_update(None, "busy_timeout", "5000")?;
// 只读连接设置为只读模式
read_connection.pragma_update(None, "query_only", "ON")?;
println!("只读数据库连接创建成功");
let database = Database {
connection: Arc::new(Mutex::new(connection)),
read_connection: Arc::new(Mutex::new(read_connection)),
pool,
};
@ -197,11 +214,17 @@ impl Database {
Ok(database)
}
/// 获取数据库连接
/// 获取数据库连接(用于写操作)
pub fn get_connection(&self) -> Arc<Mutex<Connection>> {
Arc::clone(&self.connection)
}
/// 获取只读数据库连接(用于读操作)
/// 这个连接专门用于查询操作,避免与写操作竞争锁
pub fn get_read_connection(&self) -> Arc<Mutex<Connection>> {
Arc::clone(&self.read_connection)
}
/// 检查数据库连接状态
pub fn check_connection_status(&self) -> String {
match self.connection.try_lock() {
@ -211,7 +234,7 @@ impl Database {
}
}
/// 尝试非阻塞获取数据库连接
/// 尝试非阻塞获取数据库连接(写连接)
/// 如果连接被占用,立即返回 None
pub fn try_get_connection(&self) -> Option<std::sync::MutexGuard<Connection>> {
match self.connection.try_lock() {
@ -220,6 +243,55 @@ impl Database {
}
}
/// 尝试非阻塞获取只读数据库连接
/// 如果连接被占用,立即返回 None
pub fn try_get_read_connection(&self) -> Option<std::sync::MutexGuard<Connection>> {
match self.read_connection.try_lock() {
Ok(conn) => Some(conn),
Err(_) => None,
}
}
/// 获取最佳的只读连接
/// 优先使用专用只读连接,如果不可用则尝试主连接
pub fn get_best_read_connection(&self) -> Result<ConnectionHandle> {
// 首先尝试专用只读连接
match self.read_connection.try_lock() {
Ok(conn) => {
println!("使用专用只读连接");
return Ok(ConnectionHandle::Single(conn));
},
Err(_) => {
println!("只读连接被占用,尝试连接池");
}
}
// 如果只读连接被占用,尝试连接池
if let Some(pool) = &self.pool {
match pool.try_acquire()? {
Some(conn) => {
println!("使用连接池连接进行读操作");
return Ok(ConnectionHandle::Pooled(conn));
},
None => {
println!("连接池无可用连接,尝试主连接");
}
}
}
// 最后尝试主连接(非阻塞)
match self.connection.try_lock() {
Ok(conn) => {
println!("使用主连接进行读操作");
Ok(ConnectionHandle::Single(conn))
},
Err(_) => {
println!("所有连接都被占用,读操作失败");
Err(anyhow!("所有数据库连接都被占用"))
}
}
}
/// 检查是否启用了连接池
pub fn has_pool(&self) -> bool {
self.pool.is_some()