Skip to content

Commit 9ddad55

Browse files
committed
fix: sqlite database lock solved by setting max_conn to 1
1 parent 22575db commit 9ddad55

File tree

5 files changed

+186
-181
lines changed

5 files changed

+186
-181
lines changed

internal/config/database.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ func DefaultDatabaseConfig() *DatabaseConfig {
2525
return &DatabaseConfig{
2626
DataDir: utils.DbDir,
2727
DatabaseName: "codebase_indexer.db",
28-
MaxOpenConns: 5,
29-
MaxIdleConns: 3,
30-
ConnMaxLifetime: 15 * time.Minute,
31-
ConnMaxIdleTime: 3 * time.Minute, // 连接空闲超过3分钟则关闭
28+
MaxOpenConns: 1, // SQLite单写设计,强制单连接避免SQLITE_BUSY
29+
MaxIdleConns: 1,
30+
ConnMaxLifetime: 0, // 不限制,避免频繁重建连接
31+
ConnMaxIdleTime: 0, // 不关闭空闲连接
3232
EnableWAL: true,
3333
EnableForeignKeys: true,
3434
BatchDeleteSize: 1000, // 默认每批删除1000条记录

internal/database/manager.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ func (m *SQLiteManager) Initialize() error {
5959

6060
// 打开数据库连接
6161
// db, err := sql.Open("sqlite3", dbPath+"?_foreign_keys=on&_journal_mode=WAL")
62-
db, err := sql.Open("sqlite", dbPath+"?_foreign_keys=on&_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL")
62+
db, err := sql.Open("sqlite", dbPath+
63+
"?_foreign_keys=on"+
64+
"&_journal_mode=WAL"+
65+
"&_busy_timeout=10000"+ // 增加到10秒,给足够等待时间
66+
"&_synchronous=NORMAL"+
67+
"&cache_size=-8000"+ // 8MB缓存,轻量级
68+
"&_wal_autocheckpoint=100") // 每100页checkpoint,减小WAL文件
6369
if err != nil {
6470
return err
6571
}

internal/database/manager_test.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,9 @@ func TestSQLiteManagerConcurrency(t *testing.T) {
219219
dbConfig := &config.DatabaseConfig{
220220
DataDir: tempDir,
221221
DatabaseName: "test-concurrency.db",
222-
MaxOpenConns: 10,
223-
MaxIdleConns: 5,
224-
ConnMaxLifetime: 30 * time.Minute,
222+
MaxOpenConns: 1, // 使用单连接,符合生产环境配置
223+
MaxIdleConns: 1,
224+
ConnMaxLifetime: 0,
225225
}
226226

227227
// 创建数据库管理器
@@ -257,9 +257,8 @@ func TestSQLiteManagerConcurrency(t *testing.T) {
257257

258258
t.Run("ConcurrentTransactions", func(t *testing.T) {
259259
// 测试并发事务
260-
// 注意:SQLite即使在WAL模式下,也只能有一个写事务同时提交
261-
// 这个测试使用较少的并发数和更长的重试来验证重试机制
262-
concurrentCount := 3 // 减少并发数从5到3
260+
// 使用单连接模式(MaxOpenConns=1),事务会自动串行执行,不会出现SQLITE_BUSY
261+
concurrentCount := 3
263262
done := make(chan error, concurrentCount)
264263

265264
for i := 0; i < concurrentCount; i++ {
@@ -270,14 +269,14 @@ func TestSQLiteManagerConcurrency(t *testing.T) {
270269
}
271270
}()
272271

273-
// 添加重试逻辑处理SQLITE_BUSY错误
274-
maxRetries := 50 // 增加重试次数
272+
// 单连接模式下,事务会排队执行,增加重试次数和延迟以应对排队等待
273+
maxRetries := 100 // 增加重试次数以应对排队
275274
var lastErr error
276275
for retry := 0; retry < maxRetries; retry++ {
277276
tx, err := dbManager.BeginTransaction()
278277
if err != nil {
279278
lastErr = err
280-
time.Sleep(time.Millisecond * 100) // 增加延迟
279+
time.Sleep(time.Millisecond * 200) // 增加延迟以给其他事务完成的时间
281280
continue
282281
}
283282

@@ -287,7 +286,7 @@ func TestSQLiteManagerConcurrency(t *testing.T) {
287286
if err != nil {
288287
tx.Rollback()
289288
lastErr = err
290-
time.Sleep(time.Millisecond * 100)
289+
time.Sleep(time.Millisecond * 200)
291290
continue
292291
}
293292

@@ -297,7 +296,7 @@ func TestSQLiteManagerConcurrency(t *testing.T) {
297296
return
298297
}
299298
lastErr = err
300-
time.Sleep(time.Millisecond * 100)
299+
time.Sleep(time.Millisecond * 200)
301300
}
302301
done <- fmt.Errorf("failed after %d retries: %v", maxRetries, lastErr)
303302
}(i)

internal/database/transaction.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package database
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
)
7+
8+
// ExecuteInTransaction 在事务中执行函数,自动处理提交和回滚(遵循 DRY 原则)
9+
//
10+
// 此函数封装了常见的事务处理模式:
11+
// - 开启事务
12+
// - 执行业务逻辑
13+
// - 发生错误时回滚
14+
// - 成功时提交
15+
// - 处理 panic 确保回滚
16+
//
17+
// 示例:
18+
//
19+
// err := ExecuteInTransaction(db, func(tx *sql.Tx) error {
20+
// _, err := tx.Exec("INSERT INTO ...")
21+
// return err
22+
// })
23+
func ExecuteInTransaction(db DatabaseManager, fn func(tx *sql.Tx) error) error {
24+
tx, err := db.BeginTransaction()
25+
if err != nil {
26+
return fmt.Errorf("failed to begin transaction: %w", err)
27+
}
28+
29+
// 处理 panic 确保回滚
30+
defer func() {
31+
if p := recover(); p != nil {
32+
tx.Rollback()
33+
panic(p) // 重新抛出 panic
34+
}
35+
}()
36+
37+
// 执行业务逻辑
38+
if err := fn(tx); err != nil {
39+
// 回滚失败也要记录
40+
if rbErr := tx.Rollback(); rbErr != nil {
41+
return fmt.Errorf("transaction failed: %w, rollback error: %v", err, rbErr)
42+
}
43+
return err
44+
}
45+
46+
// 提交事务
47+
if err := tx.Commit(); err != nil {
48+
return fmt.Errorf("failed to commit transaction: %w", err)
49+
}
50+
51+
return nil
52+
}

0 commit comments

Comments
 (0)