From 0ea1b2cd381465e926ae60cf9335429c625074b4 Mon Sep 17 00:00:00 2001 From: imeepos Date: Tue, 15 Jul 2025 18:51:09 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E5=8F=AA=E8=AF=BB?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=20=E8=AF=BB=E5=86=99=E5=88=86=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/data/repositories/model_repository.rs | 89 +++++++++++++------ .../src-tauri/src/infrastructure/database.rs | 82 +++++++++++++++-- 2 files changed, 138 insertions(+), 33 deletions(-) diff --git a/apps/desktop/src-tauri/src/data/repositories/model_repository.rs b/apps/desktop/src-tauri/src/data/repositories/model_repository.rs index 9d9aa34..f16515a 100644 --- a/apps/desktop/src-tauri/src/data/repositories/model_repository.rs +++ b/apps/desktop/src-tauri/src/data/repositories/model_repository.rs @@ -74,29 +74,52 @@ impl ModelRepository { } /// 根据ID获取模特 + /// 使用读写分离避免嵌套锁问题 pub fn get_by_id(&self, id: &str) -> Result> { - 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> { 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()) } diff --git a/apps/desktop/src-tauri/src/infrastructure/database.rs b/apps/desktop/src-tauri/src/infrastructure/database.rs index a04bf5f..4f0b8c4 100644 --- a/apps/desktop/src-tauri/src/infrastructure/database.rs +++ b/apps/desktop/src-tauri/src/infrastructure/database.rs @@ -36,11 +36,13 @@ impl<'a> DerefMut for ConnectionHandle<'a> { /// 数据库管理器 /// 遵循 Tauri 开发规范的数据库设计模式 -/// 支持连接池以提高并发性能 +/// 支持连接池以提高并发性能,实现读写分离 pub struct Database { - // 保留原有的单连接模式以兼容现有代码 + // 主连接(用于写操作和复杂事务) connection: Arc>, - // 新增连接池支持 + // 只读连接(专门用于读操作,避免锁竞争) + read_connection: Arc>, + // 连接池支持 pool: Option>, } @@ -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> { Arc::clone(&self.connection) } + /// 获取只读数据库连接(用于读操作) + /// 这个连接专门用于查询操作,避免与写操作竞争锁 + pub fn get_read_connection(&self) -> Arc> { + 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> { match self.connection.try_lock() { @@ -220,6 +243,55 @@ impl Database { } } + /// 尝试非阻塞获取只读数据库连接 + /// 如果连接被占用,立即返回 None + pub fn try_get_read_connection(&self) -> Option> { + match self.read_connection.try_lock() { + Ok(conn) => Some(conn), + Err(_) => None, + } + } + + /// 获取最佳的只读连接 + /// 优先使用专用只读连接,如果不可用则尝试主连接 + pub fn get_best_read_connection(&self) -> Result { + // 首先尝试专用只读连接 + 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()