1、事务的属性(ACID)

(1)原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

(2)一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

(3)隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

(4)持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

2、并发性问题

对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

(1)脏读: 对于两个事务 T1、T2, T1 读取了已经被 T2 更新但还没有被提交的字段,之后, 若 T2 回滚, T1读取的内容就是临时且无效的。

(2)不可重复读: 对于两个事务T1、T2, T1 读取了一个字段, 然后 T2 更新了该字段,之后, T1再次读取同一个字段, 值就不同了.

(3)幻读: 对于两个事务T1、T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插 入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.

3、事务隔离级别,即一个事务与其他事务隔离的程度称为隔离级别

read uncommitted

会出现脏读、不可重复读、幻读

read committed

会出现不可重复读、幻读

repeatable read

会出现幻读

serializable(串行化)

不会出现并发性问题,但性能低下

Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED Mysql 支持 4 种事务隔离级别,

Mysql 默认的事务隔离级别为: REPEATABLE READ,可重复读能够保证一个事务执行过程中看到的数据跟这个事务在启动时看到的数据是一致的。

每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量@@tx_isolation, 表示当前的事务隔离级别.

–查看当前的隔离级别: SELECT @@tx_isolation;

–设置当前 mySQL 连接的隔离级别: set transaction isolation level read committed;

–设置数据库系统的全局的隔离级别:set global transaction isolation level read committed;

4、行级锁

行级锁,每次操作锁住对应的行数据,锁定粒度最小,发生锁冲突的概率最低,并发度最高。但innodb的行级锁是通过给索引上的索引项加锁实现的,而不是对记录加锁,这就意味着只有通过索引条件检索数据时,innodb才使用行锁否则使用表锁

对于行级锁,主要分为以下三类:

(1)行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。

(2)间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。

(3)临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。

InnoDB实现了以下两种类型的行锁(Record Lock):

共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。

排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。

Golang之mysql_字段

5、InnoDB行级锁的优化及退化

MySQL的innodb存储引擎支持行级锁。
Innodb默认是可重复读隔离级别且使用 next-key 锁进行搜索和索引扫描以防止幻读,但innodb的行锁是通过给索引项加锁实现的,这就意味着只有通过索引条件检索数据时,innodb才使用行级锁,否则退化为表锁。

索引上的等值查询(唯一索引),对已存在的记录进行等值匹配时,将会自动优化为行锁。

索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。

索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key 锁退化为间隙锁。

索引上的范围查询(唯一索引)–会访问到不满足条件的第一个值为止。

6、GORM

(1)简介

github.com/jinzhu/gorm 是Gorm v1的地址;
gorm.io/gorm 是Gorm v2的地址;

在Gorm v2在v1的基础上 进行了性能优化;事务支持save point, rollback saved point; 支持数据库的读写分离,可参考​​更新日志 | 高级主题 |《GORM 中文文档 v2》| Go 技术论坛​

(2)连接配置

SetMaxOpenConns 客户端最大连接数;

SetMaxIdleConns 最大空闲可复用连接数,即可用连接池的大小,再多的空闲连接就会被杀死,保留空闲连接是为了复用连接,不然每次执行sql都会重新建立新的连接;

SetConnMaxLifetime 连接最长持续时间,从连接建立开始计算,默认时间为永久不过期;

SetConnMaxIdleTime 连接最长空闲时间,从连接空闲开始计算,默认时间为永久不过期,一般要小于SetConnMaxLifetime;

打开的连接 = 正在使用的连接(inuse) + 处于空闲状态的连接(idle),同时如果各个客户端未释放的连接数已经达到 MySQL 的上限,就会报错 "Can not connect to MySQL server. Too many connections"

(3)Gorm链式操作

创建方法才是那些会产生 SQL 查询并且发送到数据库,通常它就是一些 CRUD 方法, 比如:Create, First, Find, Take, Save, UpdateXXX, Delete, Scan, Row, Rows。所有的链式操作都将会克隆并创建一个新的数据库连接,所以GORM 对于多个 goroutines 的并发使用是安全的。

(4)Gorm生命周期钩子

// 开启事务
BeforeSave
BeforeCreate
// 连表前的保存
// 更新时间戳 `CreatedAt`, `UpdatedAt`
// 保存自己
// 重载哪些有默认值和空的字段
// 链表后的保存
AfterCreate
AfterSave
// 提交或回滚事务

(5)Gorm事务

事务模板

// 开启事务
tx := db.Begin()

// 在事务中执行一些数据库操作 (从这里开始使用 'tx',而不是 'db')
tx.Create(...)

// ...

// 发生错误回滚事务
tx.Rollback()

// 或者提交这个事务
tx.Commit()

事务例子一

func CreateAnimals(db *gorm.DB) err {
// 注意在事务中要使用 tx 作为数据库句柄
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()

if tx.Error != nil {
return err
}

if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
tx.Rollback()
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
tx.Rollback()
return err
}

return tx.Commit().Error
}

事务例子二

func (m *Manager) TransactionHandle(callback func(db *gorm.DB) error) (err error) {
tx := m.Begin()
defer func() {
if r := recover(); r != nil {
klog.Errorf("Unexpected panic occurred, rollback transaction: %v", r)
err = fmt.Errorf("%v", r)
}
if err != nil {
rollBackErr := tx.Rollback().Error
if rollBackErr == nil {
err = fmt.Errorf("callback err : (%s) , rollBack success", err.Error())
} else {
err = fmt.Errorf("callback err : (%s) , rollBack err : (%s) ", err.Error(), rollBackErr.Error())
}
} else {
if err = tx.Commit().Error; err != nil {
err = fmt.Errorf("commit err : (%s)", err.Error())
}
}
}()
err = callback(tx)
return err
}

7、mysql 阿里巴巴设计规范

1、字段类型

(1)varchar是可变长字符串,不预先分配存储空间,长度不超过5000,如果存储长度大于此值,一般要定义字段类型为 text ,5.0.3版本以下,varchar(20),指的是*20字节*,如果存放UTF8汉字时,只能存6个(每个汉字3字节) 5.0.3版本以上,varchar(20),指的是*20字符*,无论存放的是数字、字母还是UTF8汉字(每个汉字3字节),都可以存放20个,最大大小是65532字节。因此目前 char(4) 和varchar(4)表示字符长度、其他bigint(4)都是表示字节长度,一般为2的幂次。

(2)TEXT用来存储大文本,查询效率低;

(3)BLOB是二进制类型,用来存储大文本、图像、视频等二进制资源。

因为Mysql是行存储模式,所以会把整行读取出,而查询性能主要取决于磁盘IO的性能,当记录存在大容量字段时,会导致查询任何字段都会加载大容量字段,占用大量的io导致查询很慢,所以一般建议只存储大容量路径,而非资源本身,然后将大字段存储在mongodb中。如果非要在Mysql中存储大资源,强烈推荐将大资源单独存储到一张表中,使用主键与其关联。

2、一张表字段不宜太多

一般尽量不超过20个字段,字段太多会导致查询效率低。如果实在表的字段非常多,可以分成两张表一张条件查询表,一张作为详细内容表。

3、主键设置

UUID、Auto_increment、雪花算法生成;

4、逻辑删除

优先考虑逻辑删除(is_deleted)还不是物理删除,物理删除自增主键不再连续。

5、字段设置

一般建议都设置为NOT NULL类型,防止出现空指针,其次NULL也是需要存储空间的,且可能会导致索引失败。

6、通用字段

Golang之mysql_Gorm_02

7、索引

一个表的索引一般不超过5个。索引过多的话可以通过联合索引来优化,还有覆盖索引、最左匹配原则等。

Golang之mysql_隔离级别_03

8、3NF

Golang之mysql_字段_04

9、外键

不得使用外键与级联,一切外键概念必须在应用层解决,避免外键存在的性能问题、并发死锁问题等。

10、1:N关系的设计

Golang之mysql_golang_05

11、分库分表

Golang之mysql_golang_06

Golang之mysql_Gorm_07

12、优化经验

Golang之mysql_字段_08