Mysql内连接、左外连接、右外连接、全连接
本文字数: 183 阅读时长 ≈ 1 分钟
指定方式
连接条件可以在FROM或WHERE语句中指定。
分类
内连接
典型的连接运算,使用=或者<>之类的比较运算。包括相等连接和自然连接。
内连接使用比较运算符根据每个表共有的值匹配两个表中的行。外连接
左外连接
left join 结果集包括左表中的所有行,如果左表的某行在右表中没有匹配行,结果集中显示为空。
右外连接
与左外连接相反。
全连接
返回左表和右表中所有的行。
Mysql索引
本文字数: 415 阅读时长 ≈ 1 分钟
概念
由用户创建的,能够被修改和删除的,实际存储在数据库中的物理存在。它是某一表中一列或者若干列值得集合和相应的指向表中物理标志这些值的数据页的逻辑指针清单
优点
- 通过创建唯一索引,可以保证数据库中每一行数据的唯一性。
- 大大加快数据的检索速度。
- 加速表与表之间的联系。
- 在使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间。
- 通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统性能。
缺点
- 创建和维护索引需要耗费时间,时间随数据量增加而增加。
- 索引占据物理空间,如果建立聚簇索引,占用空间更大。
分类
- 聚集索引,表中数据按照索引的顺序来存储。叶子节点存储了真实的数据行,没有另外单独的数据页。
- 非聚集索引,表数据存储顺序与索引顺序无关,叶节点包含索引字段值及指向数据页数据行的逻辑指针。
- 一张表上只能创建一个聚集索引,因为真实数据的物理顺序只能是一种。如果一张表没有聚集索引,它被称为“堆集”,没有特定的顺序,新加的元行被添加到末尾。
ArrayList原理
本文字数: 1.7k 阅读时长 ≈ 2 分钟
实现方式
存储数据是通过数组: transient Objectp[] elementData
size字段: 存储整个数组的大小,
> 注意的是: size是每次add和remove都会自增和自减的,所以增加null也会size+1.
- 是否允许空:
允许 - 是否允许重复数据:
允许 - 是否有序:
是 - 是否线程安全:
不是
默认大小 : 10
1 | public ArrayList(){ |
扩容
1 |
|
删除元素
- 根据下标删除。
根据元素删除。
删除跟元素匹配的 第一个 元素
1
2
3
4
5
6
7
8
9int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work- 1、把指定元素后面位置的所有元素,利用System.arraycopy方法整体向前移动一个位置
- 2、最后一个位置的元素指定为null,这样让gc可以去回收它
因为删除的时候,需要将后面所有元素向前移动一个位置,所以删除的时候,很消耗性能。
插入元素
- add(T t)
- add(int position,T t)
插入时候会将所有后面的元素向后移动一个位置,很消耗性能。
ArrayList的优缺点
- 底层以数组方式存储,是一种随机访问方式,并且实现了RandomAccess接口,因此查找十分快速。
添加很方便,往数组的最后添加一个就行了。
删除和添加的时候涉及到移动后面所有数据,性能消耗比较大。
线程安全
不是线程安全的,所有方法都没有Synchronized修饰,在并发情况下会出现安全问题。可以使用Collections.synchronizedList方式
为什么ArrayList的elementData是用transient修饰的?
private transient Object[] elementData;
ArrayList 实现了Serializable接口,所以是可以序列化的。但是elementData不一定是满的,没必要全部序列化。所以ArrayList重写了writeObject方法实现,增快了序列化的速度,见笑了序列化后的大小:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
java8新增功能
本文字数: 2k 阅读时长 ≈ 2 分钟
[TOC]
接口中default关键字修饰方法可以增加默认实现。
1 | interface Formula{ |
Lambda表达式
1 | Collections.sort(names,(a,b)->b.compareTo(a)); |
函数式接口
1 | @FunctionInterface |
方法和构造函数引用
- 静态方法引用
1
2
3Convert<String,Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123"); - 通过:: 关键字获取方法或者构造函数的引用
1 | class Something{ |
- 引用构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Person{
String firstName;
String lastName;
Person(){};
Person(String firstName,String lastName){
this.firstName=firstName;
this.lastName=lastName;
};
}
//新建工厂类
interface PersonFactory<P extends Person>{
P create(String firstName,String lastName);
}
//通过构造函数引用将所有东西拼到一起,通过手动实现。
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("earyant","Lee");
类库示例
Predicates(断言)
是一个布尔类型的函数,该函数只有一个输入函数,它实现了多种默认方法,用于处理复杂的逻辑动词。(and ,or,negate(否定))
1 | Predicate<String> predicate = (s)->s.length()>0; |
Functions
接收一个参数,并返回单一的结果,默认方法可以将多个函数穿在一起(compose,andThen):
1 | Function<String,Integer> toInteger = Integer:valueOf; |
Suppliers
Supplier 接口产生一个给定类型的结果,与Function不同的是,Supplier没有输入参数。
1 | Supplier<Person> personSupplier = Person:new; |
Spring中的事务管理
本文字数: 4.1k 阅读时长 ≈ 4 分钟
基本特征
- 原子性
- 一致性
- 隔离性
- 持久性
事务的分类
- 声明式事务
- 编程式事务
数据错误类型
脏读(dirty read)
当前有两个事务A和B,当A事务对数据进行更改,但是还没提交(会有缓存),此时被事务B读取,然后事务A因其他原因导致失败数据回滚,此时的事务A的操作都是失败的,对数据的操作也是失败的,但是B事务已经读取了A没提交的数据,导致B读到了错误的数据。
不可重复读(no-repeatable-read)
当前有两个事务A和B,A事务将会对数据进行两次操作,当A事务操作一次成功后,B事务此时对数据操作更改了值并提交,随后A事务再进行读取数据,发现数据已经被更改,造成A事务的数据混乱。
幻读(phantom read)
与不可重复读同样都是多次读数据不一致的问题,但是no-repeatable-read强调的是本身需要的数据集改变了, phantom read强调多次查询得出的条件数据集改变了。
事务的隔离级别
- ISOLATION_DEFAULT 默认级别
- ISOLATION_READ_UNCOMMIT 事务最低级别,允许其他事务看到此事务中未提交的数据。 这种级别会导致脏读、幻读、不可重复读
- ISOLATION_READ_COMMIT 保证数据提交后才能被另外一个事务看到。 这种级别可以防止脏读,但是不能防止不可重复读和幻读
- ISOLATION_REPEATABLE_READ 这种级别可以防止脏读、不可重复读,但是不能防止幻读
- ISOLATION_SERALIZABLE 最强级别,可以防止脏读、不可重复读、幻读
事务的传播行为
- PROPAGATION_REQUIRED 如果存在一个事务,就是用这个事务,如果没有事务,就开启一个新的事务
1
2
3
4
5
6
7
8
9
10//事务属性 PROPAGATION_REQUIRED
void methodA(){
...
methodB();
...
}
//事务属性 PROPAGATION_REQUIRED
void methodB(){
...
} - PROPAGATION_SUPPORTS 如果存在一个事务,就用这个事务,如果没有事务,就非事务执行,但是对于事务同步管理器,PROPAGATION_SUPPORTS和不使用事务有少许区别。当通过methodA调用methodB时,methodB就是用methodA的事务,如果单独调用methodB的时候,methodB就非事务执行。
1
2
3
4
5
6
7
8
9
10//事务属性 PROPAGATION_REQUIRED
void methodA(){
...
methodB();
...
}
//事务属性 PROPAGATION_SUPPORTS
void methodB(){
...
} - PROPAGATION_MANDATORY 如果存在一个事务,就是用当前事务,如果不存在事务,就抛出一个异常。
- PROPAGATION_REQUIRED_NEW 开启一个新的事务,相当于:
1
2
3
4
5
6
7
8
9
10//事务属性 PROPAGATION_REQUIRED
void methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
//事务属性 PROPAGATION_REQUIRES_NEW
void methodB(){
……
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31TransactionManager tm = null;
try{
//获得一个JTA事务管理器
tm = getTransactionManager();
tm.begin();//开启一个新的事务
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//挂起当前事务
try{
tm.begin();//重新开启第二个事务
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二个事务
}
Catch(RunTimeException ex){
ts2.rollback();//回滚第二个事务
}
finally{
//释放资源
}
//methodB执行完后,复恢第一个事务
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一个事务
}
catch(RunTimeException ex){
ts1.rollback();//回滚第一个事务
}
finally{
//释放资源
}在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了。使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。
- PROPAGATION_NOT_SUPPORTED 总是非事务执行,并挂起任何事务,也需要JTATransctionManager作为事务管理器
- PROPAGATION_NEVER 总是非事务执行,如果存在一个事务,则抛出异常。
- PROPAGATION_NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而nestedTransactionAllowed属性值默认为false;
1
2
3
4
5
6
7
8
9
10//事务属性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
//事务属性 PROPAGATION_NESTED
methodB(){
……
}如果单独调用methodB方法,则按REQUIRED属性执行。如果调用methodA方法,相当于下面的效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24Connection con = null;
Savepoint savepoint = null;
try{
con = getConnection();
con.setAutoCommit(false);
doSomeThingA();
savepoint = con2.setSavepoint();
try{
methodB();
}catch(RuntimeException ex){
con.rollback(savepoint);
}
finally{
//释放资源
}
doSomeThingB();
con.commit();
}
catch(RuntimeException ex){
con.rollback();
}
finally{
//释放资源
}PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。
使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTA TrasactionManager实现可能有不同的支持方式。
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。
另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.
PROPAGATION_REQUIRED应该是我们首先的事务传播行为。它能够满足我们大多数的事务需求。
RabbitMQ结合Aop实现高并发Log记录
本文字数: 4 阅读时长 ≈ 1 分钟
- [] 参考
aop原理及实现
本文字数: 1.3k 阅读时长 ≈ 1 分钟
静态代理、动态代理
1、AspectJ 使用静态代理,生成class文件时,会侵入到代码中。
2、Spring AOP 使用动态代理,不会侵入代码,而是在内存中临时为代码生成一个AOP对象,这个AOP对象包含目标对象的全部方法,并且在特定切点进行增强处理,并且调原对象的方法。
- Jvm动态代理: 通过反射接收代理的类,并要求被代理的类必须实现一个接口。JVM代理的核心是InvocationHandler接口和proxy类。
- CGLib(Code Generation Library): 如果目标没有实现接口,那么Spring会使用CGLib动态代理目标类。它是一个代码生成的类库,可以在运行时动态生成某个类的子类。
1
注意: CGLib是通过继承方式实现的,如果某个类被标记为final,是不能通过CGLib实现动态代理的。
示例(使用了CGLib):
定义Person类,其中sayHello方法是切点,切点被Timer注解修饰。配置切点:1
2
3
4
5
6class Person{
@Timer
public void sayHello(){
System.out.println("sayHello");
}
}在Mail类中调用。1
2
3
4
5
6
7
8
9
10
11
12@Aspect
@Component
class AdviceTest{
@Pointcut("@annotation(com.earyant.aop.Timer)")
public void pointcut(){
}
@Before("pointcut()")
public void before(){
System.out.println("before");
}
}输出结果:1
2
3
4
5
6
7
8class Main{
@Autowired
private Person person;
public void main(){
person.sayHello();
System.out.println(person.getClass().getName());
}
}1
2
3before
sayHello
com.earyant.aop.Person$$EnhancerBySpringCGLIB$$56b89168示例(JVM代理)
定义一个接口:令Person类继承自Chinese类。1
2
3interface Chinese{
sayHello();
}此代码运行的结果是:1
2
3
4
5
6
7class Person implements Chinese{
@Override
@Timer
sayHello(){
System.out.println("sayHello");
}
}证明使用了JVM代理。1
2
3before
sayHello
com.sun.proxy.$Proxy53
JVM调优
本文字数: 0 阅读时长 ≈ 1 分钟
从敲下www.baidu.com到浏览器显示页面,都发生了什么?
本文字数: 5.1k 阅读时长 ≈ 5 分钟
名词定义
URL
URL(Uniform Resource Locator),统一资源定位符,实际就是网站网址,又称域名。在茫茫网络世界中,浏览器就是靠URL来查找资源位置。URL包含协议部分,是浏览器和www万维网之间的沟通方式,它会告诉浏览器正确在网路上找到资源位置。常见的协议有http、https、ftp、file、telnet等。其中http是最常见的网络传输协议,而https则是进行加密的网络传输。
IP
为了便于记忆或辨识,人们使用域名来登录网站,每个域名背后有对应的IP地址。每个网站就是靠IP来定位的。IP是因特网中的每台连接到网络的计算机为实现相互通信而遵循的规则协议。IP分为局域网IP和全网IP。办公中常用的飞秋工具,就是使用办公室局域网IP进行通信的典型表现。每台计算机的本机IP都是127.0.0.1(即localhost)。浏览器并不能识别URL是什么,因此从输入URL开始,浏览器就要做域名解析,也就是IP寻址的工作。
DNS
DNS(Domain Name System,域名系统)——记录域名和IP地址相互映射的信息,人们可以免于记住IP数串。全国DNS信息可在网上查找到,各省都有对应分配不同的IP网段。大型企业会有自己的DNS服务器,专门存储域名和IP的映射关系,例如为大多数人熟知的谷歌DNS服务器,地址8.8.8.8。
查找
域名解析
当用户在浏览器中输入url后,你使用的电脑会发出一个DNS请求到本地DNS服务器。本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动,DNS请求到达本地DNS服务器之后会有以下几个步骤:
1. 浏览器缓存
浏览器对本地缓存进行查找,查看是否有该域名历史记录,如果有首先直接对缓存地址访问。
Chrome浏览器看本身的DNS缓存时间比较方便,在地址栏输入chrome://net-internals/#dns,就可以看到了
2. 本地hosts文件查找ip地址
浏览器若无缓存或者缓存地址访问无效,则首先对hosts文件查找对应域名对应ip地址。
3. 查找路由器缓存
如果系统缓存中也找不到,那么查询请求就会发向路由器,路由器一般会有自己的DNS缓存。
4. 查找ISP(Internet Service Provider) DNS 缓存
如果路由器缓存中也找不到,那么就查询ISP DNS 缓存服务器了。
我们都知道在我们的网络配置中都会有”DNS服务器地址”这一项,操作系统会把这个域名发送给这里设置的DNS,比如114.114.114.114,也就是本地区的域名服务器,通常是提供给你接入互联网的应用提供商。而114.114.114.114是国内移动、电信和联通通用的DNS。
5. 迭代查询
如果前面都找不到DNS缓存的话,会有以下几个步骤:
- 本地 DNS服务器将该请求转发到互联网上的根域(根域没有名字,在DNS系统中就用一个空字符串来表示。例如www.baidu.com.现在的DNS系统都不会要求域名以.来结束,即www.baidu.com就可以解析了,但是现在很多DNS解析服务商还是会要求在填写DNS记录的时候以.来结尾域名。)
- 根域将所要查询域名中的顶级域(比如要查询www.baidu.com,该域名的顶级域就是com)的服务器IP地址返回到本地DNS。
- 本地DNS根据返回的IP地址,再向顶级域(就是com域)发送请求, com域服务器再将域名中的二级域(即www.baidu.com中的baidu.com)的IP地址返回给本地DNS。
- 本地DNS再向二级域发送请求进行查询。
之后不断重复这样的过程,直到本地DNS服务器得到最终的查询结果,并返回到主机。这时候主机才能通过域名访问该网站。
下图能很好的说明这个迭代查询:
当查找到对应的IP地址之后,通过IP地址查找到对应的服务器,浏览器将用户发起的http请求发送给服务器。例如:GET http://www.baidu.com/ HTTP/1.16. CDN
CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。
服务器处理用户请求
代理
正向代理
正向代理是一种最终用户知道并主动使用的代理方式,比如三台支付服务服务器在微服务SpringCloud中的服务注册中心中注册了三个地址(1.1.1.1,2.2.2.2,3.3.3.3),用户服务服务器访问服务注册中心请求地址正向代理返回某一个地址(1.1.1.1),支付服务缓存到本地,在缓存有效期时间内,直接访问1.1.1.1服务器就可以了。
反向代理
在计算机世界里,由于单个服务器的处理客户端(用户)请求能力有一个极限,当用户的接入请求蜂拥而入时,会造成服务器忙不过来的局面,可以使用多个服务器来共同分担成千上万的用户请求,这些服务器提供相同的服务,对于用户来说,根本感觉不到任何差别。比如你只需要敲下www.baidu.com这一个域名,其实背后有很多服务器支持着,每个人的请求会被分发到不同的服务器中。
反向代理需要有一个负载均衡设备来分发用户请求,将用户请求分发到空闲的服务器上,负载均衡设备本身也可以有多个。
好处:
用户服务做域名解析时,解析到的ip地址是负载均衡设备的ip地址,不会暴露服务器地址。当需要增加、减少服务器数量时,用户一点都察觉不到。负载均衡(反向代理的实现)
硬件负载均衡
F5
软件负载均衡
- LVS(七层协议第四层)
LVS是Linux Virtual Server。lvs工作在4层,所以它可以对几乎所有应用做负载均衡,包括http、数据库、聊天室等等。同时,若跟硬件负载均衡相比它的缺点也不容忽视,LVS要求技术水平很高,操作上也比较复杂,配置也很繁琐,没有赖以保障的服务支持,稳定性来说也相对较低(人为和网络环境因素更多一些)。 - nginx(七层协议第七层)
Nginx是工作在第七层,对于网络的依赖性就小的多。与LVS相比,Nginx的安装和配置也相对简单一些,另外测试方面也更简单,主要还是因为对网络依赖性小的缘故。Nginx有一点不好的就是应用要比LVS少。一般我们做软件负载均衡的时候,通常会先考虑LVS,但是遇到比较复杂的网络环境时,用LVS可能会遇到很多麻烦,不妨就考虑尝试一下Nginx。
- LVS(七层协议第四层)
容器处理
常用Java EE容器
- tomcat
- jetty
- jBoss
tomcat服务器启动main方法,当command命令行是start,就执行start方法。反射调用Catalina类的start()方法。这里面会弄出一个StandardServer对象(内部会使用Digester库去解析server.xml,那个里面的东西),并调用它的start()方法,把那些组件都启动起来。
其中在连接器的启动过程中,会弄出一个叫做endpoint的东西去和底层的网络IO打交道,会调用其bind()方法,绑定端口号。
connector将请求交给他所在的service里对应的engine,来处理;每当HttpConnector的ServerSocket得到客户端的连接时,会创建一个Socket。1
<Engine defaultHost="localhost" name="Catalina">
考虑到客户端同时发来的请求数可能有很多, 所以tomcat 中默认维护着一个连接池
NioChannel channel = nioChannels.pop();
channel.setIOChannel(socket);
channel.reset();
getPoller0().register(channel);
context随后在自己的mapping table里寻找servlet-mapping拦截路径为*.jsp的servlet,这里找到得是JSPServlet处理
找到JSPServlet后,调用JSPServlet的doGet或doPOST方法;
context执行完毕后,将HttpServletResponse 返回给Host;
host将HttpServletResponse 返回给engine;
engine 将HttpServletResponse 返回给 connector;
connector 将 HttpServletResponse 返回给用户的browser;
tomcat容器在启动后,context对应的项目在初始化的时候,要加载相应的servlet,加载的顺序为:
$CATALINA_HOME/conf/web.xml里定义的servlet(对应于上面第5步的JSPServlet);
context对应的项目WEB-INF下的web.xml中定义的servlet;应用处理
- Controller接收到请求,转发给service;
- service进行逻辑处理,如果有redis、ehcache等缓存则优先查询缓存;
- 如果是dubbo或者springcloud分布式,则通过dubbo服务或者restful请求调用其他服务模块请求信息;
- 调用dao查询数据库;
- 返回数据给service再给controller,controller再组装数据以及view返回;
- view层jsp或者velocity等模板引擎解析数据
Mysql架构
- 服务器先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果。
- 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划;
- MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询;
- 将结果返回给客户端。
MySQL能够处理的优化类型:
- 重新定义关联表的顺序
数据表的关联并不总是按照在查询中指定的顺序进行,决定关联的顺序是优化器很重要的一部分功能。 - 将外连接转化成内连接
并不是所有的outer join语句都必须以外连接的方式执行。诸多因素,例如where条件、库表结构都可能会让外连接等价于一个内连接。MySQL能够识别这点并重写查询,让其可以调整关联顺序。 - 使用等价变换规则
MySQL可以使用一些等价变换来简化并规范表达式。它可以合并和减少一些比较,还可以移除一些恒成立和一些恒不成立的判断。例如:(5=5 and a>5)将被改写为a>5。类似的,如果有(a5 and b=c and a=5。 - 优化count()、min()和max()
索引和列是否为空通常可以帮助MySQL优化这类表达式。例如,要找到一列的最小值,只需要查询对应B-tree索引最左端的记录,MySQL可以直接获取索引的第一行记录。在优化器生成执行计划的时候就可以利用这一点,在B-tree索引中,优化器会讲这个表达式最为一个常数对待。类似的,如果要查找一个最大值,也只需要读取B-tree索引的最后一个记录。如果MySQL使用了这种类型的优化,那么在explain中就可以看到“select tables optimized away”。从字面意思可以看出,它表示优化器已经从执行计划中移除了该表,并以一个常数取而代之。
类似的,没有任何where条件的count(*)查询通常也可以使用存储引擎提供的一些优化,例如,MyISAM维护了一个变量来存放数据表的行数。 - 预估并转化为常数表达式
- 覆盖索引扫描
当索引中的列包含所有查询中需要使用的列的时候,MySQL就可以使用索引返回需要的数据,而无需查询对应的数据行。 - 子查询优化
MySQL在某些情况下可以将子查询转换成一种效率更高的形式,从而减少多个查询多次对数据进行访问。 - 提前终止查询
在发现已经满足查询需求的时候,MySQL总是能够立即终止查询。一个典型的例子就是当使用了limit子句的时候。除此之外,MySQL还有几种情况也会提前终止查询,例如发现了一个不成立的条件,这时MySQL可以立即返回一个空结果。
上面的例子可以看出,查询在优化阶段就已经终止。 - 等值传播
- 列表in()的比较
在很多数据库系统中,in()完全等同于多个or条件的字句,因为这两者是完全等价的。在MySQL中这点是不成立的,MySQL将in()列表中的数据先进行排序,然后通过二分查找的方式来确定列表中的值是否满足条件,这是一个o(log n)复杂度的操作,等价转换成or的查询的复杂度为o(n),对于in()列表中有大量取值的时候,MySQL的处理速度会更快。
- 重新定义关联表的顺序