Go language operation database MySQL

connect

The database/sql package in Go language provides a universal interface to ensure SQL or SQL like databases, and does not provide specific database drivers. When using database/sql package, you must inject (at least) one database driver.

Our commonly used databases basically have a complete third-party implementation. For example: MySQL driver

Download dependency

go get -u github.com/go-sql-driver/mysql

Using MySQL driver

func Open(driverName, dataSourceName string) (*DB, error)

Open opens a database specified by dirverName. dataSourceName specifies the data source. Generally, it includes at least the database file name and other necessary information for connection.

import (
	"database/sql"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
   // DSN:Data Source Name
	dsn := "user:password@tcp(127.0.0.1:3306)/dbname"
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		panic(err)
	}
	defer db.Close()  // Note that this line of code should be written below err judgment above
}

Question: why is defer dB in the above code The close() statement should not be written in if err= In front of nil?

Initialize connection

The Open function may just verify whether its parameters are in the correct format, but it does not actually create a connection to the database. If you want to check whether the name of the data source is true and valid, you should call the Ping method.

The returned DB object can be safely used by multiple goroutine s concurrently, and maintain its own free connection pool. Therefore, the Open function should only be called once and it is rarely necessary to close the DB object.

// Define a global object db
var db *sql.DB

// Define a function to initialize the database
func initDB() (err error) {
	// DSN:Data Source Name
	dsn := "user:password@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
	// It will not verify whether the account password is correct
	// be careful!!! Don't use: =, we assign a value to the global variable, and then use the global variable db in the main function
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	// Try to establish a connection with the database (verify that the dsn is correct)
	err = db.Ping()
	if err != nil {
		return err
	}
	return nil
}

func main() {
	err := initDB() // Call the function of outputting the database
	if err != nil {
		fmt.Printf("init db failed,err:%v\n", err)
		return
	}
}

Where SQL DB is a database object (structure instance) that represents a connection. It stores all information related to connecting to the database. It internally maintains a connection pool with zero to multiple underlying connections, which can be safely used by multiple goroutine s at the same time.

SetMaxOpenConns

func (db *DB) SetMaxOpenConns(n int)

SetMaxOpenConns sets the maximum number of connections to the database. If n is greater than 0 and less than the maximum number of idle connections, the maximum number of idle connections will be reduced to match the limit of the maximum number of open connections. If n < = 0, the maximum number of open connections will not be limited, and the default is 0 (unlimited).

SetMaxIdleConns

func (db *DB) SetMaxIdleConns(n int)

SetMaxIdleConns sets the maximum number of idle connections in the connection pool. If n is greater than the maximum number of open connections, the new maximum number of idle connections will be reduced to match the limit of the maximum number of open connections. If n < = 0, idle connections are not retained.

CRUD

Database and table building

Let's first create a database named SQL in MySQL_ Test database

CREATE DATABASE sql_test;

Enter the database:

use sql_test;

Execute the following command to create a data table for testing:

CREATE TABLE `user` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(20) DEFAULT '',
    `age` INT(11) DEFAULT '0',
    PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

query

In order to facilitate query, we define a structure in advance to store the data of the user table.

type user struct {
	id   int
	age  int
	name string
}

Single line query

Single line query dB QueryRow() executes a query once and expects to return up to one Row of results (i.e. Row). QueryRow always returns non nil values and will not return delayed errors until the Scan method that returns the value is called. (for example, no result is found)

func (db *DB) QueryRow(query string, args ...interface{}) *Row

Specific example code:

// Example of querying a single piece of data
func queryRowDemo() {
	sqlStr := "select id, name, age from user where id=?"
	var u user
	// Very important: ensure that the Scan method is called after QueryRow, otherwise the database link held will not be released.
	err := db.QueryRow(sqlStr, 1).Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("scan failed, err:%v\n", err)
		return
	}
	fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}

Multi row query

Multi row query dB Query() executes a query and returns multiple lines of results (i.e. Rows). It is generally used to execute the select command. The parameter args represents the placeholder parameter in query.

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

Specific example code:

// Query multiple data examples
func queryMultiRowDemo() {
	sqlStr := "select id, name, age from user where id > ?"
	rows, err := db.Query(sqlStr, 0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	// Very important: close rows and release the database links held
	defer rows.Close()

	// Cycle through the data in the result set
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

insert data

The Exec method is used for insert, update, and delete operations.

func (db *DB) Exec(query string, args ...interface{}) (Result, error)

Exec executes commands once (including query, delete, update, insert, etc.), and the returned Result is a summary of the executed SQL commands. The parameter args represents the placeholder parameter in query.

The specific insert data example code is as follows:

// insert data
func insertRowDemo() {
	sqlStr := "insert into user(name, age) values (?,?)"
	ret, err := db.Exec(sqlStr, "Wang Wu", 38)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	theID, err := ret.LastInsertId() // id of the newly inserted data
	if err != nil {
		fmt.Printf("get lastinsert ID failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", theID)
}

Update data

The specific update data example code is as follows:

// Update data
func updateRowDemo() {
	sqlStr := "update user set age=? where id = ?"
	ret, err := db.Exec(sqlStr, 39, 3)
	if err != nil {
		fmt.Printf("update failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // Number of rows affected by the operation
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("update success, affected rows:%d\n", n)
}

Delete data

The specific example code for deleting data is as follows:

// Delete data
func deleteRowDemo() {
	sqlStr := "delete from user where id = ?"
	ret, err := db.Exec(sqlStr, 3)
	if err != nil {
		fmt.Printf("delete failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // Number of rows affected by the operation
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("delete success, affected rows:%d\n", n)
}

MySQL preprocessing

What is preprocessing?

Normal SQL statement execution process:

  1. The client replaces the placeholder of the SQL statement to get the complete SQL statement.
  2. The client sends a complete SQL statement to the MySQL server
  3. The MySQL server executes complete SQL statements and returns the results to the client.

Preprocessing execution process:

  1. The SQL statement is divided into two parts, command part and data part.
  2. First, send the command part to the MySQL server for SQL preprocessing.
  3. Then the data part is sent to the MySQL server, which replaces the placeholder of the SQL statement.
  4. The MySQL server executes complete SQL statements and returns the results to the client.

Why preprocessing?

  1. Optimizing the method of repeated SQL execution of MySQL server can improve the server performance, let the server compile in advance, compile and execute multiple times at a time, and save the cost of subsequent compilation.
  2. Avoid SQL injection problems.

Go implements MySQL preprocessing

The following Prepare method is used in database/sql to implement preprocessing operations.

func (db *DB) Prepare(query string) (*Stmt, error)

The Prepare method will first send the sql statement to the MySQL server and return a prepared status for subsequent queries and commands. The return value can execute multiple queries and commands at the same time.

The preprocessing example code of query operation is as follows:

// Preprocessing query example
func prepareQueryDemo() {
	sqlStr := "select id, name, age from user where id > ?"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	rows, err := stmt.Query(0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	defer rows.Close()
	// Cycle through the data in the result set
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

The preprocessing of insert, update and delete operations is very similar. Here, take the preprocessing of insert operations as an example:

// Preprocessing insert example
func prepareInsertDemo() {
	sqlStr := "insert into user(name, age) values (?,?)"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec("princeling", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	_, err = stmt.Exec("Shahe Naza", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	fmt.Println("insert success.")
}

SQL injection problem

We should not splice SQL statements ourselves at any time!

Here we demonstrate an example of splicing SQL statements by ourselves. Write a function to query the user table according to the name field as follows:

// sql injection example
func sqlInjectDemo(name string) {
	sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'", name)
	fmt.Printf("SQL:%s\n", sqlStr)
	var u user
	err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("exec failed, err:%v\n", err)
		return
	}
	fmt.Printf("user:%#v\n", u)
}

At this time, the following input strings can cause SQL injection problems:

sqlInjectDemo("xxx' or 1=1#")
sqlInjectDemo("xxx' union select * from user #")
sqlInjectDemo("xxx' and (select count(*) from user) <10 #")

Supplement: the placeholder syntax used by SQL statements is different in different databases.

databasePlaceholder syntax
MySQL?
PostgreSQL$1, $2, etc
SQLite? And $1
Oracle:name

Go implements MySQL transactions

What is a transaction?

Transaction: a smallest non separable unit of work; Usually, a transaction corresponds to a complete business (such as bank account transfer business, which is the smallest work unit). At the same time, this complete business needs to be completed jointly by executing multiple DML(insert, update, delete) statements. A transfers money to B, which requires two update operations.

In MySQL, only databases or tables that use the Innodb database engine support transactions. Transaction processing can be used to maintain the integrity of the database and ensure that batch SQL statements are either executed or not executed.

ACID of the transaction

Generally, transactions must meet four conditions (ACID): Atomicity (or indivisibility), Consistency, Isolation (also known as independence) and persistence.

conditionexplain
AtomicityAll operations in a transaction are either completed or not completed, and will not end at an intermediate stage. If an error occurs during the execution of the transaction, it will be rolled back to the state before the transaction starts, as if the transaction had never been executed.
uniformityBefore and after the transaction, the integrity of the database is not destroyed. This means that the written data must fully comply with all preset rules, including the accuracy and serialization of the data, and the subsequent database can spontaneously complete the predetermined work.
IsolationThe database allows multiple concurrent transactions to read, write and modify their data at the same time. Isolation can prevent data inconsistency caused by cross execution when multiple transactions are executed concurrently. Transaction isolation is divided into different levels, including Read uncommitted, read committed, repeatable read, and Serializable.
persistenceAfter the transaction is completed, the data modification is permanent and will not be lost even if the system fails.

Transaction related methods

Go language uses the following three methods to implement transaction operations in MySQL. Start transaction

func (db *DB) Begin() (*Tx, error)

Commit transaction

func (tx *Tx) Commit() error

Rollback transaction

func (tx *Tx) Rollback() error

Transaction example

The following code demonstrates a simple transaction operation, which can ensure that the two update operations either succeed or fail at the same time, and there will be no intermediate state.

// Transaction operation example
func transactionDemo() {
	tx, err := db.Begin() // Open transaction
	if err != nil {
		if tx != nil {
			tx.Rollback() // RollBACK 
		}
		fmt.Printf("begin trans failed, err:%v\n", err)
		return
	}
	sqlStr1 := "Update user set age=30 where id=?"
	ret1, err := tx.Exec(sqlStr1, 2)
	if err != nil {
		tx.Rollback() // RollBACK 
		fmt.Printf("exec sql1 failed, err:%v\n", err)
		return
	}
	affRow1, err := ret1.RowsAffected()
	if err != nil {
		tx.Rollback() // RollBACK 
		fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
		return
	}

	sqlStr2 := "Update user set age=40 where id=?"
	ret2, err := tx.Exec(sqlStr2, 3)
	if err != nil {
		tx.Rollback() // RollBACK 
		fmt.Printf("exec sql2 failed, err:%v\n", err)
		return
	}
	affRow2, err := ret2.RowsAffected()
	if err != nil {
		tx.Rollback() // RollBACK 
		fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
		return
	}

	fmt.Println(affRow1, affRow2)
	if affRow1 == 1 && affRow2 == 1 {
		fmt.Println("The transaction is committed...")
		tx.Commit() // Commit transaction
	} else {
		tx.Rollback()
		fmt.Println("Transaction rollback...")
	}

	fmt.Println("exec trans success!")
}

More powerful and better used sqlx Library

Keywords: Go

Added by CheesierAngel on Wed, 29 Dec 2021 22:08:16 +0200