一、Java进阶-MySQL-进阶
1.1 单表访问方法
MySQL执行查询语句的方式称之为访问方法或者访问类型。
通过主键或者唯一二级索引列来定位一条记录的访问方法定义为:const,意思是常数级别的,代价是可以忽略不计的。const访问方法只能在主键列或者唯一二级索引列和一个常数进行等值比较时才有效,如果主键或者唯一二级索引是由多个列构成的话,索引中的每一个列都需要与常数进行等值比较,这个const访问方法才有效(这是因为只有该索引中全部列都采用等值比较才可以定位唯一的一条记录)
搜索条件为二级索引列与常数等值比较,采用二级索引来执行查询的访问方法称为:ref。
不仅想找出某个二级索引列的值等于某个常数的记录,还想把该列的值为NULL的记录也找出来,当使用二级索引而不是全表扫描的方式执行这种查询时,这种类型的查询使用的访问方法就称为ref_or_null。
SELECT * FROM single_table WHERE key1 = 'abc' OR key1 IS NULL;
利用索引进行范围匹配的访问方法称之为:range。
直接遍历二级索引比直接遍历聚簇索引的成本要小很多,采用遍历二级索引记录的执行方式称之为:index。
使用全表扫描执行查询的方式称之为:all。
一个使用到索引的搜索条件和没有使用该索引的搜索条件使用OR连接起来后是无法使用该索引的。
SELECT * FROM single_table WHERE key2 > 100 OR common_field = 'abc';
SELECT * FROM single_table WHERE key2 > 100 OR TRUE;
SELECT * FROM single_table WHERE TRUE;
1.2 连接的原理
连接查询执行过程:
- 首先确定第一个需要查询的表,这个表称之为驱动表。只需要选取代价最小的那种访问方法去执行单表查询语句。
- 针对上一步骤中从驱动表产生的结果集中的每一条记录,分别需要到t2表中查找匹配的记录,所谓匹配的记录,指的是符合过滤条件的记录。因为是根据t1表中的记录去找t2表中的记录,所以t2表也可以被称之为被驱动表。
驱动表中的记录即使在被驱动表中没有匹配的记录,也仍然需要加入到结果集。
- 对于内连接的两个表,驱动表中的记录在被驱动表中找不到匹配的记录,该记录不会加入到最后的结果集。
- 对于外连接的两个表,驱动表中的记录即使在被驱动表中没有匹配的记录,也仍然需要加入到结果集。(左外连接、右外连接)
驱动表只访问一次,但被驱动表却可能被多次访问。
在连接查询中对被驱动表使用主键值或者唯一二级索引列的值进行等值查找的查询执行方式称之为:eq_ref。
join buffer就是执行连接查询前申请的一块固定大小的内存,先把若干条驱动表结果集中的记录装在这个join buffer中,然后开始扫描被驱动表,每一条被驱动表的记录一次性和join buffer中的多条驱动表记录做匹配,因为匹配的过程都是在内存中完成的,所以这样可以显著减少被驱动表的I/O代价。加入了join buffer的嵌套循环连接算法称之为基于块的嵌套连接(Block Nested-Loop Join)算法。
1.3 Explain
EXPLAIN FORMAT=JSON //查看某个执行计划花费的成本的方式
SHOW VARIABLES LIKE 'optimizer_trace';
1.4 InnoDB的Buffer Pool
Buffer Pool本质上是InnoDB向操作系统申请的一段连续的内存空间,可以通过innodb_buffer_pool_size来调整它的大小。
Buffer Pool向操作系统申请的连续内存由控制块和缓存页组成,每个控制块和缓存页都是一一对应的,在填充足够多的控制块和缓存页的组合后,Buffer Pool剩余的空间可能产生不够填充一组控制块和缓存页,这部分空间不能被使用,也被称为碎片。
为了快速定位某个页是否被加载到Buffer Pool,使用表空间号+页号作为key,缓存页作为value,建立哈希表。
1.5 事务简介
- 原子性:要么全做,要么全不做的规则称之为原子性。
- 隔离性:不仅要保证这些操作以原子性的方式执行完成,而且要保证其它的状态转换不会影响到本次状态转换,这个规则被称之为隔离性。
- 一致性:如果数据库中的数据全部符合现实世界中的约束(all defined rules),我们说这些数据就是一致的,或者说符合一致性的。数据库某些操作的原子性和隔离性都是保证一致性的一种手段,在操作执行完成后保证符合所有既定的约束则是一种结果。
- 持久性:当现实世界的一个状态转换完成后,这个转换的结果将永久的保留,这个规则被称为持久性。
把需要保证原子性、隔离性、一致性和持久性的一个或多个数据库操作称之为一个事务(英文名是:transaction)。
事务状态:
只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束了。对于已经提交的事务来说,该事务对数据库所做的修改将永久生效,对于处于中止状态的事务,该事务对数据库所做的所有修改都会被回滚到没执行该事务之前的状态。
保存点:就是在事务对应的数据库语句中打几个点,我们在调用ROLLBACK语句时可以指定会滚到哪个点,而不是回到最初的原点。
1.6 redo日志
在事务提交时,把上述内容刷新到磁盘中,即使之后系统崩溃了,重启之后只要按照上述内容所记录的步骤重新更新一下数据页,那么该事务对数据库中所做的修改又可以被恢复出来,也就意味着满足持久性的要求。
上述内容也被称之为重做日志,英文名为redo log,称之为redo日志。
- redo日志占用的空间非常小
- redo日志是顺序写入磁盘的
redo日志本质上只是记录了一下事务对数据库做了哪些修改,在之后系统崩溃重启后可以把事务所做的任何修改都恢复出来。redo日志通用结构如下:
- type:该条redo日志的类型
- space ID:表空间ID
- page number:页号
- data:该条redo日志的具体内容
对底层页面中的一次原子访问的过程称之为一个Mini-Transaction,简称mtr。一个所谓的mtr可以包含一组redo日志,在进行崩溃恢复时这一组redo日志作为一个不可分割的整体。一个事务可以包含若干条语句,每一条语句其实是由若干个mtr组成,每一个mtr又可以包含若干条redo日志。
1.7 undo日志
为了回滚而记录的信息称之为撤销日志,英文名为undo log,称之为undo日志。
只有在事务对表中的记录做改动时才会为这个事务分配一个唯一的事务id。
roll_pointer隐藏列:本质上就是一个指向记录对应的undo日志的一个指针,占7个字节。
1.8 事务隔离级别和MVCC
事务并发执行遇到的问题:
- 脏写:如果一个事务修改了另一个未提交事务修改过的数据,那就意味着发生了脏写。
- 脏读:如果一个事务读到了另一个未提交事务修改过的数据,那就意味着发生了脏读。
- 不可重复读:如果一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值,那就意味着发生了不可重复读。
- 幻读:如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来,那就意味着发生了幻读。幻读强调的是一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录。
事务隔离级别:
MVCC原理:
版本链结构:
- trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列。
- roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表。
对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id。
ReadView:
不同的隔离级别:需要判断一下版本链中的哪个版本是当前事务可见。
ReadView结构:
- m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
- min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
- max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
- creator_trx_id:表示生成该ReadView的事务的事务id。
事务是否可见比较规则:
READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同。
- 使用READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。
- 使用REPEATABLE READ隔离级别的事务来说,只会在第一次执行查询语句时生成一个ReadView。
MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。
1.9 锁
- 共享锁,英文名:Shared Locks,简称S锁。在事务要读取一条记录时,需要先获取该记录的S锁。
- 独占锁,也常称排他锁,英文名:Exclusive Locks,简称X锁。在事务要改动一条记录时,需要先获取该记录的X锁。
S锁和S锁是兼容的,S锁和X锁是不兼容的,X锁和X锁也是不兼容的。
- 意向共享锁,英文名:Intention Shared Lock,简称IS锁。当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。
- 意向独占锁,英文名:Intention Exclusive Lock,简称IX锁。当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。
IS、IX锁是表级锁,它们的提出仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录,也就是说其实IS锁和IX锁是兼容的,IX锁和IX锁是兼容的。
表级别的AUTO-INC锁:采用AUTO-INC锁,也就是在执行插入语句时就在表级别加一个AUTO-INC锁,然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值,在该语句执行结束后,再把AUTO-INC锁释放掉。
行锁类型:
- Record Locks:LOCK_REC_NOT_GAP(正经记录锁)
- Gap Locks:LOCK_GAP,简称为gap锁。对一条记录加了gap锁(不论是共享gap锁还是独占gap锁),并不会限制其他事务对这条记录加正经记录锁或者继续加gap锁,gap锁的作用仅仅是为了防止插入幻影记录的而已。
- Next-Key Locks:既想锁住某条记录,又想阻止其他事务在该记录前边的间隙插入新记录。LOCK_ORDINARY,我们也可以简称为next-key锁。next-key锁的本质就是一个正经记录锁和一个gap锁的合体,它既能保护该条记录,又能阻止别的事务将新记录插入被保护记录前边的间隙。
- Insert Intention Locks:LOCK_INSERT_INTENTION,称为插入意向锁
- 隐式锁
InnoDB锁的内存结构: