如何连接 Mysql ?

在 Java 中,通常使用 JDBC(Java Database Connectivity)来连接数据库。JDBC 定义了一套用于访问数据库的 API,它提供了一种标准的接口,使得我们可以通过 Java 代码与各种数据库进行交互。

常见的 Java Web 系统是部署在 Tomcat 中的,那么 Tomcat 本身肯定是有多个线程来并发处理接收到的多个请求的,Mysql 是怎么处理的呢?

在 Mysql 中,引入了连接池,连接池会维护一组可重用的数据库连接,应用程序需要访问数据库时可以从连接池中获取一个可用连接,执行完毕后将连接归还给连接池。这样可以减少连接的频繁创建和销毁,提升性能。

如下所示:

Mysql 如何处理连接请求的?

当 Mysql 接收到一个网络连接请求后,它是如何去处理该请求的,如何执行 SQL 的,总体的步骤可以分为一下几步:

  1. 开启一个端口监听的线程,用于网络连接以及读取请求。

  2. Mysql 内部提供了一个 SQL 接口(SQL Interface)的组件,用来专门执行 SQL 语句的接口

  3. 通过查询优化器选择最优的查询路径来执行

  4. 调用执行器,根据执行计划调用存储引擎的接口

  5. 调用存储引擎接口,真正执行 SQL 语句

  6. 存储引擎管理和存储数据

比如:InnoDB、MyISAM、Memory,我们可以自己选择使用哪种存储引擎来负责具体的 SQL 语句执行。
现在 MySQL 一般都是默认使用 InnoDB 存储引擎

接着来分析 InnoDB 存储引擎是如何管理和存储我们的数据。

InnoDB 的重要内存结构:缓冲池

InnoDB 存储引擎中有一个非常重要的组件:缓冲池(Buffer Pool)。 Buffer Pool 会将磁盘上的数据页缓存到内存中。

Buffer Pool 使用 LRU(Least Recently Used,最近最少使用)算法来管理内存中的数据页。当查询需要访问数据时,InnoDB 首先检查缓冲池中是否存在相应的数据页。如果存在,它会直接从内存中获取数据,而不是从磁盘中读取,这大大提高了查询性能。如果数据页不在缓冲池中,InnoDB 会将其读取到缓冲池,并将其保留在内存中供后续查询使用。

比如 SQL 语句:update user set name='xxx' where id=1,Mysql 会先从 Buffer Pool 中查询存不存在,存在直接操作缓存中的数据,如果不在的话,先从磁盘里加载到缓冲池里来。

默认配置下 Buffer Pool 只有 128MB ,可以通过配置innodb_buffer_pool_size调整 Buffer Pool 大小。 通过适当配置缓冲池的大小,可以使常用的数据页始终在内存中,提高查询效率。

undo 日志文件:让更新的数据可以回滚

Undo 日志文件用于记录数据库中正在进行的事务操作,用于回滚数据。当有更新、删除或插入操作发生时,InnoDB 引擎会将相关信息记录到 Undo 日志文件中。

当需要回滚事务时,InnoDB 引擎使用 Undo 日志来还原到事务开始之前的数据状态。它通过逆向操作来撤销对数据的修改,并将数据还原为先前的状态。

当要更新一条数据时,首先会从磁盘文件中加载数据到缓冲池,然后然后对这条数据加锁,接着把更新前的旧值写入 undo 日志文件。这时才开始更新这条记录更新的时候,先更新缓冲池中的记录,此时这条数据变成了脏数据。

redo 日志文件:保证数据的一致性和持久性

如果修改操作已经写入缓存中,但是没有同步到磁盘进行持久化,此时,Mysql 的机器宕机了那么缓存中的数据也会丢失,那么本次更新的数据也就丢失了。

为了保障 Mysql 数据的一致性和持久性,InnoDB 引擎引入了 redo 日志文件。

Redo Log 日志是一种物理日志,主要用于记录在事务提交前对数据库进行的修改操作。当数据库崩溃或发生故障时,通过 Redo Log 可以恢复到最后一次提交的状态,保证数据的持久性。

Redo Log 的作用主要体现在以下两个方面:

数据恢复:当数据库发生故障时,通过 Redo Log 可以将未提交的修改操作重新应用到数据库中,从而恢复到最后一次提交的状态。

提高性能:通过将修改操作记录到 Redo Log 中,可以将磁盘 IO 操作转化为顺序写操作,大幅提高了数据库的写入性能。

因此,当更新操作执行后,Mysql 会把对内存所做的修改写入到一个 Redo Log Buffer 里去,这也是内存里的一个缓冲区,是用来存放 redo 日志的。如下图所示:

可以配置innodb_log_buffer_size来指定 Redo Log 的缓冲区大小,默认为 8MB。较大的值可以减少频繁的刷新操作,提高性能,但同时也会占用更多的内存。

redo 日志写入磁盘有三种策略,可以通过 innodb_flush_log_at_trx_commit 来配置的:

① 参数值为 0,redo log 不进磁盘

表示不刷写 Redo Log 到磁盘,即异步写入策略。事务提交时,Redo Log 的修改操作只会写入到操作系统的页缓存中,并不会马上刷写到磁盘。这样可以提供最好的写入性能,但在数据库崩溃或发生故障时,可能会造成一定程度的数据丢失。

② 参数值为 1,redo log 进磁盘【默认值】

表示同步刷写 Redo Log 到磁盘。事务提交时,Redo Log 的修改操作会立即写入磁盘并等待 IO 操作完成。确保数据持久性的同时,也会对性能产生一定的影响。这是最常用的设置,适合大多数应用场景。

③ 参数值为 2,redo log 进 os cache 缓存

表示每次事务提交时将 Redo Log 的修改操作写入磁盘,但不会等待 IO 操作完成。事务提交时,Redo Log 会先写入到操作系统的页缓存,然后由后台线程异步地将数据刷写到磁盘。这种设置可以提供较好的性能和一定程度的数据保护,但仍然存在一定的风险。

选择适当的 innodb_flush_log_at_trx_commit 值取决于对数据的持久性和性能的需求。如果对数据的持久性要求非常高,可以将其设置为 1。如果对性能要求较高且可以接受一定程度的数据丢失,可以将其设置为 0。如果在保证一定程度的数据保护的同时追求更好的性能,可以选择设置为 2。

binlog 到底是什么东西?

binlog 不是 InnoDB 存储引擎特有的日志文件,是属于 mysql server 自己的日志文件。用于记录对 MySQL 数据库执行的更改操作,包括语句的发生时间、执行时长,主要用于数据库恢复和主从复制。

redo log 和 binlog 都是记录修改的日志,但是两者是有差别的。redo log 是一种偏向物理性质的重做日志,它里面记录的类似对哪个数据页中的什么记录,做了个什么修改,而 binlog 叫做归档日志,它里面记录的是偏向于逻辑性的日志,类似于对 xxx 表中的 id=1 的一行数据做了更新操作,更新以后的值是什么

因此在提交事务的时候,同时也会写入 binlog:

binlog 日志的刷盘策略分析

对于 binlog 日志,有两种刷盘策略,可以通过 sync_binlog 设置:

① 参数值为 0,【默认值】

把 binlog 写入磁盘的时候,不是直接进入磁盘文件,而是进入 os cache 内存缓存。
所以跟之前分析的一样,如果此时机器宕机,那么在 os cache 里的 binlog 日志是会丢失的:

② 参数值为 1

强制在提交事务的时候,把 binlog 直接写入到磁盘文件里去,那么这样提交事务之后,哪怕机器宕机,磁盘上的 binlog 是不会丢失的。

当把 binlog 写入磁盘文件之后,接着就会完成最终的事务提交,此时会把本次更新对应的 binlog 文件名称和本次更新的 binlog 日志在文件里的位置,都写入到 redo log 日志文件里去,同时在 redo log 日志文件里写入一个 commit 标记。

在完成这个事情之后,才算最终完成了事务的提交,我们看下图的示意:

后台 IO 线程随机将内存更新后的脏数据刷回磁盘

MySQL 有一个后台的 IO 线程,会在之后某个时间里,随机的把内存 buffer pool 中的修改后的脏数据给刷回到磁盘上的数据文件里去,我们看下图:

在 IO 线程把脏数据刷回磁盘之前,哪怕 mysql 宕机崩溃也没关系,因为重启之后,会根据 redo 日志恢复之前提交事务做过的修改到内存里去,然后等适当时机,IO 线程自然还是会把这个修改后的数据刷到磁盘上的数据文件里去的。

总结

InnoDB 存储引擎主要就是包含了一些 Buffer Pool、redo log buffer 等内存里的缓存数据,同时还包含了一些 undo 日志文件,redo 日志文件等东西,同时 mysql server 自己还有 binlog 日志文件。

在执行更新的时候,每条 SQL 语句,都会对应修改 buffer pool 里的缓存数据、写 undo 日志、写 redo log buffer 几个步骤;当提交事务的时候,一定会把 redo log 刷入磁盘,binlog 刷入磁盘,完成 redo log 中的事务 commit 标记;最后后台的 IO 线程会随机的把 buffer pool 里的脏数据刷入磁盘里去。

参考

https://developer.aliyun.com/article/1286718