-
Notifications
You must be signed in to change notification settings - Fork 1
/
db.restore.xml
240 lines (239 loc) · 10.6 KB
/
db.restore.xml
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V5.0//EN"
"/usr/share/xml/docbook/schema/dtd/5.0/docbook.dtd" [
<!ENTITY article.author.xml SYSTEM "../common/article.author.xml">
<!ENTITY book.info.legalnotice.xml SYSTEM "../common/book.info.legalnotice.xml">
<!ENTITY book.info.abstract.xml SYSTEM "../common/book.info.abstract.xml">
]>
<article xml:base="http://netkiller.github.io/journal/"
xmlns="http://docbook.org/ns/docbook" xml:lang="zh-cn">
<articleinfo>
<title>数据库恢复方案</title>
<subtitle>http://netkiller.github.io/journal/db.restore.html</subtitle>
&article.author.xml;
<pubdate>$Date$</pubdate>
<releaseinfo>$Id$</releaseinfo>
&book.info.legalnotice.xml;
<abstract>
<para>这里所谈的内容是对备份数据的恢复,不是对损坏数据表的恢复,或者说灾难恢复。 </para>
</abstract>
&book.info.abstract.xml;
<keywordset>
<keyword>mysql</keyword>
<keyword>plugin, udf</keyword>
<keyword>images</keyword>
</keywordset>
</articleinfo>
<section>
<title>背景</title>
<para>我们来假设一个场景。</para>
<para>你是否适用 mysqldump 每隔一段时间备份一次数据库,每个备份一个数据文件。</para>
<para>公司决策你是不是因为数据持续增加,有些数据已经不会再查询,会删除旧的历史数据。</para>
<para>有时公司突然说要恢复历史数据,有可能全补回复,有可能部分恢复。</para>
<para>你将怎么做?</para>
</section>
<section>
<title>备份方式分析</title>
<para>首先看看备份方式,你是不是采用这种方法备份</para>
<para>我使用一串数字表述数据库数据递增情况,数据的增长变化</para>
<para>垂直轴表示备份时间轴</para>
<para>最常见的备份方法,完全备份</para>
<screen>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
|.......| 第一次备份
|.................| 第二次备份
|...........................| 第三次备份
|......................................| 第四次备份
|................................................| 第五次备份
</screen>
<para>下面这种备份方式也比较常见,这种方式很有规律。</para>
<screen>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
|.......| 第一次备份
|..........| 删除上一次以备份内容,第二次备份
|..........| 删除上一次以备份内容,第三次备份数据库
|..........| 删除上一次以备份内容,第四次备份
|.........| 删除上一次以备份内容,第五次备份
</screen>
<para>更复杂的情况,无规律可循</para>
<screen>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
|.......| 第一次备份
|..................| 第二次备份
|......................| 删除一部分数据后同时做第三次备份数据库
|......................| 又删除一部分数据,第四次备份
|.............................| 第五次备份,没有删除数据
|......................................| 第六次备份,依然没有删除数据
|..........................| 删除很多数据,第七次备份
</screen>
<para>以此类推,删除原因有多种,如空间不足,改善查询性能...等等</para>
<para>最杂的情况,无规律可循,同时交叉数据可能会有更新</para>
<screen>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
|...o...| 第一次备份
|.....o............| 第二次备份
|....o...o.............| 删除一部分数据后同时做第三次备份数据库
|.o..o..o..............| 又删除一部分数据,第四次备份
|....o......o.......o.........| 第五次备份,没有删除数据
|.......o.......o.........o............| 第六次备份,依然没有删除数据
|.o....o......o............| 删除很多数据,第七次备份
</screen>
<para>我用'o' 表示与上次备份中有差异的部分。</para>
</section>
<section>
<title>恢复方案</title>
<para>,最好恢复,第二种。</para>
<variablelist>
<title>上面所提三种备份方式</title>
<varlistentry>
<term>第一种</term>
<listitem>
<para>最好恢复,100% 都能搞定.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>第二种</term>
<listitem>
<para>恢复起来稍复杂,仍能搞得定.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>第三种</term>
<listitem>
<para>比较复杂,因为本档案中存在重复记录,费点脑筋</para>
</listitem>
</varlistentry>
<varlistentry>
<term>第四种</term>
<listitem>
<para>最复杂,看似复杂,其实也不复杂,跟第三种差不多.</para>
</listitem>
</varlistentry>
</variablelist>
<section>
<title>第一种</title>
<para>这种备份非常简单,菜鸟也搞搞定</para>
<para>文本格式回复</para>
<screen>
cat dbname.sql | mysql -u user -p pass -h localhost yourdb
</screen>
<para>压缩格式恢复</para>
<screen>
zcat dbname.sql.gz | mysql -u user -p pass -h localhost yourdb
</screen>
<para>或者先使用gunzip解压,再恢复数据</para>
<screen>
gunzip dbname.sql.gz
cat dbname.sql | mysql -u user -p pass -h localhost yourdb
</screen>
<tip>
<para>很多人喜欢用tar打包,我不见这样做,一个文件时无需使用tar打包的,画蛇添足</para>
<para>仅使用gzip压缩,可以方便使用zcat直接操作文件。</para>
</tip>
</section>
<section>
<title>第二种</title>
<para>这种备份是连续有规律的,只要依次按顺序恢复即可。</para>
<screen>
zcat dbname1.sql.gz | mysql -u user -p pass -h localhost yourdb
zcat dbname2.sql.gz | mysql -u user -p pass -h localhost yourdb
zcat dbname3.sql.gz | mysql -u user -p pass -h localhost yourdb
...
...
zcat dbname10.sql.gz | mysql -u user -p pass -h localhost yourdb
</screen>
<para>也可以跳跃恢复数据</para>
<screen>
zcat dbname2.sql.gz | mysql -u user -p pass -h localhost yourdb
zcat dbname3.sql.gz | mysql -u user -p pass -h localhost yourdb
zcat dbname5.sql.gz | mysql -u user -p pass -h localhost yourdb
zcat dbname10.sql.gz | mysql -u user -p pass -h localhost yourdb
</screen>
<para>反向恢复数据</para>
<screen>
zcat dbname20.sql.gz | mysql -u user -p pass -h localhost yourdb
zcat dbname15.sql.gz | mysql -u user -p pass -h localhost yourdb
zcat dbname13.sql.gz | mysql -u user -p pass -h localhost yourdb
zcat dbname1.sql.gz | mysql -u user -p pass -h localhost yourdb
</screen>
<para>总之怎么恢复都可以</para>
</section>
<section>
<title>第三种</title>
<para>这种恢复建议按照顺序进行,因为重叠数据没有变化,所以即可以顺时间轴恢复也可以逆时间轴,条件是表结构需要有主键(PK)</para>
<note>
<title>insert 方式有要求</title>
<para>必须是</para>
<screen>
INSERT INTO dbtable(f1, f2, f3...) value (v1, v2, v3);
INSERT INTO dbtable(f1, f2, f3...) value (v1, v2, v3);
INSERT INTO dbtable(f1, f2, f3...) value (v1, v2, v3);
</screen>
<para>不能是</para>
<screen>
INSERT INTO dbtable(f1, f2, f3...) value (v1, v2, v3), (v1, v2, v3), value (v1, v2, v3);
</screen>
<para>这种备份参数如下</para>
<screen>
mysqldump -C -hlocalhost -ubackup -p1AyBoc5mp02LDJki5wJ8 --skip-extended-insert -t db_name tbl_name
</screen>
<para>这种方式备份,备份文件尺寸会比较大。</para>
</note>
<para>正时序恢复案例,</para>
<screen>
zcat dbname1.sql.gz | sed 's/^INSERT INTO/INSERT IGNORE INTO/' | mysql -u user -p pass -h localhost yourdb
zcat dbname2.sql.gz | sed 's/^INSERT INTO/INSERT IGNORE INTO/' | mysql -u user -p pass -h localhost yourdb
zcat dbname3.sql.gz | sed 's/^INSERT INTO/INSERT IGNORE INTO/' | mysql -u user -p pass -h localhost yourdb
</screen>
<para>逆时序恢复数据</para>
<screen>
zcat dbname3.sql.gz | sed 's/^INSERT INTO/INSERT IGNORE INTO/' | mysql -u user -p pass -h localhost yourdb
zcat dbname2.sql.gz | sed 's/^INSERT INTO/INSERT IGNORE INTO/' | mysql -u user -p pass -h localhost yourdb
zcat dbname1.sql.gz | sed 's/^INSERT INTO/INSERT IGNORE INTO/' | mysql -u user -p pass -h localhost yourdb
</screen>
<para>因为有主键,所以已存在的重复记录不会被重复插入。</para>
</section>
<section>
<title>第四种</title>
<para>这种恢复必须按照顺序进行,即可以顺时间轴恢复也可以逆时间轴,但处理上稍有不同.一旦操作错误数据就会损坏,同时也有很多条件。</para>
<para>顺时序恢复数据, 只需将 insert 替换为 replace 即可 </para>
<screen>
replace into dbtable(f1, f2, f3...) value (v1, v2, v3);
replace into dbtable(f1, f2, f3...) value (v1, v2, v3);
replace into dbtable(f1, f2, f3...) value (v1, v2, v3);
</screen>
<para>新数据总会覆盖旧数据</para>
<para>但逆向就不同了,逆时序恢复数据与上面第三种相同, 恢复过程中旧数据在 insert 的时候不会覆盖现有的新数据。仅仅将失去的数据恢复到数据库中。</para>
<para>操作要十分谨慎,理解正向与逆向的不同,方能操作。</para>
</section>
</section>
<section>
<title>手工恢复</title>
<para>有时上面所讲的四种恢复方法不能满足你需求,我们模拟一个场景,假如你需要恢复一个时间段的数据,或者ID字段去一个范围等等,上面所举例子均为一刀切。该怎么办呢?</para>
<para>不用担心方法总是有的</para>
<para>INSERT ... SELECT</para>
<screen>
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name [(col_name,...)]
SELECT ...
[ ON DUPLICATE KEY UPDATE col_name=expr, ... ]
</screen>
<para>REPLACE ... SELECT</para>
<screen>
REPLACE [LOW_PRIORITY | DELAYED]
[INTO] tbl_name
[PARTITION (partition_name,...)]
[(col_name,...)]
SELECT ...
</screen>
<example>
<title>INSERT ... SELECT</title>
<screen>
INSERT IGNORE INTO tbl_name_new SELECT * FROM tbl_name_old WHERE name = 'netkiller';
INSERT IGNORE INTO db_new.tbl_name SELECT * FROM db_old.tbl_name WHERE id > '10000';
</screen>
<para>IGNORE 将忽略 ERROR 1062 (23000) at line 24: Duplicate entry '100' for key 'PRIMARY'</para>
</example>
<para>这里仅给一个简单实例,因为每个人的需求都不同,你只需灵活变通,发挥你的想象力。</para>
</section>
</article>