🐬
GolangでMySQL/MariaDB(その1)
はじめに
前回は、Golang で PostgreSQL の各種データ型の入出力を検証しました。
今回は、MySQL と MariaDB を評価します。
MySQL と MariaDB
データベース
Amazon RDS 上に構築した MySQL サーバーを使用します。
設定 | 値 |
---|---|
エンジンバージョン | 8.0.28 |
インスタンスクラス | db.t3.micro |
vCPU | 2 |
RAM | 1GB |
ストレージ | 20GiB |
同じく MariaDB サーバーです。
設定 | 値 |
---|---|
エンジンバージョン | 10.6.10 |
インスタンスクラス | db.t3.micro |
vCPU | 2 |
RAM | 1GB |
ストレージ | 20GiB |
クライアント
Amazon EC2 の Amazon Linux 2 から接続します。
ツール
mysql を使用しますので、Amazon Linux 2 の場合は、以下のようにインストールします。
$ sudo yum install mariadb
MySQL [agra]> status
--------------
mysql Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (x86_64) using readline 5.1
Connection id: 17
Current database: agra
Current user: agra@■.■.■.■
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server: MySQL
Server version: 8.0.28 Source distribution
Protocol version: 10
Connection: mysql.■■■■.ap-northeast-1.rds.amazonaws.com via TCP/IP
Server characterset: utf8mb4
Db characterset: utf8mb3
Client characterset: utf8mb3
Conn. characterset: utf8mb3
TCP port: 3306
Uptime: 30 min 20 sec
Threads: 3 Questions: 1399 Slow queries: 0 Opens: 262 Flush tables: 3 Open tables: 180 Queries per second avg: 0.768
--------------
MySQL [agra]> show variables like "character%";
+--------------------------+-------------------------------------------+
| Variable_name | Value |
+--------------------------+-------------------------------------------+
| character_set_client | utf8mb3 |
| character_set_connection | utf8mb3 |
| character_set_database | utf8mb3 |
| character_set_filesystem | binary |
| character_set_results | utf8mb3 |
| character_set_server | utf8mb4 |
| character_set_system | utf8mb3 |
| character_sets_dir | /rdsdbbin/mysql-8.0.28.R4/share/charsets/ |
+--------------------------+-------------------------------------------+
8 rows in set (0.00 sec)
MariaDB [agra]> status
--------------
mysql Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (x86_64) using readline 5.1
Connection id: 62
Current database: agra
Current user: agra@■.■.■.■
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server: MariaDB
Server version: 10.6.10-MariaDB-log managed by https://aws.amazon.com/rds/
Protocol version: 10
Connection: maria.■■■■.ap-northeast-1.rds.amazonaws.com via TCP/IP
Server characterset: latin1
Db characterset: latin1
Client characterset: utf8mb3
Conn. characterset: utf8mb3
TCP port: 3306
Uptime: 38 min 32 sec
Threads: 3 Questions: 1387 Slow queries: 0 Opens: 44 Open tables: 15 Queries per second avg: 0.599
--------------
MariaDB [agra]> show variables like "character%";
+--------------------------+----------------------------------------------+
| Variable_name | Value |
+--------------------------+----------------------------------------------+
| character_set_client | utf8mb3 |
| character_set_connection | utf8mb3 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | utf8mb3 |
| character_set_server | latin1 |
| character_set_system | utf8mb3 |
| character_sets_dir | /rdsdbbin/mariadb-10.6.10.R1/share/charsets/ |
+--------------------------+----------------------------------------------+
8 rows in set (0.01 sec)
テスト用のテーブル
データ型の一覧
マニュアル から抜粋します。
名称 | 別名 | 説明 |
---|---|---|
tinyint | 1byte整数 | |
smallint | 2byte整数 | |
mediumint | 3byte整数 | |
int | 4byte整数 | |
bigint | 8byte整数 | |
decimal | numeric | 固定小数点数 |
float | 4byte浮動小数点数 | |
double | 8byte浮動小数点数 | |
bit | ビット値 | |
date | 日付 | |
datetime | 日時 | |
timestamp | UNIXタイムスタンプ | |
time | 時間値 | |
year | 年(1901~2155) | |
char | 固定長文字列 | |
varchar | 可変長文字列 | |
binary | 固定長byte列 | |
varbinary | 可変長byte列 | |
tinyblob | binary large object 大きさ順 | |
blob | ||
mediumblob | ||
longblob | ||
tinytext | character large object 大きさ順 | |
text | ||
mediumtext | ||
longtext | ||
enum | 列挙 | |
set | 集合 | |
geometry | pointかlinestringかpolygon | |
point | 座標 | |
linestring | 直線で保管される座標の列 | |
polygon | 単純かつ閉じたlinestring | |
multipoint | pointの列 | |
multilinestring | linestringの列 | |
multipolygon | polygonの列 | |
geometrycollection | geometryの列 | |
json | JSON文字列 |
テスト用のテーブル
そのまま CREATE TABLE文にします。
CREATE TABLE datatypes (
col01 tinyint COMMENT '1byte整数',
col02 smallint COMMENT '2byte整数',
col03 mediumint COMMENT '3byte整数',
col04 int COMMENT '4byte整数',
col05 bigint COMMENT '8byte整数',
col06 decimal(10,4) COMMENT '固定小数点数',
col07 float COMMENT '4byte浮動小数点数',
col08 double COMMENT '8byte浮動小数点数',
col09 bit(3) COMMENT 'ビット値',
col10 date COMMENT '日付',
col11 datetime COMMENT '日時',
col12 timestamp COMMENT 'UNIXタイムスタンプ',
col13 time COMMENT '時間値',
col14 year COMMENT '年',
col15 char(10) COMMENT '固定長文字列',
col16 varchar(10) COMMENT '可変長文字列',
col17 binary(10) COMMENT '固定長byte列',
col18 varbinary(10) COMMENT '可変長byte列',
col19 tinyblob COMMENT 'tinyblob',
col20 blob COMMENT 'blob',
col21 mediumblob COMMENT 'mediumblob',
col22 longblob COMMENT 'longblob',
col23 tinytext COMMENT 'tinytext',
col24 text COMMENT 'text',
col25 mediumtext COMMENT 'mediumtext',
col26 longtext COMMENT 'longtext',
col27 enum('S', 'M', 'L') COMMENT '列挙',
col28 set('R', 'G', 'B') COMMENT '集合',
col29 geometry COMMENT 'POINTかLINESTRINGかPOLYGON',
col30 point COMMENT '座標',
col31 linestring COMMENT '直線で補間される一連の点',
col32 polygon COMMENT '単純かつ閉じたLINESTRING',
col33 multipoint COMMENT '座標の集まり',
col34 multilinestring COMMENT 'LINESTRINGの集まり',
col35 multipolygon COMMENT 'POLYGONの集まり',
col36 geometrycollection COMMENT 'GEOMETRYの集まり',
col37 json COMMENT 'JSON文字列',
CONSTRAINT pk_datatypes PRIMARY KEY(col01)
) COMMENT='データ型テスト';
Golang
処理系
$ go version
go version go1.18.6 linux/amd64
ライブラリ
github.com/go-sql-driver/mysql の v1.6.0 を使用します。データ型については、以下の仕様でつくられているようです。
アプリケーション
テスト用のテーブルに対して入出力を行うアプリケーションを実装します。歯抜けの状態から徐々に埋めていけるように、Nullable 型で入出力します。
main.go
package main
import (
"database/sql"
"log"
"os"
"reflect"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/google/go-cmp/cmp"
"github.com/jmoiron/sqlx"
)
// Datatypes は、データ型テストテーブル
type Datatypes struct {
Col01 sql.NullInt32 `db:"col01"` // tinyint
Col02 sql.NullInt32 `db:"col02"` // smallint
Col03 sql.NullInt64 `db:"col03"` // mediumint
Col04 sql.NullInt64 `db:"col04"` // int
Col05 sql.NullInt64 `db:"col05"` // bigint
Col06 sql.NullString `db:"col06"` // decimal(10,4)
Col07 sql.NullFloat64 `db:"col07"` // float
Col08 sql.NullFloat64 `db:"col08"` // double
Col09 []byte `db:"col09"` // bit(3)
Col10 sql.NullTime `db:"col10"` // date
Col11 sql.NullTime `db:"col11"` // datetime
Col12 sql.NullTime `db:"col12"` // timestamp
Col13 sql.NullString `db:"col13"` // time
Col14 sql.NullInt32 `db:"col14"` // year
Col15 sql.NullString `db:"col15"` // char(10)
Col16 sql.NullString `db:"col16"` // varchar(10)
Col17 []byte `db:"col17"` // binary(10)
Col18 []byte `db:"col18"` // varbinary(10)
Col19 []byte `db:"col19"` // tinyblob
Col20 []byte `db:"col20"` // blob
Col21 []byte `db:"col21"` // mediumblob
Col22 []byte `db:"col22"` // longblob
Col23 sql.NullString `db:"col23"` // tinytext
Col24 sql.NullString `db:"col24"` // text
Col25 sql.NullString `db:"col25"` // mediumtext
Col26 sql.NullString `db:"col26"` // longtext
Col27 sql.NullString `db:"col27"` // enum('S', 'M', 'L')
Col28 sql.NullString `db:"col28"` // set('R', 'G', 'B')
Col29 []byte `db:"col29"` // geometry
Col30 []byte `db:"col30"` // point
Col31 []byte `db:"col31"` // linestring
Col32 []byte `db:"col32"` // polygon
Col33 []byte `db:"col33"` // multipoint
Col34 []byte `db:"col34"` // multilinestring
Col35 []byte `db:"col35"` // multipolygon
Col36 []byte `db:"col36"` // geometrycollection
Col37 sql.NullString `db:"col37"` // json
}
// Key は、データ型テストテーブルのキー
type Key struct {
Col01 int32 `db:"col01"`
}
func main() {
dsn := os.Getenv("DSN")
db, err := sqlx.Open("mysql", dsn)
if err != nil {
log.Printf("sql.Open error %s", err)
}
JST, _ := time.LoadLocation("Asia/Tokyo")
key := Key{1}
src := Datatypes{
Col01: sql.NullInt32{Int32: 1, Valid: true},
Col02: sql.NullInt32{Int32: 2, Valid: true},
Col03: sql.NullInt64{Int64: 3, Valid: true},
Col04: sql.NullInt64{Int64: 4, Valid: true},
Col05: sql.NullInt64{Int64: 5, Valid: true},
Col06: sql.NullString{String: "123456.1234", Valid: true},
Col07: sql.NullFloat64{Float64: 1.5, Valid: true},
Col08: sql.NullFloat64{Float64: 1.25, Valid: true},
Col09: []byte{3},
Col10: sql.NullTime{Time: time.Date(2001, 2, 3, 0, 0, 0, 0, JST), Valid: true}, // loc 指定で JST
Col11: sql.NullTime{Time: time.Date(2001, 2, 3, 4, 5, 6, 0, JST), Valid: true}, // loc 指定で JST, ミリ秒は0
Col12: sql.NullTime{Time: time.Date(2001, 2, 3, 4, 5, 6, 0, JST), Valid: true}, // MariaDB は default が Now
Col13: sql.NullString{String: "12:34:56", Valid: true},
Col14: sql.NullInt32{Int32: 2000, Valid: true},
Col15: sql.NullString{String: "char(10)", Valid: true}, // 末尾空白なし
Col16: sql.NullString{String: "varchar", Valid: true},
Col17: []byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0}, // 末尾 0 埋め
Col18: []byte{1, 2, 3, 4, 5, 6, 7, 8},
Col19: []byte{1, 2, 3, 4, 5, 6, 7, 8},
Col20: []byte{1, 2, 3, 4, 5, 6, 7, 8},
Col21: []byte{1, 2, 3, 4, 5, 6, 7, 8},
Col22: []byte{1, 2, 3, 4, 5, 6, 7, 8},
Col23: sql.NullString{String: "tinytext", Valid: true},
Col24: sql.NullString{String: "text", Valid: true},
Col25: sql.NullString{String: "mediumtext", Valid: true},
Col26: sql.NullString{String: "longtext", Valid: true},
Col27: sql.NullString{String: "S", Valid: true},
Col28: sql.NullString{String: "R,G,B", Valid: true},
Col29: []byte{
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
0xBF,
}, // POINT(1,-1)
Col37: sql.NullString{String: `{"key": 1}`, Valid: true}, // : の後ろのスペースは1つ
}
_, err = db.NamedExec(`
INSERT INTO datatypes (
col01, col02, col03, col04, col05, col06, col07, col08, col09, col10,
col11, col12, col13, col14, col15, col16, col17, col18, col19, col20,
col21, col22, col23, col24, col25, col26, col27, col28, col29, col30,
col31, col32, col33, col34, col35, col36, col37
) VALUES (
:col01, :col02, :col03, :col04, :col05, :col06, :col07, :col08, :col09, :col10,
:col11, :col12, :col13, :col14, :col15, :col16, :col17, :col18, :col19, :col20,
:col21, :col22, :col23, :col24, :col25, :col26, :col27, :col28, :col29, :col30,
:col31, :col32, :col33, :col34, :col35, :col36, :col37
)`,
src,
)
if err != nil {
log.Printf("db.Exec error %s", err)
}
dst := Datatypes{}
query, args, err := db.BindNamed(`
SELECT
col01, col02, col03, col04, col05, col06, col07, col08, col09, col10,
col11, col12, col13, col14, col15, col16, col17, col18, col19, col20,
col21, col22, col23, col24, col25, col26, col27, col28, col29, col30,
col31, col32, col33, col34, col35, col36, col37
FROM datatypes
WHERE col01 = :col01`,
key,
)
if err != nil {
log.Printf("db.BindNamed error %s", err)
}
err = db.QueryRowx(query,
args...,
).StructScan(
&dst,
)
if err != nil {
log.Printf("db.QueryRow error %s", err)
}
if !reflect.DeepEqual(src, dst) {
// log.Printf("\nsrc = %#v\ndst = %#v\n", src, dst)
diff := cmp.Diff(src, dst)
if len(diff) > 0 {
log.Print(diff)
}
}
_, err = db.NamedExec(`
DELETE FROM datatypes
WHERE col01 = :col01`,
key,
)
if err != nil {
log.Printf("db.Exec error %s", err)
}
}
おわりに
コメントにも書きましたが、気がついたこととしては、
- 接続文字列に
?parseTime=true&loc=Asia%2FTokyo
のようにオプションを付けることで、time.Time が使え、タイムゾーンを日本標準時に設定できます。 - UNIXタイムスタンプのカラムは、MariaDB では NOT NULL になって、デフォルト値 Now が設定されました。
- CHAR の末尾の空白はデフォルトでは出力されませんでした。
- CHAR とは対照的に BINARY の末尾の 0 は出力されます。
- BINARY 系は、文字列でも受けられなくはないですが、[]byte を string にキャストしたような状態になるので、[]byte が安全と思います。
- GEOMETRY 系は、組み込み関数を使わない場合は、[]byte での入出力になりますが、解析が面倒なので省略しました。
日本語については、別途、テストしたいと思います。
GitHubで編集を提案
Discussion