11.请介绍一下JVM内存模型??用过什么垃圾回收器都说说呗
可参考
https://www.jianshu.com/p/bf158fbb2432
https://www.jianshu.com/p/76959115d486
JVM内存模型
1. 类加载器(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中。
2. 执行引擎:负责执行class文件中包含的字节码指令;
3. 内存区(也叫运行时数据区):是在JVM运行的时候操作所分配的内存区。运行时内存区主要可以划分为5个区域:
1. 方法区(Method Area):用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。虽然JVM规范把方法区描述为堆的一个逻辑部分, 但它却有个别名non-heap(非堆),所以大家不要搞混淆了。方法区还包含一个运行时常量池。
2. java堆(Heap):存储java实例或者对象的地方。这块是GC的主要区域(后面解释)。从存储的内容我们可以很容易知道,方法区和堆是被所有java线程共享的。
3. java栈(Stack):java栈总是和线程关联在一起,每当创建一个线程时,JVM就会为这个线程创建一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是现成私有的。
4. 程序计数器(PC Register):用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。
5. 本地方法栈(Native Method Stack):和java栈的作用差不多,只不过是为JVM使用到的native方法服务的。
4. 本地方法接口:主要是调用C或C++实现的本地方法及返回结果。
垃圾回收器
就功能上来言,垃圾回收存在以下几个维度的划分:
1. 按线程数分,可以分为串行垃圾回收器和并行垃圾回收器。串行垃圾回收器一次只使用一个线程进行垃圾回收;并行垃圾回收器一次将开启多个线程同时进行垃圾回收。在并行能力较强的 CPU 上,使用并行垃圾回收器可以缩短 GC 的停顿时间。
2. 按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器。并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间;独占式垃圾回收器 (Stop the world) 一旦运行,就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束。
3. 按碎片处理方式可分为压缩式垃圾回收器和非压缩式垃圾回收器。压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片;非压缩式的垃圾回收器不进行这步操作。
4. 按工作的内存区间,又可分为新生代垃圾回收器和老年代垃圾回收器。
常用的垃圾回收器有以下四种:
1.串行回收器
2.并行/吞吐量回收器
3.CMS回收器
4.G1回收器
目前来说大多数公司选择CMS回收器(标记压缩)
但java8以来提供的G1回收器可能在以后取代这种方式:其特征是吧堆中的string字符串所占用的大量对象空间整合通过不同字符串中的字符可能指向同一地址的方式。但是目前表现弱于CMS回收器。在堆内存空间分配到4g以上的服务中可以考虑使用。
垃圾回收器Java中提供了System.gc()的方法激活垃圾回收线程回收。
12.线上发送频繁full gc如何处理? CPU 使用率过高怎么办?.如何定位问题?如何解决说一下解决思路和处理方法
最初解决:dump文件分析,程序是否出错。
最终方案:增大空间,无论是调节比例还是整体扩展。
1.System.gc()方法的调用,如果是串行回收器不建议在程序中使用System.gc(),频繁的System.gc()会导致系统暂停频率变高。
2.老年代空间不足:可能是程序中创建为大对象、大数组导致对象不能及时在Minor GC阶段被回收,程序优化。
3.永生区空间不足:系统过大,加载类过多,或者是频繁调用反射。推荐转为使用CMS GC。
4.CMS中出现full gc 没救了 最终方案吧!
cpu使用率过高最初解决方案审查代码中是否存在死循环这样
cpu使用率过高最终解决方案加核提配置
一般流程为获取java应用进程的pid 查找线程及占用cpu情况 找到最高的几个线程拿出来进行堆栈打印,定位代码进行审查。
还有一种可能是gc问题 频繁被占满导致无法回收资源引起的cpu使用率过高。
13.知道字节码吗?字节码都有哪些?Integer x =5,int y =5,比较x =y 都经过哪些步骤?
字节码也就是class文件。
在程序中获取类对象的方法就是获取字节码的方式。
1.类名.class,例如System.class;
2.对象.getClass(),例如New Date().getClass()
3.Class.forName("类名"),例如Class.forName("Java.Util.Date");
字节码角度讲int y=5单纯压入int类型变量,赋值为5,而Integer x=5则会调用Integer.valueOf()的方法。x==y会使用到if_icmp方法比较。
14.讲讲类加载机制呗 都有哪些类加载器,这些类加载器都加载哪些文件?手写一下类加载Demo
核心库的类Bootstrap ClassLoader:加载是由原生代码实现的 不是继承ClassLoader的类。如rt.jar
扩展库的类Extension ClassLoader:加载是由ExtClassLoader实现,目录为jre/ext/**jar
应用程序类Application ClassLoader:加载由AppClassLoader实现。程序中的类由此加载。
自定义类加载器:继承ClassLoader自己定义的。
类加载器用来加载 Java 类到 Java 虚拟机中。也就是把编译后的.class文件生成一个Class类的实例的作用。
手写一下就是继承classLoader 重写其findClass,若希望打破原有的双亲委派模型则重写loadClass方法 加载class文件为字节流,使用父类的defineClass将其转化为Class类型返回
15.知道osgi吗? 他是如何实现的?
osgi是模块之间的解耦和分离,是java的一种动态模型系统。
osgi让应用程序无需重新引导可以被远程安装、启动、升级和卸载。
osgi首先依赖于热部署的jetty,然后本身分为三层,模块层,生命周期层和服务层
模块可以直接的理解成jar包,能够动态的添加卸载。
生命周期负责控制动态安装、开启、关闭、更新和卸载模块。
服务层负责将功能暴露和隐藏,以及发现绑定服务
16.请问你做过哪些JVM优化?使用什么方法达到什么效果???
jvm调优主要也就是内存空间的分配
最终策略:提高系统性能
主要策略有
1.增加eden空间,让更多的对象留在年轻代。
2.大对象直接放到老年代,以免扰乱年轻代高频率的gc。(XX:PretenureSizeThreshold设置大对象直接进入年老代的阈值)
并且尽量避免使用短时间存在的大对象。
3.合理调整进入老年代的年龄
4.稳定的堆大小对垃圾回收是有利的。获得一个稳定的堆大小的方法是使-Xms 和-Xmx 的大小一致,即最大堆和最小堆 (初始堆) 一样。如果这样设置,系统在运行时堆大小理论上是恒定的,稳定的堆空间可以减少 GC 的次数。
5.调整内存分页(真实内存虚拟内存)
6.选用合适的gc
17.class.forName("java.lang.String")和String class.getClassLoader() LoadClass("java.lang.String") 什么区别啊?
在比较它俩之前需先了解一下java类装载的过程
java类装载过程分为3步:
1:加载
Jvm把class文件字节码加载到内存中,并将这些静态数据装换成运行时数据区中方法区的类型数据,在运行时数据区堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
*释:方法区不仅仅是存放方法,它存放的是类的类型信息。
2:链接:执行下面的校验、准备和解析步骤,其中解析步骤是可选的
a:校验:检查加载的class文件的正确性和安全性
b:准备:为类变量分配存储空间并设置类变量初始值,类变量随类型信息存放在方法区中,生命周期很长,使用不当和容易造成内存泄漏。
*释:类变量就是static变量;初始值指的是类变量类型的默认值而不是实际要赋的值
c:解析:jvm将常量池内的符号引用转换为直接引用
3:初始化:执行类变量赋值和静态代码块
在了解了类装载过程之后我们继续比较二者区别:
Classloder.loaderClass(String name)
其实该方法内部调用的是:Classloder. loadClass(name, false)
方法:Classloder. loadClass(String name, boolean resolve)
1:参数name代表类的全限定类名
2:参数resolve代表是否解析,resolve为true是解析该类
Class.forName(String name)
其实该方法内部调用的是:Class.forName(className, true, ClassLoader.getClassLoader(caller))
方法:Class.forName0(String name, boolean initialize, ClassLoader loader)
参数name代表全限定类名
参数initialize表示是否初始化该类,为true是初始化该类
参数loader 对应的类加载器
两者最大的区别
Class.forName得到的class是已经初始化完成的
Classloder.loaderClass得到的class是还没有链接的
怎么使用
有些情况是只需要知道这个类的存在而不需要初始化的情况使用Classloder.loaderClass,而有些时候又必须执行初始化就选择Class.forName
例如:数据库驱动加载就是使用Class.forName(“com.mysql.jdbc.Driver”),
下面我们来看看Driver的源代码:
从Driver的源码中我们可以看出Driver这个类只有一个static块,这样我们需要初始化后才能得到DriverManager,所以我们选择使用Class.forName()
18.探查Tomcat的运行机制及框架?
先不去关技术细节,对一个servlet容器,我觉得它首先要做以下事情:
1:实现Servlet api规范。这是最基础的一个实现,servlet api大部分都是接口规范。如request、response、session、cookie。为了我们应用端能正常使用,容器必须有一套完整实现。
2:启动Socket监听端口,等待http请求。
3:获取http请求,分发请求给不同的协议处理器,如http和https在处理上是不一样的。
4:封装请求,构造HttpServletRequest。把socket获取的用户请求字节流转换成java对象httprequest。构造httpResponse。
5:调用(若未创建,则先加载)servlet,调用init初始化,执行servlet.service()方法。
6:为httpResponse添加header等头部信息。
7:socket回写流,返回满足http协议格式的数据给浏览器。
8:实现JSP语法分析器,JSP标记解释器。JSPservlet实现和渲染引擎。
9:JNDI、JMX等服务实现。容器一般额外提供命名空间服务管理。
10:线程池管理,创建线程池,并为每个请求分配线程
21.分析Tomcat线程模型?
配置方法:在tomcat conf 下找到server.xml
在<Connector port="8080" protocol="HTTP/1.1"/>
BIO: protocol =" org.apache.coyote.http11.Http11Protocol"
NIO: protocol ="org.apache.coyote.http11.Http11NioProtocol"
AIO: protocol ="org.apache.coyote.http11.Http11Nio2Protocol"
APR: protocol ="org.apache.coyote.http11.Http11AprProtocol"
22.Tomcat系统参数认识和调优?
Tomcat自身的调优是针对conf/server.xml中的几个参数的调优设置。首先是对这几个参数的含义要有深刻而清楚的理解。以tomcat8.5为例,讲解参数。
同时也得认识到一点,tomcat调优也受制于linux内核。linux内核对tcp连接也有几个参数可以调优。
因此我们可以将tomcat调优分为linux内核优化、java虚拟机调优和tomcat自身的优化。
一、Tomcat自身优化
1. maxThreads :tomcat创建的最大线程数,也就是同时处理的请求最大并发数。默认值是200
maxThreads如何配置(转)
一般的服务器操作都包括量方面:1计算(主要消耗cpu),2等待(io、数据库等)
第一种极端情况,如果我们的操作是纯粹的计算,那么系统响应时间的主要限制就是cpu的运算能力,此时maxThreads应该尽量设的小,降低同一时间内争抢cpu的线程个数,可以提高计算效率,提高系统的整体处理能力。
第二种极端情况,如果我们的操作纯粹是IO或者数据库,那么响应时间的主要限制就变为等待外部资源,此时maxThreads应该尽量设的大,这样才能提高同时处理请求的个数,从而提高系统整体的处理能力。此情况下因为tomcat同时处理的请求量会比较大,所以需要关注一下tomcat的虚拟机内存设置和linux的open file限制。
我在测试时遇到一个问题,maxThreads我设置的比较大比如3000,当服务的线程数大到一定程度时,一般是2000出头,单次请求的响应时间就会急剧的增加,
百思不得其解这是为什么,四处寻求答案无果,最后我总结的原因可能是cpu在线程切换时消耗的时间随着线程数量的增加越来越大,
cpu把大多数时间都用来在这2000多个线程直接切换上了,当然cpu就没有时间来处理我们的程序了。
以前一直简单的认为多线程=高效率。。其实多线程本身并不能提高cpu效率,线程过多反而会降低cpu效率。
当cpu核心数<线程数时,cpu就需要在多个线程直接来回切换,以保证每个线程都会获得cpu时间,即通常我们说的并发执行。
所以maxThreads的配置绝对不是越大越好。
现实应用中,我们的操作都会包含以上两种类型(计算、等待),所以maxThreads的配置并没有一个最优值,一定要根据具体情况来配置。
最好的做法是:在不断测试的基础上,不断调整、优化,才能得到最合理的配置。
2. acceptCount:当tomcat的线程数达到了最大时,接收排队的最大请求个数。默认值为100
maxThreads与acceptCount这两个值是如何起作用的呢?
情况1:接受一个请求,此时tomcat起动的线程数没有到达maxThreads,tomcat会起动一个线程来处理此请求。
情况2:接受一个请求,此时tomcat起动的线程数已经到达maxThreads,tomcat会把此请求放入等待队列,等待空闲线程。
情况3:接受一个请求,此时tomcat起动的线程数已经到达maxThreads,等待队列中的请求个数也达到了acceptCount,此时tomcat会直接拒绝此次请求,返回connection refused。
对于第3种情况,在看过一篇分析connection timeout问题产生的原因后,等待队列的请求个数这个值可能是由acceptCount参数决定,也有可能由linux内核参数net.core.somaxconn决定。
关联:我在网上看来一篇文章写分析linux上TCP connection timeout的原因,这篇文章中提到一个内核参数 net.core.somaxconn。
我就想tomcat的acceptCount与net.core.somaxconn到底是什么关系呢。
我做了一个实验,
1. 我将tomcat的acceptCount设置为3000 ,net.core.somaxconn设置为8192
那么我用ss -lt 指令查看在tomcat起的端口上的send_q值是3000 可见这是acceptCount的值。
2.我将tomcat的acceptCount设置为10000,net.core.somaxconn设置为8192
同样用ss -lt指令查看在tomcat起的端口上的send_q值是8192,可见这是somaxconn的值。
所以,我总结的是,acceptCount设置的值要一般小于net.core.somaxconn这个参数,这样acceptCount的值才会起作用。net.core.somaxconn 这个参数默认值是128 ,所以需要改这个参数值。后面再介绍改这个值的方法。
acceptCount如何配置?(转)
我一般是设置的跟maxThreads一样大,这个值应该是主要根据应用的访问峰值与平均值来权衡配置的。
如果设的较小,可以保证接受的请求较快相应,但是超出的请求可能就直接被拒绝
如果设的较大,可能就会出现大量的请求超时的情况,因为我们系统的处理能力是一定的。
3. maxConnections
For NIO and NIO2 the default is 10000. For APR/native, the default is 8192
Tomcat允许的同时存在的最大连接数
acceptCount、maxConnections是tcp层相关的参数
4.connectionTimeOut:connectionTimeOut=10000是说建立一个socket连接后,如果一直没有收到客户端的FIN,也没有数据过来,那么此连接也必须等到10s后,才能被超时释放,我理解是tomcat就直接释放这个连接。以毫秒为单位,server.xml默认设置是20秒。
修改方法:
vi server.xml 打开server.xml文件
将
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
修改为:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="1500" minSpareThreads="50" prestartminSpareThreads="true"/>
将
<Connector
port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
/>
修改为
<Connector executor ="tomcatThreadPool" port="8009" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000" maxConnections="10000" redirectPort="8443" acceptCount="1500"/>
二、Linux内核参数优化
1. linux系统对当前用户的单一进程同时可打开的文件数量的限制
查看系统允许当前用户进程打开的文件数量的限制: ulimit -u 默认值为1024 。即是Linux操作系统对一个进程打开的文件句柄数量的限制
对于想支持更高数量的TCP并发连接的通讯处理程序,就必须修改Linux对当前用户的进程同时打开的文件数量的软限制(soft limit)和硬限制(hardlimit)。其中软限制是指Linux在当前系统能够承受的范围内进一步限制用户同时打开的文件数;硬限制则是根据系统硬件资源状况(主要是系统内存)计算出来的系统最多可同时打开的文件数量。通常软限制小于或等于硬限制。
修改方法:
sudo vi /etc/security/limits.conf
增加如下:
prouser soft nofile 65536
prouser hard nofile 65536
prouser soft nproc 65536
prouser hard nproc 65536
修改完后保存此文件。
nproc是操作系统级别对每个用户创建的进程数的限制
2.Linux网络内核对TCP连接的有关限制
修改方法:
sudo vi /etc/sysctl.conf
增加如下:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 10000 65000
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 10000
net.core.somaxconn=8192 accept队列的长度跟这个参数有关
sudo /sbin/sysctl -p
实时生效
三、JVM调优
JAVA_OPTS="$JAVA_OPTS -server -Xmn2000m -Xms4000m -Xmx4000m -XX:PermSize=128m -XX:+UseConcMarkSweepGC -XX:MaxPermSize=512m -Djuli-logback.configurationFile=file:$CATALINA_HOME/conf/logback.xml"
23.MySQL底层B+Tree机制?
Mysql中B+Tree:在经典B+Tree的基础上进行了优化,增加了顺序访问指针。在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。这样就提高了区间访问性能:如果要查询key为从18到49的所有数据记录,当找到18后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率(无需返回上层父节点重复遍历查找减少IO操作)。
为什么不用二叉搜索树?
一句话:因为磁盘IO问题。之前说过,索引是存储在磁盘上的,对数据库表中一列或多列进行排序的数据结构。数据库中的数据可能很大,在大量的数据存储在磁盘上时。计算机无法一次性将数据全部加载进内存。而是通过逐一加载每一磁盘页。而加载磁盘页对应着索引树的节点。那么一次查找所经历的索引树的深度对应着磁盘IO交互的次数。如果采取二叉树结构,那么显而易见,可能导致遍历的深度太大导致磁盘IO交互的次数太多。而相较于内存查询,磁盘IO才是影响查询以及更新表中数据的关键。那么由二叉树这样的瘦长结构自然容易联想到如何将它变的矮胖。这样做虽然没有降低比较次数,由于深度的降低,可以极大的减少磁盘IO次数。从而可以提升查询以及更新数据的性能。
24.SQL执行计划详解?
分析:
MySql提供了EXPLAIN语法用来进行查询分析,在SQL语句前加一个"EXPLAIN"即可。比如我们要分析如下SQL语句:
explain select * from table where table.id = 1
运行上面的sql语句后你会看到,下面的表头信息:
table | type | possible_keys | key | key_len | ref | rows | Extra
EXPLAIN列的解释
table
显示这一行的数据是关于哪张表的
type
这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、index和ALL
说明:不同连接类型的解释(按照效率高低的顺序排序)
system:表只有一行:system表。这是const连接类型的特殊情况。
const :表中的一个记录的最大值能够匹配这个查询(索引可以是主键或惟一索引)。因为只有一行,这个值实际就是常数,因为MYSQL先读这个值然后把它当做常数来对待。
eq_ref:在连接中,MYSQL在查询时,从前面的表中,对每一个记录的联合都从表中读取一个记录,它在查询使用了索引为主键或惟一键的全部时使用。
ref:这个连接类型只有在查询使用了不是惟一或主键的键或者是这些类型的部分(比如,利用最左边前缀)时发生。对于之前的表的每一个行联合,全部记录都将从表中读出。这个类型严重依赖于根据索引匹配的记录多少—越少越好。
range:这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西时发生的情况。
index:这个连接类型对前面的表中的每一个记录联合进行完全扫描(比ALL更好,因为索引一般小于表数据)。
ALL:这个连接类型对于前面的每一个记录联合进行完全扫描,这一般比较糟糕,应该尽量避免。
possible_keys
显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句
key
实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MYSQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引
key_len
使用的索引的长度。在不损失精确性的情况下,长度越短越好
ref
显示索引的哪一列被使用了,如果可能的话,是一个常数
rows
MYSQL认为必须检查的用来返回请求数据的行数
Extra
关于MYSQL如何解析查询的额外信息。将在表4.3中讨论,但这里可以看到的坏的例子是Using temporary和Using filesort,意思MYSQL根本不能使用索引,结果是检索会很慢
说明:extra列返回的描述的意义
Distinct :一旦mysql找到了与行相联合匹配的行,就不再搜索了。
Not exists :mysql优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了。
Range checked for each Record(index map:#) :没有找到理想的索引,因此对从前面表中来的每一个行组合,mysql检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一。
Using filesort :看到这个的时候,查询就需要优化了。mysql需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。
Using index :列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候。
Using temporary :看到这个的时候,查询需要优化了。这里,mysql需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上。
Where used :使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题。
因此,弄明白了explain语法返回的每一项结果,我们就能知道查询大致的运行时间了,如果查询里没有用到索引、或者需要扫描的行过多,那么可以感到明显的延迟。因此需要改变查询方式或者新建索引。mysql中的explain语法可以帮助我们改写查询,优化表的结构和索引的设置,从而最大地提高查询效率。当然,在大规模数据量时,索引的建立和维护的代价也是很高的,往往需要较长的时间和较大的空间,如果在不同的列组合上建立索引,空间的开销会更大。因此索引最好设置在需要经常查询的字段中。
25.索引优化详解?
26.SQL语句如如如何优化?
1、观察,查看慢SQL情况;
2、开启查询日志,设置阀值;
3、explain 分析;
4、show profile 查看执行细节和生命周期情况
5、dba 进行参数调优
慢查询日志
响应时间超过long_query_tine的SQL,被记录到慢查询日志中。
// SHOW VARIABLES LIKE '%slow_query_log%' ; 查看是否开启,默认没开启
// set global slow_quary_log = 1; 开启,仅本数据库有效,重启MySQL之后失效。
// show variables like '%long_query_time%'; 查看当前多少秒算慢
// set global long_query_time = 3; 设置慢的阙值时间
// show global status like '%Slow_queries%'; 查看当前数据库有多少条慢SQL
Show Profile
是MySQL提供可以用来分析当前会话中语句执行的资源情况,可以用于SQL的调优的测量。
默认关闭,并保存最近15次结果;
// set profiling = on ; 开启
// show profiles; 查看执行过的sql
// SHOW PROFILE cpu,block io FOR QUERY 87; 查看这个执行sql 的生命周期相关信息。
converting HEAP to MyISAM 查询结果太大,内存不够用了往磁盘上搬;
Creating tmp table 创建临时表,拷贝数据到临时表,用完再删除;
Copying to tmp table on disk 把内存中临时表复制到磁盘,危险!
locked