ThreadLocal

线程局部缓存:为线程缓存数据,将数据本地化(脱离共享)

原理

  1. 每个线程有一个ThreadLocalMap属性,本质就是一个map

  2. map里面存储的<key, value>称为键值对,存储键值对时需要先求取哈希值

​ 由于哈希值会出现冲突,所以会造成“错位”元素的出现(元素“理想位置”和实际存储位置不一样)

​ “理想位置”是指该ThreadLocal对象初次计算出的哈希值

​ 如果从“理想位置”到实际存储位置是连续的,这里称该序列是“紧凑”的

  1. map里存储的key是一个弱引用,其包装了当前线程中构造的ThreadLocal对象

​ 这意味着,只要ThreadLocal对象丢掉了强引用,那么在下次GC后,map中的ThreadLocal对象也会被清除

​ 对于那些ThreadLocal对象为空的map元素,这里称其为【垃圾值】,稍后会被主动清理

  1. map里存储的value就是缓存到当前线程的值,这个value没有弱引用去包装,需要专门的释放策略(如果key为null进行回收)

  2. 一个线程对应多个ThreadLocal,一个ThreadLocal只对应一个值

MySQL日志系统

MySQL里面最重要的两个日志: 物理日志redo log和逻辑日志binlog

  • redo log(WAL Write-ahead logging,预写式日志)用于保证crash-safe能力(属于InnoDB引擎)

    数据页上做了什么修改

    循环写的,空间固定会用完

    redo log的写入拆成了两个步骤:prepare和commit,这就是”两阶段提交”

  • binlog主要做的是MySQL功能层面的事情(Server层实现)

    语句的原始逻辑

    binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志

MySQL基础架构

MySQL可以分为Server层和存储引擎层两部分.

Server层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认存储引擎。

ArrayList

以数组实现。节约空间,但数组有容量限制。超出限制时会增加50%容量,用System.arraycopy()复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组
  按照数组索引访问元素:get(int index)/set(int index)的性能很高,这是数组的优势。直接在数组末尾加入元素:add(e)的性能也高,但如果按索引插入、删除元素:add(i,e)、remove(i)、remove(e),则要用System.arraycopy()来移动部分受影响的元素,性能就变差了,这是数组的劣势。
  ArrayList不是线程安全的,只能在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List list)方法返回一个线程安全的ArrayList对象,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
  ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上根据源码我们知道就是通过索引序号进行快速访问,实现了Cloneable接口,能被克隆。

数据库索引

结构

Hash结构

哈希表这种结构适用于只有等值查询的场景,比如Memcached及其他一些NoSQL引擎。

BTree结构

BTree又叫做多路平衡搜索树, 一颗m叉到BTree特性如下:

  • 树中每个节点最多包含m个孩子
  • 除根节点与叶子节点外, 每个节点至少有ceil(m/2)个孩子
  • 若根节点不是叶子节点,则至少有两个孩子
  • 所有的叶子节点都在同一层
  • 每个非叶子节点由n个key与n+1个指针组成, 其中ceil(m/2) <= n <= m-1

已5叉BTree为例, key的数量: 2 <= n <= 4, 当n>4时,中间节点分裂到父节点, 两边节点分裂.

关键字插入操作

生成从空树开始,逐个插入关键字。但是由于B_树节点关键字必须大于等于[ceil(m/2)-1],(其中ceil(x)是一个取上限的函数)所以每次插入一个关键字;首先在最底层(叶子节点那一层)的某个非终端节点中添加一个“关键字”,该结点的关键字不超过m-1,则插入完成;否则要产生结点的“分裂”,将一半数量的关键字分裂到新的其相邻右结点中,中间关键字上移到父结点中。

B树结构

B树结构

关键字删除操作

首先查找B树中需删除的关键字,如果该关键字在B树中存在,则将该关键字在其结点中进行删除,如果删除该关键字后,首先判断其结点是否有左右孩子 结点,如果有,则上移孩子结点中的某相近关键字到父节点中,然后是判断移动之后的情况;如果没有,直接删除后,判断删除之后的情况。
删除元素,移动相近元素之后,如果某结点中关键字数小于ceil(m/2)-1,
(m为阶数)则需要看其某相邻兄弟结点是否丰满
如果丰满,则向父节点借一个元素来满足条件;如果其相邻兄弟都刚脱贫,即借了之后其结点数目小于ceil(m/2)-1则该结点与其相邻的某一兄弟结点进行“合并”成一个结点,以此来满足条件。
(结点中关键字个数大于ceil(m/2)-1除根结点之外的结点的关键字的个数n必须满足: ceil(m / 2)-1)<= n <= m-1。m表示最多含有m个孩子,n表示关键字数.)

B+Tree结构

一颗m阶的B+树和m阶的B树的差异在于:

1.非叶子结点的子树指针与关键字个数相同;
2.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间)
3.为所有叶子结点增加一个链指针。图中Q是通过指针连在一起的。
4.所有关键字都在叶子结点出现。(5 8 9 10 15 18 20 26 …等等)叶子结点相当于是存储(关键字)数据的数据层;
5.B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中)
6.所有的非终端结点可以看成是索引部分,结点中的关键字是有其孩子指向的子树中最大(或最小)关键字。比如第二层5 它的子树为5 8 9 (而B 树的非终节点也包含需要查找的有效信息)

B+树结构

MySql索引在B+Tree进行优化,在原基础上增加了指向相邻叶子节点的链表指针.就形成了带有顺序指针的B+Tree. 提高区间访问性能

MySql B+树结构

B+树比B树更适合数据库索引?

1、 B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。

2、B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

3、由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。

索引分类

类别:

  1. BTREE索引
  2. 位图索引
  3. 函数索引

分类:

  1. 单值索引: 一个索引只包含一列

  2. 唯一索引: 索引列的值必须唯一, 可以为NULL

  3. 复合索引(覆盖索引): 一个索引包含多列

    由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

    最左前缀原则:

    ​ 联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符

    ​ 如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的

    索引下推:

    ​ 在MySQL5.6之前,只能从根据最左前缀查询到ID开始一个个回表。到主键索引上找出数据行,再对 比字段值。MySQL5.6引入的索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判 断,直接过滤掉不满足条件的记录,减少回表次数。

    如果查询条件使用的是普通索引(或是联合索引的最左原则字段),查询结果是联合索引的字段或是主键,不用回表操作,直接返回结果,减少IO磁盘读写读取正行数据

索引设计原则

  • 查询频次高, 数据量比较大 [建立索引]

  • 索引字段的选择, 经常where子句中创建索引, 挑选最常用、过滤效果最好的列的组合 [建立索引]

    如果读取字段索引中不存在(索引只包含索引字段和主键字段),需要回到主键索引树搜索的过程(回表). 大大降低执行开销. 尽量减少回表操作.

  • 最好使用唯一索引, 区分度越高, 使用索引的效率越高 [建立索引]

  • 索引不是越多越好, DML操作时需要维护索引, 多条索引选可用索引提高了选择代价.

    一个数据页满了,按照B+Tree算法,新增加一个数据页,叫做页分裂,会导致性能下降。空间利用率降低大概50%。当相邻的两个数据页利用率很低的时候会做数据页合并,合并的过程是分裂过程的逆过程。

  • 尽可能使用短索引, 提高索引访问I/O的效率, 构成索引的字段总长度比较短,那么在给定大小的存储块内可以存储更多的索引值,提高I/O效率.

  • 利用最左前缀, N个列组合而成的组合索引, 那么相当于是创建N个索引, 如果查询时where子句使用了组成该索引的前几个字段,那么这条查询SQL可以利用组合索引来提升查询效率.

    1
    CREATE INDEX idx_name ON tab_name (NAME, EMAIL, STATUS)

    就相当于对name创建索引、NAME+EMAIL创建索引、NAME+EMAIL+STATUS创建索引

  • 主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小

    普通索引数据字段使用主键值

    从性能和存储空间方面考量,自增主键往往是更合理的选择

索引技巧

  • 重建主键的索引最好不要删除主键索引.

    不论是删除主键还是创建主键,都会将整个表重建

    可以使用 alter table T engine=InnoDB来重键索引

数据库优化

方法步骤

查看SQL执行频率(Mysql|Oracle)

1
2
3
4
5
6
7
8
-- Mysql 查看当前session中所有统计参数的值
-- 查看工作时间、执行DML次数、连接次数、SELECT次数等
show [global] status;

-- Oracle
SELECT * FROM V$SQLAREA;
SELECT * FROM V$SQL;
-- 查看AWR报告

定位低效SQL语句(Mysql|Oracle)

  • 查询慢查询日志
1
2
3
4
5
6
7
8
-- Mysql
-- 使用--log-show-queries启动. sql执行时间超过多久进行记录

-- 查看当前执行sql的情况
show processlist;

-- Oracle
-- 查看当前正在执行的sql

explain分析执行计划(Mysql|Oracle)

mysql使用

1
EXPLAIN SELECT * FROM jg_cust JOIN jg_zp ON jg_cust.id = jg_zp.cust;

字段 含义
id 序列号, 执行select子句或者操作表顺序
select_type 表示SELECT类型: SIMPLE(简单表, 不使用表连接或者子查询)、PARMARY(主查询、即外层的查询)、UNION(UNION中第二个或者后面的查询语句)、SUBQUERY(子查询中第一个SELECT)
table 输出的表
partitions
type 类型
possible_keys 可能用到的索引
key 实际用到的索引
key_len 索引长度
ref 引用
rows 扫描的行数
filtered
Extra 执行情况的说明和描述

oracle使用

默认情况下,Oracle会将执行计划插入如到一张名为PLAN_TABLE的表中。可以使用脚本utlexplain.sql来创建自己的计划表。这个脚本位于Oracle软件安装目录的子目录$ORACLE_HMOE/rmdbs/admin/中。

从Oracle 10g开始,Oracle会创建一个全局临时表PLAN_TABLE供所有用户使用,所以通常情况下不需要创建自己的计划表。由于此默认的计划表是一个全局临时表,所以你无法看到其他会话插入的执行计划,你的执行计划也会随着自己会话的结束而自动消失。

1
EXPLAIN PLAN FOR SELECT * FROM DUAL ;
列名 类型 描述
STATEMENT_ID VARCHAR2(30) 在EXPLAIN PLAN的SET STATEMENT_ID子句提供的SQL语句的唯一标志符。
PLAN_ID NUMBER 执行计划的在全局表plan_table中的唯一标识符
TIMESTAMP DATE EXPLAN PLAN语句执行的日期和时间
REMARKS VARCHAR2(80) 注释
OPERATION VARCHAR2(30) 执行的操作类型。如TABLE ACCESS,SORT或HASH JOIN
OPTIONS VARCHAR2(225) 操作的附加信息,例如,以TABLE SCAN为例,选项可能是FULL或BY ROWID
OBJECT_NODE VARCHAR2(128) 如果是分布式查询,这一列表示用于引用对象的数据库链接名称。如果并行查询,它的值可能对应一个临时的结果集。
OBJECT_OWNER VARCHAR2(30) 对象的名字
OBJECT_NAME VARCHAR2(30) 对象名称
OBJECT_ALIAS VARCHAR2(65) 对象的别名
OBJECT_INSTANCE NUMERIC 对象在SQL语句中的位置
OBJECT_TYPE VARCHAR2(30) 对象的类型(表,索引等)
OPTIMIZER VARCHAR2(255) 解释SQL语句时生效的优化器
SEARCH_COLUMNS NUMBERIC 未使用
ID NUMERIC 执行计划的ID号
PARENT_ID NUMERIC 上一个步骤的ID号
DEPTH NUMERIC 操作的深度
POSITION NUMERIC 如果两个步骤有相同的父步骤,有更低POSITION值的步骤将被先执行
COST NUMERIC 优化器估算出来的此操作的相对成本
CARDINALITY NUMERIC 优化器预期这一步将饭后的记录数
BYTES NUMERIC 预计这一步将返回的字节数
OTHER_TAG VARCHAR2(255) 标识OTHER列中的值的类型。
PARTITION_START VARCHAR2(255) 访问的分区范围的起始分区
PARTITION_STOP VARCHAR2(255) 访问的分区范围的结束分区
PARTITION_ID NUMERIC 计算PARTITION_START和PARTITION_STOP列的值对的ID
OTHER LONG 对于分布式查询,这列可能是包含发往远程数据库的SQL语句的文本。对于并行查询,它比啊是并行从属进程执行的SQL语句。
DISTRIBUTION VARCHAR2(30) 描述记录是如何从一组并行查询从属进程分配到后续的“消费者”从属进程的。
CPU_COST NUMERIC 估算出来的操作的CPU成本
IO_COST NUMERIC 估算出来的的操作的IO成本
TEMP_SPACE NUMERIC 估算出来的这一步操作所使用的临时存储的空间大小
ACCESS_PREDICATES VARCHAR2(4000) SQL语句中,确定如何在当前步骤中提取记录的子句。
FILTER_PREDICATES VARCHAR2(4000) SQL语句中确定对见记录进行过滤的子句路,如WHERE子句在非索引列上的条件。
PROJECTION VARCHAR2(4000) 决定将返回的记录的子句,通常是SELECT后面的字段列表
TIME NUMBER(20,2) 优化器为这一步执行估算的时间消耗
QBLOCK_NAME VARCHAR2(30) 查询块的唯一标识符。

#什么影响

全表扫描

  1. 表上的索引失效或无法被使用的情形(如对谓词使用函数、计算NULL值、不等运算符、类型转换)

    1
    select * from t where t.id + 1 = 3
  2. 查询条件返回了整个表的大部分数据

    当表上所返回的数据行数接近于表上的30%时,Oracle 倾向于使用全表扫描

    1
    select count(pad) from t where n<=990; -- 表数量为 991 条
  3. 使用了并行方式访问表(parallel 强行启用Oracle的多线程处理功能)

    1
    select /*+ parallel(3) */ count(pad) from t where n<=10;
  4. 使用full 提示

    1
    select /*+ full(t) */ count(pad) from t where n<=10;
  5. 统计信息缺失时使得Oracle认为全表扫描比索引扫描更高效

  6. 表上的数据块小于DB_FILE_MULTIBLOCK_READ_COUNT值的情形可能产生全表扫描

内存模型

重排序

顺序一致性

happens-before

as-if-serial

JMM的内存可见性保证