MySQL 事务隔离整理(持续更新)

事务

事务就是一组原子性的 SQL 操作,事务中的语句,要么全部执行成功,要么全部执行失败。

事务正确执行的四个要素(ACID)

  • 原子性(atomicity)
  • 一致性(consistency)
  • 隔离性(isolation)
  • 持久性(durability)

原子性

一个事务内的一组 SQL 语句被视为一个不可分割的单元,事务中的语句,要么全部执行成功,要么全部执行失败。

一致性

数据库总是从一个一致性的状态转移到另一个一致性的状态。

应用系统从一个正确的状态到另一个正确的状态,而 ACID 就是说事务能够通过 AID 来保证这个 C 的过程。C 是目的, AID 都是手段。

如何理解数据库事务中的一致性的概念? - 孟波的回答 - 知乎 https://www.zhihu.com/question/31346392/answer/362597203

隔离性

“通常来说”,一个事务所做的修改在提交之前,对其他事务是不可见的。

持久性

事务一旦提交,其所做的修改就会保存到数据库中,此时即使系统崩溃,修改的数据也不会丢失。

Note: redo log 和 binlog

事务现象

执行事务时可能会出现三个特别的现象:

  • 脏读:一个事务可以读取到其他事务未提交的数据。
  • 不可重复读:一个事务内,执行两次相同的查询,得到的数据不一样。(特指列上的数据不一样)
  • 幻读:一个事务内,查询相同范围的数据,得到的记录数一样,这样产生幻影行的现象。

隔离级别

MySQL 支持四种隔离级别:

  • read uncommitted(读未提交)
  • read committed(读提交)
  • repeatable read(可重复读)
  • serializable(串行化)

read uncommitted(读未提交)

一个事务可以读取到其他事务未提交的数据。

read committed(读提交)

一个事务提交之前,所做的修改对其他事务不可见。但是由此会产生不可重复读的问题。

repeatable read(可重复读)

解决了不可重复读的问题,一个事务内,执行两次相同的查询,得到的数据一样。

通过多版本并发控制(MVCC)解决了幻读的问题。

rr 是 MySQL 的默认隔离级别。

serializable(串行化)

这个级别下,强制事务串行执行,会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。

图片来自《高性能 MySQL》:

其他

查看隔离级别:

1
2
3
4
5
6
7
mysql> show global variables like '%tx_iso%';

+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+

设置隔离级别:

1
mysql> set transaction isolation level read committed;

支持事务的引擎:

  • InnoDB
  • NDB Cluster

多版本并发控制

MySQL 的事务型存储引擎基于并发性能的考虑实现了 MVCC。MVCC 是行级锁的一个变种,但是在很多情况下避免了加锁操作,开销更低。

MVCC 的实现,是通过保存数据在某个时间点的快照来实现的。

InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。一个列保存了行的创建时间,另一个列保存行的过期时间(或删除时间)。不是具体的时间值,而是版本号。每开始一个新的事务,系统版本号会自动递增,事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。

MVCC 只在 repeatable read 和 read committed 隔离级别下工作。

Go range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func main() {
users := []string{"alice", "bob", "claire"}

for k, v := range users {
fmt.Println(k, ":", v)
}
// 0 : alice
// 1 : bob
// 2 : claire

for k := range users {
fmt.Println(k)
}
// 0
// 1
// 2

user := map[string]interface{}{
"name": "alice",
"age": 20,
}

for k, v := range user {
fmt.Println(k, ":", v)
}
// name : alice
// age : 20

for k := range user {
fmt.Println(k)
}
// age
// name

// 遍历 map 类型的值时,元素是随机的

ch := make(chan int)

go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()

for c := range ch {
fmt.Println(c)
}
// 0
// 1
// 2
// 3
// 4

获取整型数下标处的值

1
2
3
4
5
6
func getIntIndexValue(n, l, i int) int {
if i >= l {
panic(fmt.Sprintf("index should less than length, index %d length %d", i, l))
}
return n / int(math.Pow10(i)) % 10
}

判断整型数有几位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func getIntLength(n int) int {
if n < 10 {
return 1
}

v := n
l := 0
for v > 0 {
v /= 10
l++
}

return l
}

快速排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func quickSort(data []int, start, end int) {
if start >= end {
return
}
l := start
for i := start; i < end; i++ {
if data[i] < data[end] {
data[i], data[l] = data[l], data[i]
l++
}
}
data[l], data[end] = data[end], data[l]
quickSort(data, start, l-1)
quickSort(data, l+1, end)
}

MacBook 外接显示器无法被点亮

我用的 MacBook Pro 15-Inch (2017),经常会遇到开机或睡眠唤醒后外接显示器无法被点亮的问题,外接显示器是用 USB-C 直连的。

根据我的经验,你可以尝试使用下面的方法解决问题(不保证一定成功):

  • 拔掉连接电脑的那一端,再连接
  • 拔掉连接显示器的那一端,再连接
  • 稍微拔一点连接显示器的那一端,试着晃动(多试几次)
  • 重启电脑

4月25日更新:

上次忘了说了,目前操作系统是 Catalina,High Sierra 的时候上面的几个方法还是好使的,Catalina 就不行。今天试了下用转接头连接,显示器正常显示了。

话说升级到 Catalina 之后问题是真的多,以后不乱升级了。

二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func binarySearch(data []int, item int) int {
start := 0
end := len(data) - 1
for start <= end {
mid := (start + end) / 2
if data[mid] == item {
return mid
}
if data[mid] < item {
start = mid + 1
} else {
end = mid - 1
}
}
return -1
}

选择排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func selectionSort(data []int) {
len := len(data)
for i := 0; i < len; i++ {
pos := i
for j := i + 1; j < len; j++ {
if data[j] < data[pos] {
pos = j
}
}
if pos != i {
data[pos], data[i] = data[i], data[pos]
}
}
}

GORM 连接 MySQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"time"
)

func main() {
dialect := "mysql"
source := "user:password@(host)/dbname?charset=utf8mb4&parseTime=True&loc=Local"

db, err := gorm.Open(dialect, source)
if err != nil {
panic(err)
}
defer db.Close()

db.LogMode(true)
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
db.DB().SetConnMaxLifetime(time.Hour)
}

json.Marshal() 中 omitempty 的使用

我们使用 json.Marshal() 将结构体转换为 json。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Role struct {
Id int `json:"id"`
Name string `json:"Name"`
}

type User struct {
Id int `json:"id"`
Username string `json:"username"`
Role *Role `json:"role"`
}

user := &User{
Id: 1,
Username: "user1",
Role: nil,
}

b, err := json.Marshal(user)
if err != nil {
panic(err)
}

fmt.Println(string(b)) // 输出:{"id":1,"username":"user1","role":null}

json.Marshal() 会将空指针转换为 null,所以 role 字段的值为 null

我们可能有的时候就是不想让值为 null 的字段出现在 json 中,这个时候可以使用 omitempty tag。例如:

1
2
3
4
5
6
7
type User struct {
Id int `json:"id"`
Username string `json:"username"`
Role *Role `json:"role,omitempty"`
}

fmt.Println(string(b)) // 输出:{"id":1,"username":"user1"}

加了 omitempty 之后,role 字段就被忽略了,不会出现在 json 中。实际上加了 omitempty 后,会忽略所有零值,不仅仅是空指针。

如果 omitempty 没有生效,看一下你是不是写成了 json:"role, omitempty"