-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy path3.3.html
More file actions
1674 lines (1669 loc) · 127 KB
/
3.3.html
File metadata and controls
1674 lines (1669 loc) · 127 KB
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>日常维护 :: IvorySQL文档中心</title>
<link rel="canonical" href="https://docs.ivorysql.org/ivorysql-doc/master/3.3.html">
<link rel="prev" href="3.2.html">
<link rel="next" href="4.1.html">
<meta name="generator" content="Antora 3.1.7">
<link rel="stylesheet" href="../../_/css/site.css">
<script>var uiRootPath = '../../_'</script>
</head>
<body class="article">
<header class="header">
<nav class="navbar">
<div class="navbar-brand">
<a class="navbar-item" href="https://docs.ivorysql.org">IvorySQL文档中心</a>
<div class="navbar-item search hide-for-print">
<div id="search-field" class="field">
<input id="search-input" type="text" placeholder="Search the docs">
</div>
</div>
<button class="navbar-burger" data-target="topbar-nav">
<span></span>
<span></span>
<span></span>
</button>
</div>
<div id="topbar-nav" class="navbar-menu">
<div class="navbar-end">
<a class="navbar-item" href="https://www.ivorysql.org/">官方网站</a>
</div>
</div>
</nav>
</header>
<div class="body">
<div class="nav-container" data-component="ivorysql-doc" data-version="master">
<aside class="nav">
<div class="panels">
<div class="nav-panel-menu is-active" data-panel="menu">
<nav class="nav-menu">
<h3 class="title"><a href="welcome.html">文档中心</a></h3>
<ul class="nav-list">
<li class="nav-item" data-depth="0">
<ul class="nav-list">
<li class="nav-item" data-depth="1">
<button class="nav-item-toggle"></button>
<span class="nav-text">IvorySQL</span>
<ul class="nav-list">
<li class="nav-item" data-depth="2">
<a class="nav-link" href="welcome.html">欢迎</a>
</li>
<li class="nav-item" data-depth="2">
<a class="nav-link" href="1.html">发行说明</a>
</li>
<li class="nav-item" data-depth="2">
<a class="nav-link" href="2.html">关于IvorySQL</a>
</li>
<li class="nav-item" data-depth="2">
<button class="nav-item-toggle"></button>
<span class="nav-text">IvorySQL入门</span>
<ul class="nav-list">
<li class="nav-item" data-depth="3">
<a class="nav-link" href="3.1.html">快速开始</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="3.2.html">日常监控</a>
</li>
<li class="nav-item is-current-page" data-depth="3">
<a class="nav-link" href="3.3.html">日常维护</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="2">
<button class="nav-item-toggle"></button>
<span class="nav-text">IvorySQL高级</span>
<ul class="nav-list">
<li class="nav-item" data-depth="3">
<a class="nav-link" href="4.1.html">安装指南</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="4.2.html">集群搭建</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="4.3.html">开发者指南</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="4.4.html">运维管理指南</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="4.5.html">迁移指南</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="2">
<button class="nav-item-toggle"></button>
<span class="nav-text">IvorySQL生态</span>
<ul class="nav-list">
<li class="nav-item" data-depth="3">
<a class="nav-link" href="5.1.html">PostGIS</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="5.2.html">pgvector</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="2">
<button class="nav-item-toggle"></button>
<span class="nav-text">IvorySQL架构设计</span>
<ul class="nav-list">
<li class="nav-item" data-depth="3">
<button class="nav-item-toggle"></button>
<span class="nav-text">查询处理</span>
<ul class="nav-list">
<li class="nav-item" data-depth="4">
<a class="nav-link" href="6.1.1.html">双parser</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="3">
<button class="nav-item-toggle"></button>
<span class="nav-text">兼容框架</span>
<ul class="nav-list">
<li class="nav-item" data-depth="4">
<a class="nav-link" href="6.2.1.html">initdb过程</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="3">
<button class="nav-item-toggle"></button>
<span class="nav-text">兼容特性</span>
<ul class="nav-list">
<li class="nav-item" data-depth="4">
<a class="nav-link" href="6.3.1.html">like</a>
</li>
<li class="nav-item" data-depth="4">
<a class="nav-link" href="6.3.3.html">RowID</a>
</li>
<li class="nav-item" data-depth="4">
<a class="nav-link" href="6.3.2.html">OUT 参数</a>
</li>
<li class="nav-item" data-depth="4">
<a class="nav-link" href="6.3.4.html">%TYPE、%ROWTYPE</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="6.4.html">国标GB18030</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="2">
<button class="nav-item-toggle"></button>
<span class="nav-text">Oracle兼容功能列表</span>
<ul class="nav-list">
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.1.html">1、框架设计</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.2.html">2、GUC框架</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.3.html">3、大小写转换</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.4.html">4、双模式设计</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.5.html">5、兼容Oracle like</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.6.html">6、兼容Oracle匿名块</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.7.html">7、兼容Oracle函数与存储过程</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.8.html">8、内置数据类型与内置函数</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.9.html">9、新增Oracle兼容模式的端口与IP</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.10.html">10、XML函数</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.11.html">11、兼容Oracle sequence</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.12.html">12、包</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.13.html">13、不可见列</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.14.html">14、RowID</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.15.html">15、OUT 参数</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="7.16.html">16、%TYPE、%ROWTYPE</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="2">
<button class="nav-item-toggle"></button>
<span class="nav-text">IvorySQL贡献指南</span>
<ul class="nav-list">
<li class="nav-item" data-depth="3">
<a class="nav-link" href="8.1.html">社区贡献指南</a>
</li>
<li class="nav-item" data-depth="3">
<a class="nav-link" href="8.2.html">asciidoc语法快速参考</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="2">
<a class="nav-link" href="9.html">工具参考</a>
</li>
<li class="nav-item" data-depth="2">
<a class="nav-link" href="10.html">FAQ</a>
</li>
</ul>
</li>
<li class="nav-item" data-depth="1">
<button class="nav-item-toggle"></button>
<span class="nav-text">PostgreSQL</span>
<ul class="nav-list">
<li class="nav-item" data-depth="2">
<a class="nav-link" href="100.html">PG参数参考手册</a>
</li>
<li class="nav-item" data-depth="2">
<a class="nav-link" href="110.html">PG函数参考手册</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</div>
<div class="nav-panel-explore" data-panel="explore">
<div class="context">
<span class="title">文档中心</span>
<span class="version">master</span>
</div>
<ul class="components">
<li class="component is-current">
<a class="title" href="welcome.html">文档中心</a>
<ul class="versions">
<li class="version is-current is-latest">
<a href="welcome.html">master</a>
</li>
<li class="version">
<a href="../v4.6/v4.6/welcome.html">v4.6</a>
</li>
<li class="version">
<a href="../v4.5/v4.5/welcome.html">v4.5</a>
</li>
<li class="version">
<a href="../v4.4/v4.4/welcome.html">v4.4</a>
</li>
<li class="version">
<a href="../v4.2/v4.2/welcome.html">v4.2</a>
</li>
<li class="version">
<a href="../v4.0/v4.0/welcome.html">v4.0</a>
</li>
<li class="version">
<a href="../v3.4/v3.4/welcome.html">v3.4</a>
</li>
<li class="version">
<a href="../v3.3/v3.3/welcome.html">v3.3</a>
</li>
<li class="version">
<a href="../v3.2/v3.2/welcome.html">v3.2</a>
</li>
<li class="version">
<a href="../v3.1/v3.1/welcome.html">v3.1</a>
</li>
<li class="version">
<a href="../v3.0/v3.0/welcome.html">v3.0</a>
</li>
<li class="version">
<a href="../v2.3/v2.3/welcome.html">v2.3</a>
</li>
<li class="version">
<a href="../v2.2/v2.2/welcome.html">v2.2</a>
</li>
<li class="version">
<a href="../v2.1/v2.1/welcome.html">v2.1</a>
</li>
<li class="version">
<a href="../v1.17/v1.17/welcome.html">v1.17</a>
</li>
<li class="version">
<a href="../v1.8/v1.8/welcome.html">v1.8</a>
</li>
<li class="version">
<a href="../v1.5/v1.5/welcome.html">v1.5</a>
</li>
<li class="version">
<a href="../v1.4/v1.4/welcome.html">v1.4</a>
</li>
<li class="version">
<a href="../v1.3/v1.3/welcome.html">v1.3</a>
</li>
<li class="version">
<a href="../v1.2/v1.2/welcome.html">v1.2</a>
</li>
<li class="version">
<a href="../v1.1/v1.1/welcome.html">v1.1</a>
</li>
<li class="version">
<a href="../v1.0/v1.0/welcome.html">v1.0</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</aside>
</div>
<main class="article">
<div class="toolbar" role="navigation">
<button class="nav-toggle"></button>
<nav class="breadcrumbs" aria-label="breadcrumbs">
<ul>
<li><a href="welcome.html">文档中心</a></li>
<li>IvorySQL</li>
<li>IvorySQL入门</li>
<li><a href="3.3.html">日常维护</a></li>
</ul>
</nav>
<div class="page-versions">
<button class="version-menu-toggle" title="switch to English">CN</button>
<div class="version-menu">
<a class="version is-current" href="">CN</a>
<a class="version" href="../../../../en/ivorysql-doc/master/3.3.html">EN</a>
</div>
</div>
<div class="edit-this-page"><a href="https://github.com/IvorySQL/ivorysql_docs/edit/master/CN/modules/ROOT/pages/master/3.3.adoc">编辑此页面</a></div>
</div>
<div class="content">
<aside class="toc sidebar" data-title="目录" data-levels="2">
<div class="toc-menu"></div>
</aside>
<article class="doc">
<h1 class="page"><strong>日常维护</strong></h1>
<div class="sect1">
<h2 id="日常清理"><a class="anchor" href="#日常清理"></a>1. 日常清理</h2>
<div class="sectionbody">
<div class="paragraph">
<p>IvorySQL数据库要求周期性的清理维护。对于很多安装,让自动清理守护进程来执行清理已经足够,你也可能需要调整其中描述的自动清理参数来获得最佳结果。某些数据库管理员会希望使用手动管理的`VACUUM`命令来对后台进程的活动进行补充或者替换,这通常使用cron或任务计划程序脚本来执行。要正确地设置手动管理的清理。</p>
</div>
<div class="sect2">
<h3 id="清理的基础知识"><a class="anchor" href="#清理的基础知识"></a>1.1. 清理的基础知识</h3>
<div class="paragraph">
<p>IvorySQL的 VACUUM命令出于几个原因必须定期处理每一个表:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>恢复或重用被已更新或已删除行所占用的磁盘空间。</p>
</li>
<li>
<p>更新被IvorySQL查询规划器使用的数据统计信息。</p>
</li>
<li>
<p>更新可见性映射,它可以加速只用索引的扫描。</p>
</li>
<li>
<p>保护老旧数据不会由于事务ID回卷或多事务ID回卷而丢失。</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>正如后续小节中解释的,每一个原因都将指示以不同的频率和范围执行`VACUUM`操作。</p>
</div>
<div class="paragraph">
<p>有两种`VACUUM`的变体:标准`VACUUM`和`VACUUM FULL`。<code>VACUUM FULL`可以收回更多磁盘空间但是运行起来更慢。另外,标准形式的`VACUUM`可以和生产数据库操作并行运行(`SELECT</code>、<code>INSERT</code>、<code>UPDATE`和`DELETE`等命令将继续正常工作,但在清理期间你无法使用`ALTER TABLE`等命令来更新表的定义)。`VACUUM FULL`要求在其工作的表上得到一个`ACCESS EXCLUSIVE</code> 锁,因此无法和对此表的其他使用并行。因此,通常管理员应该努力使用标准`VACUUM`并且避免`VACUUM FULL`。</p>
</div>
<div class="paragraph">
<p>`VACUUM`会产生大量I/O流量,这将导致其他活动会话性能变差。可以调整一些配置参数来后台清理活动造成的性能冲击.</p>
</div>
</div>
<div class="sect2">
<h3 id="恢复磁盘空间"><a class="anchor" href="#恢复磁盘空间"></a>1.2. 恢复磁盘空间</h3>
<div class="paragraph">
<p>在IvorySQL中,一次行的`UPDATE`或`DELETE`不会立即移除该行的旧版本。这种方法对于从多版本并发控制获益是必需的:当旧版本仍可能对其他事务可见时,它不能被删除。但是最后,任何事务都不会再对一个过时的或者被删除的行版本感兴趣。它所占用的空间必须被回收来用于新行,这样可避免磁盘空间需求的无限制增长。这通过运行`VACUUM`完成。</p>
</div>
<div class="paragraph">
<p>`VACUUM`的标准形式移除表和索引中的死亡行版本并将该空间标记为可在未来重用。不过,它将不会把该空间交还给操作系统,除非在特殊的情况中表尾部的一个或多个页面变成完全空闲并且能够很容易地得到一个排他表锁。相反,`VACUUM FULL`通过把死亡空间之外的内容写成一个完整的新版本表文件来主动紧缩表。这将最小化表的尺寸,但是要花较长的时间。它也需要额外的磁盘空间用于表的新副本,直到操作完成。</p>
</div>
<div class="paragraph">
<p>例行清理的一般目标是多做标准的`VACUUM`来避免需要`VACUUM FULL`。自动清理守护进程尝试这样工作,并且实际上永远不会发出`VACUUM FULL`。在这种方法中,其思想不是让表保持它们的最小尺寸,而是保持磁盘空间使用的稳定状态:每个表占用的空间等于其最小尺寸外加清理之间将使用的空间量。尽管`VACUUM FULL`可被用来把一个表收缩回它的最小尺寸并将该磁盘空间交还给操作系统,但是如果该表将在未来再次增长这样就没什么意义。因此,对于维护频繁被更新的表,适度运行标准`VACUUM`运行比少量运行`VACUUM FULL`要更好。</p>
</div>
<div class="paragraph">
<p>一些管理员更喜欢自己计划清理,例如在晚上负载低时做所有的工作。根据一个固定日程来做清理的难点在于,如果一个表有一次预期之外的更新活动尖峰,它可能膨胀得真正需要`VACUUM FULL`来回收空间。使用自动清理守护进程可以减轻这个问题,因为守护进程会根据更新活动动态规划清理操作。除非你的负载是完全可以预估的,完全禁用守护进程是不理智的。一种可能的折中方案是设置守护进程的参数,这样它将只对异常的大量更新活动做出反应,因而保证事情不会失控,而在负载正常时采用有计划的`VACUUM`来做批量工作。</p>
</div>
<div class="paragraph">
<p>对于那些不使用自动清理的用户,一种典型的方法是计划一个数据库范围的`VACUUM`,该操作每天在低使用量时段执行一次,并根据需要辅以在重度更新表上的更频繁的清理(一些有着极高更新率的安装会每几分钟清理一次它们的最繁忙的表)。如果你在一个集簇中有多个数据库,别忘记`VACUUM`每一个,你会用得上vacuumdb程序。</p>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">提示</div>
<div class="paragraph">
<p>当一个表因为大量更新或删除活动而包含大量死亡行版本时,纯粹的`VACUUM`可能不能令人满意。如果你有这样一个表并且你需要回收它占用的过量磁盘空间,你将需要使用`VACUUM FULL`,或者CLUSTER,或者ALTER TABLE的表重写变体之一。这些命令重写该表的一整个新拷贝并且为它构建新索引。所有这些选项都要求`ACCESS EXCLUSIVE` 锁。注意它们也临时使用大约等于该表尺寸的额外磁盘空间,因为直到新表和索引完成之前旧表和索引都不能被释放。</p>
</div>
</div>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">提示</div>
<div class="paragraph">
<p>如果你有一个表,它的整个内容会被周期性删除,考虑用TRUNCATE而不是先用`DELETE`再用`VACUUM`。`TRUNCATE`会立刻移除该表的整个内容,而不需要一次后续的`VACUUM`或`VACUUM FULL`来回收现在未被使用的磁盘空间。其缺点是会违背严格的 MVCC 语义。</p>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="更新规划器统计信息"><a class="anchor" href="#更新规划器统计信息"></a>1.3. 更新规划器统计信息</h3>
<div class="paragraph">
<p>IvorySQL查询规划器依赖于有关表内容的统计信息来为查询产生好的计划。这些统计信息由ANALYZE命令收集,它除了直接被调用之外还可以作为`VACUUM`的一个可选步骤被调用。拥有适度准确的统计信息很重要,否则差的计划可能降低数据库性能。</p>
</div>
<div class="paragraph">
<p>自动清理守护进程如果被启用,当一个表的内容被改变得足够多时,它将自动发出`ANALYZE`命令。不过,管理员可能更喜欢依靠手动的`ANALYZE`操作,特别是如果知道一个表上的更新活动将不会影响“感兴趣的”列的统计信息时。守护进程严格地按照一个被插入或更新行数的函数来计划`ANALYZE`,它不知道那是否将导致有意义的统计信息改变。</p>
</div>
<div class="paragraph">
<p>正如用于空间恢复的清理一样,频繁更新统计信息对重度更新的表更加有用。但即使对于一个重度更新的表,如果该数据的统计分布没有很大改变,也没有必要更新统计信息。一个简单的经验法则是考虑表中列的最大和最小值改变了多少。例如,一个包含行被更新时间的`timestamp`列将在行被增加和更新时有一直增加的最大值;这样一列将可能需要更频繁的统计更新,而一个包含一个网站上被访问页面 URL 的列则不需要。URL 列可以经常被更改,但是其值的统计分布的变化相对很慢。</p>
</div>
<div class="paragraph">
<p>可以在指定表上运行`ANALYZE`甚至在表的指定列上运行,因此如果你的应用需要,可以更加频繁地更新某些统计。但实际上,通常只分析整个数据库是最好的,因为它是一种很快的操作。`ANALYZE`对一个表的行使用一种统计的随机采样,而不是读取每一个单一行。</p>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">提示</div>
<div class="paragraph">
<p>尽管对每列的`ANALYZE`频度调整可能不是非常富有成效,你可能会发现值得为每列调整被`ANALYZE`收集统计信息的详细程度。经常在`WHERE`中被用到的列以及数据分布非常不规则的列可能需要比其他列更细粒度的数据直方图。见`ALTER TABLE SET STATISTICS`,或者使用default_statistics_target配置参数改变数据库范围的默认值。</p>
</div>
</div>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">提示</div>
<div class="paragraph">
<p>自动清理守护进程不会为外部表发出`ANALYZE`命令,因为无法确定一个合适的频度。如果你的查询需要外部表的统计信息来正确地进行规划,比较好的方式是按照一个合适的时间表在那些表上手工运行`ANALYZE`命令。</p>
</div>
</div>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">提示</div>
<div class="paragraph">
<p>autovacuum守护进程不会对分区表发出ANALYZE命令。继承性父表只有在父表本身发生变化时才会被分析—​对子表的变化不会触发对父表的自动分析。如果你的查询需要对父表进行统计以进行正确的规划,那么有必要定期对这些表运行手动ANALYZE以保持统计的最新性。</p>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="更新可见性映射"><a class="anchor" href="#更新可见性映射"></a>1.4. 更新可见性映射</h3>
<div class="paragraph">
<p>清理机制为每一个表维护着一个可见性映射,它被用来跟踪哪些页面只包含对所有活动事务(以及所有未来的事务,直到该页面被再次修改)可见的元组。这样做有两个目的。第一,清理本身可以在下一次运行时跳过这样的页面,因为其中没有什么需要被清除。</p>
</div>
<div class="paragraph">
<p>第二,这允许IvorySQL回答一些只用索引的查询,而不需要引用底层表。因为IvorySQL的索引不包含元组的可见性信息,一次普通的索引扫描会为每一个匹配的索引项获取堆元组,用来检查它是否能被当前事务所见。另一方面,一次*只用索引的扫描*会首先检查可见性映射。如果它了解到在该页面上的所有元组都是可见的,堆获取就可以被跳过。这对大数据集很有用,因为可见性映射可以防止磁盘访问。可见性映射比堆小很多,因此即使堆非常大,可见性映射也可以很容易地被缓存起来。</p>
</div>
</div>
<div class="sect2">
<h3 id="防止事务id回卷失败"><a class="anchor" href="#防止事务id回卷失败"></a>1.5. 防止事务ID回卷失败</h3>
<div class="paragraph">
<p>IvorySQL的 MVCC 事务语义依赖于能够比较事务 ID(XID)数字:如果一个行版本的插入 XID 大于当前事务的 XID,它就是“属于未来的”并且不应该对当前事务可见。但是因为事务 ID 的尺寸有限(32位),一个长时间(超过 40 亿个事务)运行的集簇会遭受到*事务 ID 回卷*问题:XID 计数器回卷到 0,并且本来属于过去的事务突然间就变成了属于未来 — 这意味着它们的输出变成不可见。简而言之,灾难性的数据丢失(实际上数据仍然在那里,但是如果你不能得到它也无济于事)。为了避免发生这种情况,有必要至少每 20 亿个事务就清理每个数据库中的每个表。</p>
</div>
<div class="paragraph">
<p>周期性的清理能够解决该问题的原因是,<code>VACUUM`会把行标记为 冻结,这表示它们是被一个在足够远的过去提交的事务所插入,这样从 MVCC 的角度来看,效果就是该插入事务对所有当前和未来事务来说当然都 是可见的。IvorySQL保留了一个特殊的 XID (`FrozenTransactionId</code>),这个 XID 并不遵循普通 XID 的比较规则 并且总是被认为比任何普通 XID 要老。普通 XID 使用模-232算 法来比较。这意味着对于每一个普通 XID都有 20 亿个 XID “更老”并且 有 20 亿个“更新”,另一种解释的方法是普通 XID 空间是没有端点的环。因此,一旦一个行版本创建时被分配了一个特定的普通 XID,该行版本将成为接下 来 20 亿个事务的“过去”(与我们谈论的具体哪个普通 XID 无关)。如果在 20 亿个事务之后该行版本仍然存在,它将突然变得好像在未来。要阻止这一切 发生,被冻结行版本会被看成其插入 XID 为`FrozenTransactionId`, 这样它们对所有普通事务来说都是“在过去”,而不管回卷问题。并且这样的行版本将一直有效直到被删除,不管它有多旧。</p>
</div>
<div class="paragraph">
<p>vacuum_freeze_min_age控制在其行版本被冻结前一个 XID 值应该有多老。如果被冻结的行将很快会被再次修改,增加这个设置可以避免不必要 的工作。但是减少这个设置会增加在表必须再次被清理之前能够流逝的事务数。</p>
</div>
<div class="paragraph">
<p>`VACUUM`通常会跳过不含有任何死亡行版本的页面,但是不会跳过那些含有带旧 XID 值的行版本的页面。要保证所有旧的行版本都已经被冻结,需要对整个表做一次扫描。vacuum_freeze_table_age控制`VACUUM`什么时候这样做:如果该表经过`vacuum_freeze_table_age`减去`vacuum_freeze_min_age`个事务还没有被完全扫描过,则会强制一次全表清扫。将这个参数设置为 0 将强制`VACUUM`总是扫描所有页面而实际上忽略可见性映射。</p>
</div>
<div class="paragraph">
<p>一个表能保持不被清理的最长时间是 20 亿个事务减去`VACUUM`上次扫描全表时的`vacuum_freeze_min_age`值。如果它超过该时间没有被清理,可能会导致数据丢失。要保证这不会发生,将在任何包含比autovacuum_freeze_max_age配置参数所指定的年龄更老的 XID 的未冻结行的表上调用自动清理(即使自动清理被禁用也会发生)。</p>
</div>
<div class="paragraph">
<p>这意味着如果一个表没有被清理,大约每`autovacuum_freeze_max_age`减去`vacuum_freeze_min_age`事务就会在该表上调用一次自动清理。对那些为了空间回收目的而被正常清理的表,这是无关紧要的。然而,对静态表(包括接收插入但没有更新或删除的表)就没有为空间回收而清理的需要,因此尝试在非常大的静态表上强制自动清理的间隔最大化会非常有用。显然我们可以通过增加`autovacuum_freeze_max_age`或减少`vacuum_freeze_min_age`来实现此目的。</p>
</div>
<div class="paragraph">
<p><code>vacuum_freeze_table_age`的实际最大值是 0.95 * `autovacuum_freeze_max_age</code>,高于它的设置将被上限到最大值。一个高于`autovacuum_freeze_max_age`的值没有意义,因为不管怎样在那个点上都会触发一次防回卷自动清理,并且 0.95 的乘数为在防回卷自动清理发生之前运行一次手动`VACUUM`留出了一些空间。作为一种经验法则,`vacuum_freeze_table_age`应当被设置成一个低于`autovacuum_freeze_max_age`的值,留出一个足够的空间让一次被正常调度的`VACUUM`或一次被正常删除和更新活动触发的自动清理可以在这个窗口中被运行。将它设置得太接近可能导致防回卷自动清理,即使该表最近因为回收空间的目的被清理过,而较低的值将导致更频繁的全表扫描。</p>
</div>
<div class="paragraph">
<p>增加`autovacuum_freeze_max_age`(以及和它一起的`vacuum_freeze_table_age`)的唯一不足是数据库集簇的`pg_xact`和`pg_commit_ts`子目录将占据更多空间,因为它必须存储所有向后`autovacuum_freeze_max_age`范围内的所有事务的提交状态和(如果启用了`track_commit_timestamp`)时间戳。提交状态为每个事务使用两个二进制位,因此如果`autovacuum_freeze_max_age`被设置为它的最大允许值 20 亿,`pg_xact`将会增长到大约 0.5 吉字节,`pg_commit_ts`大约20GB。如果这对于你的总数据库尺寸是微小的,我们推荐设置`autovacuum_freeze_max_age`为它的最大允许值。否则,基于你想要允许`pg_xact`和`pg_commit_ts`使用的存储空间大小来设置它(默认情况下 2 亿个事务大约等于`pg_xact`的 50 MB存储空间,`pg_commit_ts`的2GB的存储空间)。</p>
</div>
<div class="paragraph">
<p>减小`vacuum_freeze_min_age`的一个不足之处是它可能导致`VACUUM`做无用的工作:如果该行在被替换成`FrozenXID`之后很快就被修改(导致该行获得一个新的 XID),那么冻结一个行版本就是浪费时间。因此该设置应该足够大,这样直到行不再可能被修改之前,它们都不会被冻结。</p>
</div>
<div class="paragraph">
<p>为了跟踪一个数据库中最老的未冻结 XID 的年龄,`VACUUM`在系统表`pg_class`和`pg_database`中存储 XID 的统计信息。特别地,一个表的`pg_class`行的`relfrozenxid`列包含被该表的上一次全表`VACUUM`所用的冻结截止 XID。该表中所有被有比这个截断 XID 老的普通 XID 的事务插入的行 都确保被冻结。相似地,一个数据库的`pg_database`行的`datfrozenxid`列是出现在该数据库中的未冻结 XID 的下界 — 它只是数据库中每一个表的`relfrozenxid`值的最小值。一种检查这些信息的方便方法是执行这样的查询:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">SELECT c.oid::regclass as table_name,
greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm');
SELECT datname, age(datfrozenxid) FROM pg_database;</code></pre>
</div>
</div>
<div class="paragraph">
<p>`age`列度量从该截断 XID 到当前事务 XID 的事务数。</p>
</div>
<div class="paragraph">
<p>`VACUUM`通常只扫描从上次清理后备修改过的页面,但是只有当全表被扫描时`relfrozenxid`才能被推进。当`relfrozenxid`比`vacuum_freeze_table_age`个事务还老时、当`VACUUM`的`FREEZE`选项被使用时或当所有页面正好要求清理来移除死亡行版本时,全表将被扫描。当`VACUUM`扫描全表时,在它被完成后,`age(relfrozenxid)`应该比被使用的`vacuum_freeze_min_age`设置略大(比在`VACUUM`开始后开始的事务数多)。如果在`autovacuum_freeze_max_age`被达到之前没有全表扫描`VACUUM`在该表上被发出,将很快为该表强制一次自动清理。</p>
</div>
<div class="paragraph">
<p>如果出于某种原因自动清理无法从一个表中清除旧的 XID,当数据库的最旧 XID 和回卷点之间达到 4 千万个事务时,系统将开始发出这样的警告消息:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">WARNING: database "mydb" must be vacuumed within 39985967 transactions
HINT: To avoid a database shutdown, execute a database-wide VACUUM in that database.</code></pre>
</div>
</div>
<div class="paragraph">
<p>(如该示意所建议的,一次手动的`VACUUM`应该会修复该问题;但是注意该次`VACUUM`必须由一个超级用户来执行,否则它将无法处理系统目录并且因而不能推进数据库的`datfrozenxid`)。如果这些警告被忽略,一旦距离回卷点只剩下 3 百万个事务时,该系统将会关闭并且拒绝开始任何新的事务:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">ERROR: database is not accepting commands to avoid wraparound data loss in database "mydb"
HINT: Stop the postmaster and vacuum that database in single-user mode.</code></pre>
</div>
</div>
<div class="paragraph">
<p>这 3 百万个事务的安全余量是为了让管理员能通过手动执行所要求的`VACUUM`命令进行恢复而不丢失数据。但是,由于一旦系统进入到安全关闭模式,它将不会执行命令。做这个操作的唯一方法是停止服务器并且以单一用户启动服务器来执行`VACUUM`。单一用户模式中不会强制该关闭模式。</p>
</div>
<div class="paragraph">
<p>*Multixact ID*被用来支持被多个事务锁定的行。由于在一个元组头部 只有有限的空间可以用来存储锁信息,所以只要有多于一个事务并发地锁住一个行, 锁信息将使用一个“多个事务 ID”(或简称多事务 ID)来编码。任何特定 多事务 ID 中包括的事务 ID 的信息被独立地存储在`pg_multixact`子目 录中,并且只有多事务 ID 出现在元组头部的`xmax`域中。和事务 ID 类似,多事务 ID 也是用一个 32 位计数器实现,并且也采用了相似的存储,这些都要 求仔细的年龄管理、存储清除和回卷处理。在每个多事务中都有一个独立的存储区域 保存成员列表,它也使用一个 32 位计数器并且也应被管理。</p>
</div>
<div class="paragraph">
<p>在一次`VACUUM`表扫描(部分或者全部)期间,任何比 vacuum_multixact_freeze_min_age 要老的多事务 ID 会被替换为一个不同的值,该值可以是零值、 一个单一事务 ID 或者一个更新的多事务 ID。 对于每一个表,<code>pg_class</code>.<code>relminmxid</code> 存储了在该表任意元组中仍然存在的最老可能多事务 ID。如果这个值比 vacuum_multixact_freeze_table_age老, 将强制一次全表扫描。可以在 <code>pg_class</code>.<code>relminmxid</code> 上使用`mxid_age()`来找到它的年龄。</p>
</div>
<div class="paragraph">
<p>全表`VACUUM`扫描(不管是什么导致它们)将为表推进该值。 最后,当所有数据库中的所有表被扫描并且它们的最老多事务值被推进, 较老的多事务的磁盘存储可以被移除。</p>
</div>
<div class="paragraph">
<p>作为一种安全设备,对任何多事务年龄超过 autovacuum_multixact_freeze_max_age的表, 都将发生一次全表清理扫描。当多事务成员占用的存储超过 2GB 时,从那些具有最老多事务年龄的表开始,全表清理扫描也将逐步在所有表上进行。即使自动清理被 在名义上被禁用,也会发生这两种主动扫描。</p>
</div>
</div>
<div class="sect2">
<h3 id="自动清理后台进程"><a class="anchor" href="#自动清理后台进程"></a>1.6. 自动清理后台进程</h3>
<div class="paragraph">
<p>IvorySQL有一个可选的但是被高度推荐的特性*autovacuum*,它的目的是自动执行`VACUUM`和`ANALYZE`命令。当它被启用时,自动清理会检查被大量插入、更新或删除元组的表。这些检查会利用统计信息收集功能,因此除非track_counts被设置为`true`,自动清理不能被使用。在默认配置下,自动清理是被启用的并且相关配置参数已被正确配置。</p>
</div>
<div class="paragraph">
<p>“自动清理后台进程”实际上由多个进程组成。有一个称为 <strong>自动清理启动器*的常驻后台进程, 它负责为所有数据库启动*自动清理工作者*进程。 启动器将把工作散布在一段时间上,它每隔 autovacuum_naptime秒尝试在每个数据库中启动一个工作者 (因此,如果安装中有</strong><code>N</code><strong>个数据库,则每 <code>autovacuum_naptime</code>/</strong><code>N</code>*秒将启动一个新的工作者)。 在同一时间只允许最多autovacuum_max_workers个工作者进程运行。如果有超过`autovacuum_max_workers` 个数据库需要被处理,下一个数据库将在第一个工作者结束后马上被处理。 每一个工作者进程将检查其数据库中的每一个表并且在需要时执行 <code>VACUUM`和/或`ANALYZE</code>。 可以设置log_autovacuum_min_duration来监控自动清理工作者的活动。</p>
</div>
<div class="paragraph">
<p>如果在一小段时间内多个大型表都变得可以被清理,所有的自动清理工作者可能都会被占用来在一段长的时间内清理这些表。这将会造成其他的表和数据库无法被清理,直到一个工作者变得可用。对于一个数据库中的工作者数量并没有限制,但是工作者确实会试图避免重复已经被其他工作者完成的工作。注意运行着的工作者的数量不会被计入max_connections或superuser_reserved_connections限制。</p>
</div>
<div class="paragraph">
<p>`relfrozenxid`值比autovacuum_freeze_max_age事务年龄更大的表总是会被清理(这页表示这些表的冻结最大年龄被通过表的存储参数修改过,参见后文)。否则,如果从上次`VACUUM`以来失效的元组数超过“清理阈值”,表也会被清理。清理阈值定义为:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">清理阈值 = 清理基本阈值 + 清理缩放系数 * 元组数</code></pre>
</div>
</div>
<div class="paragraph">
<p>其中清理基本阈值为autovacuum_vacuum_threshold, 清理缩放系数为autovacuum_vacuum_scale_factor, 元组数为`pg_class`.<code>reltuples</code>。</p>
</div>
<div class="paragraph">
<p>如果自上次清理以来插入的元组数量超过了定义的插入阈值,表也会被清理,该阈值定义为:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">清理插入阈值 = 清理基础插入阈值 + 清理插入缩放系数 * 元组数</code></pre>
</div>
</div>
<div class="paragraph">
<p>清理插入基础阈值为autovacuum_vacuum_insert_threshold,清理插入缩放系数为autovacuum_vacuum_insert_scale_factor。 这样的清理可以允许部分的表被标识为*all visible*,并且也可以允许元组被冻结,可以减小后续清理的工作需要。 对于可以接收`INSERT`操作但是不能或几乎不能`UPDATE`/<code>DELETE`操作的表, 可能会从降低表的autovacuum_freeze_min_age中受益,因为这可能允许元组在早期清理中被冻结。 废弃元组的数量和插入元组的数量可从统计收集器中获得;它是一个半精确的计数,由每个`UPDATE</code>、<code>DELETE</code> 和 <code>INSERT</code> 操作进行更新。 (它只是半精确的,因为一些信息可能会在重负载情况下丢失。) 如果表的`relfrozenxid`值大于`vacuum_freeze_table_age` 事务老的, 执行一个主动的清理来冻结旧的元组,并推进`relfrozenxid`;否则,只有上次清理以后修改过的页面被扫描。</p>
</div>
<div class="paragraph">
<p>对于分析,也使用了一个相似的阈值:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">分析阈值 = 分析基本阈值 + 分析缩放系数 * 元组数</code></pre>
</div>
</div>
<div class="paragraph">
<p>该阈值将与自从上次`ANALYZE`以来被插入、更新或删除的元组数进行比较。</p>
</div>
<div class="paragraph">
<p>临时表不能被自动清理访问。因此,临时表的清理和分析操作必须通过会话期间的SQL命令来执行。</p>
</div>
<div class="paragraph">
<p>默认的阈值和缩放系数都取自于`postgresql.conf`,但是可以为每一个表重写它们(和许多其他自动清理控制参数), 详情参见Storage Parameters。 如果一个设置已经通过一个表的存储参数修改,那么在处理该表时使用该值,否则使用全局设置。</p>
</div>
<div class="paragraph">
<p>当多个工作者运行时,在所有运行着的工作者之间自动清理代价延迟参数是 “平衡的”,这样不管实际运行的工作者数量是多少, 对于系统的总体 I/O 影响总是相同的。不过,任何正在处理已经设置了每表 <code>autovacuum_vacuum_cost_delay`或 `autovacuum_vacuum_cost_limit</code> 存储参数的表的工作者不会被考虑在均衡算法中。</p>
</div>
<div class="paragraph">
<p>autovacuum工作进程通常不会阻止其他命令。如果某个进程尝试获取与autovacuum持有的`SHARE UPDATE EXCLUSIVE`锁冲突的锁,则锁获取将中断该autovacuum。有关冲突的锁定模式,但是,如果autovacuum正在运行以防止事务ID回卷(即在`pg_stat_activity`视图中的autovacuum查询名以`(to prevent wraparound)`结尾),则autovacuum不会被自动中断。</p>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">警告</div>
<div class="paragraph">
<p>定期运行需要获取与`SHARE UPDATE EXCLUSIVE`锁冲突的锁的命令(例如ANALYZE)可能会让autovacuum始终无法完成。</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="日常重建索引"><a class="anchor" href="#日常重建索引"></a>2. 日常重建索引</h2>
<div class="sectionbody">
<div class="paragraph">
<p>在某些情况下值得周期性地使用REINDEX命令或一系列独立重构步骤来重建索引。</p>
</div>
<div class="paragraph">
<p>已经完全变成空的B树索引页面被收回重用。但是,还是有一种低效的空间利用的可能性:如果一个页面上除少量索引键之外的全部键被删除,该页面仍然被分配。因此,在这种每个范围中大部分但不是全部键最终被删除的使用模式中,可以看到空间的使用是很差的。对于这样的使用模式,推荐使用定期重索引。</p>
</div>
<div class="paragraph">
<p>对于非B树索引可能的膨胀还没有很好地定量分析。在使用非B树索引时定期监控索引的物理尺寸是个好主意。</p>
</div>
<div class="paragraph">
<p>还有,对于B树索引,一个新建立的索引比更新了多次的索引访问起来要略快, 因为在新建立的索引上,逻辑上相邻的页面通常物理上也相邻(这样的考虑目前并不适用于非B树索引)。仅仅为了提高访问速度也值得定期重索引。</p>
</div>
<div class="paragraph">
<p>REINDEX在所有情况下都可以安全和容易地使用。 默认情况下,此命令需要一个`ACCESS EXCLUSIVE`锁,因此通常最好使用`CONCURRENTLY`选项执行它,该选项仅需要获取`SHARE UPDATE EXCLUSIVE`锁。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="日志文件维护"><a class="anchor" href="#日志文件维护"></a>3. 日志文件维护</h2>
<div class="sectionbody">
<div class="paragraph">
<p>把数据库服务器的日志输出保存在一个地方是个好主意, 而不是仅仅通过`/dev/null`丢弃它们。 在进行问题诊断的时候,日志输出是非常宝贵的。不过,日志输出可能很庞大(特别是在比较高的调试级别上), 因此你不会希望无休止地保存它们。你需要轮转日志文件, 这样在一段合理的时间后会开始新的日志文件并且移除旧的。</p>
</div>
<div class="paragraph">
<p>如果你简单地把`postgres`的stderr定向到一个文件中,你会得到日志输出,但是截断该日志文件的唯一方法是停止并重起服务器。这样做对于开发环境中使用的IvorySQL可能是可接受的,但是你肯定不想在生产环境上这么干。</p>
</div>
<div class="paragraph">
<p>一个更好的办法是把服务器的stderr输出发送到某种日志轮转程序里。我们有一个内建的日志轮转程序,你可以通过在 `postgresql.conf`里设置配置参数`logging_collector`为`true`的办法启用它。你也可以使用这种方法把日志数据捕捉成机器可读的CSV(逗号分隔值)格式。</p>
</div>
<div class="paragraph">
<p>另外,如果在你已经使用的其他服务器软件中有一个外部日志轮转程序,你可能更喜欢使用它。 比如,包含在Apache发布里的rotatelogs工具就可以用于IvorySQL。要做到这一点,方法之一是把服务器的stderr用管道重定向到要用的程序。 如果你用`pg_ctl`启动服务器,那么stderr已经重定向到stdout, 因此你只需要一个管道命令,比如:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">pg_ctl start | rotatelogs /var/log/pgsql_log 86400</code></pre>
</div>
</div>
<div class="paragraph">
<p>您可以通过设置logrotate来收集由IvorySQL内置日志收集器生成的日志文件来组合这些方法。在这种情况下,日志收集器定义日志文件的名称和位置,而logrotate 则定期归档这些文件。启动日志轮转时,logrotate必须确保应用程序将进一步的输出发送到新文件。这通常是通过`postrotate`脚本完成的,该脚本向应用程序发送`SIGHUP`信号,使其重新打开日志文件。在IvorySQL中,您可以使用`logrotate`选项运行`pg_ctl`。服务器收到此命令后,服务器将切换到新的日志文件或重新打开现有文件,具体取决于日志记录配置。</p>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">注意</div>
<div class="paragraph">
<p>定期运行需要获取与`SHARE UPDATE EXCLUSIVE`锁冲突的锁的命令(例如ANALYZE)可能会让autovacuum始终无法完成使用静态日志文件名时,如果达到最大打开文件数限制或发生文件表溢出,则服务器可能无法重新打开日志文件。 在这种情况下,日志消息将发送到旧的日志文件,直到成功进行日志轮转为止。 如果将logrotate配置为压缩日志文件并将其删除,则服务器可能会丢失此时间范围内记录的消息。 为避免此问题,可以将日志收集器配置为动态分配日志文件名,并使用`prerotate`脚本忽略打开的日志文件。</p>
</div>
</div>
</div>
<div class="paragraph">
<p>另外一种生产级的管理日志输出的方法就是把它们发送给syslog,让syslog处理文件轮转。 要利用这个工具,我们需要设置`postgresql.conf`里的`log_destination`配置参数设置为`syslog`(记录`syslog`日志)。然后在你想强迫syslog守护进程开始写入一个新日志文件的时候, 你就可以发送一个 `SIGHUP`信号给它。 如果你想自动进行日志轮转,可以配置logrotate程序处理 来自syslog的日志文件。</p>
</div>
<div class="paragraph">
<p>不过,在很多系统上,syslog不是非常可靠,特别是在面对大量日志消息的情况下; 它可能在你最需要那些消息的时候截断或者丢弃它们。另外,在Linux,syslog会把每个消息刷写到磁盘上, 这将导致很差的性能(你可以在syslog配置文件里面的文件名开头使用一个“<code>-</code>”来禁用这种行为)。</p>
</div>
<div class="paragraph">
<p>请注意上面描述的所有解决方案关注的是在可配置的间隔上开始一个新的日志文件, 但它们并没有处理对旧的、不再需要的日志文件的删除。你可能还需要设置一个批处理任务来定期地删除旧日志文件。 另一种可能的方法是配置日志轮转程序,让它循环地覆盖旧的日志文件。</p>
</div>
<div class="paragraph">
<p><a href="https://pgbadger.darold.net/">pgBadger</a> 是一个外部项目,它可以进行日志文件的深度分析。check_postgres可在重要消息出现在日志文件中时向Nagios提供警告,也可以探测很多其他的特别情况。</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="高可用负载均衡和复制"><a class="anchor" href="#高可用负载均衡和复制"></a>4. 高可用、负载均衡和复制</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="不同方案的比较"><a class="anchor" href="#不同方案的比较"></a>4.1. 不同方案的比较</h3>
<div class="sect3">
<h4 id="共享磁盘故障转移"><a class="anchor" href="#共享磁盘故障转移"></a>4.1.1. 共享磁盘故障转移</h4>
<div class="paragraph">
<p>共享磁盘故障转移避免了只使用一份数据库拷贝带来的同步开销。它使用一个由多个服务器共享的单一磁盘阵列。如果主数据库服务器失效,后备服务器则可以挂载并启动数据库,就好像它从一次数据库崩溃中恢复过来了。这是一种快速的故障转移,并且不存在数据丢失。</p>
</div>
<div class="paragraph">
<p>共享硬件功能在网络存储设备中很常见。也可以使用一个网络文件系统,但是要注意的是该文件系统应具有完全的POSIX行为。这种方法的一个重大限制是如果共享磁盘阵列失效或损坏,主要和后备服务器都会变得无法工作。另一个问题是在主要服务器运行时,后备服务器永远不能访问共享存储。</p>
</div>
</div>
<div class="sect3">
<h4 id="文件系统块设备复制"><a class="anchor" href="#文件系统块设备复制"></a>4.1.2. 文件系统(块设备)复制</h4>
<div class="paragraph">
<p>共享硬件功能的一种修改版本是文件系统复制,在其中对一个文件系统的所有改变会被镜像到位于另一台计算机上的一个文件系统。唯一的限制是该镜像过程必须能保证后备服务器有一份该文件系统的一致的拷贝 — 特别是对后备服务器的写入必须按照主控机上相同的顺序进行。DRBD是用于 Linux 的一种流行的文件系统复制方案。</p>
</div>
</div>
<div class="sect3">
<h4 id="预写式日志传送"><a class="anchor" href="#预写式日志传送"></a>4.1.3. 预写式日志传送</h4>
<div class="paragraph">
<p>温备和热备服务器能够通过读取一个预写式日志(WAL)记录的流来保持为当前状态。如果主服务器失效,后备服务器拥有主服务器的几乎所有数据,并且能够快速地被变成新的主数据库服务器。这可以是同步的或异步的,并且只能用于整个数据库服务器。</p>
</div>
<div class="paragraph">
<p>可以使用基于文件的日志传送、流复制或两者的组合来实现一个后备服务器。</p>
</div>
</div>
<div class="sect3">
<h4 id="逻辑复制"><a class="anchor" href="#逻辑复制"></a>4.1.4. 逻辑复制</h4>
<div class="paragraph">
<p>逻辑复制允许数据库服务器发送数据更新流给另一台服务器。IvorySQL逻辑复制从WAL构建出逻辑数据更新流。逻辑复制允许您逐个表复制数据更改。此外,发布数据更新的服务器可以同时订阅其他服务器的更改,从而允许数据在多个方向流动。第三方扩展也能提供类似的功能。</p>
</div>
</div>
<div class="sect3">
<h4 id="基于触发器的主-备复制"><a class="anchor" href="#基于触发器的主-备复制"></a>4.1.5. 基于触发器的主-备复制</h4>
<div class="paragraph">
<p>基于触发器的复制通常会将修改数据的查询发送到指定的主服务器。它在逐个表的基础上工作,主服务器(通常)将数据更改异步发送到备用服务器。 主服务器运行时,备用服务器可以响应查询,并执行本地数据修改或写入操作。这种形式的复制通常用于减轻大数据分析型平台或者数据仓库查询负荷。</p>
</div>
<div class="paragraph">
<p>Slony-I是这种复制类型的一个例子。它使用表粒度,并且支持多个后备服务器。因为它会异步更新后备服务器(批量),在故障转移时可能会有数据丢失。</p>
</div>
</div>
<div class="sect3">
<h4 id="基于sql的复制中间件"><a class="anchor" href="#基于sql的复制中间件"></a>4.1.6. 基于SQL的复制中间件</h4>
<div class="paragraph">
<p>通过基于SQL的复制中间件,一个程序拦截每一个 SQL 查询并把它发送给一个或所有服务器。每一个服务器独立地操作。读写查询必须被发送给所有服务器,这样每一个服务器都能接收到任何修改。但只读查询可以被只发送给一个服务器,这样允许读负载在服务器之间分布。</p>
</div>
<div class="paragraph">
<p>如果查询未经修改发送,则函数的`random()`随机值和`CURRENT_TIMESTAMP`函数的当前时间和序列值可能因不同服务器而异。 因为每个服务器独立运行,并且它发送 SQL 查询而没有真正的更改数据。如果这是不可接受的,那么中间件或应用程序必须从单一服务器源确定此类值,并将结果用于写入查询。 还必须注意确保所有服务器在提交或中止事务时都是相同的。这将涉及使用 两阶段提交PREPARE TRANSACTION和COMMIT PREPARED。 Pgpool-II和Continuent Tungsten就是这种复制的例子。</p>
</div>
</div>
<div class="sect3">
<h4 id="异步多主控机复制"><a class="anchor" href="#异步多主控机复制"></a>4.1.7. 异步多主控机复制</h4>
<div class="paragraph">
<p>对于不会被定期连接或通讯链路较慢的服务器,如笔记本或远程服务器,保持服务器间的数据一致是一个挑战。通过使用异步的多主控机复制,每一个服务器独立工作并且定期与其他服务器通信来确定冲突的事务。这些冲突可以由用户或冲突解决规则来解决。Bucardo 是这种复制类型的一个例子。</p>
</div>
</div>
<div class="sect3">
<h4 id="同步多主控机复制"><a class="anchor" href="#同步多主控机复制"></a>4.1.8. 同步多主控机复制</h4>
<div class="paragraph">
<p>在同步多主控机复制中,每一个服务器能够接受写请求,并且在每一个事务提交之前,被修改的数据会被从原始服务器传送给每一个其他服务器。繁重的写活动可能导致过多的锁定和提交延迟,进而导致很差的性能。读请求可以被发送给任意服务器。某些实现使用共享磁盘来减少通信负荷。同步多主控机复制主要对于读负载最好,尽管它的大优点是任意服务器都能接受写请求 — 没有必要在主服务器和后备服务器之间划分负载,并且因为数据修改被从一个服务器发送到另一个服务器,不会有非确定函数(如`random()`)的问题。</p>
</div>
<div class="paragraph">
<p>IvorySQL不提供这种复制类型,尽管在应用代码或中间件中可以使用 IvorySQL 的两阶段提交PREPARE TRANSACTION和COMMIT PREPARED来实现这种复制。</p>
</div>
<div class="paragraph">
<p>下表总结了上述多种方案的能力。</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 11.1111%;">
<col style="width: 11.1111%;">
<col style="width: 11.1111%;">
<col style="width: 11.1111%;">
<col style="width: 11.1111%;">
<col style="width: 11.1111%;">
<col style="width: 11.1111%;">
<col style="width: 11.1111%;">
<col style="width: 11.1112%;">
</colgroup>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">特性</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">共享磁盘</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">文件系统复制</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">预写式日志传送</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">逻辑复制</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">基于触发器的复制</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SQL复制中间件</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">异步多主控机复制</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">同步多主控机复制</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">常用的示例</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">NAS</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">DRBD</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">内建流复制</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">内建逻辑复制,pglogical</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Londiste,Slony</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">pgpool-II</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Bucardo</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">通信方法</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">共享磁盘</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">磁盘块</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">WAL</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">逻辑解码</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">表行</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SQL</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">表行</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">表行和行锁</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">不要求特殊硬件</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">允许多个主控机服务器</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">无主服务器负载</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">不等待多个服务器</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">with sync off</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">with sync off</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">主控机失效将永不丢失数据</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">with sync on</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">with sync on</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">复制体接受只读查询</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">with hot</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">每个表粒度</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">不需要冲突解决</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">•</p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>有一些方案不适合上述的类别:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>数据分区</p>
<div class="literalblock">
<div class="content">
<pre>数据分区将表分开成数据集。每个集合只能被一个服务器修改。例如,数据可以根据办公室划分,如伦敦和巴黎,每一个办公室有一个服务器。如果查询有必要组合伦敦和巴黎的数据,一个应用可以查询两个服务器,或者可以使用主/备复制来在每一台服务器上保持其他办公室数据的一个只读拷贝。</pre>
</div>
</div>
</li>
<li>
<p>多服务器并行查询执行</p>
<div class="literalblock">
<div class="content">
<pre>上述的很多方案允许多个服务器来处理多个查询,但是没有一个允许一个单一查询使用多个服务器来更快完成。 这种方案允许多个服务器在一个单一查询上并发工作。 这通常通过把数据在服务器之间划分并且让每一个服务器执行该查询中属于它的部分,然后将结果返回给一个中心服务器,由它整合结果并发回给用户。 这也可以使用PL/Proxy工具集来实现这种方案。</pre>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="sect2">
<h3 id="日志传送后备服务器"><a class="anchor" href="#日志传送后备服务器"></a>4.2. 日志传送后备服务器</h3>
<div class="sect3">
<h4 id="规划"><a class="anchor" href="#规划"></a>4.2.1. 规划</h4>
<div class="paragraph">
<p>创建主服务器和后备服务器通常是明智的,因此它们可以尽可能相似,至少从数据库服务器的角度来看是这样。特别地,与表空间相关的路径名将被未经修改地传递,因此如果该特性被使用,主、备服务器必须对表空间具有完全相同的挂载路径。记住如果CREATE TABLESPACE在主服务器上被执行,在命令被执行前,它所需要的任何新挂载点必须在主服务器和所有后备服务器上先创建好。硬件不需要完全相同,但是经验显示,在应用和系统的生命期内维护两个相同的系统比维护两个不相似的系统更容易。在任何情况下硬件架构必须相同 — 从一个 32 位系统传送到一个 64 位系统将不会工作。</p>
</div>
<div class="paragraph">
<p>通常,不能在两个运行着不同主版本IvorySQL的服务器之间传送日志。IvorySQL 全球开发组的策略是不在次版本升级中改变磁盘格式,因此在主服务器和后备服务器上运行不同次版本将会成功地工作。不过,在这方面并没有提供正式的支持,因此我们建议让主备服务器上运行的版本尽可能相同。当升级到一个新的次版本时,最安全的策略是先升级后备服务器 — 一个新的次版本发行更可能兼容从前一个次版本读取 WAL 文件。</p>
</div>
</div>
<div class="sect3">
<h4 id="后备服务器操作"><a class="anchor" href="#后备服务器操作"></a>4.2.2. 后备服务器操作</h4>
<div class="paragraph">
<p>服务器启动时,数据目录中存在 <code>standby.signal</code> 文件,服务器进入standby模式。</p>
</div>
<div class="paragraph">
<p>在后备模式中,服务器持续地应用从主控服务器接收到的 WAL。后备服务器可以从一个 WAL 归档restore_command或者通过一个 TCP 连接直接从主控机(流复制)读取 WAL。后备服务器将也尝试恢复在后备集簇的`pg_wal`目录中找到的 WAL。那通常在一次数据库重启后发生,那时后备机将在重启之前重播从主控机流过来的 WAL,但是你也可以在任何时候手动拷贝文件到`pg_wal`让它们被重播。</p>
</div>
<div class="paragraph">
<p>在启动时,后备机通过恢复归档位置所有可用的 WAL 来开始,这称为`restore_command`。一旦它到达那里可用的 WAL 的末尾并且`restore_command`失败,它会尝试恢复`pg_wal`目录中可用的任何 WAL。如果那也失败并且流复制已被配置,后备机会尝试连接到主服务器并且从在归档或`pg_wal`中找到的最后一个可用记录开始流式传送 WAL。如果那失败并且没有配置流复制,或者该连接后来断开,后备机会返回到步骤 1 并且尝试再次从归档里的文件恢复。这种尝试归档、`pg_wal`和流复制的循环会一直重复知道服务器停止或者一个触发器文件触发了故障转移。</p>
</div>
<div class="paragraph">
<p>当`pg_ctl promote`被运行,<code>pg_promote()`被调用,或一个触发器文件被找到(`promote_trigger_file</code>),后备模式会退出并且服务器会切换到普通操作。 在故障转移之前,在归档或`pg_wal`中立即可用的任何 WAL 将被恢复,但不会尝试连接到主控机。</p>
</div>
</div>
<div class="sect3">
<h4 id="为后备服务器准备主控机"><a class="anchor" href="#为后备服务器准备主控机"></a>4.2.3. 为后备服务器准备主控机</h4>
<div class="paragraph">
<p>在主服务器上设置连续归档到一个后备服务器可访问的归档目录。即使主服务器垮掉该归档位置也应当是后备服务器可访问的,即它应当位于后备服务器本身或者另一个可信赖的服务器,而不是位于主控服务器上。</p>
</div>
<div class="paragraph">
<p>如果你想要使用流复制,在主服务器上设置认证来允许来自后备服务器的复制连接。即创建一个角色并且在`pg_hba.conf`中提供一个或多个数据库域被设置为`replication`的项。还要保证在主服务器的配置文件中`max_wal_senders`被设置为足够大的值。如果要使用复制槽,请确保`max_replication_slots`也被设置得足够高。</p>
</div>
</div>
<div class="sect3">
<h4 id="建立一个后备服务器"><a class="anchor" href="#建立一个后备服务器"></a>4.2.4. 建立一个后备服务器</h4>
<div class="paragraph">
<p>要建立后备服务器,恢复从主服务器取得的基础备份。在后备服务器的集簇数据目录中创建一个文件standby.signal。将restore_command设置为一个从 WAL 归档中复制文件的简单命令。 如果你计划为了高可用性目的建立多个后备服务器,确认`recovery_target_timeline`被设置为`latest` (默认)来使得该后备服务器遵循发生在故障转移到另一个后备服务器之后发生的时间线改变。</p>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">注意</div>
<div class="paragraph">
<p>restore_command应该立即返回,如果必要该服务器将再次尝试该命令。</p>
</div>
</div>
</div>
<div class="paragraph">
<p>如果你想要使用流复制,在primary_conninfo中填入一个 libpq 连接字符串,其中包括主机名(或 IP 地址)和连接到主服务器所需的任何附加细节。如果主服务器需要一个口令用于认证,口令也应该被指定primary_conninfo中。</p>
</div>
<div class="paragraph">
<p>如果你正在为高性能目的建立后备服务器,像主服务器一样建立 WAL 归档、连接和认证,因为在故障转移后该后备服务器将作为一个主服务器工作。</p>
</div>
<div class="paragraph">
<p>如果你正在使用一个 WAL 归档,可以使用archive_cleanup_command参数来移除后备服务器不再需要的文件,这样可以最小化 WAL 归档的尺寸。pg_archivecleanup工具被特别设计为在典型单一后备配置下与`archive_cleanup_command`共同使用,见pg_archivecleanup。不过要注意,如果你正在为备份目的使用归档,有一些文件即使后备服务器不再需要你也需要保留它们,它们被用来从至少最后一个基础备份恢复。</p>
</div>
<div class="paragraph">
<p>配置的一个简单例子是:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass options=''-c wal_sender_timeout=5000'''
restore_command = 'cp /path/to/archive/%f %p'
archive_cleanup_command = 'pg_archivecleanup /path/to/archive %r'</code></pre>
</div>
</div>
<div class="paragraph">
<p>你可以有任意数量的后备服务器,但是如果你使用流复制,确保你在主服务器上将`max_wal_senders`设置得足够高,这样可以允许它们能同时连接。</p>
</div>
</div>
<div class="sect3">
<h4 id="流复制"><a class="anchor" href="#流复制"></a>4.2.5. 流复制</h4>
<div class="paragraph">
<p>流复制允许一台后备服务器比使用基于文件的日志传送更能保持为最新的状态。后备服务器连接到主服务器,主服务器则在 WAL 记录产生时即将它们以流式传送给后备服务器而不必等到 WAL 文件被填充。</p>
</div>
<div class="paragraph">
<p>默认情况下流复制是异步的,在这种情况下主服务器上提交一个事务与该变化在后备服务器上变得可见之间存在短暂的延迟。不过这种延迟比基于文件的日志传送方式中要小得多,在后备服务器的能力足以跟得上负载的前提下延迟通常低于一秒。在流复制中,不需要`archive_timeout`来缩减数据丢失窗口。</p>
</div>
<div class="paragraph">
<p>如果你使用的流复制没有基于文件的连续归档,该服务器可能在后备机收到 WAL 段之 前回收这些旧的 WAL 段。如果发生这种情况,后备机将需要重新从一个新的基础备 份初始化。通过设置`wal_keep_size`为一个足够高的值来确保旧 的 WAL 段不会被太早重用或者为后备机配置一个复制槽,可以避免发生这种情况。如 果设置了一个后备机可以访问的 WAL 归档,就不需要这些解决方案,因为该归档可以 为后备机保留足够的段,后备机总是可以使用该归档来追赶主控机。</p>
</div>
<div class="paragraph">
<p>要使用流复制,建立一个基于文件的日志传送后备服务器。将一个基于文件日志传送后备服务器转变成流复制后备服务器的步骤是把`recovery.conf`文件中的设置以指向主服务器。设置主服务器上的listen_addresses和认证选项(见`pg_hba.conf`),这样后备服务器可以连接到主服务器上的伪数据库`replication`。</p>