Skip to content

Commit 08def65

Browse files
committed
Saved 2022年11月15日 星期二 00时11分31秒 CST
1 parent 93db250 commit 08def65

5 files changed

+338
-1
lines changed

MySQL/MySQL semi-sync.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ If something goes wrong such that the slave does not process the transaction, th
4444

4545
=="Engine Commit" makes data permanent and release locks on the data. So other sessions can reach the data since then==, even if the session is still waiting for the acknowledgement. It will cause ==phantom read== if master crashes and slave takes work over.
4646

47-
![image-20221114231510230](/home/littleneko/.config/Typora/typora-user-images/image-20221114231510230.png)
47+
![image.png](https://littleneko.oss-cn-beijing.aliyuncs.com/img/1586189316982-4ef4d477-fbec-4741-94dc-b5102ae40139-20221114233912363.png)
4848

4949
# Links
5050

MySQL/gh-ost/1. MySQL DDL.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# MySQL 5.5 版本及之前版本的 DDL 实现方式
2+
<img src="https://littleneko.oss-cn-beijing.aliyuncs.com/img/1594804850601-aa9177c7-d300-410d-94a4-5ebebec88e76.png" style="zoom:50%;" />
3+
4+
上图不难看出,5.5 及之前版本 DDL 实现的方式存在如下问题:
5+
6+
1. copy data 的过程需要耗费额外的存储空间,并且耗时很长。
7+
2. copy data 的过程有写锁,无法持续对业务提供正常服务。
8+
9+
虽然在 MySQL5.5 版本中增加了 IN-Place 方式,但依然会阻塞 INSERT、UPDATE、DELETE 操作。
10+
# MySQL 5.6 和 5.7 的 Online DDL
11+
MySQL 5.6 的时候,支持了在线上 部分 DDL 的过程中不阻塞 DML 操作,真正意义上的实现了 Online DDL,在 5.7 的时候又增加了更多的 Online DDL 操作。具体的支持可以查看 MySQL 的官方文档:[https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html](https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html)
12+
13+
现在,MySQL 的创建/删除索引(非全文索引)、加减列、更改列名、增加 varchar 的长度等操作已经支持 online,即操作过程中表是可以正常读写的;但是比如说更改列类型操作是不支持 online 的。
14+
15+
对于官方文档中一些名词的解释:
16+
17+
- **In Place:**所有操作都在 InnoDB 引擎层完成,不需要经过临时表的中转
18+
- **Rebuilds Table**:涉及到表的重建,在原表路径下创建新的 .frm 和 .ibd 文件,消耗的 IO 会较多。期间(原表可以修改)会申请 row log 空间记录 DDL 执行期间的 DML 操作,这部分操作会在 DDL 提交阶段应用新的表空间中
19+
- **Permits Concurrent DML:**是否 online
20+
- **Only Modifies Metadata**
21+
## MySQL 5.7 Online DDL 流程
22+
Online DDL operations can be viewed as having three phases:
23+
24+
- _Phase 1: Initialization_
25+
In the initialization phase, the server determines how much concurrency is permitted during the operation, taking into account storage engine capabilities, operations specified in the statement, and user-specified `ALGORITHM` and `LOCK` options. During this phase, a shared upgradeable metadata lock is taken to protect the current table definition.
26+
- _Phase 2: Execution_
27+
In this phase, the statement is prepared and executed. Whether the metadata lock is upgraded to exclusive depends on the factors assessed in the initialization phase. If an exclusive metadata lock is required, it is only taken briefly during statement preparation.
28+
- _Phase 3: Commit Table Definition_
29+
In the commit table definition phase, the metadata lock is upgraded to exclusive to evict the old table definition and commit the new one. Once granted, the duration of the exclusive metadata lock is brief.
30+
## MySQL 5.7 Online DDL 使用限制与问题
31+
32+
1. 仍然存在排他锁,有锁等待的风险。
33+
2. 跟 5.6 一样,增量日志大小是有限制的(由 innodb_online_alter_log_max_size 参数决定大小)
34+
3. 有可能造成主从延迟
35+
4. 无法暂停,只能中断
36+
37+
在 DDL 期间产生的数据,会按照正常操作一样,写入原表,记 redolog、undolog、binlog,并同步到从库去执行,只是额外会记录在 row log 中,并且写入 row log 的操作本身也会记录 redolog,而在提交阶段才进行 row log 重做,此阶段会锁表,此时主库(新表空间+row log)和从库(表空间)数据是一致的,在主库DDL 操作执行完成并提交,这个 DDL 才会写入 binlog 传到从库执行,在从库执行该 DDL 时,这个 DDL 对于从库本地来讲仍然是 online 的,也就是在从库本地直接写入数据是不会阻塞的,也会像主库一样产生 row log。但是对于主库同步过来 DML,此时会被阻塞,是 offline 的,DDL 是排他锁的在复制线程中也是一样,所以不只会阻塞该表,而是后续所有从主库同步过来的操作(主要是在复制线程并行时会排他,同一时间只有他自己在执行)。所以大表的 DDL 操作,会造成同步延迟。
38+
# 其他 Online DDL 工具
39+
## PT-OSC(Percona Toolkit Online Schema Change)/Facebook-OSC
40+
存在的风险:
41+
42+
1. 触发器开销
43+
2. 触发器与原语句在同一个事物
44+
3. 无法暂停和限流

MySQL/gh-ost/2. gh-ost 原理.md

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# gh-ost 是什么
2+
GitHub's Online Schema Transmogrifier/Translator/Transformer/Transfigurator
3+
4+
## GitHub's online schema migration for MySQL
5+
`gh-ost` is a ==triggerless== ==online== schema migration solution for MySQL. It is testable and provides pausability, dynamic control/reconfiguration, auditing, and many operational perks.
6+
7+
`gh-ost` produces a light workload on the master throughout the migration, decoupled from the existing workload on the migrated table.
8+
It has been designed based on years of experience with existing solutions, and changes the paradigm of table migrations.
9+
10+
# gh-ost 如何工作
11+
## gh-ost 工作模式
12+
<img src="https://github.com/github/gh-ost/raw/master/doc/images/gh-ost-operation-modes.png#crop=0&crop=0&crop=1&crop=1&height=803&id=zWco7&originHeight=803&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=1920" style="zoom:50%;" />
13+
14+
## 流程
15+
gh-ost 在主库上创建一张与原始表定义相同的 gho 表,在未同步数据前先把 gho 表 alter 改好表定义,然后慢慢地把数据从原始表拷到 gho 表,同时 gh-ost 充当从库,从另一个从库不断地把进行中的原始表上的数据操作(所有应用在原始表上的插入、删除、更新操作)也以 binlog 增量变更的方式异步拉取应用过来。当 gh-ost 把所有数据都拷贝完毕,两边数据同步了之后,它就用这张 gho 表来替代原始表。
16+
17+
<img src="https://github.com/github/gh-ost/raw/master/doc/images/gh-ost-general-flow.png#crop=0&crop=0&crop=1&crop=1&height=772&id=Ju9hl&originHeight=772&originWidth=1724&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=1724" style="zoom:50%;" />
18+
19+
### 准备阶段
20+
21+
1. 验证权限,replica
22+
2. 验证 binlog 格式为 row
23+
3. ==**确定 binlog apply 的起始位点并开始 dump binlog**==
24+
4. **创建 changelog 表(记录任务元信息)和 ghost 表**
25+
5. ==**reads min/max values that will be used for rowcopy(根据主键或唯一键确定)**==
26+
27+
注意:
28+
29+
1. 这里必须先记录 binlog 位点,然后再开始计算 rowcopy 的范围。因为在操作的过程中,原表是有写入的,如果先计算了 rowcopy 的范围,再开始 binlog dump,中间的数据会丢失;先 binlog dump 再计算 row copy 范围,有部分数据会即在 binlog 中有记录,也在 rowcopy 中有记录,但是这种情况可以处理。
30+
31+
### rowcopy 和 binlog apply
32+
#### 数据正确性保证
33+
数据迁移分二个部分:RowCopy 和 BinlogApply,RowCopy 和 BinlogApply 是同时进行的,但 BinlogApply 优先级高于 RowCopy。
34+
35+
在迁移过程中,数据变量有:A(RowCopy),B(对原表的数据操作 [insert/update/delete]),C(BinlogApply)。C 操作肯定在 B 操作之后,因为只有对原表的数据记录进行操作 B 才会触发 C 操作。
36+
37+
所以,数据迁移模型:ABC、BCA、BAC
38+
39+
<img src="https://littleneko.oss-cn-beijing.aliyuncs.com/img/1584238644133-286b852b-c7fd-4017-8dca-971024a9f2a3.png" alt="image.png" style="zoom:50%;" />
40+
41+
上图中表示对于特定一行数据的处理顺序:
42+
43+
- **INSERT**
44+
1. row copy → DML → binlog apply:数据被 binlog 覆盖,最终是最新数据
45+
2. DML → row copy → binlog apply:binlog apply 为空操作,最终为最新数据
46+
3. DML → binlog apply → row copy:row copy 数据 ignore,最终是最新数据
47+
- **UPDATE**
48+
1. row copy → DML → binlog apply:数据被 binlog 覆盖,最终是最新数据
49+
2. DML → row copy → binlog apply:binlog apply 为空操作,最终为最新数据
50+
3. DML → binlog apply → row copy:row copy 数据 ignore,最终是最新数据
51+
- **DELETE**
52+
1. row copy → dml → binlog apply:老数据先 insert,binlog apply 把老数据删除
53+
2. dml → row copy → binlog apply:row copy 和 binlog apply 都是空操作
54+
3. dml → binlog apply → row copy:binlog apply 和 row copy 都是空操作
55+
56+
apply-binlog 和 row-copy 同时进行可能带来的问题:打破了原本有序的数据,导致聚簇索引利用率不高,做完 DDL 后空间增大(特别是对于大字段)
57+
58+
#### 伪代码
59+
```go
60+
// go/binlog/gomysql_reader.go读取binlog,然后go/logic/applier.go将binlog转化写入ghost表中
61+
// go/logic/migrator.go:: executeWriteFuncs 、 iterateChunks 和 consumeRowCopyComplete
62+
63+
executeWriteFuncs:
64+
for {
65+
select { ### BinlogApply 与 RowCopy 同时可操作,BinlogApply 优先处理
66+
case eventStruct := <-this.applyEventsQueue: ### BinlogApply 的队列长度为 100,Event 事件由 streamer 提供
67+
this.onApplyEventStruct(eventStruct)
68+
default:
69+
select {
70+
case copyRowsFunc := <-this.copyRowsQueue: ### RowCopy的队列长度为1
71+
copyRowsFunc()
72+
default:
73+
time.Sleep(time.Second) ### 既没有BinlogApply,又没有RowCopy,可能是超负载
74+
}
75+
}
76+
}
77+
78+
79+
iterateChunks:
80+
terminateRowIteration := func(err error) error {
81+
this.rowCopyComplete <- true ### 标记RowCopy结束的信道
82+
}
83+
for {
84+
copyRowsFunc := func() error {
85+
if atomic.LoadInt64(&this.rowCopyCompleteFlag) == 1{ ### 表示RowCopy结束
86+
return nil
87+
}
88+
hasFurtherRange, err := this.applier.CalculateNextIterationRangeEndValues() ###探测是否有数据需要迁移
89+
if !hasFurtherRange {
90+
return terminateRowIteration(nil)
91+
}
92+
applyCopyRowsFunc := func() error {
93+
_, rowsAffected, _, err := this.applier.ApplyIterationInsertQuery() ### insert ignore into ghost from (select * from originalTbl)
94+
}
95+
return this.retryOperation(applyCopyRowsFunc) ### 执行applyCopyRowsFunc()函数
96+
}
97+
this.copyRowsQueue <- copyRowsFunc
98+
}
99+
100+
101+
consumeRowCopyComplete:
102+
<-this.rowCopyComplete ### 等待RowCopy结束
103+
atomic.StoreInt64(&this.rowCopyCompleteFlag, 1) ### 标记RowCopy结束
104+
```
105+
106+
### Cutover
107+
在 row copy 结束后就可以开始 cutover 流程了,此时 binlog apply 仍然继续进行。
108+
109+
这里不能直接 rename,因为原表还有写入,一旦直接 rename,数据会不一致。而且 rename 命令会试图获取 MDL 写锁,而当系统中有长事物时,rename 会阻塞在 MDL 锁获取,其他操作也不能继续进行。所以 rename 之前要先 lock 原表,不让写入,直到 binlog 全部追上主库,原表和 gh-ost 表数据完全一致。
110+
111+
在 pt-osc 中,pt-osc 采用同步模式,在 copyrow 阶段完成之后,直接通过这条原子性的语句完成 rename,语句如下:
112+
```sql
113+
RENAME TABLE tbl TO tbl_old, tbl_new TO tbl;
114+
```
115+
116+
在 fb-osc 中,fb-osc 采用异步模式,完成 rename 阶段,语句如下:
117+
```sql
118+
LOCK TABLES tbl WRITE;
119+
ALTER TABLE tbl RENAME TO tbl_old;
120+
ALTER TABLE tbl_new RENAME TO tbl;
121+
UNLOCK TABLES;
122+
```
123+
124+
- 在 pt-osc 中,rename 操作一般是耗时比较短,但如果表结构变更过程中,有大查询进来,那么在 rename 操作的时候,会触发 MDL 锁的等待,如果在高峰期,这就是个严重的问题。
125+
- 在 fb-osc 中,在 tbl 被更改为 tbl_old 之后,在 tbl_new 被更改为 tbl 之前,会存在一段较短时间没有 tbl,可能对应用带来错误,或许并不能捕捉没有表的错误信息。
126+
127+
gh-ost 也是异步模式,利用 Mysql 一个特性,就是==在所有被 blocked 的请求中,rename 请求是永远最优先的==。一条连接对原表加锁,另一条连接进行 rename 操作,此时会被 blocked 掉,当 unlock 后,rename 请求会优先被处理,其他的请求会应用到新表上。
128+
129+
其中作者写三篇文章对 cut-over 阶段进行分析,比较有趣,详情参考:[gh-ost atomic cutover specification](http://wiki.intra.xiaojukeji.com/display/techfoundation/gh-ost+atomic+cutover+specification)
130+
131+
#### Automic Cutover流程
132+
The solution we offer is now based on two connections only (as opposed to three, in the optimistic approach). "Our" connections will be C10, C20. The "normal" app connections are C1..C9, C11..C19, C21..C29.
133+
134+
- Connections C1..C9 operate on _tbl_ with normal DML: INSERT, UPDATE, DELETE
135+
- Connection **C10**: `CREATE TABLE tbl_old (id int primary key) COMMENT='magic-be-here'`
136+
- Connection **C10**: `LOCK TABLES tbl WRITE, tbl_old WRITE`
137+
- Connections C11..C19, newly incoming, issue queries on `tbl` but are blocked due to the `LOCK`
138+
- Connection **C20**: `RENAME TABLE tbl TO tbl_old, ghost TO tbl` (需要等待原表的 DML 操作 binlog 全部同步完成)
139+
- This is blocked due to the `LOCK`, but gets prioritized on top connections C11..C19 and on top C1..C9 or any other connection that attempts DML on _tbl_
140+
- Connections C21..C29, newly incoming, issue queries on `tbl` but are blocked due to the `LOCK` and due to the `RENAME`, waiting in queue
141+
- Connection **C10**: checks that C20's `RENAME` is applied (looks for the blocked `RENAME` in `show processlist`)
142+
- Connection **C10**: `DROP TABLE tbl_old`. Nothing happens yet; `tbl` is still locked. All other connections still blocked.
143+
- Connection **C10**: `UNLOCK TABLES`
144+
145+
**BAM!** The `RENAME` is first to execute, ghost table is swapped in place of `tbl`, then C1..C9, C11..C19, C21..C29 all get to operate on the new and shiny `tbl`
146+
147+
![gh-ost_cutover.svg](https://littleneko.oss-cn-beijing.aliyuncs.com/img/1594804616659-4ae96da3-0631-4271-a24f-74c9286427df.svg)
148+
149+
**异常情况下的正确性:**
150+
151+
1. T5 rename 之前,如果 C10 连接断开:rename 出错,因为 tbl_old 表存在
152+
2. T7 - T8 - T9 之间 C20 连接断开:无影响,rename 不会进行
153+
154+
其他异常情况参见 gh-ost 的 cutover 流程 [http://code.openark.org/blog/mysql/solving-the-non-atomic-table-swap-take-iii-making-it-atomic](http://code.openark.org/blog/mysql/solving-the-non-atomic-table-swap-take-iii-making-it-atomic)
155+
156+
#### 伪代码
157+
```go
158+
// go/logic/migrator.go :: atomicCutOver
159+
atomicCutOver: ### 连接 C3
160+
go AtomicCutOverMagicLock ### 对应锁原表那条连接
161+
<- tableLocked ### 锁住原表后才进行 rename 操作,做到同步
162+
go AtomicCutOverRename ### 对应rename操作那条连接
163+
ExpectProcess(renameSessionId, "metadata lock", "rename") ### 由于 rename 操作会被阻塞住,不会立即返回结果,所以通过此函数检查 rename 真正地被阻塞住
164+
ExpectUsedLock(lockOriginalSessionId)
165+
okToUnlockTable <- true ### 表示可以进行解锁
166+
167+
168+
// go/logic/applier.go :: AtomicCutOverMagicLock 和 AtomicCutOverRename
169+
AtomicCutOverMagicLock: ### 连接C1
170+
set session lock_wait_timeout := CutOverLockTimeoutSeconds * 2 ### 防止 lock tables 等待太久,CutOverLockTimeoutSeconds 可配置
171+
create table _'originalTbl'_del ### 防止rename过早
172+
lock tables originalTbl write, _'originalTbl'_ write
173+
tableLocked <- nil ### 表示可以进行 rename 操作
174+
<- okToUnlockTable ### 等待解锁信号
175+
drop table _'originalTbl'_del
176+
unlock tables
177+
178+
AtomicCutOverRename: ### 连接 C2
179+
set session lock_wait_timeout := CutOverLockTimeoutSeconds ### 防止 rename 被阻塞死,如果 rename 操作超出设置 lock
180+
```
181+
182+
# 限流(Throttler)
183+
## 实现方式
184+
将 BinlogApply 和 RowCopy 放在一个协程内,BinlogApply 优先于 RowCopy。
185+
186+
- 如果正在数据迁移过程中,检测到需要节流,则完成当前批次数据迁移后再节流
187+
- 如果没有数据迁移,检测到需要节流,立即节流
188+
- 节流是通过休眠当前协程来完成,即即使满足数据迁移条件,也要等到不再需要节流,才能进行数据迁移 
189+
190+
## 触发限流方法
191+
192+
- 手动设置 throttle:echo throttle | nc -U /tmp/gh-ost.test.sample_data_0.sock
193+
- 创建标示文件来节流:--throttle-flag-file
194+
- 设置 Mysql 的状态阈值:--max-load
195+
- 设置一个限流 SQL:--throttle-query
196+
- `gh-ost` 内置了心跳机制,从而对主从复制延迟时间进行监控,当前从库的主从复制延迟时间或由--throttle-control-replicas 指定的从库中最大复制延迟时间大于设定的延迟阈值:--max-lag-millis
197+
198+
## 伪代码
199+
```go
200+
// go/logic/migrator.go :: executeWriteFuncs
201+
// 节流操作、binlog应用以及行复制是同步的
202+
executeWriteFuncs:
203+
for {
204+
throttle() // 节流操作
205+
select {
206+
case:
207+
BinlogApply // binlog应用
208+
default:
209+
select {
210+
case:
211+
RowCopy // 行复制
212+
default:
213+
}
214+
}
215+
}
216+
217+
// go/logic/throttler.go :: throttle
218+
throttle:
219+
for {
220+
if ! IsThrottled() { // 判断是否可以节流
221+
return
222+
}
223+
time.Sleep(250 * time.Millisecond) // 通过休眠当前协程来节流
224+
}
225+
```
226+
227+
# Links
228+
229+
1. [https://github.com/github/gh-ost/blob/master/doc/cheatsheet.md](https://github.com/github/gh-ost/blob/master/doc/cheatsheet.md)
230+
2. [https://cloud.tencent.com/developer/article/1005177](https://cloud.tencent.com/developer/article/1005177)
231+
3. [http://code.openark.org/blog/mysql/solving-the-facebook-osc-non-atomic-table-swap-problem](http://code.openark.org/blog/mysql/solving-the-facebook-osc-non-atomic-table-swap-problem)
232+
4. [https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl.html](https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl.html)

0 commit comments

Comments
 (0)