{"record":{"id":"de275495c9dbd815","repo":"golang-migrate/migrate","slug":"conn-v-db-v-de2754","errorCode":null,"errorMessage":"conn: %v, db: %v","messagePattern":"conn: (.+?), db: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"database/mysql/mysql.go","lineNumber":280,"sourceCode":"\t\tNoLock:           noLock,\n\t\tStatementTimeout: time.Duration(statementTimeout) * time.Millisecond,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn mx, nil\n}\n\nfunc (m *Mysql) Close() error {\n\tconnErr := m.conn.Close()\n\tvar dbErr error\n\tif m.db != nil {\n\t\tdbErr = m.db.Close()\n\t}\n\n\tif connErr != nil || dbErr != nil {\n\t\treturn fmt.Errorf(\"conn: %v, db: %v\", connErr, dbErr)\n\t}\n\treturn nil\n}\n\nfunc (m *Mysql) Lock() error {\n\treturn database.CasRestoreOnErr(&m.isLocked, false, true, database.ErrLocked, func() error {\n\t\tif m.config.NoLock {\n\t\t\treturn nil\n\t\t}\n\t\taid, err := database.GenerateAdvisoryLockId(\n\t\t\tfmt.Sprintf(\"%s:%s\", m.config.DatabaseName, m.config.MigrationsTable))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tquery := \"SELECT GET_LOCK(?, 10)\"\n\t\tvar success bool\n\t\tif err := m.conn.QueryRowContext(context.Background(), query, aid).Scan(&success); err != nil {","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/golang-migrate/migrate/blob/01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a/database/mysql/mysql.go#L262-L298","documentation":"Close shuts down both the underlying sql.DB and the dedicated migration connection and, if either Close fails, returns a combined 'conn: %v, db: %v' message showing both error values. One of the two may print as <nil>; the format always includes both so you can tell which half failed. It is raised in Mysql.Close (database/mysql/mysql.go:280).","triggerScenarios":"Calling Close (directly or via defer) after Open/WithInstance succeeded, when the dedicated conn.Close() or db.Close() returns an error, e.g. the connection is already broken or the pool was already closed elsewhere.","commonSituations":"Network drop between migration end and Close; closing the *sql.DB yourself before calling driver Close (double-close); timeouts during shutdown of an app holding an idle-pooled connection that has been killed server-side.","solutions":["Inspect the non-<nil> part of the message to see whether the dedicated conn or the pool failed","Ensure you are not closing the *sql.DB yourself before handing it to the driver / calling driver Close","Check server logs and network health; usually the underlying connection was already dead and cleanup can be treated as best-effort","Call Close only once per driver instance"],"exampleFix":"// before\ndb.Close()\ndrv, _ := mysql.WithInstance(db, cfg)\n...\ndrv.Close() // double-close -> \"conn: ..., db: sql: database is closed\"\n// after\ndrv, _ := mysql.WithInstance(db, cfg)\n...\nif err := drv.Close(); err != nil {\n    log.Printf(\"migration close (best-effort): %v\", err)\n}\ndb.Close()","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := drv.Close(); err != nil {\n    // message is \"conn: %v, db: %v\" — parse which side failed\n    log.Printf(\"migrate close (best-effort, check conn vs db part): %v\", err)\n}\n// ensure single ownership: only the driver closes resources it opened;\n// you close the *sql.DB yourself after driver Close","preventionTips":["Call Close exactly once per driver instance","Never close the *sql.DB yourself before calling driver Close if the driver owns it","Treat Close errors during shutdown as best-effort and log rather than fail the process","Monitor server-side connection kills/timeouts that leave connections half-dead"],"tags":["mysql","close","connection-lifecycle","resource-cleanup"],"backgroundTag":"connection-close-error","analyzedSha":"01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a","analyzedAt":"2026-09-02T19:38:29.671Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}