mysql指引(三):mysql线程处理模型

上一讲 提到了 mysql 的线程模型,本节我们就来看看。

One-Thread-Per-Connection模型与 Pool-Threads模型

MySQL每个连接使用一个线程,另外还有内部处理线程、特殊用途的线程、以及所有存储引擎创建的线程。-- 《高性能MySQL》

站在客户端视角来看,也就是下面的 conn 对象就可以对应到 server 端的线程A:

		// 从DriverManager处获取数据库连接
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql://数据库ip/数据库名称",
				"账号",
				"密码" );

客户端对这个 conn 对象执行SQL语句时,server端的这个线程A就会处理SQL语句,也就是走 上一讲 提到的整体流程。下图中的 work 线程就是包含解析器、优化器等的那部分模块。

当我们在代码中执行SQL时,如前文的:

		Statement st = conn.createStatement();

         // 只查找一行数据
		ResultSet rs = st.executeQuery("SELECT id, username, password FROM sys_user LIMIT 1");

这部分代码对应的 server 端的执行过程就是在 work 线程中执行的。由 work 线程利用 解析器、优化器等模块方法来执行。这种模型叫做:One-Thread-Per-Connection

那么,当连接数过多的时候,比如1万个连接,就要创建1万个 work 线程。又由于我们在客户端缓存了 conn 对象,也就是保持了这个长连接。那么在这种模型下(每个连接使用一个线程),server 端也需要保持1万个线程。

虽然每个 work 线程可以复用来处理同一个 conn 对象的多个 SQL 语句,但是维护这么多的线程意味着内存占用开销大,意味着CPU调度开销大,性能肯定上不来。


所以呢?所以 mysql server 的线程处理模型是不是应该换一换呢?

我们先说结论,不是的。原因如下:

  1. 数据库的长连接一般不会很多,属于常量连接;但是数据库的请求是非常多的,也就是一个长连接中,可能来回收发请求数达到成百上千。由于客户端的连接池将连接缓存并做了最大限制,实际应用中数据库长连接不是很多。
  2. 我们可以看下 mysql5.7 官方文档是怎么说的:

The default thread-handling model in MySQL Server executes statements using one thread per client connection

首先,官方说是默认的线程处理模型是对每个客户端连接都创建一个线程,这个和我们认知一致。但是呢,还有其他线程处理模型,这就是 Pool-Threads 处理模型,官方说明如下:

to limit the number of concurrently executing statements/queries and transactions to ensure that each has sufficient CPU and memory resources to fulfill its task

主要的目的是限流以确保有足够的资源来完成已经接受的任务。那么原先的 One-Thread-Per-Connection 缺陷为:

As more clients connect to the server and execute statements, overall performance degrades.

就是大量客户端连接到服务器并执行各种语句,会导致服务端的整体性能下降。那么使用了 Pool-Threads 模型后,会怎么样呢?

The Thread Pool plugin increases server performance by efficiently managing statement execution threads for large numbers of client connections, especially on modern multi-CPU/Core systems

通过有效的线程管理会提升服务器性能。但是我们想用也难,因为:

For MySQL 5.7, the Thread Pool plugin is included in MySQL Enterprise Edition, a commercial product.

企业版才配置这个功能,也就是才支持这个线程处理模型。


所以,结论就是 mysql 的线程模型是 One-Thread-Per-Connection ,即每个连接都创建一个新的处理线程。

实际上,完整的线程模型如下图:

因为我们是基于 TCP 长连接的,所以前级有一个 Dispatcher线程 (实际上叫什么都行),它就是来监听TCP的连接请求,然后创建一个 Work线程 并将这个连接绑定到上面。

接着,work线程执行读取请求信息操作,也就是 read 操作,这样子就收到了客户端发来的信息,比如查询请求。然后调用业务处理,这里的业务处理就是上一讲中的解析器等一堆业务操作。

最后,将查询到的数据返回给客户端。

并发处理

既然讲到了线程模型,就需要提及一下并发处理模型。此处不区分线程和进程,进行简单的解析,后续会深入。

一个线程对应一个连接

这就是 One-Thread-Per-Connection 模型,不再赘述。这种思路下,资源消耗巨大,能否换种思路,让资源消耗降低,从而多余出去的资源给到真正需要用的地方。也就是 一个线程同时对应多条连接

一个线程对应多条连接

这就是大名鼎鼎的 I/O多路复用技术 。可以大大降低线程的数量,从而解决一个线程对应一个连接的调度问题。

当连接建立后,我们需要对连接做什么:

  1. 监听连接上的请求信息
  2. 监听到了,就去读取信息内容,并执行后续处理。即 read --> 业务处理 --> send

那么当我们的线程持有比如1万个连接时,如何处理第一步呢?即如何监听哪个或者哪些长连接目前发来了请求信息呢?

方法1:轮询

线程A一次轮询每个长连接,看是否有请求发出,有请求则执行第二步。

很容易想到,当第二步阻塞时,即该连接的数据还没有发送读取完成,没法立即返回结果,则排在后面的长连接就算有信息到来也无法立刻被线程A读取。

方法2:select

线程A利用 select 系统调用向操作系统内核注册长连接的文件句柄,当某个长连接有数据发来并且数据可以读取时,线程A就可以再次检查是哪个长连接的文件句柄发生了变化,就可以去依次读取发生变化的数据信息。由于此时数据已经准备好了,所以 read 操作几乎瞬间就可以完成。

但是文件句柄数量有限而且还需要再次检查是哪个句柄发生了变化,浪费性能。

方法3:epoll

线程A利用 epoll 系统调用向系统内核同样注册文件句柄,但是操作系统只返回可读的文件句柄。这样,就避免了重复轮询大量没有准备好数据的文件句柄了。

不过,虽然是 epoll非阻塞,但是 read、send这些仍然是同步读取数据的,这里还是会慢。

方法4:异步IO

异步IO是将 read 和 send 操作异步化,实现数据从网卡拷贝到内存中也是非阻塞的。但是目前 linux 层面对于异步IO的支持不完善,而且真正的异步化不好实现,所以一般也不采用。


这些实际上是IO模型,也就是下图:

图中右侧的两个阶段,就对应上文的两个步骤。等待数据对应的是监听连接上的请求信息,将数据从内核拷贝到用户空间,就对应的是第二步 read 过程。

方法1 对应 非阻塞I/O,方法2 对应I/O复用,方法3 对应 信号驱动I/O,方法4 对应 异步I/O。


在第一个阶段等待数据处,图中的后四种都是非阻塞的,因为他们不会阻塞在同一个长连接上;然而只有最后一种是异步的,因为在第二个阶段拷贝数据时,异步I O将数据拷贝到用户空间后再通知程序数据可读,不像其他四个,都需要主动去读取数据,所以他们又叫做同步IO。

到这里,等待数据、拷贝数据并读取都理清了。那么业务处理怎么办?对于处理速度较快的业务,可以直接在同一个线程中进行操作,完了再处理其他准备好的连接。对于业务处理慢的,比如有I/O操作的业务,则可以另起线程池进行操作。

其实,I/O复用模型 + 线程池 的组合就是非常重要的 Reactor 并发模式,后续文章会继续涉及。

再看One-Thread-Per-Connection模型

对于每个连接创建一个“线程“,这个线程是否就是操作系统内核管理的线程呢?答案是否定的。这个”线程“实际上是 mysql thread,其只是一个对象,需要和操作系统的真实线程 os thread 关联起来。

每个连接对应一个 mysql thread ,每个 mysql thread 对应一个 os thread 。当连接关闭时,mysql thread 也随之消失,但是 os thread 可以继续被其他的 mysql thread 继续使用。

下一个mysql文章中,我们会对关联查询的底层原理进行分析。在此之前,会先补充一个短篇,关于线程和操作系统的关系介绍。

发布了44 篇原创文章 · 获赞 85 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/zhou307/article/details/104117585