feat(sql-analysis): 完善慢 SQL 监控与执行计划诊断

- 完善慢查询采集、聚合、迁移与跨进程持久化

- 加固只读诊断边界并补齐多数据库执行计划解析

- 优化慢 SQL 面板、计划图、响应式布局与交互体验

- 补充后端、前端及 SQL Server 会话回归测试
This commit is contained in:
Syngnat
2026-07-11 16:25:07 +08:00
parent 5c80f6e9a2
commit 034e968888
49 changed files with 4817 additions and 686 deletions

View File

@@ -8,6 +8,13 @@ import "strings"
// 避免在这些上下文中错误拆分。
// 同时支持 SQL 标准的转义单引号(两个连续单引号 ” 表示字面量引号)。
func splitSQLStatements(sql string) []string {
return splitSQLStatementsForDialect("", sql)
}
// splitSQLStatementsForDialect keeps the legacy generic splitter available to
// existing execution paths while allowing security-sensitive callers to apply
// the actual comment and dollar-quote rules of the target database.
func splitSQLStatementsForDialect(dbType, sql string) []string {
text := strings.ReplaceAll(sql, "\r\n", "\n")
var statements []string
@@ -169,12 +176,12 @@ func splitSQLStatements(sql string) []string {
}
// 行注释开始
if ch == '-' && next == '-' {
if ch == '-' && next == '-' && isSQLDashLineCommentStart(dbType, text, i) {
inLineComment = true
cur.WriteByte(ch)
continue
}
if ch == '#' {
if ch == '#' && supportsSQLHashLineComment(dbType) {
inLineComment = true
cur.WriteByte(ch)
continue
@@ -198,8 +205,8 @@ func splitSQLStatements(sql string) []string {
}
// Dollar-quoting 开始
if ch == '$' {
if tag := parseSQLDollarTag(text[i:]); tag != "" {
if ch == '$' && supportsSQLDollarQuote(dbType) {
if tag := parseSQLDollarTagAt(text, i); tag != "" {
dollarTag = tag
cur.WriteString(tag)
i += len(tag) - 1
@@ -248,6 +255,61 @@ func splitSQLStatements(sql string) []string {
return statements
}
func isSQLDashLineCommentStart(dbType, text string, index int) bool {
switch normalizeExplainLexicalDBType(dbType) {
case "mysql", "mariadb", "oceanbase", "diros", "starrocks", "goldendb":
return isMySQLDashCommentStart(text, index)
default:
return true
}
}
func supportsSQLHashLineComment(dbType string) bool {
normalized := normalizeExplainLexicalDBType(dbType)
if normalized == "" {
return true
}
switch normalized {
case "mysql", "mariadb", "oceanbase", "diros", "starrocks", "goldendb", "clickhouse":
return true
default:
return false
}
}
func supportsSQLDollarQuote(dbType string) bool {
normalized := normalizeExplainLexicalDBType(dbType)
if normalized == "" {
return true
}
switch normalized {
case "postgres", "opengauss", "gaussdb", "kingbase", "highgo", "vastbase":
return true
default:
return false
}
}
func normalizeExplainLexicalDBType(dbType string) string {
normalized := strings.ToLower(strings.TrimSpace(dbType))
switch normalized {
case "postgresql", "pg", "pq", "pgx":
return "postgres"
case "doris":
return "diros"
case "open_gauss", "open-gauss":
return "opengauss"
case "gauss_db", "gauss-db":
return "gaussdb"
case "kingbase8", "kingbasees", "kingbasev8":
return "kingbase"
case "greatdb", "gdb":
return "goldendb"
default:
return normalized
}
}
func isSQLIdentifierStart(ch byte) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'
}
@@ -507,3 +569,30 @@ func parseSQLDollarTag(s string) string {
}
return ""
}
func parseSQLDollarTagAt(text string, start int) string {
if start < 0 || start >= len(text) || text[start] != '$' {
return ""
}
if start > 0 && isSQLIdentifierPart(text[start-1]) {
return ""
}
if start+1 >= len(text) {
return ""
}
if text[start+1] == '$' {
return "$$"
}
if !isSQLIdentifierStart(text[start+1]) {
return ""
}
for end := start + 2; end < len(text); end++ {
if text[end] == '$' {
return text[start : end+1]
}
if !isSQLIdentifierPart(text[end]) || text[end] == '$' || text[end] == '#' {
return ""
}
}
return ""
}