Using MySQL with Go
This document provides examples of how to connect to and interact with a MySQL database using the Go programming language. It covers installation, connection management, basic queries, and transactions.
Prerequisites
- Go installed and configured.
- MySQL server running and accessible.
1. Install Dependencies
Use the go get
command to install the required MySQL driver.
go get github.com/go-sql-driver/mysql
2. Import Dependencies
Import the necessary packages in your Go code. Note the blank import _ "github.com/go-sql-driver/mysql"
. This registers the MySQL driver with the database/sql
package.
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
3. Establishing a Connection
The following function demonstrates how to open a connection to a MySQL database, configure connection pool settings, and handle potential errors.
func GetConnection() *sql.DB {
db, err := sql.Open("mysql", "user:password@tcp(host:port)/database_name")
if err != nil {
log.Fatalf("Error opening database: %s", err)
}
// Set connection pool settings
db.SetMaxIdleConns(10) // Minimum number of idle connections in the pool
db.SetMaxOpenConns(100) // Maximum number of open connections to the database
db.SetConnMaxIdleTime(5 * time.Minute) // Maximum amount of time a connection may be idle before being closed.
db.SetConnMaxLifetime(60 * time.Minute) // Maximum amount of time a connection may be reused.
// Test the connection
err = db.Ping()
if err != nil {
log.Fatalf("Error pinging database: %s", err)
}
return db
}
func main() {
db := GetConnection()
defer db.Close()
fmt.Println("Successfully connected to MySQL!")
// Your database operations will go here...
}
Explanation:
sql.Open("mysql", "user:password@tcp(host:port)/database_name")
: Opens a database connection using the MySQL driver.- Replace
"user"
,"password"
,"host"
,"port"
, and"database_name"
with your actual database credentials. For example:"root:secret@tcp(localhost:3306)/my_database"
.
- Replace
db.SetMaxIdleConns(10)
: Sets the maximum number of connections in the idle connection pool.db.SetMaxOpenConns(100)
: Sets the maximum number of open connections to the database.db.SetConnMaxIdleTime(5 * time.Minute)
: Sets the maximum amount of time a connection may be idle.db.SetConnMaxLifetime(60 * time.Minute)
: Sets the maximum lifetime of a connection. Connections older than this will be closed and re-established. Usingtime.Hour
or multiples thereof is considered best practice, as it is both easier to reason through and less error-prone.db.Ping()
: Verifies that the connection to the database is active. It's good practice to callPing()
after opening the connection to catch any connection errors early.defer db.Close()
: Ensures that the database connection is closed when themain
function exits. This is important to release resources.- Logging: Using the
log
package is generally a better practice thanpanic
in a real-world application, aspanic
will abruptly terminate the program.
4. Query Examples
These examples demonstrate common database operations (insert, select, and parameterized queries).
4.1. Insert Example
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func GetConnection() *sql.DB {
db, err := sql.Open("mysql", "user:password@tcp(host:port)/database_name")
if err != nil {
log.Fatalf("Error opening database: %s", err)
}
// Set connection pool settings
db.SetMaxIdleConns(10) // Minimum number of idle connections in the pool
db.SetMaxOpenConns(100) // Maximum number of open connections to the database
db.SetConnMaxIdleTime(5 * time.Minute) // Maximum amount of time a connection may be idle before being closed.
db.SetConnMaxLifetime(60 * time.Minute) // Maximum amount of time a connection may be reused.
// Test the connection
err = db.Ping()
if err != nil {
log.Fatalf("Error pinging database: %s", err)
}
return db
}
func main() {
db := GetConnection()
defer db.Close()
ctx := context.Background() // Use a background context
query := "INSERT INTO customer(id, name) VALUES (?, ?)" // Use placeholders
result, err := db.ExecContext(ctx, query, 3, "Charlie") // Pass parameters
if err != nil {
log.Fatalf("Error inserting data: %s", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
log.Fatalf("Error getting affected rows: %s", err)
}
fmt.Printf("INSERT Success. Rows affected: %d\n", rowsAffected)
}
Key Improvements:
- Using Placeholders: Changed to use parameterized queries with
?
placeholders instead of directly inserting values into the query string. This is crucial for preventing SQL injection vulnerabilities. Critically important. - Context: added
context.Background()
so the database operation can be be controlled - Error Checking: added
rowsAffected
afterresult
4.2. GET Example (Query)
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func GetConnection() *sql.DB {
db, err := sql.Open("mysql", "user:password@tcp(host:port)/database_name")
if err != nil {
log.Fatalf("Error opening database: %s", err)
}
// Set connection pool settings
db.SetMaxIdleConns(10) // Minimum number of idle connections in the pool
db.SetMaxOpenConns(100) // Maximum number of open connections to the database
db.SetConnMaxIdleTime(5 * time.Minute) // Maximum amount of time a connection may be idle before being closed.
db.SetConnMaxLifetime(60 * time.Minute) // Maximum amount of time a connection may be reused.
// Test the connection
err = db.Ping()
if err != nil {
log.Fatalf("Error pinging database: %s", err)
}
return db
}
func main() {
db := GetConnection()
defer db.Close()
ctx := context.Background()
query := "SELECT id, name FROM customer"
rows, err := db.QueryContext(ctx, query)
if err != nil {
log.Fatalf("Error querying data: %s", err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
err := rows.Scan(&id, &name)
if err != nil {
log.Fatalf("Error scanning row: %s", err)
}
fmt.Printf("Id: %d, Name: %s\n", id, name)
}
if err := rows.Err(); err != nil {
log.Fatalf("Error iterating rows: %s", err)
}
fmt.Println("GET Success")
}
Key improvements:
- Uses int id type:
id
should have the correct data type that matches your table schema. - Error Handling in Loop: Checks for errors during
rows.Scan()
. - Checks for errors after iteration: Checks for errors after iterating with
rows.Err()
. - Projection: Selects only the
id
andname
columns for better performance.
4.3. GET With Params Example (Parameterized Query)
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func GetConnection() *sql.DB {
db, err := sql.Open("mysql", "user:password@tcp(host:port)/database_name")
if err != nil {
log.Fatalf("Error opening database: %s", err)
}
// Set connection pool settings
db.SetMaxIdleConns(10) // Minimum number of idle connections in the pool
db.SetMaxOpenConns(100) // Maximum number of open connections to the database
db.SetConnMaxIdleTime(5 * time.Minute) // Maximum amount of time a connection may be idle before being closed.
db.SetConnMaxLifetime(60 * time.Minute) // Maximum amount of time a connection may be reused.
// Test the connection
err = db.Ping()
if err != nil {
log.Fatalf("Error pinging database: %s", err)
}
return db
}
func main() {
db := GetConnection()
defer db.Close()
ctx := context.Background()
username := "test"
password := "password123" // Replace with your test password
query := "SELECT id, name FROM customer WHERE name = ? AND password = ?"
rows, err := db.QueryContext(ctx, query, username, password)
if err != nil {
log.Fatalf("Error querying data: %s", err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
err := rows.Scan(&id, &name)
if err != nil {
log.Fatalf("Error scanning row: %s", err)
}
fmt.Printf("Id: %d, Name: %s\n", id, name)
}
if err := rows.Err(); err != nil {
log.Fatalf("Error iterating rows: %s", err)
}
fmt.Println("GET with Params Success")
}
Key Improvements:
- SQL Injection Prevention: Uses parameterized queries (
?
placeholders) to prevent SQL injection vulnerabilities. Always use parameterized queries when accepting user input. - Example Values: replaced the
username
andpassword
to be more descriptive
5. Transactions
Transactions ensure that a series of database operations are treated as a single unit of work. If any operation within the transaction fails, the entire transaction is rolled back, maintaining data consistency.
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func GetConnection() *sql.DB {
db, err := sql.Open("mysql", "user:password@tcp(host:port)/database_name")
if err != nil {
log.Fatalf("Error opening database: %s", err)
}
// Set connection pool settings
db.SetMaxIdleConns(10) // Minimum number of idle connections in the pool
db.SetMaxOpenConns(100) // Maximum number of open connections to the database
db.SetConnMaxIdleTime(5 * time.Minute) // Maximum amount of time a connection may be idle before being closed.
db.SetConnMaxLifetime(60 * time.Minute) // Maximum amount of time a connection may be reused.
// Test the connection
err = db.Ping()
if err != nil {
log.Fatalf("Error pinging database: %s", err)
}
return db
}
func main() {
db := GetConnection()
defer db.Close()
ctx := context.Background()
tx, err := db.BeginTx(ctx, nil) // Start a transaction
if err != nil {
log.Fatalf("Error starting transaction: %s", err)
}
defer func() {
if p := recover(); p != nil {
err := tx.Rollback()
if err != nil {
log.Fatalf("Error during rollback: %s", err)
}
panic(p) // Re-throw the panic after rollback
} else if err != nil {
err := tx.Rollback()
if err != nil {
log.Fatalf("Error during rollback: %s", err)
}
} else {
err = tx.Commit()
if err != nil {
log.Fatalf("Error during commit: %s", err)
}
fmt.Println("Transaction committed successfully")
}
}()
name := "NewUser"
password := "SecurePassword"
query := "INSERT INTO customer(name, password) VALUES (?, ?)"
_, err = tx.ExecContext(ctx, query, name, password)
if err != nil {
log.Fatalf("Error executing statement: %s", err) // Critical to log error
}
// ... Additional database operations within the transaction ...
fmt.Println("Transaction completed successfully")
}
Key Improvements:
BeginTx
with Context: Usesdb.BeginTx
to start a transaction with a context. This allows you to control the transaction's lifecycle (e.g., setting a timeout).- Error Handling and Rollback: Includes comprehensive error handling and rollback logic. The
defer
statement ensures that the transaction is either committed or rolled back, even if a panic occurs. Therecover()
block handles panics and performs a rollback before re-throwing the panic. This pattern is the recommended way to handle transactions in Go. - Clearer Error Messages: Improved error messages to provide more context.
- Use of Defer block: The defer block now handles panics and errors more robustly.
- SQL injection: use the
placeholder
instead of the actual value.
Important Notes
- Error Handling: Always check for errors after each database operation. Use
log.Fatal
or appropriate error handling to prevent unexpected program behavior. - Security: Never hardcode database credentials directly into your code. Use environment variables or configuration files to store sensitive information. Be especially diligent in preventing SQL injection vulnerabilities by using parameterized queries.
- Connection Pooling: Use connection pooling (as demonstrated in the
GetConnection
function) to improve performance by reusing database connections. - Context: Use
context.Context
for managing the lifecycle of database operations, including setting deadlines and cancellation signals.
This comprehensive guide provides a solid foundation for working with MySQL databases in Go. Remember to adapt the examples to your specific use cases and follow best practices for security and error handling.