1、JDBC Connection实例是线程安全的吗?
Connection实例是线程安全的吗?
能不能只创建一次,共享Connection对象?
答案是不能的, Connection不是线程安全的,他会在多线程环境下,导致数据库操作的混乱,特别是在事务存在的情况下:可能一个线程刚开启事务con.setAutoCommit(true),而另一个线程直接提交事务con.commit();
对于单独查询的情况,似乎不会出现数据错乱的情况。是因为在JDBC中,使用了锁进行同步
**源码:**com.mysql.cj.jdbc.CallableStatement#executeQuery
connection本身是线程不安全的,并且connection创建开销比较大,所以一般使用数据库连接池来统一的管理connection对象,例如druid连接池,c3p0连接池等等
数据库连接池
在使用数据库连接池时,一个线程中所有DB操作都是使用同一个Connection实例吗?
在Spring环境中,获取connection源码如下所示:
源码:org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
非事务场景:在非事务创建中(同时没有使用Spring事务管理器),每一次访问数据库,都是在DataSource中取出一个Connection实例,调用完毕之后归还资源,因此多次调用,应该是不同的Connection实例
事务场景:在使用事务的情况下,实际上是在ConnectionHolder中获取的Connection。而ConnectionHolder是在TransactionSynchronizationManager中获取的resources属性的值,即connection对象信息
源码:
org.springframework.transaction.support.TransactionSynchronizationManager#doGetResource
而ThreadLocal<Map<Object,Object>>线程上下文共享。即Connection对于与Thread绑定。因此在事务中无论操作多少次DB,事实上都是操作的同一个Connection对象
2、MySQL架构设计
SQL接口:SQL的标准来接受
SQL解析器:理解这个SQL语句要干什么事情
查询优化器:选择一个最优的查询路径
执行器:执行器就会去根据我们的优化器生成一套执行计划,然后不停的调用存储引擎的各种接口去完成SQL语句的执行计划
存储引擎:存储引擎其实就是执行SQL语句的,他会按照一定的步骤去查询内存数据,更新磁盘数据,查询磁盘数据,等等,执行诸如此类的一系列的操作,MySQL的架构设计中,SQL接口,SQL解析器,查询优化器其实都是通用的,他就是一套组件而已,但是存储引擎的话,他是支持各种各样的存储引擎的,比如我们常见的InnoDB、MyISAM、Memory等等
3、InnoDB内存结构
4、Buffer Pool
Buffer Pool: 缓冲池,简称BP。其作用是用来缓存表数据与索引数据,减少磁盘IO操作,提供效率
Buffer Pool由缓存数据页和对缓存数据页进行描述的控制块组成,控制块中存储着对应缓存页的所属的表空间、数据页的编号、以及对应缓存页在Buffer Pool中的地址等信息
Buffer Pool默认大小是128M,以Page页为单位,Page页默认大小16kb,而控制块的大小为数据页的5%,大概是800字节
注意:Buffer Pool大小为128M指的就是缓存页的大小,控制块则一般占5%,所以每次会多申请6M的内存空间用于存放控制块
如何判断一个页是否在Buffer Pool中缓存?
MySQL中有一个哈希表数据结构,它使用表空间号+数据页号,作为一个key,然后缓存页对应的控制块作为value
- 当需要访问某个页的数据时,先从哈希表中根据表空间号+页号看看是否存在对应的缓存页
- 如果有,则直接使用;如果没有,就从free链表中选出一个空闲的缓存页,然后把磁盘中对应的页加载到缓存页的位置
Page页
Buffer Pool的底层采用链表数据结构管理Page。在InnoDB访问表记录和索引时会在Page页中缓存,以后使用可以减少磁盘IO操作,提升效率
Page分类
Page页根据状态分为三种类型 :
free page : 空闲page,未被使用
clean page : 被使用page,数据没有被修改过
dirty page : 脏页,被使用page,数据被修改过,页中数据和磁盘数据产生了不一致
Page如何管理
针对上面所说的三种page类型,InnoDB通过三种链表结构来维护和管理
free list : 表示空闲缓冲区,管理free page
Buffer Pool的初始化过程中,是先向操作系统申请连续的内存空间,然后把它划分成若干个控制块&缓存页的键值对
free链表是把所有空闲的缓存页的控制块作为一个个的节点放到一个链表中,这个链表便称之为free链表
基节点:free链表中只有一个基节点是不记录缓存页信息(单独申请空间),它里面就存放了free链表头节点地址,尾节点地址,还有free链表当前有多少个节点
磁盘加载过程 :- 从free链表中取出一个空闲的控制块(对应缓存页)
- 把该缓存页对应的控制块的信息填上(例如:页所在的表空间、页号之类的信息)
- 把该缓存页对应的free链表节点(即:控制块)从连接中移除。表示该缓存页已经被使用
- flush list : 表示需要刷新到磁盘的缓冲区,管理dirty page,内部page按修改时间排序
InnoDB引擎为了提高处理效率,在每次修改缓存页后,并不是立刻把修改刷新到磁盘上,而是在未来的某个时间点进行刷新操作,所以需要使用到flush链表存储脏页,凡是被修改过的缓存页对应的控制块都会作为节点加入到flush链表
脏页即存在于flush链表,也在LRU链表中,但是两种互不影响,LRU链表负责管理page的可用性和释放,而flush链表负责管理脏页的刷盘操作 LRU list : 表示正在使用的缓冲区,管理clean page和dirty page
普通LRU算法
LRU = Least Recently Used(最近最少使用) : 就是末尾淘汰法,新数据从链表头部增加,释放空间的从末尾淘汰- 当要访问某个页时,如果不在Buffer Pool,需要把该页加载到缓冲池,并且把缓存页对应的控制块作为节点添加到LRU链表的头部
- 当要访问某个页时,如果在Buffer Pool中,则直接把该页对应的控制块移动到LRU链表的头部
- 当需要释放空间时,从末尾淘汰
待继续。。。,喜欢的话,点赞加关注哈!