# 服务器重启数据加载修复说明 ## 问题描述 服务器重启后没有正确载入底层数据库中的数据,导致内存存储为空。 ## 根本原因 在之前的实现中,服务器启动时只创建了空的 `MemoryStore`,但没有从数据库中加载已有的数据到内存中。这导致: 1. 服务器重启后,内存中的数据丢失 2. 查询操作无法找到已有数据 3. 插入操作可能产生重复数据 ## 修复方案 ### 1. 添加 `ListCollections` 方法到数据库适配器 为所有数据库适配器实现了 `ListCollections` 方法,用于获取数据库中所有现有的表(集合): **文件修改:** - `internal/database/base.go` - 添加基础方法声明 - `internal/database/sqlite/adapter.go` - SQLite 实现 - `internal/database/postgres/adapter.go` - PostgreSQL 实现 - `internal/database/dm8/adapter.go` - DM8 实现 **SQLite 示例代码:** ```go // ListCollections 获取所有集合(表)列表 func (a *SQLiteAdapter) ListCollections(ctx context.Context) ([]string, error) { query := `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name` rows, err := a.GetDB().QueryContext(ctx, query) if err != nil { return nil, err } defer rows.Close() var tables []string for rows.Next() { var table string if err := rows.Scan(&table); err != nil { return nil, err } tables = append(tables, table) } return tables, rows.Err() } ``` ### 2. 添加 `Initialize` 方法到 MemoryStore 在 `internal/engine/memory_store.go` 中添加了 `Initialize` 方法: ```go // Initialize 从数据库加载所有现有集合到内存 func (ms *MemoryStore) Initialize(ctx context.Context) error { if ms.adapter == nil { log.Println("[INFO] No database adapter, skipping initialization") return nil } // 获取所有现有集合 tables, err := ms.adapter.ListCollections(ctx) if err != nil { // 如果 ListCollections 未实现,返回 nil(不加载) if err.Error() == "not implemented" { log.Println("[WARN] ListCollections not implemented, skipping initialization") return nil } return fmt.Errorf("failed to list collections: %w", err) } log.Printf("[INFO] Found %d collections in database", len(tables)) // 逐个加载集合 loadedCount := 0 for _, tableName := range tables { // 从数据库加载所有文档 docs, err := ms.adapter.FindAll(ctx, tableName) if err != nil { log.Printf("[WARN] Failed to load collection %s: %v", tableName, err) continue } // 创建集合并加载文档 ms.mu.Lock() coll := &Collection{ name: tableName, documents: make(map[string]types.Document), } for _, doc := range docs { coll.documents[doc.ID] = doc } ms.collections[tableName] = coll ms.mu.Unlock() loadedCount++ log.Printf("[DEBUG] Loaded collection %s with %d documents", tableName, len(docs)) } log.Printf("[INFO] Successfully loaded %d collections from database", loadedCount) return nil } ``` ### 3. 在服务器启动时调用初始化 修改 `cmd/server/main.go`,在创建内存存储后立即调用初始化: ```go // 创建内存存储 store := engine.NewMemoryStore(adapter) // 从数据库加载现有数据到内存 log.Println("[INFO] Initializing memory store from database...") if err := store.Initialize(ctx); err != nil { log.Printf("[WARN] Failed to initialize memory store: %v", err) // 不阻止启动,继续运行 } // 创建 CRUD 处理器 crud := engine.NewCRUDHandler(store, adapter) ``` ## 工作流程 ``` 服务器启动流程: 1. 连接数据库 ↓ 2. 创建 MemoryStore ↓ 3. 【新增】调用 Initialize() 从数据库加载数据 ↓ 4. 创建 CRUDHandler ↓ 5. 启动 HTTP/TCP 服务器 ``` ## 测试方法 ### 快速测试(推荐) ```bash cd /home/kingecg/code/gomog ./test_quick.sh ``` **预期输出:** ``` ✅ 成功!服务器重启后正确加载了数据库中的数据 === 测试结果:SUCCESS === ``` ### 详细测试 ```bash ./test_reload_simple.sh ``` ### 手动测试 1. **启动服务器并插入数据** ```bash ./bin/gomog -config config.yaml # 在另一个终端插入数据 curl -X POST http://localhost:8080/api/v1/testdb/users/insert \ -H "Content-Type: application/json" \ -d '{"documents": [{"name": "Alice", "age": 30}]}' ``` 2. **验证数据已存入数据库** ```bash sqlite3 gomog.db "SELECT * FROM users;" ``` 3. **停止并重启服务器** ```bash # Ctrl+C 停止服务器 ./bin/gomog -config config.yaml ``` 4. **查询数据(验证是否加载)** ```bash curl -X POST http://localhost:8080/api/v1/testdb/users/find \ -H "Content-Type: application/json" \ -d '{"filter": {}}' ``` 应该能看到之前插入的数据。 ## 日志输出示例 成功的初始化日志: ``` 2026/03/14 22:00:00 [INFO] Connected to sqlite database 2026/03/14 22:00:00 [INFO] Initializing memory store from database... 2026/03/14 22:00:00 [INFO] Found 1 collections in database 2026/03/14 22:00:00 [DEBUG] Loaded collection users with 2 documents 2026/03/14 22:00:00 [INFO] Successfully loaded 1 collections from database 2026/03/14 22:00:00 Starting HTTP server on :8080 2026/03/14 22:00:00 Gomog server started successfully ``` ## 关键技术细节 ### 集合名称映射 由于 HTTP API 使用 `dbName.collection` 格式(如 `testdb.users`),而 SQLite 数据库中表名不带前缀(如 `users`),实现了智能名称映射: **1. Initialize 方法加载数据** ```go // 从数据库加载时使用纯表名(例如:users) ms.collections[tableName] = coll ``` **2. GetCollection 方法支持两种查找方式** ```go // 首先尝试完整名称(例如:testdb.users) coll, exists := ms.collections[name] if exists { return coll, nil } // 如果找不到,尝试去掉数据库前缀(例如:users) if idx := strings.Index(name, "."); idx > 0 { tableName := name[idx+1:] coll, exists = ms.collections[tableName] if exists { return coll, nil } } ``` 这样确保了: - 新插入的数据可以使用 `testdb.users` 格式 - 重启后加载的数据也能通过 `testdb.users` 查询到 - 向后兼容,不影响现有功能 ## 容错处理 修复实现了多层容错机制: 1. **无数据库适配器**:如果未配置数据库,跳过初始化 2. **ListCollections 未实现**:如果数据库不支持列出表,记录警告但不阻止启动 3. **单个集合加载失败**:记录错误但继续加载其他集合 4. **初始化失败**:记录错误但服务器继续运行(不会崩溃) ## 影响范围 - ✅ 服务器重启后数据自动恢复 - ✅ HTTP API 和 TCP 协议行为一致 - ✅ 支持 SQLite、PostgreSQL、DM8 三种数据库 - ✅ 向后兼容,不影响现有功能 - ✅ 优雅降级,初始化失败不影响服务器启动 ## 相关文件清单 ### 修改的文件 - `internal/engine/memory_store.go` - 添加 Initialize 方法 - `internal/database/base.go` - 添加 ListCollections 基础方法 - `internal/database/sqlite/adapter.go` - SQLite 实现 - `internal/database/postgres/adapter.go` - PostgreSQL 实现 - `internal/database/dm8/adapter.go` - DM8 实现 - `cmd/server/main.go` - 启动时调用初始化 ### 新增的文件 - `test_reload.sh` - 自动化测试脚本 - `RELOAD_FIX.md` - 本文档 ## 后续优化建议 1. **增量加载**:对于大数据量场景,可以考虑分页加载 2. **懒加载**:只在第一次访问集合时才从数据库加载 3. **并发加载**:并行加载多个集合以提高启动速度 4. **加载进度监控**:添加更详细的进度日志和指标