如何在进程崩溃后打印堆栈并防止数据丢失

如何在进程崩溃后打印堆栈并防止数据丢失,第1张

进程在运行过程中遇到逻辑错误, 比如除零, 空指针等等, 系统会触发一个软件中断

这个中断会以信号的方式通知进程, 这些信号的默认处理方式是结束进程

发生这种情况, 我们就认为进程崩溃了

进程崩溃后, 我们会希望知道它是为何崩溃的, 是哪个函数, 哪行代码引起的错误

另外, 在进程退出前, 我们还希望做一些善后处理, 比如把某些数据存入数据库, 等等

下面, 我会介绍一些技术来达成这两个目标

1 在core文件中查看堆栈信息

如果进程崩溃时, 我们能看到当时的堆栈信息, 就能很快定位到错误的代码

在 gcc 中加入 -g 选项, 可执行文件中便会包含调试信息 进程崩溃后, 会生成一个 core 文件

我们可以用 gdb 查看这个 core 文件, 从而知道进程崩溃时的环境

在调试阶段, core文件能给我们带来很多便利 但是在正式环境中, 它有很大的局限:

1 包含调试信息的可执行文件会很大 并且运行速度也会大幅降低

2 一个 core 文件常常很大, 如果进程频繁崩溃, 硬盘资源会变得很紧张

所以, 在正式环境中运行的程序, 不会包含调试信息

它的core文件的大小, 我们会把它设为0, 也就是不会输入core文件

在这个前提下, 我们如何得到进程的堆栈信息呢

2 动态获取线程的堆栈

c 语言提供了 backtrace 函数, 通过这个函数可以动态的获取当前线程的堆栈

要使用 backtrace 函数, 有两点要求:

1 程序使用的是 ELF 二进制格式

2 程序连接时使用了 -rdynamic 选项

-rdynamic可用来通知链接器将所有符号添加到动态符号表中, 这些信息比 -g 选项的信息要少得多

下面是将要用到的函数说明:

#include <execinfoh>

int backtrace(void buffer,int size);

用于获取当前线程的调用堆栈, 获取的信息将会被存放在buffer中, 它是一个指针列表。

参数 size 用来指定buffer中可以保存多少个void 元素。

函数返回值是实际获取的指针个数, 最大不超过size大小

注意: 某些编译器的优化选项对获取正确的调用堆栈有干扰,

另外内联函数没有堆栈框架; 删除框架指针也会导致无法正确解析堆栈内容;

char backtrace_symbols (void const buffer, int size)

把从backtrace函数获取的信息转化为一个字符串数组

参数buffer应该是从backtrace函数获取的指针数组,

size是该数组中的元素个数(backtrace的返回值) ;

函数返回值是一个指向字符串数组的指针, 它的大小同buffer相同

每个字符串包含了一个相对于buffer中对应元素的可打印信息

它包括函数名,函数的偏移地址, 和实际的返回地址

该函数的返回值是通过malloc函数申请的空间, 因此调用者必须使用free函数来释放指针

注意: 如果不能为字符串获取足够的空间, 函数的返回值将会为NULL

void backtrace_symbols_fd (void const buffer, int size, int fd)

与backtrace_symbols 函数具有相同的功能,

不同的是它不会给调用者返回字符串数组, 而是将结果写入文件描述符为fd的文件中,每个函数对应一行

3 捕捉信号

我们希望在进程崩溃时打印堆栈, 所以我们需要捕捉到相应的信号 方法很简单

#include <signalh>

void (signal(int signum,void( handler)(int)))(int);

或者: typedef void(sig_t) ( int );

sig_t signal(int signum,sig_t handler);

参数说明:

第一个参数signum指明了所要处理的信号类型,它可以是除了SIGKILL和SIGSTOP外的任何一种信号。

第二个参数handler描述了与信号关联的动作,它可以取以下三种值:

1 一个返回值为正数的函数的地址, 也就是我们的信号处理函数

这个函数应有如下形式的定义: int func(int sig); sig是传递给它的唯一参数。

执行了signal()调用后,进程只要接收到类型为sig的信号,不管其正在执行程序的哪一部分,就立即执行func()函数。

当func()函数执行结束后,控制权返回进程被中断的那一点继续执行。

2 SIGIGN, 忽略该信号

3 SIGDFL, 恢复系统对信号的默认处理。

返回值: 返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。

注意:

当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,

直到信号处理函数执行完毕再重新调用相应的处理函数。

如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。

在信号发生跳转到自定的handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式,

如果要改变此 *** 作请改用sigaction()。

4 实例

下面我们实际编码, 看看具体如何在捕捉到信号后, 打印进程堆栈, 然后结束进程

#include <iostream>

#include <timeh>

#include <signalh>

#include <stringh>

#include <execinfoh>

#include <fcntlh>

#include <map>

using namespace std;

map<int, string> SIG_LIST;

#define SET_SIG(sig) SIG_LIST[sig] = #sig;

void SetSigList(){

SIG_LISTclear();

SET_SIG(SIGILL)//非法指令

SET_SIG(SIGBUS)//总线错误

SET_SIG(SIGFPE)//浮点异常

SET_SIG(SIGABRT)//来自abort函数的终止信号

SET_SIG(SIGSEGV)//无效的存储器引用(段错误)

SET_SIG(SIGPIPE)//向一个没有读用户的管道做写 *** 作

SET_SIG(SIGTERM)//软件终止信号

SET_SIG(SIGSTKFLT)//协处理器上的栈故障

SET_SIG(SIGXFSZ)//文件大小超出限制

SET_SIG(SIGTRAP)//跟踪陷阱

}

string& GetSigName(int sig){

return SIG_LIST[sig];

}

void SaveBackTrace(int sig){

//打开文件

time_t tSetTime;

time(&tSetTime);

tm ptm = localtime(&tSetTime);

char fname[256] = {0};

sprintf(fname, "core%d-%d-%d_%d_%d_%d",

ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,

ptm->tm_hour, ptm->tm_min, ptm->tm_sec);

FILE f = fopen(fname, "a");

if (f == NULL){

exit(1);

}

int fd = fileno(f);

//锁定文件

flock fl;

fll_type = F_WRLCK;

fll_start = 0;

fll_whence = SEEK_SET;

fll_len = 0;

fll_pid = getpid();

fcntl(fd, F_SETLKW, &fl);

//输出程序的绝对路径

char buffer[4096];

memset(buffer, 0, sizeof(buffer));

int count = readlink("/proc/self/exe", buffer, sizeof(buffer));

if(count > 0){

buffer[count] = '\n';

buffer[count + 1] = 0;

fwrite(buffer, 1, count+1, f);

}

//输出信息的时间

memset(buffer, 0, sizeof(buffer));

sprintf(buffer, "Dump Time: %d-%d-%d %d:%d:%d\n",

ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,

ptm->tm_hour, ptm->tm_min, ptm->tm_sec);

fwrite(buffer, 1, strlen(buffer), f);

//线程和信号

sprintf(buffer, "Curr thread: %d, Catch signal:%s\n",

pthread_self(), GetSigName(sig)c_str());

fwrite(buffer, 1, strlen(buffer), f);

//堆栈

void DumpArray[256];

int nSize = backtrace(DumpArray, 256);

sprintf(buffer, "backtrace rank = %d\n", nSize);

fwrite(buffer, 1, strlen(buffer), f);

if (nSize > 0){

char symbols = backtrace_symbols(DumpArray, nSize);

if (symbols != NULL){

for (int i=0; i<nSize; i++){

fwrite(symbols[i], 1, strlen(symbols[i]), f);

fwrite("\n", 1, 1, f);

}

free(symbols);

}

}

//文件解锁后关闭, 最后终止进程

fll_type = F_UNLCK;

fcntl(fd, F_SETLK, &fl);

fclose(f);

exit(1);

}

void SetSigCatchFun(){

map<int, string>::iterator it;

for (it=SIG_LISTbegin(); it!=SIG_LISTend(); it++){

signal(it->first, SaveBackTrace);

}

}

void Fun(){

int a = 0;

int b = 1 / a;

}

static void ThreadFun(void arg){

Fun();

return NULL;

}

int main(){

SetSigList();

SetSigCatchFun();

printf("main thread id = %d\n", (pthread_t)pthread_self());

pthread_t pid;

if (pthread_create(&pid, NULL, ThreadFun, NULL)){

exit(1);

}

printf("fun thread id = %d\n", pid);

for(;;){

sleep(1);

}

return 0;

}

文件名为 btcpp

编译: g++ btcpp -rdynamic -I /usr/local/include -L /usr/local/lib -pthread -o bt

主线程创建了 fun 线程, fun 线程有一个除零错误, 系统抛出 SIGFPE 信号

该信号使 fun 线程中断, 我们注册的 SaveBackTrace 函数捕获到这个信号, 打印相关信息, 然后终止进程

在输出的core文件中, 我们可以看到简单的堆栈信息

5 善后处理

在上面的例子中, fun 线程被 SIGFPE 中断, 转而执行 SaveBackTrace 函数

此时, main 线程仍然在正常运行

如果我们把 SaveBackTrace 函数最后的 exit(1); 替换成 for(;;)sleep(1);

main 线程就可以一直正常的运行下去

利用这个特点, 我们可以做很多其它事情

游戏的服务器进程常常有这些线程:

网络线程, 数据库线程, 业务处理线程 引发逻辑错误的代码常常位于业务处理线程

而数据库线程由于功能稳定, 逻辑简单, 是十分强壮的

那么, 如果业务处理线程有逻辑错误, 我们捕捉到信号后, 可以在信号处理函数的最后,

通知数据库线程保存游戏数据

直到数据库线程把游戏信息全部存入数据库, 信号处理函数才返回

这样, 服务器宕机不会导致回档, 损失被大大降低

要实现这个机制, 要求数据库模块和业务处理模块具有低耦合度

当然, 实际应用的时候, 还有许多细节要考虑

比如, 业务处理线程正在处理玩家的数据, 由于发生不可预知的错误, 玩家的数据被损坏了, 这些玩家的数据就不应该被存入数据库

使用mysql_udf_>

触发器仅是数据库内部的应用,与外部程序无关。所以理论上也不存在什么通知外部程序。

有些数据库产品,在触发器内可以调用一些外部命令,但MYSQL目前的标准版本中没有这种功能。

关键看你想实现什么样的功能。

SystemNetMailMailMessage msg = new SystemNetMailMailMessage();

msgToAdd("邮件地址");

msgFrom = new SystemNetMailMailAddress("发件人地址", "发件人名", SystemTextEncodingUTF8);

msgSubject = "通知邮件标题";//邮件标题

msgSubjectEncoding = SystemTextEncodingUTF8;//邮件标题编码

msgBody = "通知内容";//邮件内容

msgBodyEncoding = SystemTextEncodingUTF8;//邮件内容编码

msgIsBodyHtml =true;//HTML标记

SystemNetMailSmtpClient client = new SystemNetMailSmtpClient();

clientCredentials = new SystemNetNetworkCredential("发件人邮件", "发件人密码");

clientHost = "发信服务器"; //这个现在通常要求认证

object userState=msg;

try

{

clientSend(msg);

}

catch (SystemNetMailSmtpException ex)

{

}

存完调一下就可以了

PostgresSQL提供了许多数据库配置参数,本章将介绍每个参数的作用和如何配置每一个参数。

101 如何设置数据库参数

所有的参数的名称都是不区分大小写的。每个参数的取值是布尔型、整型、浮点型和字符串型这四种类型中的一个,分别用boolean

、integer、 floating point和string表示。布尔型的值可以写成ON、OFF、 TRUE、 FALSE、 YES、 NO、 1和 0,而且不区分大小

写。

有些参数用来配置内存大小和时间值。内存大小的单位可以是KB、MB和GB。时间的单位可以是毫秒、秒、分钟、小时和天。用ms表示

毫秒,用s表示秒,用 min表示分钟,用h表示小时,用d表示天。表示内存大小和时间值的参数参数都有一个默认的单位,如果用户

在设置参数的值时没有指定单位,则以参数默认的 单位为准。例如,参数shared_buffers表示数据缓冲区的大小,它的默认单位是

数据块的个数,如果把它的值设成8,因为每个数据块的大小是 8KB,则数据缓冲区的大小是88=64KB,如果将它的值设成128MB,

则数据缓冲区的大小是128MB。参数vacuum_cost_delay 的默认单位是毫秒,如果把它的值设成10,则它的值是10毫秒,如果把它的

值设成100s,则它的值是100秒。

所有的参数都放在文件 postgresqlconf中,下面是一个文件实例:

#这是注释

log_connections = yes

log_destination = 'syslog'

search_path = '"$user", public'

每一行只能指定一个参数,空格和空白行都会被忽略。“ #”表示注释,注释信息不用单独占一行,可以出现在配置文件的任何地方

。如果参数的值不是简单的标识符和数字,应该用单引号引起来。如果参数的值中有单引号,应该写两个单引号,或者在单引号前面

加一个反斜杠。

一个配置文件也可以包含其它配置文件,使用include指令能够达到这个目的,例如,假设postgresqlconf文件中有下面一行:

include ‘myconfg’

文件myconfig中的配置信息也会被数据库读入。include指令指定的配置文件也可以用include指令再包含其它配置文件。如果

include指令中指定的文件名不是绝对路径,数据库会在postgresqlconf文件所在的目录下查找这个文件。

用户也可以在数据库启动以后修改postgresqlconf配置文件,使用命令pg_ctl reload来通知数据库重新读取配置文件。注意,有些

参数在数据库启动以后,不能被修改,只有重新启动数据库以后,新的参数值才能生效。另外一些参数可 以在数据库运行过程中被

修改而且新的值可以立即生效。所以数据库在运行过程中重新读取参数配置文件以后,不是所有的参数都会被赋给新的值。

用户可以在自己建立的会话中执行命令SET修改某些配置参数的值(注意不是全部参数),例如:

SET ENABLE_SEQSCAN TO OFF;

另外,有些参数只有数据库超级用户才能使用SET命令修改它们。用户可以在psql中执行命令show来查看所有的数据库参数的当前值

。例如:

(1)show all; --查看所有数据库参数的值

(2)show search_path; --查看参数search_path的值

102 连接与认证

1021 连接设置

listen_addresses (string)

这个参数只有在启动数据库时,才能被设置。它指定数据库用来监听客户端连接的TCP/IP地址。默认是值是 ,表示数据库在启动以

后将在运行数据的机器上的所有的IP地址上监听用户请求(如果机器只有一个网卡,只有一个IP地址,有多个网卡的机器有多个 IP

地址)。可以写成机器的名字,也可以写成IP地址,不同的值用逗号分开,例如,’server01’, ’1408717149, 1408717121

’。如果被设成localhost,表示数据库只能接受本地的客户端连接请求,不能接受远程的客户端连接请求。

port (integer)

这个参数只有在启动数据库时,才能被设置。它指定数据库监听户端连接的TCP端口。默认值是5432。

max_connections (integer)

这个参数只有在启动数据库时,才能被设置。它决定数据库可以同时建立的最大的客户端连接的数目。默认值是100。

superuser_reserved_connections (integer)

这个参数只有在启动数据库时,才能被设置。它表示预留给超级用户的数据库连接数目。它的值必须小于max_connections。 普通用

户可以在数据库中建立的最大的并发连接的数目是max_connections- superuser_reserved_connections, 默认值是3。

unix_socket_group (string)

这个参数只有在启动数据库时,才能被设置。设置Unix-domain socket所在的 *** 作系统用户组。默认值是空串,用启动数据库的 *** 作

系统用户所在的组作为Unix-domain socket的用户组。

unix_socket_permissions (integer)

这个参数只有在启动数据库时,才能被设置。它设置Unix-domain socket的访问权限,格式与 *** 作系统的文件访问权限是一样的。默

认值是0770,表示任何 *** 作系统用户都能访问Unix-domain socket。可以设为0770(所有Unix-domain socket文件的所有者所在的组

包含的用户都能访问)和0700(只有Unix-domain socket文件的所有者才能访问)。对于Unix-domain socket,只有写权限才有意义,

读和执行权限是没有意义的。

tcp_keepalives_idle (integer)

这个参数可以在任何时候被设置。默认值是0,意思是使用 *** 作系统的默认值。它设置TCP套接字的TCP_KEEPIDLE属性。这个参数对于

通过Unix-domain socket建立的数据库连接没有任何影响。

tcp_keepalives_interval (integer)

这个参数可以在任何时候被设置。默认值是0,意思是使用 *** 作系统的默认值。它设置TCP套接字的TCP_KEEPINTVL属性。这个参数对

于通过Unix-domain socket建立的数据库连接没有任何影响。

tcp_keepalives_count (integer)

这个参数可以在任何时候被设置。默认值是0,意思是使用 *** 作系统的默认值。它设置TCP套接字的TCP_KEEPCNT属性。这个参数对于

通过Unix-domain socket建立的数据库连接没有任何影响。

1022 安全与认证

authentication_timeout (integer)

这个参数只能在postgresqlconf文件中被设置,它指定一个时间长度,在这个时间长度内,必须完成客户端认证 *** 作,否则客户端

连接请求将被拒绝。它可以阻止某些客户端进行认证时长时间占用数据库连接。单位是秒,默认值是60。

ssl (boolean)

这个参数只有在启动数据库时,才能被设置。决定数据库是否接受SSL连接。默认值是off。

ssl_ciphers (string)

指定可以使用的SSL加密算法。查看 *** 作系统关于openssl的用户手册可以得到完整的加密算法列表(执行命令openssl ciphers –v

也可以得到)。

103 资源消耗

1031 内存

shared_buffers (integer)

这个参数只有在启动数据库时,才能被设置。它表示数据缓冲区中的数据块的个数,每个数据块的大小是8KB。数据缓冲区位于数据

库的共享内存中,它越大越好,不能小于128KB。默认值是1024。

temp_buffers (integer)

这个参数可以在任何时候被设置。默认值是8MB。它决定存放临时表的数据缓冲区中的数据块的个数,每个数据块的大小是8KB。临时

表缓冲区存放在每个数据库进程的私有内存中,而不是存放在数据库的共享内存中。默认值是1024。

max_prepared_transactions (integer)

这个参数只有在启动数据库时,才能被设置。它决定能够同时处于prepared状态的事务的最大数目(参考PREPARE TRANSACTION命令

)。如果它的值被设为0。则将数据库将关闭prepared事务的特性。它的值通常应该和max_connections的值 一样大。默认值是5。

work_mem (integer)

这个参数可以在任何时候被设置。它决定数据库的排序 *** 作和哈希表使用的内存缓冲区的大小。如何work_mem指定的内存被耗尽,数

据库将使用磁盘文件进 行完成 *** 作,速度会慢很多。ORDER BY、DISTINCT和merge连接会使用排序 *** 作。哈希表在Hash连接、hash聚

集函数和用哈希表来处理IN谓词中的子查询中被使用。单位是 KB,默认值是1024。

maintenance_work_mem (integer)

这个参数可以在任何时候被设置。它决定数据库的维护 *** 作使用的内存空间的大小。数据库的维护 *** 作包括VACUUM、CREATE INDEX和

ALTER TABLE ADD FOREIGN KEY等 *** 作。 maintenance_work_mem的值如果比较大,通常可以缩短VACUUM数据库和从dump文件中恢复数

据库需要的时间。 maintenance_work_mem存放在每个数据库进程的私有内存中,而不是存放在数据库的共享内存中。单位是KB,默

认值是16384。

max_stack_depth (integer)

这个参数可以在任何时候被设置,但只有数据库超级用户才能修改它。它决定一个数据库进程在运行时的STACK所占的空间的最大值

。数据库进程在运行时,会 自动检查自己的STACK大小是否超过max_stack_depth,如果超过,会自动终止当前事务。这个值应该比

*** 作系统设置的进程STACK的大小 的上限小1MB。使用 *** 作系统命令“ulimit –s“可以得到 *** 作系统设置的进程STACK的最大值。单

位是KB,默认值是100。

1032 Free Space Map

数据库的所有可用空间信息都存放在一个叫free space map (FSM)的结构中,它记载数据文件中每个数据块的可用空间的大小。FSM

中没有记录的数据块,即使有可用空间,也不会系统使用。系统如果需要新的物理存 储空间,会首先在FSM中查找,如果FSM中没有

一个数据页有足够的可用空间,系统就会自动扩展数据文件。所以,FSM如果太小,会导致系统频繁地扩展数 据文件,浪费物理存储

空间。命令VACUUM VERBOSE在执行结束以后,会提示当前的FSM设置是否满足需要,如果FSM的参数值太小,它会提示增大参数。

FSM存放在数据库的共享内存中,由于物理内存的限制,FSM不可能跟踪数据库的所有的数据文件的所有数据块的可用空间信息,只能

跟踪一部分数据块的可用空间信息。

max_fsm_relations (integer)

这个参数只有在启动数据库时,才能被设置。默认值是1000。它决定FSM跟踪的表和索引的个数的上限。每个表和索引在FSM中占7个

字节的存储空间。

max_fsm_pages (integer)

这个参数只有在启动数据库时,才能被设置。它决定FSM中跟踪的数据块的个数的上限。initdb在创建数据库集群时会根据物理内存

的大小决定它的值。每 个数据块在fsm中占6个字节的存储空间。它的大小不能小于16 max_fsm_relations。默认值是20000。

1033 内核资源

max_files_per_process (integer)

这个参数只有在启动数据库时,才能被设置。他设定每个数据库进程能够打开的文件的数目。默认值是1000。

shared_preload_libraries (string)

这个参数只有在启动数据库时,才能被设置。它设置数据库在启动时要加载的 *** 作系统共享库文件。如果有多个库文件,名字用逗号

分开。如果数据库在启动时未找到shared_preload_libraries指定的某个库文件,数据库将无法启动。默认值为空串。

1034 垃圾收集

执行VACUUM 和ANALYZE命令时,因为它们会消耗大量的CPU与IO资源,而且执行一次要花很长时间,这样会干扰系统执行应用程序发

出的SQL命令。为了解决这个 问题,VACUUM 和ANALYZE命令执行一段时间后,系统会暂时终止它们的运行,过一段时间后再继续执行

这两个命令。这个特性在默认的情况下是关闭的。将参数 vacuum_cost_delay设为一个非零的正整数就可以打开这个特性。

用户通常只需要设置参数vacuum_cost_delay和vacuum_cost_limit,其它的参数使用默认值即可。VACUUM 和ANALYZE命令在执行过程

中,系统会计算它们执行消耗的资源,资源的数量用一个正整数表示,如果资源的数量超过 vacuum_cost_limit,则执行命令的进程

会进入睡眠状态,睡眠的时间长度是是vacuum_cost_delay。 vacuum_cost_limit的值越大,VACUUM 和ANALYZE命令在执行的过程中

,睡眠的次数就越少,反之,vacuum_cost_limit的值越小,VACUUM 和ANALYZE命令在执行的过程中,睡眠的次数就越多。

vacuum_cost_delay (integer)

这个参数可以在任何时候被设置。默认值是0。它决定执行VACUUM 和ANALYZE命令的进程的睡眠时间。单位是微秒。它的值最好是10

的整数,如果不是10的整数,系统会自动将它设为比该值大的并且最接近该值的是10 的倍数的整数。如果值是0,VACUUM 和ANALYZE

命令在执行过程中不会主动进入睡眠状态,会一直执行下去直到结束。

vacuum_cost_page_hit (integer)

这个参数可以在任何时候被设置。默认值是1。

vacuum_cost_page_miss (integer)

这个参数可以在任何时候被设置。默认值是10。

vacuum_cost_page_dirty (integer)

这个参数可以在任何时候被设置。默认值是20。

vacuum_cost_limit (integer)

这个参数可以在任何时候被设置。默认值是200。

1035 后台写数据库进程

后台写数据库进程负责将数据缓冲区中的被修改的数据块(又叫脏数据块)写回到数据库物理文件中。

bgwriter_delay (integer)

这个参数只能在文件postgresqlconf中设置。它决定后台写数据库进程的睡眠时间。后台写数据库进程每次完成写数据到物理文件

中的任务以后, 就会睡眠bgwriter_delay指定的时间。 bgwriter_delay的值应该是10的倍数,如果用户设定的值不是10的倍数,数

据库会自动将参数的值设为比用户指定的值大的最接近用户指定的值 的同时是10的倍数的值。单位是毫秒,默认值是200。

bgwriter_lru_maxpages (integer)

这个参数只能在文件postgresqlconf中设置。默认值是100。后台写数据库进程每次写脏数据块时,写到外部文件中的脏数据块的个

数不能超过 bgwriter_lru_maxpages指定的值。例如,如果它的值是500,则后台写数据库进程每次写到物理文件的数据页的个数不

能超过500,若 超过,进程将进入睡眠状态,等下次醒来再执行写物理文件的任务。如果它的值被设为0, 后台写数据库进程将不会

写任何物理文件(但还会执行检查点 *** 作)。

bgwriter_lru_multiplier (floating point)

这个参数只能在文件postgresqlconf中设置。默认值是20。它决定后台写数据库进程每次写物理文件时,写到外部文件中的脏数据

块的个数 (不能超过bgwriter_lru_maxpages指定的值)。一般使用默认值即可,不需要修改这个参数。这个参数的值越大,后台写

数据库进程每次写 的脏数据块的个数就越多。

104 事务日志

full_page_writes (boolean)

这个参数只能在postgresqlconf文件中被设置。默认值是on。打开这个参数,可以提高数据库的可靠性,减少数据丢失的概率,但

是会产生过多的事务日志,降低数据库的性能。

wal_buffers (integer)

这个参数只有在启动数据库时,才能被设置。默认值是8。它指定事务日志缓冲区中包含的数据块的个数,每个数据块的大小是8KB,

所以默认的事务日志缓冲区的大小是88=64KB。事务日志缓冲区位于数据库的共享内存中。

wal_writer_delay (integer)

这个参数只能在postgresqlconf文件中被设置。它决定写事务日志进程的睡眠时间。WAL进程每次在完成写事务日志的任务后,就会

睡眠 wal_writer_delay指定的时间,然后醒来,继续将新产生的事务日志从缓冲区写到WAL文件中。单位是毫秒(millisecond),

默认 值是200。

commit_delay (integer)

这个参数可以在任何时候被设置。它设定事务在发出提交命令以后的睡眠时间,只有在睡眠了commit_delay指定的时间以后,事务产

生的事务日志才会 被写到事务日志文件中,事务才能真正地提交。增大这个参数会增加用户的等待时间,但是可以让多个事务被同

时提交,提高系统的性能。如果数据库中的负载比较 高,而且大部分事务都是更新类型的事务,可以考虑增大这个参数的值。下面

的参数commit_siblings会影响commit_delay是否生效。 默认值是0,单位是微秒(microsecond)。

commit_siblings (integer)

这个参数可以在任何时候被设置。这个参数的值决定参数commit_delay是否生效。假设commit_siblings的值是5,如果一个事务发出

一个提交请求,此时,如果数据库中正在执行的事务的个数大于或等于5,那么该事务将睡眠commit_delay指定的时间。如果数据库

中正在执行的事务 的个数小于5,这个事务将直接提交。默认值是5。

105 检查点

checkpoint_segments (integer)

这个参数只能在postgresqlconf文件中被设置。默认值是3。它影响系统何时启动一个检查点 *** 作。如果上次检查点 *** 作结束以后,

系统产生的事 务日志文件的个数超过checkpoint_segments的值,系统就会自动启动一个检查点 *** 作。增大这个参数会增加数据库崩

溃以后恢复 *** 作需要的时 间。

checkpoint_timeout (integer)

这个参数只能在postgresqlconf文件中被设置。单位是秒,默认值是300。它影响系统何时启动一个检查点 *** 作。如果现在的时间减

去上次检查 点 *** 作结束的时间超过了checkpoint_timeout的值,系统就会自动启动一个检查点 *** 作。增大这个参数会增加数据库崩

溃以后恢复 *** 作需要的时 间。

checkpoint_completion_target (floating point)

这个参数控制检查点 *** 作的执行时间。合法的取值在0到1之间,默认值是05。不要轻易地改变这个参数的值,使用默认值即可。 这

个参数只能在postgresqlconf文件中被设置。

106 归档模式

archive_mode (boolean)

这个参数只有在启动数据库时,才能被设置。默认值是off。它决定数据库是否打开归档模式。

archive_dir (string)

这个参数只有在启动数据库时,才能被设置。默认值是空串。它设定存放归档事务日志文件的目录。

archive_timeout (integer)

这个参数只能在postgresqlconf文件中被设置。默认值是0。单位是秒。如果archive_timeout的值不是0,而且当前时间减去数 据

库上次进行事务日志文件切换的时间大于archive_timeout的值,数据库将进行一次事务日志文件切换。一般情况下,数据库只有在

一个事务日志 文件写满以后,才会切换到下一个事务日志文件,设定这个参数可以让数据库在一个事务日志文件尚未写满的情况下

切换到下一个事务日志文件。

107 优化器参数

1071 存取方法参数

下列参数控制查询优化器是否使用特定的存取方法。除非对优化器特别了解,一般情况下,使用它们默认值即可。

enable_bitmapscan (boolean)

打开或者关闭bitmap-scan 。默认值是 on。

enable_hashagg (boolean)

打开或者关闭hashed aggregation。默认值是 on。

enable_hashjoin (boolean)

打开或者关闭hash-join。默认值是 on。

enable_indexscan (boolean)

打开或者关闭index-scan。默认值是 on。

enable_mergejoin (boolean)

打开或者关闭merge-join。默认值是 on。

enable_nestloop (boolean)

打开或者关闭nested-loop join。默认值是 on。不可能完全不使用nested-loop join,关闭这个参数会让系统在有其它存取方法可

用的情况下,不使用nested-loop join。

enable_seqscan (boolean)

打开或者关闭sequential scan。默认值是 on。不可能完全不使用sequential scan,关闭这个参数会让系统在有其它存取方法可用

的情况下,不使用sequential scan。

public class MyDbOpenHeler extends SQLiteOpenHelper {

    private static final String name="zhoukedb";

    private static final int version=1;

    public MyDbOpenHeler(Context context) {

        super(context, name, null, version);

    }

    @Override

    public void onCreate(SQLiteDatabase db) {

        dbexecSQL("create table if not exists word(_id integer primary key autoincrement,name varchar(20),age int)");

    }

    @Override

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

}

------------------------------------------------

public class Main2Activity extends AppCompatActivity {

    ListView lv;

    MyDbOpenHeler heler;

    SQLiteDatabase db;

    SimpleCursorAdapter adapter;

    EditText et1;

    //通知

    NotificationManager manage;

    //---

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        superonCreate(savedInstanceState);

        setContentView(Rlayoutactivity_main2);

        lv= (ListView) findViewById(Ridlv);

        //实例化数据库帮助类

        heler=new MyDbOpenHeler(this);

        //获取一个可读写的数据库 *** 作对象

        db=helergetWritableDatabase();

        Cursor word = dbquery("word", null, null, null, null, null, "_id desc");

        adapter=new SimpleCursorAdapter(this,Rlayoutlist_ltem,word,

                new String[]{"_id","name","age"},new int[]{Ridtv1,Ridtv2,Ridtv3},

                CursorAdapterFLAG_REGISTER_CONTENT_OBSERVER);

        lvsetAdapter(adapter);

//        if(wordisClosed()){

//            wordclose();

//        }

        registerForContextMenu(lv);

        //通知的实例化

        manage= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        //---

        et1= (EditText) findViewById(Ridet1);

    }

    //创建选项菜单

    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        menuadd(1,1,0,"增");

        return superonCreateOptionsMenu(menu);

    }

    //选项菜单监听事件

    @Override

    public boolean onOptionsItemSelected(MenuItem item) {

        View inflate = Viewinflate(this, Rlayoutdialog, null);

        final EditText et1 = (EditText) inflatefindViewById(Ridet1);

        final EditText et2 = (EditText) inflatefindViewById(Ridet2);

        new AlertDialogBuilder(this)setTitle("增加新学生信息")setView(inflate)

                setPositiveButton("确认", new DialogInterfaceOnClickListener() {

                    @Override

                    public void onClick(DialogInterface dialog, int which) {

                        ContentValues cv=new ContentValues();

                        cvput("name",et1getText()toString());

                        cvput("age", et2getText()toString());

                        long word = dbinsert("word", null, cv);

                        if (word>0){

                            ToastmakeText(Main2Activitythis,"增加数据成功",ToastLENGTH_SHORT)show();

                            Cursor word2 = dbquery("word", null, null, null, null, null, "_id desc");

                            adapterchangeCursor(word2);

                            //通知---开始

                            NotificationCompatBuilder builder=new NotificationCompatBuilder(Main2Activitythis);

                            buildersetSmallIcon(Rdrawableaa)

                                    setContentText("年龄:"+et2getText()toString())

                                    setContentTitle(et1getText()toString())

                                    setAutoCancel(true);

                            managenotify(1,builderbuild());

                            //通知 --结束

                        }

                    }

                })setNegativeButton("取消", new DialogInterfaceOnClickListener() {

            @Override

            public void onClick(DialogInterface dialog, int which) {

            }

        })show();

        return superonOptionsItemSelected(item);

    }

    //创建上下文菜单

    @Override

    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuContextMenuInfo menuInfo) {

        superonCreateContextMenu(menu, v, menuInfo);

        menuadd(1,2,0,"修改");

        menuadd(1,3,0,"删除");

    }

    //上下文菜单监听事件

    @Override

    public boolean onContextItemSelected(MenuItem item) {

        //获取当前点击项的索引

        AdapterViewAdapterContextMenuInfo info= (AdapterViewAdapterContextMenuInfo) itemgetMenuInfo();

        int position = infoposition;

        //根据索引获取这一行数据,把它转化为Cursor

        Cursor  cursor= (Cursor) adaptergetItem(position);

        //从结果集中取出id

        final int _id = cursorgetInt(0);

        switch (itemgetItemId()){

            case 2:

                View inflate = Viewinflate(this, Rlayoutdialog, null);

                final EditText et1 = (EditText) inflatefindViewById(Ridet1);

                final EditText et2 = (EditText) inflatefindViewById(Ridet2);

                //获取单词名称

                String name=cursorgetString(1);

                String age=cursorgetString(2);

                et1setText(name);

                et2setText(age);

                new AlertDialogBuilder(this)setTitle("修改学生信息")setView(inflate)

                        setPositiveButton("确认", new DialogInterfaceOnClickListener() {

                            @Override

                            public void onClick(DialogInterface dialog, int which) {

                                ContentValues cv=new ContentValues();

                                cvput("name",et1getText()toString());

                                cvput("age", et2getText()toString());

                                int word=dbupdate("word",cv,"_id=",new String[]{StringvalueOf(_id)});

                                if (word>0){

                                    ToastmakeText(Main2Activitythis,"修改数据成功",ToastLENGTH_SHORT)show();

                                    Cursor word2 = dbquery("word", null, null, null, null, null, "_id desc");

                                    adapterchangeCursor(word2);

                                }

                            }

                        })setNegativeButton("取消", new DialogInterfaceOnClickListener() {

                    @Override

                    public void onClick(DialogInterface dialog, int which) {

                    }

                })show();

                break;

            case 3:

                int word = dbdelete("word", "_id=", new String[]{StringvalueOf(_id)});

                if (word>0){

                    ToastmakeText(Main2Activitythis,"删除数据成功",ToastLENGTH_SHORT)show();

                    Cursor word2 = dbquery("word", null, null, null, null, null, "_id desc");

                    adapterchangeCursor(word2);

                }

                break;

        }

        return superonContextItemSelected(item);

    }

    public void click1(View view) {

        //模糊查询

        String aa=et1getText()toString();

        Cursor word2 = dbquery("word", null, "name like ", new String[]{"%"+aa+"%"}, null, null, "_id desc");

        adapterchangeCursor(word2);

        //正常查询

 //       Cursor word2 = dbquery("word", null, "name=", new String[]{aa}, null, null, "_id desc");

    }

}

以上就是关于如何在进程崩溃后打印堆栈并防止数据丢失全部的内容,包括:如何在进程崩溃后打印堆栈并防止数据丢失、gauss数据库中数据改变,怎么通知springboot项目、Mysql数据库内的触发器能够主动发送通知到服务器吗等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/9544247.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)