万级并发服务器内核调优总结

问题模型

在这里插入图片描述
tcp三次握手建立过程

问题1:当n多个client在极短时间内同时发出多个syn=1请求建立连接时

这时server端内核中的syn队列会发生溢出,部分连接请求会丢失。使用dmesg打印内核环形缓冲区会得到如下输出:possible syn flooding on port 30005
在这里插入图片描述
出现这样的原因是因为syn队列设置的太小,使得短时间内syn队列溢出,让部分连接请求丢失。

使用nstat可查看SNMP计数器。
TcpExtListenOverflows:Accept queue队列超过上限时加1
TcpExtListenDrops:任何原因,包括Accept queue超限,创建新连接,继承端口失败等,加1

nstat -az TcpExtListenOverflows TcpExtListenDrops

使用如下命令可以查看当前内核中syn队列大小。

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

解决方案1:扩大服务端SYN队列大小,减少连接请求失效数

修改/etc/sysctl.conf文件,在其末尾添加上net.ipv4.tcp_max_syn_backlog = 4096

sudo vim /etc/sysctl.conf
sudo /sbin/sysctl -p //使sysctl.conf配置立即生效。

解决方案2:启用服务端tcp_syncookies,对那些无法放入syn队列请求,使用syn-cookies的方式处理

修改/etc/sysctl.conf文件,在其末尾添加net.ipv4.tcp_syncookies = 1

sudo vim /etc/sysctl.conf
sudo /sbin/sysctl -p //让配置生效

解决方案3:减少客户端SYN的retry次数,提前返回错误至客户端,让客户端处理,从源头上解决。

在客户端中的/etc/sysctl.conf文件末尾添加net.ipv4.tcp_syn_retries = 2

sudo vim /etc/sysctl.conf
sudo /sbin/sysctl -p //让配置生效

解决方案4:减少服务端SYN队列中syn+ack尝试次数,让服务器尽早释放SYN队列多次尝试的连接,腾出SYN队列空位。

在服务端中的/etc/sysctl.conf文件末尾添加net.ipv4.tcp_synack_retries = 2

sudo vim /etc/sysctl.conf
sudo /sbin/sysctl -p //让配置生效

问题2:当syn队列中的请求完成SYN_RCVD并收到来自客户端的ACK后,将SYN队列中的socket添加到acp队列,但应用层调用accept速度不够快,使得acp队列溢出。

同问题1,可以通过SNMP计数器来观察是否存在acp队列存在溢出情况。

解决方案1:扩大acp队列大小,减少连接请求失效数

使用如下命令可查看因为acp队列满,而丢失的SYN队列请求

netstat -s |grep "SYNs to LISTEN"

在这里插入图片描述

在linux2.2之前,syn+acp队列的总大小由listen(fd,backlog)函数中的backlog确定,但在linux2.2之后,listen中的backlog仅仅指定acp队列大小。
使用如下命令,可以看到如下提示。

man listen

在这里插入图片描述
因此可以通过修改backlog参数来扩大acp队列大小,但这还不够,紧接着下面这句话。
在这里插入图片描述
因此可以得知acp队列实际大小为min(backlog,somaxconn),这是为什么仅仅修改backlog参数,并不会使得acp队列扩大的原因。

同样的修改/etc/sysctl.conf,在其后面添加net.core.somaxconn = 4096

sudo vim /etc/sysctl.conf
sudo /sbin/sysctl -p //使配置立即生效

解决方案2:启用tcp_abort_on_overflow,对那些在syn队列中完成了三次握手,即将从syn队列加入到acp队列的请求直接丢弃。

修改/etc/sysctl.conf文件,在其末尾添加net.ipv4.tcp_abort_on_overflow = 1

sudo vim /etc/sysctl.conf
sudo /sbin/sysctl -p //使配置立即生效

若启用该选项,那么会部分客户端会出现104 Connection reset by peer的错误。

内核中的问题1和问题2

在sock结构体中,存有两个 unsigned short变量,一个sk_ack_backlog,一个sk_max_ack_backlog,这两个变量标识了问题模型中的acp队列的大小。

struct sock{  
	....
    unsigned short      sk_ack_backlog;  
    unsigned short      sk_max_ack_backlog;  
	.....
}  

当client发送SYN=1的报文到服务端后,服务端进行如下调用
->tcp_v4_rcv
->tcp_v4_do_rcv
->tcp_rcv_state_process
->tcp_v4_conn_request
->tcp_conn_request

最后在tcp_conn_request中调用inet_reqsk_alloc创建一个request_sock(不占用文件描述符),并将其加入到SYN队列中。

int tcp_conn_request(...)
{
...
	  //分配request_sock
      req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie);
...
	 //将req加入到syn队列中 
      inet_csk_reqsk_queue_hash_add(sk, req,
                tcp_timeout_init((struct sock *)req));  
    
      //发送syn+ack tcp_v4_send_synack
      af_ops->send_synack(sk, dst, &fl, req, &foc,
                     !want_cookie ? TCP_SYNACK_NORMAL :
                            TCP_SYNACK_COOKIE);
...
}

修订该部分内容已更新,syn队列实际实现早已不是这样的
查看最新blog:https://editor.csdn.net/md/?articleId=114981573
在listen_sock结构体中可以看到存储request_sock的实体*request_sock syn_table[0]; 。其实际路径为
->inet_csk_reqsk_queue_hash_add()//函数
->icsk_accept_queue//变量名
->struct request_sock_queue icsk_accept_queue;//变量名的定义
->struct request_sock_queue//结构体
->struct listen_sock *listen_opt;
->listen_opt
->struct request_sock *syn_table[0];

struct inet_connection_sock {
        /* inet_sock has to be the first member! */
        struct inet_sock          icsk_inet;
        struct request_sock_queue icsk_accept_queue;
}
struct request_sock_queue {  
/*Points to the request_sock accept queue, when after 3 handshake will add the request_sock from syn_table to here*/  
    struct request_sock    *rskq_accept_head;  
    struct request_sock    *rskq_accept_tail;  
    rwlock_t        syn_wait_lock;  
    u8            rskq_defer_accept;  
    /* 3 bytes hole, try to pack */  
    struct listen_sock    *listen_opt;  
}; 
struct listen_sock {  
    u8            max_qlen_log; /*2^max_qlen_log is the length of the accpet queue, max of max_qlen_log is 10. (2^10=1024)*/  
    /* 3 bytes hole, try to use */  
    int            qlen; /* qlen is the current length of the accpet queue*/  
    int            qlen_young;  
    int            clock_hand;  
    u32            hash_rnd;  
    u32            nr_table_entries; /*nr_table_entries is the number of the syn_table,max is 512*/  
    struct request_sock    *syn_table[0];  
};  

最终能够看到syn队列长度的定义max_qlen_log。

在tcp_v4_conn_request函数中,能够看到内核时如何丢弃报文的。其中使用inet_csk_reqsk_queue_is_full判断syn队列是否满,如果满且没有配置tcp_syncookies 那么就直接丢弃goto drop。
sysctl_tcp_syncookies = 2无条件生成syn-cookie,现在内核都默认开启了sysctl_tcp_syncookies =1,一旦当syn队列溢出后,就会立即启用syncookies来处理syn队列溢出的情况。

int tcp_conn_request(struct sock *sk, struct sk_buff *skb)
{
    ...
	if ((net->ipv4.sysctl_tcp_syncookies == 2 ||
	     inet_csk_reqsk_queue_is_full(sk)) && !isn) {
		want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);
		if (!want_cookie)
			goto drop;
	}
	...
	if (want_cookie) {
	#ifdef CONFIG_SYN_COOKIES
			syn_flood_warning(skb);  // 输出 "possible SYN flooding on port %d. Sending cookies.\n"
			req->cookie_ts = tmp_opt.tstamp_ok;  // 为当前 socket 设置启用 cookie 标识
	#endif
			// 生成 syncookie
			isn = cookie_v4_init_sequence(sk, skb, &req->mss);
		} else if (!isn) {
		    ...
		}
...
}

在 tcp 三次握手完成后,将连接置入 ESTABLISHED 状态并交付给应用程序的 acp队列时,会检查 acp队列是否已满。若已满,通常行为是将连接还原至 SYN_ACK 状态,以造成 3 路握手最后的 ACK 包意外丢失假象, 这样在客户端等待超时后可重发 ACK , 以再次尝试进入 ESTABLISHED 状态 ,作为一种修复/重试机制。

struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
        struct request_sock *req,
        struct request_sock **prev)
{
    ...
	/* OK, ACK is valid, create big socket and
	 * feed this segment to it. It will repeat all
	 * the tests. THIS SEGMENT MUST MOVE SOCKET TO
	 * ESTABLISHED STATE. If it will be dropped after
	 * socket is created, wait for troubles.
	 */
	// 调用 net/ipv4/tcp_ipv4.c 中的 tcp_v4_syn_recv_sock 函数
	// 判定 accept queue 是否已经满,若已满,则返回的 child 为 NULL
	child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
	if (child == NULL)
		goto listen_overflow;

	// 在 accept queue 未满的情况下,将 ESTABLISHED 连接从 SYN queue 搬移到 accept queue 中
	inet_csk_reqsk_queue_unlink(sk, req, prev);
	inet_csk_reqsk_queue_removed(sk, req);
	inet_csk_reqsk_queue_add(sk, req, child);
	return child;


listen_overflow:
	// 若 accept queue 已满,但设置的是 net.ipv4.tcp_abort_on_overflow = 0
	if (!sysctl_tcp_abort_on_overflow) {
		inet_rsk(req)->acked = 1;    // 则只标记为 acked ,
		return NULL;
	}

embryonic_reset:
	// 若 accept queue 已满,但设置的是 net.ipv4.tcp_abort_on_overflow = 1
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);   
	if (!(flg & TCP_FLAG_RST))
		req->rsk_ops->send_reset(sk, skb);   

	inet_csk_reqsk_queue_drop(sk, req, prev);// 从 SYN queue 中删除该连接信息
    return NULL;
}

问题3.从acp队列中取出socket fd并生成文件fd绑定到进程时,因为acp队列中的socket fd太多,使得文件fd用完。

在服务端使用accept函数从acp队列出取出已经确认连接后的socket,并适用于sock_map_fd(),将socket中的file映射到文件fd上(应该是映射到网卡上,这样就可以使用read,write等操做)

struct socket
{
     socket_state  state; // socket state
      
     short   type ; // socket type
      
     unsigned long  flags; // socket flags
      
     struct fasync_struct  *fasync_list;
      
     wait_queue_head_t wait;
      
     struct file *file;
      
     struct sock *sock; 
     const struct proto_ops *ops;
}

->sock_map_fd
->get_unused_fd_flag
-> __alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags)
->newfile = sock_alloc_file(sock, flags, NULL);
->fd_install(fd,newfile )
其中RLIMIT_NOFILE为进程最大可打开的文件描述符个数,如果大于该数,那么将会直接返回错误,连接不能创立成功。

使用ulimit -a命令能够查看当前能够最大打开文件描述的个数

ulimit -a

在这里插入图片描述
通常这个数目默认为1024,对于需要建立大量连接的服务器来说,这显然是不够的。当连接数目过多,连接将无法成功建立,并会抛出错误too many open files。

如果fd能够正常获取,那么就会使用fd_install,得到该进程的flie_struct,以第一步得到的fd号,将file结构体赋值给该进程的fd_arrays[fd]。
ps.若文件打开数超过32,那么会重新分配一个fd_arrays,并更新max_fd
之后对该文件的所有read,write等操作,都会使用fd从fd_arrays中找到file结构体,然后执行file->f_ops执行驱动程序操做函数

解决方案:增大最大可打开文件数目

临时配置,退出shell复原

ulimit -HSn 100000000

more advanced method:https://www.dazhuanlan.com/2019/09/25/5d8b819277ece/

问题4.服务端作为客户端去发起请求,在请求结束后,会产生大量的TIME-WAIT连接。

为什么需要TIME-WAIT?
对客户端自己来说:TIME-WAIT等待2xMSL时间,能够保证所有已失效的报文段都消失,避免接受到来自服务端已失效的报文。

对服务端来说:TIME-WAIT等待2xMSL时间,能够保证服务端第三次FIN报文发送失败时,能够有多余时间重发FIN报文,能够从LAST_ACK进入到CLOSE状态。

解决方案:在产生大量TIME—WAIT的机器上,开启tcp_tw_reuse

TIME-WAIT多的机器开启tcp_tw_reuse和tcp_timestamps
其对应的服务端要开启tcp_timestamps
即:tcp_timestamps两端都需要开启,tcp_tw_reuse只在产生大量TIME-WAIT的机器上开启

在对应的机器上/etc/sysctl.conf文件中添加
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps =1

sudo vim /etc/sysctl.conf
sudo /sbin/sysctl -p

两方机器都开tcp_timestamps后,内核会比较数据包时间戳,由此对于客户端来说便可以保证一定不会收到失效的报文数据段。

对服务端来说,第三次FIN报文发送失败,仍处于LAST-ACK状态,但reuse会立即复用TIME-WAIT连接发送SYN包,去请求处于LAST-ACK状态的服务器时会返回FIN,ACK包,客户端再立即发送RST包置服务端close态后再次发送SYN包建立连接。
当服务器重发FIN,ACK包时,客户端仍处于连接建立状态,其sock既不是TCP_ESTABLISHED也不是TCP_LISTEN,那么最后内核就会执行到tcp_rcv_state_process中,若客户端处于TCP_SYN_SENT态,那么就会触发reset,重置tcp连接。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
	struct sock *rsk;
	//sock
	if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
	}
	//检查数据包是否完整
	if (tcp_checksum_complete(skb))
		goto csum_err;

	if (sk->sk_state == TCP_LISTEN) {
	} else
		sock_rps_save_rxhash(sk, skb);
	//进行reset操做
	if (tcp_rcv_state_process(sk, skb)) {
		rsk = sk;
		goto reset;
	}
	return 0;

内核

sk_buff

不仅可以用双向链表的形式组织,还可以用红黑树树的形式组织。

struct sk_buff {
	union {
		struct {
			/* These two members must be first. */
			struct sk_buff		*next;
			struct sk_buff		*prev;

			union {
				ktime_t		tstamp;
				struct skb_mstamp skb_mstamp;
			};
		};
		struct rb_node	rbnode; /* used in netem & tcp stack */
	};
	........
}

双链表组织形式,当sk_receive_queue中的spinlock被占用时,将数据包存储到sk_backlog上,

struct sock{
	struct sk_buff_head	sk_receive_queue;
	struct {
		atomic_t	rmem_alloc;
		int		len;
		struct sk_buff	*head;
		struct sk_buff	*tail;
	} sk_backlog;
}

在这里插入图片描述
sk_buffer的实际存储空间由四个unsinged char*的指针决定。

struct sk_buff {
    sk_buff_data_t		tail;
	sk_buff_data_t		end;
	unsigned char		*head,*data;
}

tail - data是sk_buffer的实际存储空间。

为什么说每次从sock中读取数据都要从内核态内存拷贝到用户态内存?这是因为sk_buffer是通过调用__alloc_skb函数进行分配初始化的。在__alloc_skb中通过kmem_cache_alloc_node分配内核内存地址空间,所以sk_buffer中的四个指针均指向内核地址空间。

struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
			    int flags, int node)
{
		struct sk_buff *skb;
		skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
		data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);
		skb->head = data;
	    skb->data = data;
}

sock的获取

在tcp协议栈总入口函数tcp_v4_rcv中,使用__inet_lookup_skb来通过源端口和目的端口号来确定sock。如果找不到对应的sock那么将会直接goto no_tcp_socket

int tcp_v4_rcv(struct sk_buff *skb)
{
	....
	th = (const struct tcphdr *)skb->data;
	sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,th->dest, &refcounted);
	if (!sk)
		goto no_tcp_socket;
	....
}

在__inet_lookup_skb中会尝试直接从sk_buffer中直接获取sock,如果无法找到那么会使用ip层来确定sock

static inline struct sock *__inet_lookup_skb(struct inet_hashinfo *hashinfo,
					     struct sk_buff *skb,
					     int doff,
					     const __be16 sport,
					     const __be16 dport,
					     bool *refcounted)
{
	struct sock *sk = skb_steal_sock(skb);//直接获取sock
	const struct iphdr *iph = ip_hdr(skb);

	*refcounted = true;
	if (sk)
		return sk;

	return __inet_lookup(dev_net(skb_dst(skb)->dev), hashinfo, skb,
			     doff, iph->saddr, sport,
			     iph->daddr, dport, inet_iif(skb),
			     refcounted);
}

在__inet_lookup中,会将sk_buffer转换为ip形式,然后传入(源IP,源端口,目的IP,目的端口),最后在__inet_lookup_established中,(源IP,源端口,目的IP,目的端口)会被hash为一个数,用于快速查找sock

struct sock *__inet_lookup_established(struct net *net,
				  struct inet_hashinfo *hashinfo,
				  const __be32 saddr, const __be16 sport,
				  const __be32 daddr, const u16 hnum,
				  const int dif)
{
	unsigned int hash = inet_ehashfn(net, daddr, hnum, saddr, sport);
	unsigned int slot = hash & hashinfo->ehash_mask;
	struct inet_ehash_bucket *head = &hashinfo->ehash[slot];
}

所有的sock都被统一保存在inet_hashinfo 中,其中ehash是一个struct inet_ehash_bucket型数组,链起来的一个hash。随后会使用sk_nulls_for_each_rcu从slot中遍历sock,直到找到这个sock为止才停下。

struct inet_hashinfo {
	struct inet_ehash_bucket	*ehash;
	spinlock_t			*ehash_locks;
	unsigned int			ehash_mask;
	unsigned int			ehash_locks_mask;
}

为什么要求双方都开启timestap

每个连接在初始化的时,都会调用tcp_connect_init函数,在其中会根据sysctl_tcp_timestamps 选项来确定tcp头的大小,如果没有开启sysctl_tcp_timestamps的话,那么tcp头大小将不会加上TCPOLEN_TSTAMP_ALIGNED长度。

static void tcp_connect_init(struct sock *sk)
{
	const struct dst_entry *dst = __sk_dst_get(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	__u8 rcv_wscale;

	/* We'll fix this up when we get a response from the other end.
	 * See tcp_input.c:tcp_rcv_state_process case TCP_SYN_SENT.
	 */
	tp->tcp_header_len = sizeof(struct tcphdr) +
		(sysctl_tcp_timestamps ? TCPOLEN_TSTAMP_ALIGNED : 0);
}

又因为才tcp_rcv_established中,会通过判断tcp头长度来判断是否需要检查timestamp

void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
			 const struct tcphdr *th, unsigned int len)
{
/* Check timestamp */
		if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {}

}

SYN包,在tcp_syn_options中会设置timestamp选项

static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,struct tcp_out_options *opts,struct tcp_md5sig_key **md5)
{
	if (likely(sysctl_tcp_timestamps && !*md5)) {
		opts->options |= OPTION_TS;
		opts->tsval = tcp_skb_timestamp(skb) + tp->tsoffset;
		opts->tsecr = tp->rx_opt.ts_recent;
		remaining -= TCPOLEN_TSTAMP_ALIGNED;
	}
}

SYN+ACK包,在tcp_synack_options中会设置timestamp选项,只有当发现SYN包中有tstamp_ok选项时,才会开启设置timestamp选项。

static unsigned int tcp_synack_options(struct request_sock *req,
				       unsigned int mss, struct sk_buff *skb,
				       struct tcp_out_options *opts,
				       const struct tcp_md5sig_key *md5,
				       struct tcp_fastopen_cookie *foc)
{
	if (likely(ireq->tstamp_ok)) {
		opts->options |= OPTION_TS;
		opts->tsval = tcp_skb_timestamp(skb);
		opts->tsecr = req->ts_recent;
		remaining -= TCPOLEN_TSTAMP_ALIGNED;
	}
}

ts_recent:上一个sk_buffer时间,即上一个rcv_tsval的值
rcv_tsval: 当前sk_buffer时间
rcv_tsecr:回响时间

void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
			 const struct tcphdr *th, unsigned int len)
{
	if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0) //当前包的时间戳小于上次收到的包的时间戳
     goto slow_path; //并不意味着一定是旧包,需仔细检查
}

->tcp_transmit_skb()
->skb_mstamp_get() // 获取时间并写入sk_buffer
->tcp_syn_options() or tcp_established_options() // 将sk_buffer中的时间写入到tcp option中去

如果客户端未开启sysctl.sysctl_tcp_timestamps,那么在调用tcp_transmit_skb发送SYN报文时,在使用tcp_syn_options()构造TCP option字段时,将不会启动构造tcp_timestamps选项字段,这会使得在服务段收到SYN报文时,回送SYN+ACK时,在tcp_synack_options()也不会构造TCP option字段,因为synack会判断syn报文是否启用timestap功能。

如果服务端未开启sysctl.sysctl_tcp_timestamps,虽然在构造SYN+ACK报文时,会根据request_conn来构造一个带有timestamp的SYN+ACK报文回去,但是在最后tcp_connect_init()的时候却需要判断sysctl_tcp_timestamps来决定tp->tcp_header_len的长度,如果不开启sysctl_tcp_timestamps,那么将不会生成预期长度,tcp_header_len 是一个后续用于识别是否进行timestamp操做的重要选项。并且在tcp_parseOption时也需要判断sysctl_tcp_timestamps是否开启填充rcv_tsval和rcv_tsecr的值

static void tcp_connect_init(struct sock *sk)
{
tp->tcp_header_len = sizeof(struct tcphdr) +
		(sysctl_tcp_timestamps ? TCPOLEN_TSTAMP_ALIGNED : 0);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL是一种开源的关系型数据库管理系统,常用于Web应用程序的后端数据库。MySQL内核参数可以提高MySQL的性能和可靠性,从而提高应用程序的整体性能。以下是一些价值两千万的MySQL内核参数建议: 1. 整innodb_buffer_pool_size参数 innodb_buffer_pool_size参数定义了InnoDB存储引擎使用的缓冲池大小。这个参数的值应该设置为系统内存的70-80%。如果设置得太小,会导致频繁的磁盘I/O操作,从而降低性能。如果设置得太大,会浪费系统内存资源。 2. 整innodb_io_capacity参数 innodb_io_capacity参数定义了InnoDB存储引擎的I/O能力。这个参数的值应该根据磁盘的I/O能力来设置。如果设置得太小,会限制InnoDB存储引擎的I/O性能。如果设置得太大,会浪费系统资源。 3. 整innodb_flush_method参数 innodb_flush_method参数定义了InnoDB存储引擎的刷新方式。这个参数的值应该根据系统的磁盘类型来设置。如果使用SSD磁盘,建议将这个参数设置为O_DIRECT。如果使用机械磁盘,建议将这个参数设置为O_DSYNC。 4. 整max_connections参数 max_connections参数定义了MySQL服务器可以同时处理的最大连接数。这个参数的值应该根据应用程序的需要来设置。如果设置得太小,会限制应用程序的并发性能。如果设置得太大,会浪费系统资源。 5. 整query_cache_size参数 query_cache_size参数定义了MySQL服务器使用的查询缓存大小。这个参数的值应该根据应用程序的查询频率和查询结果的大小来设置。如果设置得太小,会导致频繁的缓存miss,从而降低性能。如果设置得太大,会浪费系统内存资源。 6. 整thread_cache_size参数 thread_cache_size参数定义了MySQL服务器使用的线程缓存大小。这个参数的值应该根据应用程序的并发连接数来设置。如果设置得太小,会频繁创建和销毁线程,从而降低性能。如果设置得太大,会浪费系统资源。 7. 整innodb_thread_concurrency参数 innodb_thread_concurrency参数定义了InnoDB存储引擎使用的线程并发数。这个参数的值应该根据系统的CPU核数来设置。如果设置得太小,会限制InnoDB存储引擎的并发性能。如果设置得太大,会浪费系统资源。 8. 整innodb_log_file_size参数 innodb_log_file_size参数定义了InnoDB存储引擎使用的事务日志文件大小。这个参数的值应该根据系统的I/O能力来设置。如果设置得太小,会频繁切换事务日志文件,从而降低性能。如果设置得太大,会浪费系统磁盘空间。 9. 整innodb_flush_log_at_trx_commit参数 innodb_flush_log_at_trx_commit参数定义了InnoDB存储引擎事务日志的刷新方式。这个参数的值应该根据应用程序的安全要求来设置。如果设置为1,表示每次事务提交都会将事务日志刷新到磁盘,可以保证数据的安全性。如果设置为0,表示每次事务提交不会立即将事务日志刷新到磁盘,但是可以提高性能。 10. 整innodb_file_per_table参数 innodb_file_per_table参数定义了InnoDB存储引擎是否将每个表的数据存储在单独的文件中。这个参数的值应该根据应用程序的需要来设置。如果设置为1,表示每个表的数据存储在单独的文件中,可以提高数据恢复的速度。如果设置为0,表示所有表的数据存储在同一个文件中,可以提高性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值