mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-12 16:02:48 +08:00
⚡️ perf(database): 优化查询元数据加载和连接释放
- 查询编辑器仅预取当前库及 SQL 显式引用库的元数据 - 断开侧边栏连接时主动释放同实例后端缓存连接 - 完善 Redis 连接释放和 Wails 前端绑定 - 修复 SQL Server 存储过程消息结果显示 - 调整查询工具栏布局并补充回归测试 Close #541
This commit is contained in:
@@ -43,6 +43,7 @@ var (
|
||||
type cachedDatabase struct {
|
||||
inst db.Database
|
||||
lastPing time.Time
|
||||
config connection.ConnectionConfig
|
||||
}
|
||||
|
||||
type cachedConnectFailure struct {
|
||||
@@ -189,6 +190,7 @@ func (a *App) Shutdown() {
|
||||
logger.Error(err, "关闭数据库连接失败")
|
||||
}
|
||||
}
|
||||
a.dbCache = make(map[string]cachedDatabase)
|
||||
proxytunnel.CloseAllForwarders()
|
||||
// Close all Redis connections
|
||||
CloseAllRedisClients()
|
||||
@@ -291,6 +293,20 @@ func getCacheKey(config connection.ConnectionConfig) string {
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func normalizeConnectionReleaseMatchConfig(config connection.ConnectionConfig) connection.ConnectionConfig {
|
||||
normalized := normalizeCacheKeyConfig(config)
|
||||
normalized.Database = ""
|
||||
normalized.RedisDB = 0
|
||||
return normalized
|
||||
}
|
||||
|
||||
func getConnectionReleaseMatchKey(config connection.ConnectionConfig) string {
|
||||
normalized := normalizeConnectionReleaseMatchConfig(config)
|
||||
b, _ := json.Marshal(normalized)
|
||||
sum := sha256.Sum256(b)
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func shortCacheKey(cacheKey string) string {
|
||||
shortKey := cacheKey
|
||||
if len(shortKey) > 12 {
|
||||
@@ -726,7 +742,7 @@ func (a *App) getDatabaseWithPing(config connection.ConnectionConfig, forcePing
|
||||
}
|
||||
return existing.inst, nil
|
||||
}
|
||||
a.dbCache[key] = cachedDatabase{inst: dbInst, lastPing: now}
|
||||
a.dbCache[key] = cachedDatabase{inst: dbInst, lastPing: now, config: normalizeCacheKeyConfig(effectiveConfig)}
|
||||
a.mu.Unlock()
|
||||
|
||||
logger.Infof("数据库连接成功并写入缓存:%s 缓存Key=%s", formatConnSummary(effectiveConfig), shortKey)
|
||||
|
||||
@@ -63,6 +63,50 @@ func (a *App) DBConnect(config connection.ConnectionConfig) connection.QueryResu
|
||||
return connection.QueryResult{Success: true, Message: "连接成功"}
|
||||
}
|
||||
|
||||
func (a *App) DBReleaseConnection(config connection.ConnectionConfig) connection.QueryResult {
|
||||
dbType := strings.ToLower(strings.TrimSpace(config.Type))
|
||||
if dbType == "redis" {
|
||||
closed, err := a.releaseRedisClientsForConfig(config)
|
||||
if err != nil {
|
||||
logger.Error(err, "DBReleaseConnection 释放 Redis 连接失败:%s", formatConnSummary(config))
|
||||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||||
}
|
||||
logger.Infof("DBReleaseConnection 已释放 Redis 连接:%s 数量=%d", formatConnSummary(config), closed)
|
||||
return connection.QueryResult{Success: true, Message: "连接已释放", Data: map[string]int{"closed": closed}}
|
||||
}
|
||||
|
||||
resolvedConfig, err := a.resolveConnectionSecrets(config)
|
||||
if err != nil {
|
||||
wrapped := wrapConnectError(config, err)
|
||||
logger.Error(wrapped, "DBReleaseConnection 解析连接密文失败:%s", formatConnSummary(config))
|
||||
return connection.QueryResult{Success: false, Message: wrapped.Error()}
|
||||
}
|
||||
targetKey := getConnectionReleaseMatchKey(applyGlobalProxyToConnection(resolvedConfig))
|
||||
closed := 0
|
||||
|
||||
a.mu.Lock()
|
||||
for key, entry := range a.dbCache {
|
||||
entryConfig := entry.config
|
||||
if strings.TrimSpace(entryConfig.Type) == "" {
|
||||
continue
|
||||
}
|
||||
if getConnectionReleaseMatchKey(entryConfig) != targetKey {
|
||||
continue
|
||||
}
|
||||
if entry.inst != nil {
|
||||
if closeErr := entry.inst.Close(); closeErr != nil {
|
||||
logger.Error(closeErr, "DBReleaseConnection 关闭缓存连接失败:缓存Key=%s", shortCacheKey(key))
|
||||
}
|
||||
}
|
||||
delete(a.dbCache, key)
|
||||
closed++
|
||||
}
|
||||
a.mu.Unlock()
|
||||
|
||||
logger.Infof("DBReleaseConnection 已释放数据库连接:%s 数量=%d", formatConnSummary(resolvedConfig), closed)
|
||||
return connection.QueryResult{Success: true, Message: "连接已释放", Data: map[string]int{"closed": closed}}
|
||||
}
|
||||
|
||||
func (a *App) TestConnection(config connection.ConnectionConfig) connection.QueryResult {
|
||||
testConfig := normalizeTestConnectionConfig(config)
|
||||
started := time.Now()
|
||||
|
||||
@@ -7,6 +7,41 @@ import (
|
||||
"GoNavi-Wails/internal/connection"
|
||||
)
|
||||
|
||||
type releaseRecordingDB struct {
|
||||
closed int
|
||||
}
|
||||
|
||||
func (f *releaseRecordingDB) Connect(config connection.ConnectionConfig) error { return nil }
|
||||
func (f *releaseRecordingDB) Close() error {
|
||||
f.closed++
|
||||
return nil
|
||||
}
|
||||
func (f *releaseRecordingDB) Ping() error { return nil }
|
||||
func (f *releaseRecordingDB) Query(query string) ([]map[string]interface{}, []string, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (f *releaseRecordingDB) Exec(query string) (int64, error) { return 0, nil }
|
||||
func (f *releaseRecordingDB) GetDatabases() ([]string, error) { return nil, nil }
|
||||
func (f *releaseRecordingDB) GetTables(dbName string) ([]string, error) { return nil, nil }
|
||||
func (f *releaseRecordingDB) GetCreateStatement(dbName, tableName string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (f *releaseRecordingDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *releaseRecordingDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *releaseRecordingDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *releaseRecordingDB) GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *releaseRecordingDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDefinition, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestNormalizeTestConnectionConfig_CapsTimeout(t *testing.T) {
|
||||
cfg := connection.ConnectionConfig{Timeout: 60}
|
||||
got := normalizeTestConnectionConfig(cfg)
|
||||
@@ -130,3 +165,44 @@ func TestFormatConnSummary_DefaultTimeout(t *testing.T) {
|
||||
t.Fatalf("formatConnSummary 默认超时应为30s, got=%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBReleaseConnectionClosesAllDatabaseCacheEntriesForSameInstance(t *testing.T) {
|
||||
app := NewApp()
|
||||
mainConfig := connection.ConnectionConfig{Type: "mysql", Host: "127.0.0.1", Port: 3306, User: "root", Database: "main"}
|
||||
analyticsConfig := mainConfig
|
||||
analyticsConfig.Database = "analytics"
|
||||
otherConfig := mainConfig
|
||||
otherConfig.Port = 3307
|
||||
otherConfig.Database = "main"
|
||||
|
||||
mainDB := &releaseRecordingDB{}
|
||||
analyticsDB := &releaseRecordingDB{}
|
||||
otherDB := &releaseRecordingDB{}
|
||||
|
||||
app.dbCache[getCacheKey(mainConfig)] = cachedDatabase{
|
||||
inst: mainDB,
|
||||
config: normalizeCacheKeyConfig(mainConfig),
|
||||
}
|
||||
app.dbCache[getCacheKey(analyticsConfig)] = cachedDatabase{
|
||||
inst: analyticsDB,
|
||||
config: normalizeCacheKeyConfig(analyticsConfig),
|
||||
}
|
||||
app.dbCache[getCacheKey(otherConfig)] = cachedDatabase{
|
||||
inst: otherDB,
|
||||
config: normalizeCacheKeyConfig(otherConfig),
|
||||
}
|
||||
|
||||
result := app.DBReleaseConnection(connection.ConnectionConfig{Type: "mysql", Host: "127.0.0.1", Port: 3306, User: "root"})
|
||||
if !result.Success {
|
||||
t.Fatalf("expected release success, got %s", result.Message)
|
||||
}
|
||||
if mainDB.closed != 1 || analyticsDB.closed != 1 {
|
||||
t.Fatalf("expected both same-instance cached connections closed, got main=%d analytics=%d", mainDB.closed, analyticsDB.closed)
|
||||
}
|
||||
if otherDB.closed != 0 {
|
||||
t.Fatalf("expected other instance cache to remain open, got closed=%d", otherDB.closed)
|
||||
}
|
||||
if len(app.dbCache) != 1 {
|
||||
t.Fatalf("expected only unrelated cache entry to remain, got %d", len(app.dbCache))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
// Redis client cache
|
||||
var (
|
||||
redisCache = make(map[string]redis.RedisClient)
|
||||
redisCacheConfigs = make(map[string]connection.ConnectionConfig)
|
||||
redisCacheMu sync.Mutex
|
||||
newRedisClientFunc = redis.NewRedisClient
|
||||
)
|
||||
@@ -60,6 +61,7 @@ func (a *App) getRedisClient(config connection.ConnectionConfig) (redis.RedisCli
|
||||
}
|
||||
client.Close()
|
||||
delete(redisCache, key)
|
||||
delete(redisCacheConfigs, key)
|
||||
}
|
||||
|
||||
logger.Infof("创建 Redis 客户端实例:缓存Key=%s", shortKey)
|
||||
@@ -71,6 +73,7 @@ func (a *App) getRedisClient(config connection.ConnectionConfig) (redis.RedisCli
|
||||
}
|
||||
|
||||
redisCache[key] = client
|
||||
redisCacheConfigs[key] = normalizeCacheKeyConfig(connectedConfig)
|
||||
logger.Infof("Redis 连接成功并写入缓存:%s 缓存Key=%s", formatRedisConnSummary(connectedConfig), shortKey)
|
||||
return client, nil
|
||||
}
|
||||
@@ -142,6 +145,35 @@ func getRedisClientCacheKey(config connection.ConnectionConfig) string {
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func (a *App) releaseRedisClientsForConfig(config connection.ConnectionConfig) (int, error) {
|
||||
resolvedConfig, err := a.resolveConnectionSecrets(config)
|
||||
if err != nil {
|
||||
return 0, wrapConnectError(config, err)
|
||||
}
|
||||
targetKey := getConnectionReleaseMatchKey(applyGlobalProxyToConnection(resolvedConfig))
|
||||
closed := 0
|
||||
|
||||
redisCacheMu.Lock()
|
||||
defer redisCacheMu.Unlock()
|
||||
|
||||
for key, client := range redisCache {
|
||||
entryConfig := redisCacheConfigs[key]
|
||||
if strings.TrimSpace(entryConfig.Type) == "" {
|
||||
continue
|
||||
}
|
||||
if getConnectionReleaseMatchKey(entryConfig) != targetKey {
|
||||
continue
|
||||
}
|
||||
if client != nil {
|
||||
client.Close()
|
||||
}
|
||||
delete(redisCache, key)
|
||||
delete(redisCacheConfigs, key)
|
||||
closed++
|
||||
}
|
||||
return closed, nil
|
||||
}
|
||||
|
||||
func formatRedisConnSummary(config connection.ConnectionConfig) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("类型=redis 地址=")
|
||||
@@ -759,4 +791,5 @@ func CloseAllRedisClients() {
|
||||
}
|
||||
}
|
||||
redisCache = make(map[string]redis.RedisClient)
|
||||
redisCacheConfigs = make(map[string]connection.ConnectionConfig)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user