golang数据库编程
SQL语法简介
SQL(i/ˈsiːkwəl/; Structured Query Language)是一套语法标准,不区分大小写。MySQL、sql-server和Oracle都是关系型数据库,在一些高级语法上跟标准SQL略有出入。
以管理员登录
1 | mysql -h localhost -P 3306 -u root -p'123456' |
- -h:mysql server host,不写时默认是localhost。
- -P:mysql server port,不写时默认是3306。
- -u:user name,-u后可以加空格也可以不加。
- -p:password,密码中可能包含空格,所以要加引号。高版本的mysql不允许在命令行中直接输入密码,此时只输入-p后面不要写密码即可。
创建表
1 | create table if not exists student( |
新增记录,必须给not null且无default值的列赋值。
1 | insert into student (name,province,city,enrollment) values |
查询
1 | select id,name from student where id>0; |
修改
1 | update student set score=score+10,addr='海淀' where province='北京'; |
删除
1 | delete from student where city= '郑州'; |
MySQL最佳实践
- 写sql时一律使用小写。
- 建表时先判断表是否已存在if not exists。
- 所有的列和表都加comment。
- 字符串长度比较短时尽量使用char,定长有利于内存对齐,读写性能更好,而varchar字段频繁修改时容易产生内存碎片。
- 满足需求的前提下尽量使用短的数据类型,如tinyint vs int, float vs double, date vs datetime。
null
- default null有别于default ''和default 0
- is null, is not null有别于!= ‘’, !=0
- 尽量设为not null
- 有些DB索引列不允许包含null
- 对含有null的列进行统计,结果可能不符合预期
- null值有时候会严重拖慢系统性能
索引

- B即Balance,对于m叉树每个节点上最多有m个数据,最少有m/2个数据(根节点除外)。
- 叶节点上存储了所有数据,把叶节点链接起来可以顺序遍历所有数据。
- 每个节点设计成内存页的整倍数。MySQL的m=1200,树的前两层放在内存中。
- MySQL索引默认使用B+树。(思考:为什么不用hash table?)
- 主键默认会加索引。按主键构建的B+树里包含所有列的数据,而普通索引的B+树里只存储了主键,还需要再查一次主键对应的B+树(回表)。
- 联合索引的前缀同样具有索引的效果。
- sql语句前加explain可以查看索引使用情况。
- 如果MySQL没有选择最优的索引方案,可以在where前force index (index_name)。
规避慢查询
- 大部分的慢查询都是因为没有正确地使用索引。查看一条SQL语句使用索引的情况只需要在SQL前加个explain。
- 一次select不要超过1000行。
- 分页查询limit m,n会检索前m+n行,只是返回后n行,通常用id>x来代替这种分页方式(stmt一节会展示遍历整个table的正确姿势)。
- 批量操作时最好一条sql语句搞定;其次打包成一个事务,一次性提交(高并发情况下减少对共享资源的争用)。
- 不要使用连表操作,join逻辑在业务代码里完成。
Go SQL驱动接口解读
Go官方没有提供数据库驱动,而是为开发数据库驱动定义了一些标准接口(即database/sql ),开发者可以根据定义的接口来开发相应的数据库驱动。Go中支持MySQL的驱动比较多,如
- github.com/go-sql-driver/mysql 支持 database/sql
- github.com/ziutek/mymysql 支持 database/sql,也支持自定义的接口
- github.com/Philio/GoMySQL 不支持 database/sql,自定义接口
Driver
1 | type Driver interface { |
Conn
1 | type Conn interface { |
Stmt
1 | type Stmt interface { |
Tx
1 | type Tx interface { |
Result
1 | type Result interface { |
RowsAffected
RowsAffected是int64的别名,它实现了Result接口。
1 | type RowsAffected int64 |
Rows
1 | type Rows interface { |
Value
1 | type Value interface{} |
Value 要么是 nil,要么是下面的任意一种
- int64
- float64
- bool
- []byte
- string
- time.Time
ValueConverter
1 | type ValueConverter interface { |
数据库增删改查
下载第三方库
1 | go get github.com/go-sql-driver/mysql |
连接数据库
1 | db, err := sql.Open("mysql", "root:@tcp(localhost:3306)/test?charset=utf8") |
DSN(data source name)格式:
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&…¶mN=valueN]
例如user:password@tcp(localhost:5555)/dbname?charset=utf8mb4&parseTime=True
如果是本地MySQl,且采用默认的3306端口,可简写为:user:password@/dbname
连接参数
要支持完整的UTF-8编码,您需要将charset=utf8更改为charset=utf8mb4
想要正确的处理time.Time ,您需要带上parseTime参数
增删改
1 | func (*sql.DB).Exec(sql string) (sql.Result, error) |
查
1 | func (*sql.DB).Query(sql string) (*sql.Rows, error) |
crud.go
1 | package main |
stmt
首先看两个sql注入攻击的例子。
1 | sql = "select username,password from user where username='" + username + "' and password='" + password + "'"; |
变量username和password从前端输入框获取,如果用户输入的username为lily, password为aaa’ or ‘1’='1,则完整的sql为select username,password from user where username=‘lily’ and password=‘aaa’ or ‘1’=‘1’。会返回表里的所有记录,如果记录数大于0就允许登录,则lily的账号被盗。
1 | sql="insert into student (name) values ('"+username+" ') "; |
变量username从前端输入框获取,如果用户输入的username为lily’); drop table student;–。完整sql为insert into student (name) values (‘lily’); drop table student;–‘)。通过注释符–屏蔽掉了末尾的’),删除了整个表。
防止sql注入的方法:
- 前端输入要加正则校验、长度限制。
- 对特殊符号(<>&*; '"等)进行转义或编码转换,Go的text/template 包里面的HTMLEscapeString函数可以对字符串进行转义处理。
- 不要将用户输入直接嵌入到sql语句中,而应该使用参数化查询接口,如Prepare、Query、Exec(query string, args …interface{})。
- 使用专业的SQL注入检测工具进行检测,如sqlmap、SQLninja。
- 避免网站打印出SQL错误信息,以防止攻击者利用这些错误信息进行SQL注入。
参数化查询
1 | db.Where("merchant_id = ?", merchantId) |
拼接sql
1 | db.Where(fmt.Sprintf("merchant_id = %s", merchantId)) |
定义一个sql模板
1 | stmt, err := db.Prepare("update student set score=score+? where city=?") |
多次使用模板
1 | res, err := stmt.Exec(10, "上海") |
SQL预编译
DB执行sql分为3步:
- 词法和语义解析。
- 优化SQL语句,制定执行计划。
- 执行并返回结果。
SQL预编译技术是指将用户输入用占位符?代替,先对这个模板化的sql进行预编译,实际运行时再将用户输入代入。除了可以防止SQL注入,还可以对预编译的SQL语句进行缓存,之后的运行就省去了解析优化SQL语句的过程。
stmt_demo.go
1 | package main |
SQLBuilder
Go-SQLBuilder
Go-SQLBuilder是一个用于创建SQL语句的工具函数库,提供一系列灵活的、与原生SQL语法一致的链式函数。归属于艾润物联公司。安装方式
1 | go get -u github.com/parkingwang/go-sqlbuilder |
Go-SQLBuilder通过函数链来构造sql语句,比如select语句的构造
1 | func query() { |
为什么需要SQLBuilder?
- 写一句很长的sql容易出错,且出错后不好定位。
- 函数式编程可以直接定位到是哪个函数的问题。
- 函数式编程比一长串sql更容易编写和理解。
Gendry
Gendry是一个用于辅助操作数据库的Go包。基于go-sql-driver /mysql,它提供了一系列的方法来为你调用标准库database/sql中的方法准备参数。安装方式
1 | go get –u github.com/didi/gendry |
Gendry倾向于把复杂的筛选条件放在map中,并且跟stmt技术结合得比较紧密。
1 | func query(db *sql.DB) { |
自行实现SQLBuilder
作为练习,我们自行实现一个SQLBuilder,它最终应该支持如下函数链式的编程风格。
1 | sql := NewSelectBuilder("student").Column("id,name,city"). |
Builder设计模式的精髓在于Builder对象的方法还是返回一个Builder。首先定义一个Builder接口。
1 | type Builder interface { |
select、where、limit、orderby这些都是Builder,这里详细讲解WhereBuilder的设计与实现。
1 | type WhereBuilder struct { |
WhereBuilder中的sb负责当下,orderby和limit负责维护后面节点,prev负责维护前面的节点。
where表达式中可能包含and和or,把它们定义为WhereBuilder的方法,并且这两个方法依赖返回WhereBuilder自身。
1 | func (self *WhereBuilder) And(condition string) *WhereBuilder { |
where表达式后面可能会跟order by表达式,把OrderBy定义为WhereBuilder的方法,该方法返回OrderByBuilder。
1 | func (self *WhereBuilder) OrderBy(column string) *OrderByBuilder { |
函数链上的最后一个Builder调用ToString()方法生成写成的sql语句。
1 | func (self *LimitBuilder) ToString() string { |
每个Builder都有toString()方法,以WhereBuilder为例,它在构造函数里把where表达式放入sb成员变量里,WhereBuilder在toString()方法里调用where后面的节点的toString()方法。
1 | func newWhereBuilder(condition string) *WhereBuilder { |
GORM
ORM即Object Relational Mapping,对象关系映射。Relational指各种sql类的关系型数据库。Object指面向对象编程(object-oriented programming)中的对象。ORM在数据库记录和程序对象之间做一层映射转换,使程序中不用再去编写原生SQL,而是面向对象的思想去编写类、对象、调用相应的方法来完成数据库操作。
1 | go get -u gorm.io/gorm |
GORM是一个全能的、友好的、基于golang的ORM库。
GORM 倾向于约定,而不是配置。默认情况下,GORM 使用ID作为主键,使用结构体名的【蛇形复数】作为表名,字段名的【蛇形】作为列名,并使用CreatedAt、UpdatedAt字段追踪创建、更新时间。
GORM完全是在操作struct,看不到sql的影子。
1 | type Student struct { |
GORM同时支持使用函数链的方式写sql语句。
1 | package main |
Go操作MongoDB
NoSQL泛指非关系型数据库,如mongo,redis,HBase。mongo使用高效的二进制数据存储,文件存储格式为 BSON ( 一种json的扩展,比json性能更好,功能更强大)。MySQL中表的概念在mongo里叫集合(collection), MySQL中行的概念在mongo中叫文档(document),一个文档看上去像一个json。
安装mongo前先配置yum源: vim /etc/yum.repos.d/mongodb-org-4.2.repo
1 | [mongodb-org-4.2] |
一键安装mongo: sudo yum install -y mongodb-org
启动mongo: systemctl start mongod
mongo常用命令
1 | show databases |
安装go mongo-driver
1 | go get go.mongodb.org/mongo-driver |
示例
1 | package main |