package main import ( "context" "database/sql" "fmt" "strings" "time" _ "github.com/go-sql-driver/mysql" ) // TestMySQLConnection 测试MySQL连接字符串的各种格式 func TestMySQLConnection() { // 测试参数 host := "127.0.0.1" port := 3306 username := "root" password := "123456" timeoutDuration := 3 * time.Second timeoutStr := timeoutDuration.String() // "3s" fmt.Println("=== MySQL连接字符串测试 ===") fmt.Printf("目标: %s:%d\n", host, port) fmt.Printf("用户: %s\n", username) fmt.Printf("超时: %s\n", timeoutStr) fmt.Println() // 测试不同的连接字符串格式 testConfigs := []struct { name string dsn string desc string }{ { name: "当前fscan格式", dsn: fmt.Sprintf("%s:%s@tcp(%s:%d)/mysql?charset=utf8&timeout=%s", username, password, host, port, timeoutStr), desc: "fscan当前使用的格式,包含数据库名mysql", }, { name: "不指定数据库", dsn: fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8&timeout=%s", username, password, host, port, timeoutStr), desc: "不指定具体数据库,连接到默认数据库", }, { name: "无数据库名", dsn: fmt.Sprintf("%s:%s@tcp(%s:%d)?charset=utf8&timeout=%s", username, password, host, port, timeoutStr), desc: "完全不指定数据库名", }, { name: "标准格式+readTimeout", dsn: fmt.Sprintf("%s:%s@tcp(%s:%d)/mysql?charset=utf8&timeout=%s&readTimeout=%s&writeTimeout=%s", username, password, host, port, timeoutStr, timeoutStr, timeoutStr), desc: "添加读写超时参数", }, { name: "使用parseTime", dsn: fmt.Sprintf("%s:%s@tcp(%s:%d)/mysql?charset=utf8&timeout=%s&parseTime=true", username, password, host, port, timeoutStr), desc: "添加parseTime参数处理时间类型", }, { name: "最小参数", dsn: fmt.Sprintf("%s:%s@tcp(%s:%d)/", username, password, host, port), desc: "最简单的连接字符串", }, } for i, config := range testConfigs { fmt.Printf("[%d] %s\n", i+1, config.name) fmt.Printf("描述: %s\n", config.desc) fmt.Printf("DSN: %s\n", config.dsn) // 测试连接 success := testConnection(config.dsn, timeoutDuration) if success { fmt.Printf("结果: ✅ 连接成功\n") } else { fmt.Printf("结果: ❌ 连接失败\n") } fmt.Println(strings.Repeat("-", 80)) } // 测试context超时的影响 fmt.Println("\n=== Context超时测试 ===") testContextTimeout() } // testConnection 测试指定DSN的连接 func testConnection(dsn string, timeout time.Duration) bool { // 创建context,设置超时时间 ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // 尝试连接 db, err := sql.Open("mysql", dsn) if err != nil { fmt.Printf(" Open失败: %v\n", err) return false } defer db.Close() // 设置连接池参数 db.SetMaxOpenConns(1) db.SetMaxIdleConns(1) db.SetConnMaxLifetime(timeout) // 测试ping - 这是实际建立连接的地方 err = db.PingContext(ctx) if err != nil { fmt.Printf(" Ping失败: %v\n", err) return false } // 执行简单查询测试 var version string err = db.QueryRowContext(ctx, "SELECT VERSION()").Scan(&version) if err != nil { fmt.Printf(" 查询失败: %v\n", err) return false } fmt.Printf(" MySQL版本: %s\n", version) return true } // testContextTimeout 测试context超时对连接的影响 func testContextTimeout() { host := "127.0.0.1" port := 3306 username := "root" password := "123456" // 基础连接字符串(不设置timeout参数) baseDSN := fmt.Sprintf("%s:%s@tcp(%s:%d)/mysql?charset=utf8", username, password, host, port) timeoutTests := []struct { name string dsn string contextTimeout time.Duration desc string }{ { name: "仅Context超时3s", dsn: baseDSN, contextTimeout: 3 * time.Second, desc: "只使用context超时,不在DSN中设置timeout", }, { name: "DSN超时3s + Context超时3s", dsn: baseDSN + "&timeout=3s", contextTimeout: 3 * time.Second, desc: "同时设置DSN和context超时", }, { name: "DSN超时10s + Context超时3s", dsn: baseDSN + "&timeout=10s", contextTimeout: 3 * time.Second, desc: "DSN超时更长,context超时更短", }, { name: "DSN超时3s + Context超时10s", dsn: baseDSN + "&timeout=3s", contextTimeout: 10 * time.Second, desc: "DSN超时更短,context超时更长", }, } for i, test := range timeoutTests { fmt.Printf("[%d] %s\n", i+1, test.name) fmt.Printf("描述: %s\n", test.desc) fmt.Printf("DSN: %s\n", test.dsn) fmt.Printf("Context超时: %v\n", test.contextTimeout) start := time.Now() success := testConnectionWithTiming(test.dsn, test.contextTimeout) elapsed := time.Since(start) if success { fmt.Printf("结果: ✅ 连接成功,耗时: %v\n", elapsed) } else { fmt.Printf("结果: ❌ 连接失败,耗时: %v\n", elapsed) } fmt.Println(strings.Repeat("-", 80)) } } // testConnectionWithTiming 带时间统计的连接测试 func testConnectionWithTiming(dsn string, contextTimeout time.Duration) bool { ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) defer cancel() db, err := sql.Open("mysql", dsn) if err != nil { fmt.Printf(" Open失败: %v\n", err) return false } defer db.Close() db.SetMaxOpenConns(1) db.SetMaxIdleConns(1) err = db.PingContext(ctx) if err != nil { fmt.Printf(" Ping失败: %v\n", err) return false } return true } // 优化建议和分析 func printOptimizationSuggestions() { fmt.Println("\n=== MySQL连接字符串优化建议 ===") suggestions := []string{ "1. 连接字符串格式建议:", " 推荐: user:pass@tcp(host:port)/dbname?charset=utf8mb4&parseTime=true&timeout=3s", "", "2. 超时参数优化:", " - 使用charset=utf8mb4而不是utf8,支持完整的UTF-8字符集", " - 添加parseTime=true自动解析时间类型", " - 分别设置timeout、readTimeout、writeTimeout更精细控制", "", "3. Context vs DSN超时:", " - Context超时控制整个操作的最大时间", " - DSN timeout参数控制连接建立的超时", " - 建议Context超时 >= DSN timeout + 额外处理时间", "", "4. 连接池优化:", " - SetMaxOpenConns(1) 对于扫描场景是合适的", " - SetConnMaxLifetime应该设置适当的值避免连接泄露", " - 使用defer db.Close()确保连接被释放", "", "5. 错误处理:", " - 'context deadline exceeded' 通常表示网络问题或超时设置过短", " - 检查防火墙设置和网络连接", " - 确认MySQL服务器配置允许远程连接", } for _, suggestion := range suggestions { fmt.Println(suggestion) } } func main() { fmt.Println("MySQL连接字符串测试工具") fmt.Println("作者: Go语言代码优化专家") fmt.Println("目的: 验证fscan中MySQL连接字符串格式的正确性") fmt.Println("=" + strings.Repeat("=", 60)) // 检查必要的包 fmt.Println("\n检查依赖...") fmt.Println("✅ github.com/go-sql-driver/mysql 已导入") // 执行测试 TestMySQLConnection() // 打印优化建议 printOptimizationSuggestions() fmt.Println("\n测试完成!") fmt.Println("\n使用方法:") fmt.Println("1. 确保MySQL服务器运行在127.0.0.1:3306") fmt.Println("2. 创建用户: CREATE USER 'root'@'%' IDENTIFIED BY '123456';") fmt.Println("3. 授权: GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';") fmt.Println("4. 刷新权限: FLUSH PRIVILEGES;") fmt.Println("5. 运行: go run mysql_connection_test.go") }