<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; 性能调优</title>
	<atom:link href="https://blog.gmem.cc/tag/%e6%80%a7%e8%83%bd%e8%b0%83%e4%bc%98/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 03 Apr 2026 04:13:36 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Tomcat知识集锦</title>
		<link>https://blog.gmem.cc/tomcat-faq</link>
		<comments>https://blog.gmem.cc/tomcat-faq#comments</comments>
		<pubDate>Fri, 17 Oct 2014 09:17:31 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[Tomcat]]></category>
		<category><![CDATA[性能调优]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1944</guid>
		<description><![CDATA[<p>常见问题 零散问题 One or more listeners failed to start 报错信息： Artifact pems-web-manager:war exploded: Error during artifact deployment. See server log for details. <a class="read-more" href="https://blog.gmem.cc/tomcat-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/tomcat-faq">Tomcat知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">One or more listeners failed to start</span></div>
<p>报错信息：</p>
<p>Artifact pems-web-manager:war exploded: Error during artifact deployment. See server log for details.</p>
<p>org.apache.catalina.core.StandardContext.startInternal One or more listeners failed to start. Full details will be found in the appropriate container log file</p>
<p>报错原因：某个Web应用的Servlet监听器配置出现问题（例如监听器类找不到），上面的出错信息不明确，检查web.xml即可。<br /> </p>
<div class="blog_h1"><span class="graybg">Tomcat7生产环境配置</span></div>
<div class="blog_h2"><span class="graybg">环境准备</span></div>
<div class="blog_h3"><span class="graybg">下载软件</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;">软件</td>
<td style="text-align: center;">版本</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Tomcat</td>
<td style="width: 80px;">7.0.56</td>
<td>
<p>如果是64位Windows，可以下载：64-bit Windows zip</p>
<p>下载地址：<a href="http://tomcat.apache.org/download-70.cgi#7.0.56">http://tomcat.apache.org/download-70.cgi#7.0.56</a></p>
</td>
</tr>
<tr>
<td>JRE</td>
<td>7u71</td>
<td>
<p>如果是64位Windows，可以下载：Windows x64的tar.gz版本</p>
<p>下载地址：<a href="http://www.oracle.com/technetwork/cn/java/javase/downloads/jre7-downloads-1880261.html">http://www.oracle.com/technetwork/cn/java/javase/downloads/jre7-downloads-1880261.html</a></p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">安装软件</span></div>
<ol>
<li>解压Tomcat到某个位置，例如：D:\JavaEE\production\Tomcat7，确认Tomcat7\bin目录存在</li>
<li>解压JRE到%TOMCAT_HOME%\jre，确认%TOMCAT_HOME%\jre\bin目录存在</li>
</ol>
<div class="blog_h2"><span class="graybg">Tomcat配置</span></div>
<div class="blog_h3"><span class="graybg">使用%TOMCAT_HOME%\jre作为Java运行环境</span></div>
<ol>
<li>在%TOMCAT_HOME%下建立文件startup.bat，内容类似下面：<br />
<pre class="crayon-plain-tag">@echo off
echo *******************************************************************************
echo Portable Tomcat 7 by Alex Wang
echo.
pushd "%~dp0"
for /f "tokens=1-3 delims=/ " %%a in ('date /t') do (set md=%%a-%%b-%%c)
for /f "tokens=1-3 delims=/:." %%a in ("%TIME%") do (set mt=%%a%%b%%c)
set CUR_TIME=%md%-%mt%

set "JAVA_HOME="
set "CATALINA_HOME=%CD%"
set "JRE_HOME=%CATALINA_HOME%\jre"
set "LOG_DIR=%CATALINA_HOME%\logs"
rem 请根据服务器硬件配置、系统的特性填写下面一行
rem 响应时间优先配置示例：-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:ParallelGCThreads=8 -XX:MaxGCPauseMillis=500
rem 吞吐量优先的配置示例：-XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=8 -Xmn1280M
set JAVA_OPTS=-server -Xms2G -Xmx2G -Xss128K -XX:MaxPermSize=256M
rem 启用错误记录
set JAVA_OPTS=%JAVA_OPTS% -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=%LOG_DIR%\ -XX:ErrorFile=%LOG_DIR%\hs_err_pid%%p.log
rem 启用JMX远程管理和黑匣子
set JAVA_OPTS=%JAVA_OPTS% -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=7777 -Dcom.sun.management.jmxremote -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
rem 启用黑匣子默认记录行为
set JAVA_OPTS=%JAVA_OPTS% -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=%LOG_DIR%\jfr\dump-on-exit-%CUR_TIME%.jfr,maxage=3600s,settings=%JRE_HOME%\lib\jfr\profile.jfc
rem 记录GC详细日志
set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:%LOG_DIR%\gc.log
rem 启用远程调试
set JAVA_OPTS=%JAVA_OPTS% -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000

echo Setting CATALINA_HOME %CATALINA_HOME%
echo Setting JRE_HOME %JRE_HOME%
echo Setting JAVA_OPTS %JAVA_OPTS%
echo.
echo Launching Tomcat 7 startup.bat...
echo *******************************************************************************
echo.
call bin\startup.bat
popd</pre>
</li>
<li>类似的，编写一个shutdown.bat</li>
</ol>
<div class="blog_h3"><span class="graybg">NT服务配置</span></div>
<p>如果不想使用脚本启动（上一段的方式），可以把Tomcat配置为NT服务，把下面的脚本存放到%TOMCAT_HOME%，传入服务名、显示名、服务描述三个参数即可安装服务，内存配置需要根据机器的性能进行调整：</p>
<pre class="crayon-plain-tag">@echo off
pushd "%~dp0"
setlocal

set SERVICE_NAME=%~1
set DISPLAY_NAME=%~2
set SERVICE_DESC=%~3

set "CATALINA_HOME=%CD%"
set "EXECUTABLE=%CATALINA_HOME%\bin\tomcat7.exe"
set "CATALINA_BASE=%CATALINA_HOME%"
set "JRE_HOME=%CATALINA_HOME%\jre"
set "LOG_DIR=%CATALINA_HOME%\logs"
set "CLASSPATH=%CATALINA_HOME%\bin\bootstrap.jar;%CATALINA_BASE%\bin\tomcat-juli.jar"
rem 请根据服务器硬件配置、系统的特性填写下面一行
rem 响应时间优先配置示例：-XX:MaxPermSize=256M;-XX:+UseConcMarkSweepGC;-XX:+UseParNewGC;-XX:ParallelGCThreads=8;-XX:MaxGCPauseMillis=500;-XX:+UseParNewGC;
rem 吞吐量优先的配置示例：-XX:+UseParallelGC;-XX:+UseParallelOldGC;-XX:ParallelGCThreads=8;-Xmn1280M;
set "JVM_MEM_GC_OPTS=-XX:MaxPermSize=256M;"

rem 启用JMX远程管理和黑匣子
set "JMX_OPTS=-Dcom.sun.management.jmxremote.authenticate=false;-Dcom.sun.management.jmxremote.ssl=false;-Dcom.sun.management.jmxremote.port=7777;-Dcom.sun.management.jmxremote;-XX:+UnlockCommercialFeatures;-XX:+FlightRecorder;"
rem 启用错误记录
set "JVM_ERR_OPTS=-XX:+HeapDumpOnOutOfMemoryError;-XX:HeapDumpPath=%LOG_DIR%\;-XX:ErrorFile=%LOG_DIR%\hs_err_pid%%p.log;"
set "JFR_AUTO_RCD=-XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=%LOG_DIR%\jfr\dump-on-exit-%SERVICE_NAME%.jfr,maxage=3600s,settings=%JRE_HOME%\lib\jfr\profile.jfc;"

.\bin\tomcat7 //IS//%SERVICE_NAME% --Startup=auto ^
--DisplayName="%DISPLAY_NAME%" --Description "%SERVICE_DESC%" ^
--Jvm="%CATALINA_HOME%\jre\bin\server\jvm.dll" ^
--Classpath="%CLASSPATH%" ^
--LogPath "%CATALINA_HOME%\logs\services\%SERVICE_NAME%" --LogLevel=Info --StdOutput auto --StdError auto ^
--StartMode=jvm --StartPath "%CATALINA_HOME%" --StartClass=org.apache.catalina.startup.Bootstrap --StartParams=start ^
--StopMode=jvm --StopPath "%CATALINA_HOME%" --StopClass=org.apache.catalina.startup.Bootstrap --StopParams=stop ^
--JvmMs=2048 --JvmMx=2048 --JvmSs=128 ^
++JvmOptions "%JVM_MEM_GC_OPTS%-Dcatalina.home=%CATALINA_HOME%;-Dcatalina.base=%CATALINA_BASE%;-Djava.endorsed.dirs=%CATALINA_HOME%\endorsed;-Djava.io.tmpdir=%CATALINA_BASE%\temp;-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager;-Djava.util.logging.config.file=%CATALINA_BASE%\conf\logging.properties;%JMX_OPTS%%JVM_ERR_OPTS%%JFR_AUTO_RCD%"

popd
endlocal</pre>
<div class="blog_h3"><span class="graybg">优化Tomcat配置参数（基于Java NIO）</span></div>
<p>参考下面的代码编辑%TOMCAT_HOME%\conf\server.xml</p>
<pre class="crayon-plain-tag">&lt;!-- 注释掉 AJP --&gt;
&lt;!--&lt;Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /&gt; --&gt;
&lt;!-- 使用Java NIO优化性能，注意删除原Connector元素的同名属性，线程数和缓存依据实际需要调整 --&gt;
&lt;Connector port="8080" redirectPort="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" 
           connectionTimeout="20000" 
           URIEncoding="UTF-8" 
           useBodyEncodingForURI="true" 
           maxThreads="1024" 
           minSpareThreads="128"
           socket.appReadBufSize="1024" 
           socket.appWriteBufSize="1024"
/&gt;</pre>
<div class="blog_h2"><span class="graybg">Linux下的脚本示例（CentOS 6.3）</span></div>
<pre class="crayon-plain-tag">#!/bin/bash
PDTDIR=$(dirname $0)
export CUR_TIME=`date "+%Y%m%d%H%M%S"`
export JAVA_HOME=
export CATALINA_HOME=$PDTDIR
export JRE_HOME=$CATALINA_HOME/jre
export LOG_DIR=$CATALINA_HOME/logs

# 请根据服务器硬件配置、系统的特性填写下面一行
# 响应时间优先配置示例：-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:ParallelGCThreads=8 -XX:MaxGCPauseMillis=500
# 吞吐量优先的配置示例：-XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=8 -Xmn1280M
export JAVA_OPTS="-server -Xms2G -Xmx2G -Xss256K -XX:MaxPermSize=256M"
# 启用错误记录
export JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LOG_DIR/ -XX:ErrorFile=$LOG_DIR/hs_err_pid%p.log"
# 启用JMX远程管理和黑匣子
export JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=7777 -Dcom.sun.management.jmxremote -XX:+UnlockCommercialFeatures -XX:+FlightRecorder"
# 启用黑匣子默认记录行为
export JAVA_OPTS="$JAVA_OPTS -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$LOG_DIR/jfr/dump-on-exit-$CUR_TIME.jfr,maxage=3600s,settings=$JRE_HOME/lib/jfr/profile.jfc"
#rem 记录GC详细日志
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:$LOG_DIR/gc.log"

echo
echo "*******************************************************************************"
echo "    Portable Tomcat 7 for Linux  by Alex Wang  (Last updated: May 7th, 2015 )  "
echo "Setting CATALINA_HOME       $CATALINA_HOME"
echo "Setting JRE_HOME            $JRE_HOME"
echo "Setting JAVA_OPTS           $JAVA_OPTS"
echo
echo "Launching Tomcat 7 startup.bat...                                              "
echo "*******************************************************************************"
echo
exec "$PDTDIR"/bin/startup.sh</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/tomcat-faq">Tomcat知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/tomcat-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JVM参数与性能调优</title>
		<link>https://blog.gmem.cc/jvm-options-and-performance-tuning</link>
		<comments>https://blog.gmem.cc/jvm-options-and-performance-tuning#comments</comments>
		<pubDate>Fri, 13 Jun 2014 06:28:08 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[性能调优]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=2330</guid>
		<description><![CDATA[<p>本文主要适用于HotSpot JVM的参数设置与调优。 性能监控与故障处理工具 第三方工具 工具类型 说明 堆Dump分析 推荐使用MAT，参考使用Eclipse Memory Analyzer分析JVM堆Dump 栈Dump分析 推荐使用在线工具：http://fastthread.io/ 低成本剖析 推荐使用JMC，参考使用Oracle Java Mission Control监控JVM运行状态 JDK命令  命令  说明 jps JVM <a class="read-more" href="https://blog.gmem.cc/jvm-options-and-performance-tuning">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-options-and-performance-tuning">JVM参数与性能调优</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>本文主要适用于HotSpot JVM的参数设置与调优。</p>
<div class="blog_h1"><span class="graybg">性能监控与故障处理工具</span></div>
<div class="blog_h2"><span class="graybg">第三方工具</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">工具类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>堆Dump分析</td>
<td>推荐使用MAT，参考<a href="/jvm-dump-analysis-with-mat">使用Eclipse Memory Analyzer分析JVM堆Dump</a></td>
</tr>
<tr>
<td>栈Dump分析</td>
<td>推荐使用在线工具：<a href="http://fastthread.io/">http://fastthread.io/</a></td>
</tr>
<tr>
<td>低成本剖析</td>
<td>推荐使用JMC，参考<a href="/jvm-monitoring-with-oracle-jmc">使用Oracle Java Mission Control监控JVM运行状态</a></td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">JDK命令</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;"> 命令</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3"><a id="jps"></a>jps</td>
<td>
<p>JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程</p>
<p><span style="background-color: #c0c0c0;">格式：</span><br />jps [ options ] [ hostid ]</p>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-q 只输出LVMID,省略略主类的名称<br />-m 输出虚拟机进程启动时传递给主类main()函数的参数<br />-l 输出主类的全名.如果进程执行的是Jar包，输出Jar路径 <br />-v 输出虚拟机进程启动时JVM参数</p>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jstat"></a>jstat</td>
<td>
<p>JVM Statistics Monitoring Tool，用干收集HotSpot虚拟机各方面的运行数据（类装载、内存、垃圾收集、JIT编译等）</p>
<p><span style="background-color: #c0c0c0;">格式：</span><br />jstat [ option vmid [interval[s|ms] [count]] ]</p>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-class 监视类装载、卸载数、总空间及类装载所耗费的时间<br />-gc 监视Java堆状况，包括Eden区、2个survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息<br />-gccapacity 监视内容与-gc基本相同，但输出主要关注Java堆各个区域使用到的最大和最小空间<br />-gcutil 监视内容与-gc基本相同，但输出主要关注已使用空间占总空间的百分比<br />-gccause 与-gcutil功能一样，但是会额外输出导致上一次GC产生的原因<br />-gcnew 监视新生代GC的状况<br />-gcnewcapacity 监视内容与-gcnew基本相同，输出主要关注使用到的最大和最小空间<br />-gcold 监视老年代GC的状况<br />-gcoldcapacity 监视内容与-gcold基本相同，输出主要关注使用到的最大和最小空间<br />-gcpermcapacity 输出永久代使用到的最大和最小空间<br />-compiler 输出JIT编译器编译过的方法、耗时等信息<br />-printcompilation 输出已经被JIT编译的方法</p>
<p><span style="background-color: #c0c0c0;">举例：</span></p>
<pre class="crayon-plain-tag">#每250毫秒査询一次进程2764垃圾收集的状况，一共査询20次
jstat -gc 2764 250 20</pre>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jinfo"></a>jinfo</td>
<td>
<p>Configuration Info for Java，实时地査看和调整虚拟机的各项参数。
<p><span style="background-color: #c0c0c0;">格式：</span><br />jinfo [ option ] pid</p>
<p><span style="background-color: #c0c0c0;">举例：</span></p>
<pre class="crayon-plain-tag">jinfo -flag CMSInitiatingOccupancyFraction 1444 
#输出-XX：CMSInitiatingOccupancyFraction=85</pre>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jmap"></a>jmap</td>
<td>
<p>Memory Map for Java，生成虚拟机的内存转储快照（heapdump文件）
<p><span style="background-color: #c0c0c0;">格式：</span><br />jmap [ option ] vmid</p>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-dump 生成Java堆转储快照。格式为：-dump:[live,]format=b，file= ，live子参数说明是否只dump出存活的对象<br />-finalizerinfo 显示在F-Queue中等待Finalizer线程执行finalize方法的对象。只在Linux / Solaris平台下有效<br />-heap 显示Java堆详细信息，如使用哪种回收器、参数配置、分代状况等，只在Linux/ Solaris平台下有效<br />-histo 显示堆中对象统计信息.包括类、实例数量和合计容量<br />-permstat 以ClassLoadcr为统计口径显示永久代内存状态，只在Linux/ Solaris平台下有效<br />-F 当虚拟机进程对-dump选项没有响应时，可使用这个选项强制生成dump快照，只 在Linux / Solaris平台下有效</p>
<p><span style="background-color: #c0c0c0;">举例：</span></p>
<pre class="crayon-plain-tag">jmap -dump:format=b,file=eclipse.dump 3500
#Dumping heap to ...
#Heap dump file created

# 显示进程5732的GC可达对象直方图
jmap -histo:live 5732</pre>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jhat"></a>jhat</td>
<td>
<p>JVM Heap Dump Browser,用于分析 heapdump 文件，它会建立一个 HTTP/HTML 服务器，让用户可以在浏览器上査看分析结果
<p>注意<a href="/jvm-study-note#type-id-in-jvm">JVM内部的类型表示方式</a>。</p>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jstack"></a>jstack</td>
<td>
<p>Stack Trace for Java，显示虚拟机的线程快照</p>
<p><span style="background-color: #c0c0c0;">格式：</span><br />jstack [ option ] vmid</p>
<p><span style="background-color: #c0c0c0;">选项：</span><br />-F 当正常输出的请求不被响应时，强制输出线程堆栈<br />-l 除堆栈外，显示关于锁的附加信息<br />-m 如果调用到本地方法的话，可以显示C/C++的堆栈</p>
</td>
</tr>
<tr>
<td class="blog_h3"><a id="jcmd"></a>jcmd</td>
<td>
<p>不带任何参数运行jcmd，显示所有JVM进程的PID和Main类名</p>
<p>运行<pre class="crayon-plain-tag">jcmd 0 help</pre>，显示每个JVM支持的命令</p>
<p>命令示例：</p>
<pre class="crayon-plain-tag"># 打印JVM参数
jcmd 25464 VM.flags

# 打印所有系统属性
jcmd 25464 VM.system_properties

# Dump出线程栈
jcmd 25464 Thread.print

# Dump出堆
jcmd 25464 GC.heap_dump

# 强制GC
jcmd 25464 GC.run</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">JVM调用命令格式</span></div>
<p><pre class="crayon-plain-tag"># options：JVM参数
# class：main方法所在类
java [ options ] class [ arguments ]
# -jar 被调用的jar包路径
java [ options ] -jar file.jar [ arguments ]
#用于GUI
javaw [ options ] class [ arguments ]
javaw [ options ] -jar file.jar [ arguments ]</pre>
<div class="blog_h1"><span class="graybg">标准参数</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 参数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 200px;">-client</td>
<td>使用 Java HotSpot Client VM，64bit的JDK忽略此选项，使用-server</td>
</tr>
<tr>
<td>-server</td>
<td>使用Java HotSpot Server VM，64bit的JDK只能是这种模式，不需要指定</td>
</tr>
<tr>
<td>
<p>-agentlib:libname[=options]</p>
</td>
<td>
<p>根据名称加载本地（native）JVM代理，例如：</p>
<p>-agentlib:hprof<br />-agentlib:jdwp=help<br />-agentlib:hprof=help</p>
</td>
</tr>
<tr>
<td>
<p>-agentpath:pathname[=options]</p>
</td>
<td>
<p>根据路径全名加载本地JVM代理，例如：</p>
<p>-agentpath:D:\JavaEE\jprofiler\7.2.2\bin\windows-x64\jprofilerti.dll=offline</p>
</td>
</tr>
<tr>
<td>
<p>-classpath classpath<br />-cp classpath</p>
</td>
<td>指定一系列分号;分隔的目录、JAR、ZIP文件路径，用于从中寻找类文件，注意，此参数覆盖CLASSPATH环境变量。如果没指定，也没有CLASSPATH环境变量，当前目录（.）为类路径</td>
</tr>
<tr>
<td>-Dproperty=value</td>
<td>设置JVM系统属性</td>
</tr>
<tr>
<td>
<p>-da[:pkgname | :clsname ]</p>
</td>
<td> 禁用断言，默认是禁用的</td>
</tr>
<tr>
<td>-ea[:pkgname | :clsname ]</td>
<td>
<p> 启用断言：例如：</p>
<p>-ea:cc.gmem.demo... ，启用demo包及其子包的断言</p>
</td>
</tr>
<tr>
<td>-esa，-dsa</td>
<td>启用、禁用所有system classes的断言</td>
</tr>
<tr>
<td>-help  -?</td>
<td>显示帮助</td>
</tr>
<tr>
<td>-jar</td>
<td>执行包装在jar中的类</td>
</tr>
<tr>
<td>
<p>-javaagent:jarpath[=options]</p>
</td>
<td>加载基于Java语言的JVM代理</td>
</tr>
<tr>
<td>-splash:imagepath </td>
<td>显示启动画面</td>
</tr>
<tr>
<td>-verbose:class<br />-verbose:gc<br />-verbose:jni</td>
<td>显示类加载信息<br />显示垃圾回收信息<br />显示JNI接口活动信息 </td>
</tr>
<tr>
<td>-version<br />-showversion</td>
<td>
<p>显示版本信息并退出<br />显示版本信息并继续</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">非标准参数</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">参数 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>-X</td>
<td>显示非标准参数并退出</td>
</tr>
<tr>
<td>-Xint</td>
<td>以解释模式运行，字节码不会编译成native代码</td>
</tr>
<tr>
<td>-Xbatch</td>
<td>禁止后台编译，默认情况下，后台编译字节码，编译完成前，解释执行，禁止后，必须编译完毕后才能执行方法</td>
</tr>
<tr>
<td>-Xbootclasspath:bootclasspath</td>
<td>设置boot class的寻找路径（JAR、ZIP或者目录）</td>
</tr>
<tr>
<td>-Xbootclasspath/a:path</td>
<td>附加boot class的寻找路径</td>
</tr>
<tr>
<td>-Xbootclasspath/p:path</td>
<td>前缀boot class的寻找路径</td>
</tr>
<tr>
<td>-Xcheck:jni</td>
<td>检查JNI函数的调用，比如验证参数有效性，如果失败，则退出JVM </td>
</tr>
<tr>
<td>-Xfuture</td>
<td>进行严格的字节码格式检查</td>
</tr>
<tr>
<td>-Xnoclassgc</td>
<td>禁止类的垃圾回收</td>
</tr>
<tr>
<td>-Xincgc</td>
<td>启用增量的垃圾回收，默认禁用，减少垃圾回收导致的长时间系统停顿，但是会时时工作，影响程序效率</td>
</tr>
<tr>
<td>-Xloggc:file</td>
<td>记录垃圾回收的每个事件</td>
</tr>
<tr>
<td>-XX:AllocationPrefetchStyle=n</td>
<td>设置内存分配预读取风格，默认2</td>
</tr>
<tr>
<td style="width: 250px;">-XX:+|-DisableAttachMechanism</td>
<td>设置类似jmap、jconsole之类的命令是否可以附到（attach）到运行中的JVM，默认这个特性是禁用的</td>
</tr>
<tr>
<td>-XX:+UnlockCommercialFeatures</td>
<td>解锁商业特性</td>
</tr>
<tr>
<td>-XX:+|-FlightRecorder</td>
<td>商业特性。启用黑匣子功能</td>
</tr>
<tr>
<td>
<p>-XX:FlightRecorderOptions<br />=p=v,p1=v1...</p>
</td>
<td>
<p>配置黑匣子的选项，包括：</p>
<p>defaultrecording=true|false 黑匣子是持续运行，还是运行限定的时间，默认false<br />disk=true|false 黑匣子是否持续的写入磁盘，如果为true，defaultrecording也应该为true<br />dumponexit=true|false 如果JVM退出，是否生成黑匣子的dump文件<br />dumponexitpath=path 黑匣子dump文件的路径，同名文件被覆盖，如果指定目录，则依据时间作为文件名（7u41版本在Windows下不支持指定目录，可能是BUG）<br />globalbuffersize=size 数据存留占有主内存的最大空间，默认10M<br />loglevel=quiet|terror|warning|info|debug|trace 日志记录级别，默认info<br />maxage=time 磁盘数据存活最大时间，默认15分钟，只有disk=true才有意义<br />maxchunksize=size 数据块的最大尺寸，默认12M<br />maxsize=size 磁盘数据的最大尺寸，默认无限制<br />repository=path 临时磁盘目录的位置<br />samplethreads=true|false 是否启用线程样本，默认启用<br />settings=path 默认JAVA_HOME/jre/lib/jfr/default.jfs，包含事件的设置<br />stackdepth=depth 堆栈跟踪的最大深度<br />threadbuffersize=size 默认5kb，线程的本地缓冲大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:StartFlightRecording<br />=p=v,p1=v1...</p>
</td>
<td>
<p>启动黑匣子</p>
<p>compress=true|false 是否压缩黑匣子日志文件<br />defaultrecording=true|false<br />delay=time JVM启动后，延迟黑匣子记录的毫秒数<br />duration=time 黑匣子记录的持续时间，默认无限<br />filename=path 日志文件路径<br />name=identifier 本次记录的标识符<br />maxage=time<br />maxsize=size<br />settings=path</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常用-XX选项</span></div>
<div class="blog_h2"><span class="graybg">行为选项</span></div>
<table style="width: 100%;" border="1" width="100%" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 30%; text-align: center;" width="32%">
<p><strong>参数与默认值</strong></p>
</td>
<td style="width: 70%; text-align: center;" width="67%">
<p><strong>说明</strong></p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:-AllowUserSignalHandlers</p>
</td>
<td width="67%">
<p>允许用户定义的信号处理器（Solaris/Linux）</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:AltStackSize=16384</p>
</td>
<td width="67%">
<p>备选信号栈大小（Solaris，5.0-）</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:+FailOverToOldVerifier</p>
</td>
<td width="67%">
<p>如果验证失败，使用旧版本的验证启（6.0+）</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:PreBlockSpin=10</p>
</td>
<td width="67%">
<p> -XX:+UseSpinning使用的自旋计数器，进入系统线程同步代码前，自旋的次数 (1.4.2+)</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:-RelaxAccessControlCheck</p>
</td>
<td width="67%">
<p>放松验证器的访问控制检查 ( 6.0+)</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:-UseSpinning</p>
</td>
<td width="67%">
<p>进入系统同步化代码前，在Java监视器上启用native自旋( 1.4.2 and 5.0 )</p>
</td>
</tr>
<tr>
<td width="32%">
<p>-XX:+UseThreadPriorities</p>
</td>
<td width="67%">使用系统native的线程优先级</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">性能调优选项</span></div>
<table style="width: 100%;" border="1" width="100%" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 30%; text-align: center;" width="45%">
<p><strong>参数与默认值</strong></p>
</td>
<td style="text-align: center;" width="55%">
<p><strong>说明</strong></p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XssN</strong></span></p>
</td>
<td>设置线程栈大小。JDK5.0+为1M，以前为256K。减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数限制在3000~5000左右</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-</strong><strong>XmsN</strong></span></td>
<td>设置初始内存大小（字节），必须是1024的倍数、大于1MB，可以后缀k/K、m/M、g/G</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XmxN </strong></span></td>
<td>设置最大内存大小（字节），必须是1024的倍数、大于2MB，可以后缀k/K、m/M、g/G</td>
</tr>
<tr>
<td><span style="color: #000000;">-XX:PermSize</span></td>
<td>设置永久代初始占用内存大小</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:MaxPermSize=64m</strong></span></td>
<td>
<p>设置永久代最大占用内存大小</p>
</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XmnN</strong></span></td>
<td>年轻代的内存大小（Eden区+2个Survivor区）</td>
</tr>
<tr>
<td>-XX:NewRatio=2</td>
<td>年老代/年轻代占用空间比率</td>
</tr>
<tr>
<td>-XX:SurvivorRatio=8</td>
<td>年轻代Eden/Survivor区域尺寸比例，默认8:1。通常有2个Survivor区，因此8:1意味着Survivor总大小为2</td>
</tr>
<tr>
<td>-XX:PretenureSizeThreshold</td>
<td>用于ParNew、Serial。大于此值（字节）的对象将在年老代直接分配，避免在Eden区及Survivor区之间发生大量的内存拷贝</td>
</tr>
<tr>
<td>-XX:MaxTenuringThreshold</td>
<td>
<p>设置对象进入年老代前，在Survivor区复制（活过Minor GC）的最大次数，默认情况下，JVM会动态调整TenuringThreshold值。<br />如果设置为0的话，则年轻代对象不经过Survivor区，直接进入年老代，对于年老代比较多的应用，可以提高效率<br />将此值设置为一个较大值，则年轻代对象会在Survivor区进行多次复制，这样可以增加对象在年轻代的存活时间</p>
<p>此值设置过大，则ParNew分析Survivor区对象关系时可能消耗时间过大，影响性能。</p>
<p>如果Survivor满了，对象会强制复制到年老代</p>
</td>
</tr>
<tr>
<td>-XX:TargetSurvivorRatio=50</td>
<td>
<p>JVM会试图调整TenuringThreshold期望下次Minor GC后Survivor的From区的使用量能接近50</p>
<p>如果设置为100，相当于禁止调整TenuringThreshold</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseTLAB</strong></span></p>
</td>
<td>
<p>启用线程本地分配缓冲（Thread local allocation buffers）。启用TLAB后，只有TLAB被用满了，才会尝试到Eden区域申请内存</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:TLABSize=n</strong></span></p>
</td>
<td>
<p>TLAB的大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+ResizeTLAB</p>
</td>
<td>
<p>是否可以动态修改TLAB大小</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:MaxGCPauseMillis=n</strong></span></p>
</td>
<td>
<p>设置GC暂停时间目标。如果无法达到要求，JVM会自动调整年轻代大小</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:GCTimeRatio=n</strong></span></p>
</td>
<td>
<p>控制吞吐量，设置垃圾回收时间占程序运行时间的百分比，公式为1/(1+n)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseSerialGC</p>
</td>
<td>
<p>使用串行垃圾回收 (5.0+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseG1GC</p>
</td>
<td>
<p>启用G1垃圾回收器</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:<strong>+</strong>UseParallelGC</strong></span></p>
</td>
<td>
<p><span style="color: #000000;">吞吐量收集器。Parallel scavenge collector，对年轻代使用并行垃圾回收(1.4.1+)，<span style="background-color: #c0c0c0;">不能与CMS收集器同时使用</span>。算法针对多CPU大内存（例如10G+）进行了优化，致力于在尽量减少停顿的同时最大化吞吐量</span></p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseParallelOldGC</strong></span></p>
</td>
<td>
<p>吞吐量收集器。在使用-XX:+UseParallelGC时，对年老代使用并行垃圾回收(5.0u6)</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseParNewGC</strong></span></p>
</td>
<td>
<p>Parallel copying collector，对年轻代使用并行垃圾回收，可与CMS收集器（-XX:+UseConcMarkSweepGC）同时使用。JDK5.0以上，JVM会根据系统配置自行设置。工作方式类似于原始的复制算法，但是使用多个线程进行并行的拷贝</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:ParallelGCThreads=n</strong></span></p>
</td>
<td>
<p>并行垃圾收集使用的线程数，默认与处理器核心数相等，如果CPU很多，可以减小此值</p>
</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong> -XX:+UseAdaptiveSizePolicy</strong></span></td>
<td>
<p>设置此选项后，并行收集器（Parallel Scavenge）会自动选择年轻代区大小和相应的Survivor区比例，以达到目标系统规定的最低响应时间或者收集频率等，建议使用并行收集器时启用。使用-XX:+PrintAdaptiveSizePolicy跟踪动态变化</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseConcMarkSweepGC</strong></span></p>
</td>
<td>
<p>低停顿收集器。对年老代使用CMS垃圾回收器，同时保证应用的运行只有很短暂的停顿(1.4.1+)</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+CMSConcurrentMTEnabled</strong></span></p>
</td>
<td>
<p>CMS并发阶段（concurrent CMS phases）是否使用多线程（使用ParNewGC的情况下）。默认已经启用，设置-XX:-CMSConcurrentMTEnabled可以禁用</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+CMSParallelRemarkEnabled</strong></span></p>
</td>
<td>在CMS重标记阶段，使用多线程来降低停顿时间。默认false。不一定能达到效果，需要进行基准测试（benchmark）来确定是否开启</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:CMSScheduleRemarkEdenSizeThreshold</strong></span></p>
</td>
<td>在预清理后，启动可中断预清理阶段的Eden占用最小值，默认2MB</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:CMSScheduleRemarkEdenPenetration</strong></span></p>
</td>
<td>
<p>当新生代存活对象占Eden的比例超过多少时，终止preclean阶段并进入remark阶段，默认50</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:CMSMaxAbortablePrecleanTime</strong></span></p>
</td>
<td>
<p>设置CMS可中断预清理（preclean）阶段最大时间，单位毫秒，超过了强制进入重标记阶段</p>
</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong> -XX:+CMSScavengeBeforeRemark</strong></span></td>
<td>
<p>在CMS的重标记阶段开始前，进行YGC清理，减少重标记阶段的耗时</p>
</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:ConcGCThreads=n</strong></span></td>
<td>CMS垃圾收集（所有阶段）使用的线程数，提高此值可能改善垃圾回收性能，但是带来额外的同步成本（synchronization overhead）。默认的，此值不会明确设置，JVM根据-XX:ParallelGCThread来自动设置，公式为：ConcGCThreads = (ParallelGCThreads + 3)/4</td>
</tr>
<tr>
<td>-XX:+UseCMSInitiatingOccupancyOnly</td>
<td>禁止HostSpot自行触发CMS GC，只有在堆百分比达到阈值时才触发，配合下一条使用</td>
</tr>
<tr>
<td><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:CMSInitiatingOccupancyFraction</strong></span></td>
<td>设置还剩余多少堆（百分比）时进行CMS收集，默认68%。如果年老代增长缓慢，可以增加此值。</td>
</tr>
<tr>
<td>
<p>-XX:CMSInitiatingPermOccupancyFraction</p>
</td>
<td>
<p>设置还剩余多少永久带（百分比）时进行CMS收集</p>
</td>
</tr>
<tr>
<td>
<p>-XX:CMSFullGCsBeforeCompaction=5</p>
</td>
<td>
<p>由于CMS不对内存空间进行整理，此值设置运行多少次Full GC以后对内存空间进行整理。设置此参数可能导致promotion failure</p>
</td>
</tr>
<tr>
<td>
<p><span style="color: #000000; background-color: #c0c0c0;"><strong>-XX:+UseCMSCompactAtFullCollection</strong></span></p>
</td>
<td>Full GC后对年老代进行整理。可能会影响性能，但是可以消除碎片。默认true</td>
</tr>
<tr>
<td>
<p>-XX:+CMSIncrementalMode</p>
</td>
<td>
<p>CMS增量模式，适用于单CPU情况</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+CMSClassUnloadingEnabled</p>
</td>
<td>
<p>启用CMS类卸载功能</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+ScavengeBeforeFullGC</p>
</td>
<td>
<p>Full GC前清理年轻代( 1.4.1+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+|-UseLargePages</p>
</td>
<td>
<p>启用大内存分页支持</p>
</td>
</tr>
<tr>
<td>
<p>-XXLargePageSizeInBytes=n</p>
</td>
<td>
<p>设置大内存分页的最大尺寸</p>
</td>
</tr>
<tr>
<td>
<p>-XX:MaxHeapFreeRatio=70</p>
</td>
<td>
<p>GC后，堆的最大空余百分比</p>
</td>
</tr>
<tr>
<td>
<p>-XX:MinHeapFreeRatio=40</p>
</td>
<td>
<p>GC后，堆的最小空余百分比</p>
</td>
</tr>
<tr>
<td>
<p>-XX:ReservedCodeCacheSize=32m</p>
</td>
<td>最大代码缓存大小</td>
</tr>
<tr>
<td>
<p>-XX:CompileThreshold=10000</p>
</td>
<td>进行编译前，方法被执行的次数[-client: 1,500]</td>
</tr>
<tr>
<td>
<p>-XX:ThreadStackSize=512</p>
</td>
<td>
<p>线程堆栈大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseFastAccessorMethods</p>
</td>
<td>
<p>使用优化版的Get方法</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseLargePages</p>
</td>
<td>
<p>使用大页（large page）内存</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseMPSS</p>
</td>
<td>
<p>使用多内存页大小支持</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseStringCache</p>
</td>
<td>
<p>缓存普通分配的字符串 </p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseCompressedStrings</p>
</td>
<td>
<p>压缩字符串 (6u21+) </p>
</td>
</tr>
<tr>
<td>
<p>-XX:+OptimizeStringConcat</p>
</td>
<td>
<p>优化字符串连接(6u21+) </p>
</td>
</tr>
<tr>
<td>
<p>-XX:MaxDirectMemorySize</p>
</td>
<td>
<p>NIO中通过Direct内存来提高性能，这个区域的大小默认是64M，在适当的场景可以设置大一些</p>
</td>
</tr>
<tr>
<td>
<p>-Xrs</p>
</td>
<td>
<p>减少JVM对系统信号(Signals)使用</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+AggressiveOpts</p>
</td>
<td>
<p>启用在未来版本可能作为默认开启的优化选项(5.0u6)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:SoftRefLRUPolicyMSPerMB=0</p>
</td>
<td>
<p>启用积极的软引用处理，如果软引用对垃圾回收有影响了，可以使用。默认值为每兆1000毫秒。即对于堆中每MB大小的可用空间而言，一个软引用有1秒的存活时间（在最后一个强引用的对象被回收后）</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+DisableExplicitGC</p>
</td>
<td>
<p>禁止System.gc()的调用</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+UseGCOverheadLimit</p>
</td>
<td>
<p>抛出OutOfMemory前，允许的GC占用虚拟机时间的最大比例(6.0+)</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">G1垃圾回收器相关选项</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 35%; text-align: center;"> 参数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>-XX:+UseG1GC</td>
<td>开启G1垃圾回收器</td>
</tr>
<tr>
<td>-XX:InitiatingHeapOccupancyPercent=n</td>
<td>堆占用了多少时触发GC，默认为45</td>
</tr>
<tr>
<td>-XX:MaxTenuringThreshold</td>
<td>默认15</td>
</tr>
<tr>
<td>-XX:MaxGCPauseMillis=n</td>
<td> </td>
</tr>
<tr>
<td>-XX:NewRatio=n</td>
<td>默认2</td>
</tr>
<tr>
<td>-XX:SurvivorRatio=n</td>
<td>默认8</td>
</tr>
<tr>
<td>-XX:ParallelGCThreads=n</td>
<td>并行GC的线程数</td>
</tr>
<tr>
<td>-XX:ConcGCThreads=n</td>
<td>并发GC使用的线程数</td>
</tr>
<tr>
<td>-XX:G1ReservePercent=n</td>
<td>设置作为空闲空间的预留内存百分比，以降低目标空间溢出的风险。默认值是 10%</td>
</tr>
<tr>
<td>-XX:G1HeapRegionSize=n</td>
<td>设置G1区域的大小。值是2的幂，范围是1 MB 到32 MB</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">调试选项</span></div>
<table style="width: 100%;" border="1" width="100%" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 30%; text-align: center;" width="45%">
<p><strong>参数与默认值</strong></p>
</td>
<td style="text-align: center;" width="55%">
<p><strong>说明</strong></p>
</td>
</tr>
<tr>
<td>
<p>-XX:-CITime</p>
</td>
<td>
<p>打印JIT编译器消耗的时间(1.4.0+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:ErrorFile=./hs_err_pid%p.log</p>
</td>
<td>
<p>如果JVM错误发生，将错误保存到指定位置(6.0+)</p>
<p>例如 -XX:ErrorFile=F:\Temp\hs_err_pid%p.log（注意，在Windows的BAT脚本中%要换为%%）</p>
</td>
</tr>
<tr>
<td>-XX:+HeapDumpOnOutOfMemoryError</td>
<td>
<p>java.lang.OutOfMemoryError发生时，是否Dump堆镜像(1.4.2u12+, 5u7+)</p>
</td>
</tr>
<tr>
<td>-XX:+HeapDumpBeforeFullGC</td>
<td>
<p>在FGC前Dump出堆内存</p>
</td>
</tr>
<tr>
<td>-XX:+HeapDumpAfterFullGC</td>
<td>在FGC后Dump出堆内存</td>
</tr>
<tr>
<td>
<p>-XX:HeapDumpPath=/dir/</p>
</td>
<td>
<p>堆Dump文件的存储位置(1.4.2u12+, 5u7+)</p>
<p>例如 -XX:HeapDumpPath=F:\Temp\</p>
</td>
</tr>
<tr>
<td>
<p>-XX:OnError="CMD"</p>
</td>
<td>
<p>出现致命错误时，运行用户定义的命令(1.4.2u9+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:OnOutOfMemoryError="CMD"</p>
</td>
<td>
<p>出现内存溢出时，运行用户定义的命令(1.4.2u12+,6.0)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-PrintCommandLineFlags</p>
</td>
<td>
<p>打印附加的命令行标记 (5.0+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-PrintCompilation</p>
</td>
<td>当方法被编译时，打印信息</td>
</tr>
<tr>
<td>
<p>-XX:+PrintTLAB</p>
</td>
<td>打印TLAB分配的情况</td>
</tr>
<tr>
<td>
<p>-XX:-PrintGC</p>
</td>
<td>
<p>当GC活动时，打印信息</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-PrintGCDetails</p>
</td>
<td>
<p>打印GC详细信息 (1.4.0+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+PrintGCDateStamps</p>
</td>
<td>
<p>打印GC时间，形式：2014-11-17T16:05:24.673+0800</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-PrintGCTimeStamps</p>
</td>
<td>
<p>打印GC时间戳（相对时间），形式：0.104</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-TraceClassLoading</p>
</td>
<td>跟踪类加载</td>
</tr>
<tr>
<td>
<p>-XX:-TraceClassResolution</p>
</td>
<td>
<p>跟踪常量池解析 (1.4.2+)</p>
</td>
</tr>
<tr>
<td>
<p>-XX:-TraceClassUnloading</p>
</td>
<td>跟踪类的卸载</td>
</tr>
<tr>
<td>
<p>-XX:+PerfDataSaveToFile</p>
</td>
<td>退出时保存二进制的JVM统计信息到文件</td>
</tr>
<tr>
<td>
<p>-XX:+UseCompressedOops</p>
</td>
<td>
<p>使用压缩指针</p>
</td>
</tr>
<tr>
<td>
<p>-XX:InlineSmallCode=n</p>
</td>
<td>
<p>内联之前编译好的方法，如果其大小小于n</p>
</td>
</tr>
<tr>
<td>
<p>-XX:MaxInlineSize=35</p>
</td>
<td>
<p>内联方法最大字节码大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:FreqInlineSize=n</p>
</td>
<td>
<p>频繁使用的方法内联的最大字节码大小</p>
</td>
</tr>
<tr>
<td>
<p>-Xloggc</p>
</td>
<td>
<p>记录GC冗长信息到文件（不指定则记录到stdout）</p>
<pre class="crayon-plain-tag">-XX:+PrintFlagsFinal
-XX:PrintFLSStatistics=1
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:-TraceClassUnloading
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintTenuringDistribution
-Xloggc:%LOG_DIR%\gc.log</pre>
</td>
</tr>
<tr>
<td>
<p>-XX:-UseGCLogFileRotation
</td>
<td>记录GC冗长信息到文件时，启用文件轮换</td>
</tr>
<tr>
<td>
<p>-XX:NumberOfGClogFiles=1</p>
</td>
<td>上述文件轮换时，文件的个数</td>
</tr>
<tr>
<td>
<p>-XX:GCLogFileSize=8K</p>
</td>
<td>
<p>GC冗长日志的大小</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+PrintGCApplicationConcurrentTime</p>
</td>
<td>
<p>打印GC程序并发时间</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+PrintGCApplicationStoppedTime</p>
</td>
<td>
<p>打印GC停顿时间</p>
</td>
</tr>
<tr>
<td>
<p>-XX:+PrintTenuringDistribution</p>
</td>
<td>
<p>显示在Survivor区域里面有效的对象的年龄，ParNewGC分析此区域老对象的关系较为耗时</p>
</td>
</tr>
<tr>
<td>
<p>-XX:PrintFLSStatistics=N</p>
</td>
<td>
<p>打印FreeListSpace的统计信息，可以得到内存碎片的信息</p>
</td>
</tr>
<tr>
<td>
<p>-Xprof</p>
</td>
<td>
<p>剖析系统性能并输出到stdout </p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">JVM性能调优</span></div>
<div class="blog_h2"><span class="graybg">JVM内存与GC设置</span></div>
<div class="blog_h3"><span class="graybg">年轻代内存大小的选择</span></div>
<ol>
<li>响应时间优先：尽量大，直到<span style="background-color: #c0c0c0;">接近最低响应时间的需求</span>限制</li>
<li>吞吐量优先：尽量大，最好到达Gbits</li>
</ol>
<div class="blog_h3"><span class="graybg">年老代内存大小的选择</span></div>
<ol>
<li>响应时间优先：年老代默认CMS收集，需要考虑<span style="background-color: #c0c0c0;">会话并发数、持续时间</span>等因素，太小，造成碎片、高频回收、暂停并导致标记清除；太大，导致收集时间长。根据：CMS收集信息、次数、年轻代/年老代回收时间比率确定</li>
<li>吞吐量优先：一般都是<span style="background-color: #c0c0c0;">较大的年轻代、较小的年老代</span>。由于并发大，年轻代对象多，所以要求尽快收集年轻代对象，年老代存放长期存活对象</li>
<li>小堆引起的内存碎片问题：CMS算法不会对堆进行整理，导致碎片问题，配置 -XX:+UseCMSCompactAtFullCollection、-XX:CMSFullGCsBeforeCompaction来启用整理</li>
</ol>
<div class="blog_h3"><span class="graybg">使用G1垃圾回收器</span></div>
<p>7u9以后的版本，可以使用G1回收器，这是一个新型、综合的垃圾回收器。</p>
<p>可以使用类似如下方式启用：java -Xmx2G -Xms2G  -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1ReservePercent=20  -XX:ConcGCThreads=8</p>
<div class="blog_h3"><span class="graybg">关于64bit虚拟机</span></div>
<p>一般认为64bit虚拟机的性能低于32位，所有某些时候32bit虚拟机集群比一个大的64bit虚拟机更合适。</p>
<p>如果JVM内存配置很大，DUMP内存映像将是非常耗时的，这和磁盘写入速度有关系</p>
<p>对于年轻代来说，非常大的内存空间不会对性能造成显著影响；而对于年老代，除非使用CMS垃圾回收器（默认是吞吐量优先的Parallel Old），否则可能导致很长的GC停顿（30G以内的内存可能数十秒 ，更高的内存可能数分钟），这对于用户交互式程序是无法忍受的。</p>
<p>CMS对于大概小于4G的堆来说，其初始标记、重标记STW时间较短，对于频繁制造垃圾的大堆，则亦可能出现问题，当CMS的工作跟不上年老代被占满的步伐时，会导致STW的串行垃圾回收器被启用，在16G内存下，这可能导致30秒或者更多的停顿，尽早（年老代被占用百分比）启用CMS，可能在一定程度上减少STW出现几率。注意，越是多核心的处理器，越可能导致CMS停顿的问题，因为CMS只会使用一个核心。</p>
<p>G1垃圾回收器不能解决CMS的问题，它的吞吐量甚至低于CMS。</p>
<div class="blog_h3"><span class="graybg">一些最佳实践</span></div>
<ol>
<li>对于G1垃圾回收器：避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小，因为固定年轻代的大小会覆盖暂停时间目标</li>
<li>让堆的最小、最大尺寸一致</li>
<li>为新生代提供足够的空间，使大部分对象停留在新生代，同时，要避免老年代触发垃圾收集（保持有10-20%的空闲空间）</li>
<li>让新生代对象生命周期尽量短，避免使其进入年老代</li>
<li>避免短命的大对象，-XX:PretenureSizeThreshold设置直接进入年老代的阈值</li>
<li>将需要大量内存的模块使用C++等手工管理内存的语言实现</li>
<li>如果有海量内存，可以使用具有低延迟、无停顿GC的虚拟机，例如Azul，它的C4回收器具有这样的特征</li>
</ol>
<div class="blog_h2"><span class="graybg">GC日志分析</span></div>
<div class="blog_h3"><span class="graybg">ParNew+CMS日志分析</span></div>
<p>CMS相关垃圾回收的可能原因有：</p>
<ol>
<li>System.gc() 的调用</li>
<li>老年代占比超过-XX:CMSInitiatingOccupancyFraction</li>
<li>如果启用-XX:+CMSClassUnloadingEnabled，当永久代占比超过-XX:CMSInitiatingPermOccupancyFraction</li>
<li>晋升失败（Promotion Failed）：YGC时，Survivor区放不下所有对象，而且老年代也放不下，原因不一定是老年代空间不足，也可能是<span style="background-color: #c0c0c0;">老年代碎片引起</span></li>
<li>并发模式失败（Concurrent Mode Failure ）：CMS的同时进行YGC，导致CMS完成前老年代空间不足（Full Promotion Guarantee Failure），此情况下会发生Full GC。可能需要调小新生代或者减小-XX:CMSInitiatingOccupancyFraction</li>
</ol>
<pre class="crayon-plain-tag">'前缀时间戳，例如221810.102，表示JVM已经启动的秒数'
'时间times：user表示对于当前进程CPU消耗在user-mode的时间；sys表示对于当前进程CPU消耗在kernel-mode的时间；real表示物理时间的时间；'
'ParNew的年轻代垃圾收集（YGC），20024K为GC开始时的内存占用，497K为GC结束后的内存占用，39296K为总大小，0.0046067 为新生代局部收集消耗时间'
'后面的20024K-&gt;9715K(126720K)表示整个堆收集前后内存占用和总内存，最后的Times显示GC过程中的总时间消耗'
0.102: [GC0.102: [ParNew: 20024K-&gt;497K(39296K), 0.0046067 secs] 20024K-&gt;9715K(126720K), 0.0046827 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
0.172: [GC0.172: [ParNew: 19990K-&gt;599K(39296K), 0.0009549 secs]

'初始标记：STW，单线程（可以从user和real几乎相等看出），只标记ROOT能直接可达的对象，一般应该很快'
'786428K表示年老代已占用空间；后面的786432K表示年老代总空间'
221810.417: [GC [1 CMS-initial-mark: 786428K(786432K)] 4527562K(5740992K), 3.1298768 secs] [Times: user=3.14 sys=0.00, real=3.16 secs] 
221813.547: [CMS-concurrent-mark-start]
'并发标记阶段，不会STW'
221814.218: [CMS-concurrent-mark: 0.670/0.671 secs] [Times: user=2.46 sys=0.00, real=0.64 secs] 
'预清理，不会STW。该阶段检查并发标记阶段时从新生代晋升的对象、新分配的对象、被应用程序线程更新过的对象，减少重新标记阶段的暂停时间'
221814.218: [CMS-concurrent-preclean-start]
221814.223: [CMS-concurrent-preclean: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
'如果Eden占用量大于CMSScheduleRemarkEdenSizeThreshold（默认2M），会启动可中断预清理（不会STW）'
221814.223: [CMS-concurrent-abortable-preclean-start]
221814.223: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
'预清理阶段只是一个取样过程，它将新生代按一定间隔进行分块，标记起始位置，以便remark时可以并行的的对块进行Trace'
'预清理阶段最好发生一次YGC，-XX:CMSScavengeBeforeRemark可以强制重标记前发生YGC'
    221814.224: [GC[YG occupancy: 3774483 K (4954560 K)]
    221814.224: [Rescan (parallel) , 4.1513281 secs]
    221818.375: [weak refs processing, 0.0000280 secs]
    221818.375: [scrub string table, 0.0014394 secs] 
        '重新标记，STW，多线程，默认通过-XX:ParallelGCThreads计算得到线程数，此阶段通常是CMS中暂停时间最长的'
        [1 CMS-remark: 786428K(786432K)] 4560911K(5740992K), 4.1529812 secs] [Times: user=28.08 sys=0.00, real=4.17 secs] 

'并发清理，不会STW'
221818.377: [CMS-concurrent-sweep-start]
CMS: Large Block: 0x00000007f8000000; Proximity: 0x00000007f7851ea0 -&gt; 0x00000007f7851ea0
CMS: Large block 0x0000000000000000
221818.736: [CMS-concurrent-sweep: 0.358/0.358 secs] [Times: user=0.87 sys=0.00, real=0.36 secs] 
'重置，CMS数据结构重新初始化，为下一次CMS做准备'
221818.736: [CMS-concurrent-reset-start]
221818.738: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

'紧跟着一次并发模式失败的Full GC'
221890.743: [GC221890.743: [ParNew: 18434K-&gt;18434K(39296K), 0.0000233 secs]90.743: [CMS90.743: [CMS-concurrent-abortable-preclean: 0.000/0.028 secs] [Times: user=0.02 sys=0.00, real=0.03 secs] 
 (concurrent mode failure): 83535K-&gt;37453K(87424K), 0.0111540 secs] 101969K-&gt;37453K(126720K), [CMS Perm : 3284K-&gt;3284K(65536K)], 0.0112398 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
221890.754: [Full GC221890.754: [CMS: 37453K-&gt;37453K(87424K), 0.0051618 secs] 37453K-&gt;37453K(126720K), [CMS Perm : 3284K-&gt;3284K(65536K)], 0.0051982 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]</pre>
<div class="blog_h3"><span class="graybg">Survivor区对象年龄分布分析</span></div>
<p>开启：-XX:+PrintTenuringDistribution可以打印分布信息</p>
<pre class="crayon-plain-tag">'期望的Survivor区占用由-XX:TargetSurvivorRatio=50指定'
'threshold 即晋升的年龄限制，可由-XX:MaxTenuringThreshold指定'
Desired survivor size 2228224 bytes, new threshold 6 (max 6)
- age   1:        232 bytes,        232 total
- age   2:     478840 bytes,     479072 total '各年龄对象的字节数、数量'</pre>
<div class="blog_h3"><span class="graybg"> 停顿时间分析</span></div>
<p>开启-XX:+PrintGCApplicationConcurrentTime、-XX:+PrintGCApplicationStoppedTime可以打印并发时间、停顿时间</p>
<pre class="crayon-plain-tag">'应用和GC并发执行的时间'
Application time: 0.0362382 seconds
'应用被GC停止的时间'
Total time for which application threads were stopped: 0.0039536 seconds</pre>
<div class="blog_h3"><span class="graybg">在启动时打印所有GC标记</span></div>
<p>开启 -XX:+PrintFlagsFinal可以打印所有GC标记</p>
<pre class="crayon-plain-tag">[Global flags]
    uintx AdaptivePermSizeWeight                    = 20              {product}           
    uintx AdaptiveSizeDecrementScaleFactor          = 4               {product}           
    uintx AdaptiveSizeMajorGCDecayTimeScale         = 10              {product}           
    ……</pre>
<div class="blog_h3"><span class="graybg">分析内存碎片信息</span></div>
<p>开启-XX:PrintFLSStatistics=1可以统计内存碎片</p>
<pre class="crayon-plain-tag">Before GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 11190272
Max   Chunk Size: 11190272
Number of Blocks: 1
Av.  Block  Size: 11190272
Tree      Height: 1

After GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 9993976
Max   Chunk Size: 9993976
Number of Blocks: 1
Av.  Block  Size: 9993976
Tree      Height: 1</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jvm-options-and-performance-tuning">JVM参数与性能调优</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jvm-options-and-performance-tuning/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>High Performance MySQL学习笔记</title>
		<link>https://blog.gmem.cc/high-performance-mysql-study-note</link>
		<comments>https://blog.gmem.cc/high-performance-mysql-study-note#comments</comments>
		<pubDate>Wed, 15 Feb 2012 01:27:10 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[性能调优]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=2967</guid>
		<description><![CDATA[<p>MySQL的架构和历史 MySQL与其它数据库软件很不相同，其架构特性让其具有广泛的使用范围。 MySQL的逻辑架构 MySQL的逻辑架构可以简单的描述为下图： 最上面的一层不是MySQL专有的组件，负责网络连接的处理、身份验证、安全性等逻辑 第二层是MySQL的核心所在，包括parsing, analysis, optimization, caching和内置函数在内的功能均在此实现。提供所有跨引擎的功能，例如procedures, triggers, views 第三层是存储引擎，负责存取数据。每种存储引擎各有特长。MySQL使用Storage engine API与之通信 连接管理与安全性  每个客户端连接在服务端都有自己对应的线程，连接进行的查询在单线程中运行，对应了一个CPU或者核，MySQL会缓存线程供不同连接重用。 客户端连接时需要身份验证，可以基于用户名密码的方式，使用SSL时，则可以基于X.509数字证书验证。 客户端连接成功后，其操作会被授权判断。 优化与执行 MySQL首先会针对SELECT语句来检查查询缓存——其中包含SELECT语句和它关联的结果集——如果语句完全一样，则简单的返回缓存的结果集。 然后MySQL会把SQL查询解析为内部的Parse tree结构，并进行一系列的优化，包括： <a class="read-more" href="https://blog.gmem.cc/high-performance-mysql-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/high-performance-mysql-study-note">High Performance MySQL学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">MySQL的架构和历史</span></div>
<p>MySQL与其它数据库软件很不相同，其架构特性让其具有广泛的使用范围。</p>
<div class="blog_h3"><span class="graybg">MySQL的逻辑架构</span></div>
<p>MySQL的逻辑架构可以简单的描述为下图：</p>
<p><img class="aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2012/02/mysql-1.jpg" alt="" width="250" /></p>
<ol>
<li>最上面的一层不是MySQL专有的组件，负责<span style="background-color: #c0c0c0;">网络连接的处理、身份验证、安全性等逻辑</span></li>
<li>第二层是<span style="background-color: #c0c0c0;">MySQL的核心所在</span>，包括parsing, analysis, optimization, caching和内置函数在内的功能均在此实现。提供所有跨引擎的功能，例如procedures, triggers, views</li>
<li>第三层是<span style="background-color: #c0c0c0;">存储引擎，负责存取数据</span>。每种存储引擎各有特长。MySQL使用Storage engine API与之通信</li>
</ol>
<p><span style="text-decoration: underline;"><strong>连接管理与安全性</strong></span></p>
<p> 每个客户端<span style="background-color: #c0c0c0;">连接在服务端都有自己对应的线程</span>，连接进行的查询在单线程中运行，对应了一个CPU或者核，MySQL会缓存线程供不同连接重用。</p>
<p>客户端连接时需要身份验证，可以基于用户名密码的方式，使用SSL时，则可以基于X.509数字证书验证。</p>
<p>客户端连接成功后，其操作会被授权判断。</p>
<p><span style="text-decoration: underline;"><strong>优化与执行</strong></span></p>
<p>MySQL首先会针对SELECT语句来检查<span style="background-color: #c0c0c0;">查询缓存</span>——其中包含SELECT语句和它关联的结果集——如果语句完全一样，则<span style="background-color: #c0c0c0;">简单的返回缓存的结果集</span>。</p>
<p>然后MySQL会把SQL查询解析为内部的Parse tree结构，并进行一系列的优化，包括：</p>
<ol>
<li>重写（rewriting）查询</li>
<li>确定读取表的顺序</li>
<li>选择使用的索引</li>
</ol>
<p>通过<span style="background-color: #c0c0c0;">在SQL语句中附加提示（Hint）</span>，可以影响上面的优化行为。</p>
<p>MySQL优化器不关心特定表使用了何种存储引擎，但是存储引擎会影响查询的优化，优化器根据<span style="background-color: #c0c0c0;">引擎的特性、特定操作的成本、表的统计信息</span>来决定如何优化。</p>
<div class="blog_h3"><span class="graybg">并发控制</span></div>
<p>任何超过一个SQL需要改变数据时，都存在并发问题。处理并发最简单的手段是锁机制，但是可能带来性能问题。</p>
<p><span style="text-decoration: underline;"><strong>读写锁（Read/Write Locks）</strong></span></p>
<p>共享锁（shared locks），又称读锁，<span style="background-color: #c0c0c0;">只会阻塞其它的写锁</span></p>
<p>独占锁（exclusive locks），又称写锁，会<span style="background-color: #c0c0c0;">阻塞其它的读锁、写锁</span></p>
<p><span style="text-decoration: underline;"><strong>锁粒度（Lock Granularity）</strong></span></p>
<p>进行选择性的锁定而不是锁住整个资源，可以增大并发。锁定策略（locking strategy）是一种数据安全性与锁定成本（lock overhead）的折衷。相比起其它数据库，MySQL给予用户给多的锁粒度选择的可能（不限制引擎的实现方式）：</p>
<ol>
<li>表锁（Table locks）：写数据（insert, delete, update）时获取整张表的锁定，其它客户端不能读取或者写入此表。通常写锁在等待队列中具有比读锁更高的优先级</li>
<li>行锁（Row locks）：允许最高的并发（和最高的锁定成本），InnoDB 、XtraDB支持行锁</li>
</ol>
<div class="blog_h3"><span class="graybg">事务</span></div>
<p>考虑下面的场景：将Jane的200美元从她的活期账户转移到储蓄账户：</p>
<ol>
<li>确保获取账户的余额大于200</li>
<li>把活期账户余额减去200</li>
<li>把储蓄账户的余额增加200</li>
</ol>
<p>对应的SQL语句：</p>
<pre class="crayon-plain-tag">START TRANSACTION;
SELECT balance FROM checking WHERE customer_id = 10233276;
UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 10233276;
UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 10233276;
COMMIT;</pre>
<p> 如果执行到上面第四行，数据库服务器崩溃了，会发生什么？白白扣除Jane 200美元？要实现事务性，必须通过ACID测试：</p>
<ol>
<li> 原子性（Atomicity）：整个事务作为不可分操作完成，要么提交，要么回滚</li>
<li>一致性（Consistency）：数据库只能在两个一致性状态之间变换，例如上面的例子，即使在第四行崩溃，也不会出现数据不一致性，因为事务绝不会提交</li>
<li>隔离性（Isolation）：一般的，事务操作的结果在提交前，对其它事务不可见。这依赖于事务隔离级别的配置</li>
<li>持久性（Durability）：一旦提交，事务对数据的改变即被持久化，不会因为系统崩溃而丢失</li>
</ol>
<p><span style="text-decoration: underline;"><strong>隔离级别</strong></span></p>
<p><span style="background-color: #c0c0c0;">SQL标准定义了4种隔离级别，低的隔离级别带来更多的并发、更低的成本（overhead）：</span></p>
<ol>
<li>读取未提交（READ UNCOMMITTED）：不同事务互相看到对方未提交的修改。允许脏读（dirty read）</li>
<li>读取已提交（READ COMMITTED）：大部分数据库的默认隔离级别（MySQL不是），存在不可重复读问题，在同一个事务中两次运行同一个查询，结果可能不一样。允许不可重复读（nonrepeatable read）</li>
<li>可重复读（REPEATABLE READ）：MySQL默认级别。保证在一个事务中，多次读取同一行，其数据保持一致。允许幻影读（phantom reads），幻影读在读取一个范围的数据时会发生，出现数据变多或变少的情况</li>
<li>串行化（SERIALIZABLE）：最高的隔离级别，强制事务排队执行。</li>
</ol>
<p><strong><span style="text-decoration: underline;">死锁</span></strong></p>
<p>死锁在多个事务同时持有、而又请求对方的资源时发生。数据库依赖于死锁检测、超时等机制来解决死锁问题。例如<span style="background-color: #c0c0c0;">InnoDB会立即检测到死锁并回滚持有最少独占行锁的事务</span>。</p>
<p>锁的行为和次序是存储引擎相关的，所有同样的业务场景在某些引擎下死锁，另外一些则不会。</p>
<p><span style="text-decoration: underline;"><b>事务日志（Transaction Logging）</b></span></p>
<p>事务日志可以让事务处理的效率更高。存储引擎可以在发生数据变更时，<span style="background-color: #c0c0c0;">不去写表，而是写在内存中，随后写入事务日志中</span>。事务日志虽然和写表都是磁盘操作，但是前者是小范围的顺序写入，而后者是大范围的随机写入，故前者效率很高。</p>
<p><span style="text-decoration: underline;"><b>MySQL中的事务</b></span></p>
<p><strong>自动提交（AUTOCOMMIT）</strong></p>
<p>默认情况下，MySQL运作在自动提交模式——除非手工开启事务，否则每个语句在一个事务中运行。</p>
<p>运行：SET AUTOCOMMIT = 0;可以禁止自动提交，注意这个设置对非事务性表，例如MyISAM 、Memory表没有意义。</p>
<p>某些语句，例如DDL、LOCK TABLES可能强制性的进行提交。</p>
<p><strong>事务中混合多种引擎（Mixing storage engines in transactions）</strong></p>
<p>在一个<span style="background-color: #c0c0c0;">事务中包含多种存储引擎的操作是不可靠的</span>。例如：对于事务性表A、非事务性表B，在一个事务中进行，如果成功，则没有问题，如果失败，<span style="background-color: #c0c0c0;">非事务性表是无法回滚</span>的。</p>
<p><strong>隐含和明确锁定（Implicit and explicit locking）</strong></p>
<p> InnoDB<span style="background-color: #c0c0c0;">使用两阶段锁定协议</span>（two-phase locking protocol）。可以在事务的<span style="background-color: #c0c0c0;">任何阶段获得锁，但是只有在提交或回滚时才释放锁</span>——在同时释放所有锁。根据隔离级别的设置，InnoDB 隐含的处理所有锁。尽管如此， InnoDB支持明确锁定：</p>
<ol>
<li>SELECT ... LOCK IN SHARE MODE</li>
<li>SELECT ... FOR UPDATE</li>
</ol>
<p>此外，MySQL在上层<span style="background-color: #c0c0c0;">支持表锁定和解锁</span>（使用命令：LOCK TABLES、UNLOCK TABLES）</p>
<div class="blog_h3"><span class="graybg">多版本并发控制</span></div>
<p>大部分MySQL引擎不是简单的使用行锁定，而是使用MultiVersion Concurrency Control (MVCC)，这是很多数据库例如Oracle、PostgreSQL等都在使用的技术。MVCC在<span style="background-color: #c0c0c0;">很多情况下避免锁定</span>，因而性能较好，MVCC通常会实现<span style="background-color: #c0c0c0;">无锁读（nonlocking reads）</span>，只有在写入时要求锁定。</p>
<p>MVCC通过<span style="background-color: #c0c0c0;">保持某些时间点的数据快照</span>来实现无锁读。这意味着，单个事务可以看到一致性的数据（事务开始的时间点）；而不同事务在同一时间看同一张表，数据却可能是不同的。</p>
<p>每种存储引擎使用不同的方式实现MVCC，有些变种包括乐观、悲观并发控制的功能。InnoDB的实现方式：为每行添加额外的2个隐藏字段来记录行<span style="background-color: #c0c0c0;">被创建的时间、过期（或删除）的时间，</span>注意，InnoDB并不使用真实时间，而是数字的版本号（每个事务开始时版本号增加）来记录上述两个记录，在可重复度隔离级别下，InnoDB的MVCC的行为如下：</p>
<ol>
<li>SELECT：InnoDB必须检查每行确保满足以下2条规则：<br />a)：必须找到<span style="background-color: #c0c0c0;">行的至少小于等于事务的版本</span>——即数据在事务前即存在，或者事务创建了此数据<br />b)：行的删除版本必须未定义或者大于事务的版本——即数据不是在事务之前删除的</li>
<li>INSERT：InnoDB把当前系统版本号设置为新行的版本号</li>
<li>DELETE：InnoDB把当前系统的版本号设置为行的Deletion版本号</li>
<li>UPDATE：InnoDB写入行的拷贝，把系统版本号赋予这一新行，同时把系统版本号赋予旧行的Delete版本号</li>
</ol>
<p>以上行为保证了大部分的读不需要锁定，缺点是需要额外存储、管理许多数据。</p>
<p>MVCC仅与REPEATABLE READ 、READ COMMITTED一起工作。</p>
<div class="blog_h3"><span class="graybg">MySQL的存储引擎</span></div>
<p>MySQL存储每一个数据库（又称schema）在数据目录（data directory）下提供一个文件夹。创建表时，表的定义存放在table_name.frm文件中。</p>
<p><span style="text-decoration: underline;"><strong>InnoDB引擎</strong></span></p>
<p>最常用的引擎，也是默认的事务引擎。InnoDB把数据存放在单个或者一系列称为表空间（tablespace）的文件中，<span style="background-color: #c0c0c0;">MySQL4.1以后InnoDB存储数据和索引到不同文件</span>，支持在原始磁盘分区上（raw disk partitions）构建表空间。</p>
<p>InnoDB的默认隔离级别为<span style="background-color: #c0c0c0;">REPEATABLE READ，在此级别下，使用next-key锁策略（next-key locking strategy）来防止幻影读</span>——不仅仅锁定SQL涉及的行，还<span style="background-color: #c0c0c0;">锁定索引结构中的间隙（gaps）</span>，阻止幻影数据被插入。</p>
<p>InnoDB表建立在聚簇索引（clustered index）上。InnoDB<span style="background-color: #c0c0c0;">按主键查找的速度非常快</span>，但是普通索引（secondary indexes）包含主键列信息，因此，如果<span style="background-color: #c0c0c0;">主键大，则索引也会很大</span>，因此对于具有很多索引的大表，选择较小的主键可以提高性能。</p>
<p>InnoDB<span style="background-color: #c0c0c0;">索引的存储结构是平台中立的</span>，从Windows上拷贝到Linux上没有任何问题。</p>
<p>InnoDB支持真正的<span style="background-color: #c0c0c0;">热备份</span>。</p>
<p><strong><span style="text-decoration: underline;">MyISAM引擎</span></strong></p>
<p>MySQL 5.1或者更老版本的默认引擎。该引擎提供一系列特性，例如：<span style="background-color: #c0c0c0;">全文索引、压缩、空间（spatial）数据库</span>。但是<span style="background-color: #c0c0c0;">不支持事务和行锁</span>，此外，它还<span style="background-color: #c0c0c0;">不是宕机安全的</span>（non-crash-safe）。对于<span style="background-color: #c0c0c0;">只读数据、表不是特别大</span>（修复起来不是很痛苦），可以选择此引擎。</p>
<p>MyISAM引擎把数据文件和索引文件单独存放，扩展名分别为：.MYD 、.MYI。MyISAM支持<span style="background-color: #c0c0c0;">定长（fixed-length）的行</span>，会根据DDL自动选择是否启用。</p>
<p>MyISAM具有以下特性：</p>
<ol>
<li>锁定与并发：<span style="background-color: #c0c0c0;">MyISAM锁定整张表</span>，而不是行。读操作共享锁定其涉及的所有表，写操作独占锁定目标表。但是，<span style="background-color: #c0c0c0;">SELECT查询运行时，可以进行插入操作</span>。</li>
<li>修复：支持手工或者自动修复表。使用REPAIR TABLE语句或者离线时使用myisamchk可以修复。修复的速度是非常慢的</li>
<li>索引特性：支持对BLOB 、TEXT前500字符进行索引，支持<span style="background-color: #c0c0c0;">全文索引</span></li>
<li>延迟索引写入（Delayed key writes）：创建时标记为DELAY_KEY_WRITE的表，不会立即写入索引数据到磁盘，而是使用内存缓冲。这可以提高性能，但是宕机后索引必定坏掉，需要修复</li>
</ol>
<p>MyISAM重要的<span style="background-color: #c0c0c0;">性能问题是表锁定</span>，如果很多查询处于Locked状态，说明此问题严重。</p>
<p><span style="text-decoration: underline;"><b>选择正确的引擎</b></span></p>
<p>以下场景可以考虑MyISAM：</p>
<ol>
<li>只读或者几乎只读的表</li>
<li>需要使用全文索引</li>
</ol>
<div class="blog_h2"><span class="graybg">Benchmarking MySQL</span></div>
<p>可以使用sysbench来进行MySQL的性能测试。主要度量包括：<span style="background-color: #c0c0c0;">吞吐量</span>（Throughput，单位时间内的事务数）、<span style="background-color: #c0c0c0;">响应时间</span>（Response time or latency，任务消耗的时间，这是性能最根本的指标）、<span style="background-color: #c0c0c0;">并发</span>（Concurrency，高并发情况下的测试）、<span style="background-color: #c0c0c0;">稳定性</span>（Scalability）</p>
<div class="blog_h2"><span class="graybg">剖析服务器性能</span></div>
<div class="blog_h3"><span class="graybg">剖析MySQL查询</span></div>
<p><span style="text-decoration: underline;"><strong>分析服务器负载</strong></span></p>
<p>缓慢查询日志（slow query log）可以整体上分析服务器的性能。服务器端变量<span style="background-color: #c0c0c0;">long_query_time</span>用于设定阈值，设为零可以捕获所有查询。</p>
<p>SHOW FULL PROCESSLIST也可以看到缓慢的语句。</p>
<p id="profiling-a-single-query"><span style="text-decoration: underline;"><strong>分析单个查询</strong></span></p>
<p><strong>使用SHOW PROFILE<a id="show-profile-for-single-query"></a></strong></p>
<pre class="crayon-plain-tag">-- 启用当前会话的Session
SET profiling = 1;
-- 执行目标SQL
-- 显示剖析结果
SHOW PROFILES;
-- 显示单个查询的耗时
SHOW PROFILE FOR QUERY 1;</pre><br />
<pre class="crayon-plain-tag">-- 使用INFORMATION_SCHEMA.PROFILING表剖析
SET @query_id = 1;

SELECT
	STATE,
	SUM(DURATION) AS "总计耗时",
	ROUND(
		100 * SUM(DURATION) / (
			SELECT SUM(DURATION)
			FROM INFORMATION_SCHEMA.PROFILING
			WHERE QUERY_ID = @query_id
		),2 ) AS "耗时占比",
	COUNT(*) AS "调用次数",
	SUM(DURATION) / COUNT(*) AS "平均耗时"
FROM
	INFORMATION_SCHEMA.PROFILING
WHERE
	QUERY_ID = @query_id
GROUP BY
	STATE
ORDER BY
	Total_R DESC;</pre>
<p><strong>使用SHOW STATUS</strong></p>
<p>此命令显示一些计数器，缺点是没有时间的度量。可以使用FLUSH STATUS清空记录，并执行需要分析的SQL，再次查询检查状态的变化。</p>
<p><strong>使用Performance Schema</strong></p>
<div class="blog_h2"><span class="graybg">优化Schema和数据类型</span></div>
<div class="blog_h3"><span class="graybg">选择最佳数据类型</span></div>
<p>选择正确的数据类型 ，对于MySQL的性能具有重要作用。以下是一些指导性规则：</p>
<ol>
<li>数据尽可能的小，不论用什么类型</li>
<li>简单即可，例如整数类型比字符类型的性能好（后者具有字符集、字符比较问题）。应当使用MySQL内置类型表示时间日期、使用数字存储IP地址</li>
<li>仅可能的避免空值。可空列不利于MySQL进行查询优化</li>
<li>使用整数来代替实数（DECIMAL）</li>
<li>对于特别短的字符串（例如1字符），CHAR优于VARCHAR；对于定长、基本定长字符串，CHAR优于VARCHAR</li>
<li>对于变长（特别是最大长度比平均值大很多）字符串、UTF8字符串，适合VARCHAR</li>
<li>使用枚举代替字符串</li>
</ol>
<p><span style="text-decoration: underline;"><strong>整数类型</strong></span></p>
<p>支持<span style="background-color: #c0c0c0;">TINYINT,SMALLINT, MEDIUMINT, INT, BIGINT</span>，分别占用8, 16, <span style="background-color: #c0c0c0;">24</span>, 32, 64位空间。</p>
<p>支持<span style="background-color: #c0c0c0;">UNSIGNED</span>标记，这样不支持负数，可以增加一倍的最大值。有<span style="background-color: #c0c0c0;">无符号对性能没有影响</span>。</p>
<p><span style="background-color: #c0c0c0;">指定宽度</span>，例如INT(11)，<span style="background-color: #c0c0c0;">对于引擎来说没有任何意义</span>，只是为了交互式工具的需要。</p>
<p><span style="text-decoration: underline;"><b>实数类型</b></span></p>
<p>可以使用DECIMAL来代替BIGINT来存储非常大的整数</p>
<p>FLOAT 、DOUBLE的计算结果与平台上同类型相似，其计算不是精确的；<span style="background-color: #c0c0c0;">DECIMAL支持精确的计算</span>。这两者均支持设置精度（precision），对于DECIMAL可以<span style="background-color: #c0c0c0;">指定小数点前后允许的尾数</span>。</p>
<p>对于DECIMAL，<span style="background-color: #c0c0c0;">MySQL存储9位数字需要4字节</span>，例如DECIMAL(18, 9)，支持9位整数9位小数，需要4+4+1=9字节，1为小数点自己需要的存储。MySQL 5.0以后的版本，DECIMAL最多支持65位数字，但是在计算时，只能支持到和DOUBLE一样的数值范围。</p>
<p><span style="text-decoration: underline;"><strong>字符串类型</strong></span></p>
<p><strong>VARCHAR 和CHAR</strong></p>
<p>字符串类型的存储方式是引擎决定的</p>
<p>VARCHAR是最常用的字符串类型，相比定长的CHAR，它更加节省空间（用多少存多少），一个例外是MyISAM定长行的表。VARCHAR使用1-2字节记录其长度，对于latin1字符集，VARCHAR(10)需要11字节，而VARCHAR(1000)需要1002字节。尽管VARCHAR通过节省磁盘提供性能，但是对于会发生Upadte的行，如果VARCHAR列变了，将会发生引擎依赖的行为，对于InnoDB，将会split the page 来适应行大小的改变。</p>
<p>CHAR则是定长的类型，MySQL会清除尾部的空格。</p>
<p><strong>BLOB 和TEXT</strong></p>
<p>这两者用来存储大的基于二进制、字符的字符串。唯一的区别是一个基于二进制，一个具有字符集</p>
<p>对于TEXT，有TINYTEXT, SMALL TEXT, TEXT, MEDIUMTEXT, LONGTEXT等具体类型，TEXT是SMALLTEXT的同义词</p>
<p>对于BLOG，有TINYBLOB, SMALLBLOB, BLOB, MEDIUMBLOB, and LONGBLOB等具体类型，BLOG是SMALLBLOG的同义词</p>
<p>和其他数据类型不同，MySQL将这两种字段作为具<span style="background-color: #c0c0c0;">有自身标识符的对象</span>来处理，对于InnoDB，这些数据类型并存放在外部（external）的空间。另外这<span style="background-color: #c0c0c0;">两种字段的排序处理也特殊，只会排序max_sort_length前面</span>的字符。</p>
<p>注意内存表不支持这两字段类型，不要使用。</p>
<p><strong>枚举列</strong></p>
<p>枚举列是具有预定义值列表的字符串列。值列表的修改必要DDL。在字符串上下文中，会转换为字符串比较</p>
<p>避免枚举与VARCHAR的连接查询。</p>
<p><span style="text-decoration: underline;"><strong>日期和时间类型</strong></span></p>
<p>MySQL支持多种日期时间类型，<span style="background-color: #c0c0c0;">最细粒度为秒的存储，但是，支持毫秒级别的计算</span>。</p>
<p><strong>DATETIME</strong></p>
<p>支持<span style="background-color: #c0c0c0;">大范围的值</span>，从1001 - 9999年，精确到秒。使用<span style="background-color: #c0c0c0;">整数</span>形式<span style="background-color: #c0c0c0;">YYYYMMDDHHMMSS</span>来存储其值，值<span style="background-color: #c0c0c0;">与时区无关</span>。默认的，MySQL使用可排序无歧义方式显示该字段类型，例如2008-01-16 22:37:08。需要<span style="background-color: #c0c0c0;">8字节</span>存储</p>
<p><strong>TIMESTAMP</strong></p>
<p>存储1970-01-01以来流逝的秒数，只需要<span style="background-color: #c0c0c0;">4字节</span>存储，支持1970-2038年。使用FROM_UNIXTIME() 、UNIX_TIMESTAMP()函数可以转换Unix时间戳、日期。MySQL 4.1+版本该字段的显示与DATETIME一致，<span style="background-color: #c0c0c0;">与时区相关</span>。</p>
<p>时间戳字段默认是NOT NULL的，插入时不指定值，会插入当前时间</p>
<p><span style="text-decoration: underline;"><strong>位包装类型</strong></span></p>
<p>MySQL支持一些用单独位来存储的类型，这些类型从技术上讲属于字符串类型</p>
<p><strong>BIT</strong></p>
<p>MySQL5.0之前只是TINYINT的同义词。可以使用BIT字段存放一个或者多个true/false字段，例如：<span style="background-color: #c0c0c0;">BIT(8)可以存放8个布尔值</span>。对于MyISAM，此数据类型比较节省空间，InnoDB则是使用足够存储其的最小INT类型。在字符串上下文中，会转换为字符串比较</p>
<p><span style="text-decoration: underline;"><b>选择标识符</b></span></p>
<p>整数是最适合最为标识符列的，因为速度快、可以自增长（AUTO_INCREMENT）</p>
<p>如果可能，<span style="background-color: #c0c0c0;">避免使用字符串类型</span>作为标识符。特别是使用MyISAM时，由于其默认对字符串使用packed indexes，则可能导致<span style="background-color: #c0c0c0;">6倍的性能下降</span>。特别<span style="background-color: #c0c0c0;">小心</span>使用<span style="background-color: #c0c0c0;">随机性质的标识符</span>，例如MD5(), SHA1(), UUID()，新插入的数据可能随机的进入大表空间的任何位置，<span style="background-color: #c0c0c0;">导致插入INSERT、某些SELECT性能下降</span>：</p>
<ol>
<li>INSERT慢的原因是，数据可能插入随机的索引位置，导致<span style="background-color: #c0c0c0;">page splits、随机磁盘访问、聚簇索引碎片化</span>（fragmentation）</li>
<li>SELECT慢的原因是，<span style="background-color: #c0c0c0;">逻辑上相邻的列，在物理、内存分布上相距很远</span></li>
<li>随机值导致<span style="background-color: #c0c0c0;">所有查询的缓存效果低</span>下，这和引用的位置（locality of reference）有关——如果整个数据集的热点程度一样，将导致内存缓存命中率低</li>
</ol>
<p>如果必须使用UUID，可以去掉其中的横线，最好是使用UNHEX()转换为16字节的数字，并存储到BINARY(16)列。相比起MD5，UUID还是具有一定的非平均分布特征、序列性的，虽然这不能和INTEGER相比。</p>
<div class="blog_h3"><span class="graybg">设计Schema时的注意点</span></div>
<p>在设计MySQL表结构时，应注意不要：</p>
<ol>
<li>过多的列：存储引擎和上层服务之间的数据格式需要转换，这种转换是以行为单位的，其成本与列数量成正比</li>
<li>过多的JOIN：表连接数量最好十个以下</li>
<li>过大的枚举值列表</li>
</ol>
<div class="blog_h3"><span class="graybg">规范化和反规范化</span></div>
<p>规范化是指对范式的遵从程度。</p>
<p><span style="text-decoration: underline;"><strong>规范化的优缺点</strong></span></p>
<p>优点：</p>
<ol>
<li>UPDATE通常比反规范化设计快</li>
<li>由于没有冗余，需要更新的数据少</li>
<li>规范化设计的表通常比较小</li>
</ol>
<p>缺点：要求过多的JOIN，这不但资源消耗大，并且会导致一些索引策略无效</p>
<p><span style="text-decoration: underline;"><strong>反规范化的优缺点</strong></span></p>
<p>优点：</p>
<ol>
<li>避免JOIN，最糟糕的查询也就是全表扫描。如果数据不再内存中，这会比JOIN快很多，因为避免了随机访问</li>
<li>允许更高效的索引策略，考虑下面的场景：<br />
<pre class="crayon-plain-tag">-- 这是一个规范化的表设计
-- 需要查询高级用户的前十条（根据发布时间）消息
SELECT message_text, user_name
FROM message
    INNER JOIN user ON message.user_id=user.id
WHERE user.account_type='premium'
ORDER BY message.published DESC LIMIT 10;
-- 在上述查询中，MySQL会扫描message表的published索引，对于每一行，需要查找user表
-- 来看他是不是高级用户，如果只有很少的用户是高级的，这个索引策略将是低效的

-- 问题就出在JOIN上，它导致无法在单个索引上同时完成过滤和排序，如果使用非规范化设计，并且在account_type, published上设计联合索引，则会很高效</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">缓存和摘要（Summary）表</span></div>
<p>有时候，相比起冗余字段设计，<span style="background-color: #c0c0c0;">缓存表和摘要表是更好的选择，特别是在允许数据不准确</span>（stale）的情况下。</p>
<p>这两种表并不是精确的概念，所谓缓存表，是指其存放<span style="background-color: #c0c0c0;">获取需要很大成本的数据</span>；所谓摘要表，是指其存放经过聚合（aggregated）的数据</p>
<p><span style="text-decoration: underline;"><strong>物化视图（Materialized Views）</strong></span></p>
<p>诸如Oracle、Microsoft SQL Server之类的DBMS提供了物化视图的概念，即<span style="background-color: #c0c0c0;">预先计算并存放在磁盘、可以依据特定策略进行更新</span>的视图。MySQL没有原生的实现，但是可以使用开源的Flexviews工具达到类似的效果，它有如下特性：</p>
<ol>
<li>基于MySQL二进制日志的CDC（Change Data Capture）</li>
<li>一系列用于管理视图定义的存储过程</li>
<li>更新物化数据的工具</li>
</ol>
<p><span style="text-decoration: underline;"><strong>计数表（Counter Tables）</strong></span></p>
<p>对于只有一行数据的计数表，将会导致所有事务并串行执行，极大的降低并发性，<span style="background-color: #c0c0c0;">可以设置一个100行的计数表</span>，然后使用where slot = RAND() * 100的方式进行随机插入，获取总数时，SUM即可。</p>
<div class="blog_h2"><span class="graybg">高性能的索引</span></div>
<div class="blog_h3"><span class="graybg">索引基本知识</span></div>
<p>索引（MySQL中又称keys）<span style="background-color: #c0c0c0;">是用于快速寻找到行的数据结构</span>。不适合的索引会引起性能问题，多索引的优化是最有效的提升查询速度的手段。索引的优化可能会要求查询语句的重写。</p>
<p>索引可以包含多个列，这种情况下<span style="background-color: #c0c0c0;">列顺序很重要</span>，因为MySQL只能对<span style="background-color: #c0c0c0;">最左边的索引前缀</span>做有效的检索。</p>
<p>过多的索引可能降低INSERT、UPDATE、DELETE的性能，特别是过多索引导致超过内存限制的时候。</p>
<p><span style="text-decoration: underline;"><strong>索引的类型</strong></span></p>
<p>索引工作在引擎级别，每个引擎的实现略有不同，某些引擎不能支持部分类型的索引</p>
<p><strong>B树（B-Tree）索引</strong></p>
<p>通常情况下所说的索引，就是这种类型，大部分引擎支持，Archive在5.1之前不支持。不同引擎的实现细节不同：例如MyISAM的索引通过物理位置来引用行；InnoDB则是通过主键值来引用行。</p>
<p>BTree索引之所以能提高性能，是因为避免了全表扫描。从一个ROOT节点开始（不在下图中），其slots存放指向子节点指针，引擎则是沿着这些指针，根据node pages中的值（定义了子节点的值范围）来寻找到合适的指针，最终引擎要么确定其寻找的值不存在，或者到达叶子节点：<img class="aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2012/02/mysql-2.jpg" alt="" width="80%" /></p>
<p><span style="background-color: #c0c0c0;">叶子节点</span>的特殊之处在于，其<span style="background-color: #c0c0c0;">存放了被索引数据的指针</span>，而不是指向其它页的指针。</p>
<p><span style="background-color: #c0c0c0;">BTree适合多种查询：全值匹配、值范围匹配、值前缀匹配、多列索引的第一列匹配、多列索引的某列精确匹配+某列范围匹配、仅索引（Index-only，不去查行数据）查询</span>。</p>
<p>由于BTree节点是排序的，因此该类索引<span style="background-color: #c0c0c0;">不仅适合数据查找，还适合数据排序</span>。WHERE子句依据索引列过滤的同时，使用该索引列<span style="background-color: #c0c0c0;">ORDER BY、GROUP BY</span>不会有额外的开销。</p>
<p>考虑BTree索引key(last_name, first_name, dob)，下面是它的限制：</p>
<ol>
<li> 如果查找不是从<span style="background-color: #c0c0c0;">最左边索引列</span>开始、或者不是用<span style="background-color: #c0c0c0;">单列索引列的前缀</span>查询，则索引对查询无意义</li>
<li>不能<span style="background-color: #c0c0c0;">跳过多列索引中的某列</span>，如果在一个3列索引中，你不指定第2列的值，那么MySQL只会使用第一列进行索引查询</li>
<li>对于多列索引，<span style="background-color: #c0c0c0;">第一个范围查询</span>（非精确查询，例如 last_name = 'Wang' and first_name like 'Al%' and dob='1986-09-12'中的第二个查询条件）<span style="background-color: #c0c0c0;">后的任何索引都用不到</span></li>
</ol>
<p><strong>Hash索引</strong></p>
<p>Hash索引构建在Hash表中，仅仅在根据所有索引列进行查找时有效，引擎根据索引列计算各行的Hash Code。仅内存表支持此类索引</p>
<p><strong>空间索引Spatial (R-Tree) indexes</strong></p>
<p>针对各维度分别索引，用于GIS系统，但是MySQL这方面不是很好，最好选择PostGIS</p>
<p><strong>全文（Full-text）索引</strong></p>
<div class="blog_h3"><span class="graybg">索引的好处</span></div>
<ol>
<li>减少服务器需要检查的数据的量</li>
<li>避免服务器进行排序和临时表</li>
<li>将随机I/O变为顺序I/O</li>
</ol>
<div class="blog_h3"><span class="graybg">高性能索引策略</span></div>
<p>注意：对于 LIKE '%search%'形式的查询，无法使用索引。MySQL只支持在WHERE子句中使用等于、不等于、大于、小于等几种操作符来访问索引，对于 LIKE 'search%'这样形式的查询，MySQL会自动将其转换为大于、小于之类的操作符，从而使用索引。</p>
<p><span style="text-decoration: underline;"><strong>隔离列</strong></span></p>
<p>不要把列作为表达式或者函数调用的一部分，例如：WHERE actor_id + 1 = 5、TO_DAYS(CURRENT_DATE)</p>
<p><span style="text-decoration: underline;"><strong>前缀索引和索引选择度</strong></span></p>
<p>对于很长的列，可以选择前面若干字符进行索引，避免过大的空间占用。前缀索引导致低选择度（唯一索引具有最高的选择度：1）</p>
<p><span style="text-decoration: underline;"><strong>多列索引</strong></span></p>
<p>常见的错误包括，把<span style="background-color: #c0c0c0;">大部分或者所有列单独索引</span>，或者索引列的顺序不正确。</p>
<p>在很多列上分别建立索引，对于大部分查询来说，不能提高性能，MySQL 5.0以上版本有一种索引合并（index merge，通过解释计划可以看到类似Extra: Using union(PRIMARY,idx_fk_film_id)这样的字样）的策略可能对这种零散索引的表有点作用，老版本的MySQL则最多使用一个索引。索引合并有时候能有效工作，更多的时候则是提示表的索引质量较差：</p>
<ol>
<li>如果服务器交叉索引（AND条件）往往以为着应该对相关列建立多列索引</li>
<li>如果服务器联合索引（OR条件），有时缓冲、排序、合并操作会消耗过多的CPU和内存，特别是相关索引的选择度均不高时</li>
</ol>
<p><span style="text-decoration: underline;"><b>选择好的列顺序</b></span></p>
<p>BTree索引中列的顺序，依赖于查询如何使用索引。</p>
<p><span style="background-color: #c0c0c0;">多列索引首先根据第一列排序，然后第二列，依次类推</span>。因此，<span style="background-color: #c0c0c0;">列顺序应该和ORDER BY, GROUP BY, DISTINCT语句中声明的列顺序匹配</span>。</p>
<p>一个老生常谈的规则是“把<span style="background-color: #c0c0c0;">最具选择度的列放左边”，这个规则有时（没有分组、排序要求）</span>有用，但是注意，<span style="background-color: #c0c0c0;">避免随机I/O和排序更加重要</span>。此外，效率不单单取决于选择度，也和用来做过滤的值有关。</p>
<p><strong><span style="text-decoration: underline;">聚簇索引（Clustered Indexes）</span></strong></p>
<p> 聚簇索引不是一种索引类型，而是一种数据存储的方式，所谓<span style="background-color: #c0c0c0;">聚簇，是指具有相邻键值（key）的行被存放在一起</span>。对<span style="background-color: #c0c0c0;">于InnoDB，聚簇索引把BTree索引和对应的行数据存放在一起</span>。当表具有聚簇索引时，其行数据是存放在索引的叶子页（index’s leaf pages）上的，每张表只能具有一个聚簇索引。下图示意具有聚簇索引的表的布局，注意叶子节点包含整个行，其他节点只有索引列：</p>
<p><img class="aligncenter" style="width: 80%;" src="https://blog.gmem.cc/wp-content/uploads/2012/02/mysql-3.jpg" alt="" /></p>
<p><span style="background-color: #c0c0c0;">MySQL仅支持主键作为聚簇索引列</span>，如果不定义主键，则MySQL会<span style="background-color: #c0c0c0;">尝试使用一个非空、Unique索引</span>代替。InnoDB只会以页为单位聚簇记录，相邻的页可能距离很远。</p>
<p>聚簇索引具有以下优点：</p>
<ol>
<li>让相关的数据存放在一起</li>
<li>数据访问速度快，因为聚簇索引同时把索引、数据存放在一个BTree上</li>
<li>使用覆盖索引的查询，可以用到叶子节点上的主键</li>
</ol>
<p>聚簇索引具有以下缺点：</p>
<ol>
<li>插入顺序对插入速度影响大，最好是依据聚簇索引列的顺序来插入，乱序插入后，可以考虑OPTIMIZE TABLE</li>
<li>修改聚簇索引列的代价大，因为强制InnoDB移动其物理位置</li>
<li>构建了聚簇索引的表在插入数据时，受页拆分（Page Splits）的影响，如果被插入数据的key决定它将被插入到一个已满的页内，则split发生，页拆分会导致更多的磁盘占用</li>
<li>全表扫描的速度可能较慢，特别是数据因页拆分而非顺序的存放时</li>
<li>非聚簇索引(nonclustered)占用空间可能很大，因为其叶子节点需要存放主键列</li>
<li>非聚簇索引(nonclustered)的访问需要<span style="background-color: #c0c0c0;">两次索引查找</span>。InnoDB的Adaptive Hash Index可以减少此消耗</li>
</ol>
<p><span style="text-decoration: underline;"><strong>MyISAM和InnoDB数据布局的比较</strong></span></p>
<p>考虑如下的具有两列的表结构：</p>
<pre class="crayon-plain-tag">CREATE TABLE layout_test (
    col1 int NOT NULL,
    col2 int NOT NULL,
    PRIMARY KEY(col1),
    KEY(col2)
);</pre>
<p> 假设按随机顺序插入主键1-100000的数据，并使用OPTIMIZE TABLE来优化表（确保数据按最优方式排列在磁盘上），col1列使用1-100的随机值填充（很多重复值）。</p>
<p><strong>MyISAM的数据布局</strong></p>
<p>下图左侧标记了Row Number，由于这是一个Fixed-Size的表，所以根据Row Number可以迅速的定位到目标行。</p>
<p>主键索引的构建相当简单，就是针对主键顺序进行顺序布局，关联对应的Row Number。</p>
<p>普通索引和主键索引<span style="background-color: #c0c0c0;">没有结构上的差异</span>，只是不是Unique的而已。</p>
<p><img class="aligncenter" style="width: 100%;" src="https://blog.gmem.cc/wp-content/uploads/2012/02/MySQK-10.png" alt="MySQK-10" /></p>
<p><strong>InnoDB的数据布局</strong></p>
<p>由于聚簇索引的关系，InnoDB针对上表的布局完全不同。</p>
<p>首先，如下图，聚簇索引不单单是索引，表本身也包含在其中了：每一个BTree叶子节点包含主键值、事务ID、回滚指针（后面两者和事务、MMVC有关）、以及所有其他的列值。</p>
<p>其次，如下下图，与MyISAM的普通索引不同，InnoDB不是存储行号，而是存储被索引列+主键值。此策略可以<span style="background-color: #c0c0c0;">减少行移动、页拆分时的资源消耗</span>，缺点是索引体积大（特别是主键是很大字段时）</p>
<p><img style="width: 100%;" src="https://blog.gmem.cc/wp-content/uploads/2012/02/MySQL-11.png" alt="MySQL-11" /></p>
<p><span style="text-decoration: underline;"><strong>在InnoDB中按照主键顺序插入数据</strong></span></p>
<p>如果没有任何特殊要求，最好使用自增长（AUTO_INCREMENT）主键，这保证数据按顺序插入，并提供更好的JOIN性能。</p>
<p>最好避免随机性质的主键，例如UUID。</p>
<p>自增长主键和UUID主键的插入性能差异，在到达某个数量级后（例如300万），可能有数倍的差距；索引大小也可能有成倍的差距。</p>
<p>在随机主键场景下，Page Splits以及其造成的碎片（fragmentation）无疑影响了性能，顺序主键和随机主键的数据页变化情况如下图：</p>
<p><img src="https://blog.gmem.cc/wp-content/uploads/2012/02/MySQL-12.png" alt="MySQL-12" width="100%" /></p>
<p>对于顺序主键，InnoDB直接在前一个插入的记录的后面插入新的记录，当前页满了（InnoDB默认页满因子为15/16，留下的空间用于防止修改）后，再下一个记录被插入到新的页中。如果数据加载时按照此顺序进行，那将是非常高效的。</p>
<p>而对于随机主键，由于新插入数据与前一个数据没有递增关系，所有InnoDB通常不能把新数据插入到索引的尾部，而是需要在已有页开辟新空间，则导致以下问题：</p>
<ol>
<li>目标页可能已经被刷入磁盘，并移出缓存，或者目标页从来就没有进入过缓存，则导致了随机磁盘I/O</li>
<li>InnoDB可能不断的分页，来开辟新空间供新插入的行，这导致需要移动很多数据、修改至少3个页</li>
<li>由于不断的分页，页变得稀疏、不规则，最终导致数据碎片化。一段时间后，可能需要运行OPTIMIZE TABLE来整理碎片</li>
</ol>
<p><strong>顺序主键导致更糟糕问题的场景</strong></p>
<p>对于一个<span style="background-color: #c0c0c0;">高并发的插入场景</span>，顺序主键的最高值可能导致竞争热点：</p>
<ol>
<li>大量的并发可能<span style="background-color: #c0c0c0;">争用next-key locks</span></li>
<li>AUTO_INCREMENT的本身的锁机制，可能需要修改innodb_autoinc_lock_mode</li>
</ol>
<p><span style="text-decoration: underline;"><strong>覆盖索引（covering indexes）</strong></span></p>
<p>索引不仅仅需要为WHERE子句建立，还要考虑整个查询语句——MySQL不仅仅用索引快速的找到匹配行，还可以通过索引抓取<span style="background-color: #c0c0c0;">列数据</span>，但是，对于普通索引来说，不能抓取整个行的所有数据。<span style="background-color: #c0c0c0;">覆盖索引</span>可以模仿多聚簇索引（multiple clustered indexes），即，<span style="background-color: #c0c0c0;">抓取查询需要的所有数据</span>，不仅仅是被索引列。覆盖索引可以很好的提高性能，因为它只需要访问索引，而不需要访问数据，这种访问方式有以下好处：</p>
<ol>
<li>索引条目往往比正行数据小的多，MySQL只需要读取很少的数据，特别是对于响应时间主要消耗与拷贝数据的缓存场景（cached workloads）</li>
<li>由于索引是按其索引值来存储的（至少在单个页内），因此，对比从随机磁盘数据获取行，覆盖索引需要较少的I/O。特别是对于MyISAM之类的引擎，通过优化表，可以保证简单的索引查询完全使用顺序索引访问</li>
<li>对于MyISAM之类的引擎，只在MySQL的内存中缓存索引，而由OS缓存数据，访问缓存数据通常意味着系统调用（System Call），这意味着高代价</li>
<li>覆盖索引对于InnoDB特别有意义，由于InnoDB的普通索引需要二次查找，如果使用覆盖索引，则可避免</li>
</ol>
<p>只能使用BTree索引来创建覆盖索引，此外，内存引擎是不支持覆盖索引的。当发起一个<span style="background-color: #c0c0c0;">被索引覆盖</span>的查询，通过解释计划可以看到Extra列，其内容显示为<span style="background-color: #c0c0c0;">Extra: Using index</span>。而对于<span style="background-color: #c0c0c0;">没有被索引覆盖</span>的查询，则会显示<span style="background-color: #c0c0c0;">Extra: Using where</span>。</p>
<p>对于InnoDB，<span style="background-color: #c0c0c0;">索引必定覆盖主键列</span>。</p>
<p><span style="text-decoration: underline;"><strong>基于索引扫描的排序</strong></span></p>
<p> MySQL支持两种产生排序结果集的方法：</p>
<ol>
<li>使用排序操作</li>
<li>按顺序扫描索引</li>
</ol>
<p>MySQL可以使用索引同时完成数据过滤和排序。只有<span style="background-color: #c0c0c0;">ORDER BY子句指定的方向（ASC、DESC）与索引顺序一致，并且对于多表连接，ORDER BY只引用第一个表的列时</span>，才能进行基于索引的排序。此外，与WHERE子句类似，ORDER BY子句只能使用多列索引最左边的前缀进行排序，除非在WHERE子句中把左侧列作为常量：</p>
<pre class="crayon-plain-tag">CREATE TABLE rental (
    PRIMARY KEY (rental_id),
    -- 包含三个列的多列索引
    UNIQUE KEY rental_date (rental_date,inventory_id,customer_id),
    KEY idx_fk_inventory_id (inventory_id),
    KEY idx_fk_customer_id (customer_id),
    KEY idx_fk_staff_id (staff_id),
    ...
);
-- 在WHERE子句指定了三列索引最左侧的值为常量
-- 这样仍然支持基于索引的排序，解释计划不会出现Extra: Using filesort
EXPLAIN SELECT rental_id, staff_id FROM sakila.rental
    WHERE rental_date = '2005-05-25' -- 第一列常量
    ORDER BY inventory_id, customer_id; --排序没有使用左侧列

-- 更多的例子
... WHERE rental_date = '2005-05-25' ORDER BY inventory_id DESC;
... WHERE rental_date &gt; '2005-05-25' ORDER BY rental_date, inventory_id;

-- 下面的例子不能基于索引排序
-- 使用了两个不同的排序方向，而索引列均是升序排列
... WHERE rental_date = '2005-05-25' ORDER BY inventory_id DESC, customer_id ASC;
-- staff_id不再多列索引中
... WHERE rental_date = '2005-05-25' ORDER BY inventory_id, staff_id;
-- customer_id左边的列inventory_id必须存在于排序子句
... WHERE rental_date = '2005-05-25' ORDER BY customer_id;
-- 过滤条件不是常量
... WHERE rental_date &gt; '2005-05-25' ORDER BY inventory_id, customer_id;</pre>
<p><span style="text-decoration: underline;"><strong>打包（前缀压缩的）的索引Packed (Prefix-Compressed) Indexes </strong></span></p>
<p> MyISAM可以对索引前缀压缩，从而减小空间占用，易于在内存中完成匹配。默认MyISAM自动对字符串索引进行压缩操作，可以指定其对整数索引亦进行此操作（创建表时使用PACK_KEYS选项）。压缩索引有时可能降低性能</p>
<p><span style="text-decoration: underline;"><strong>冗余、重复和无用索引</strong></span></p>
<p>MySQL允许在同一列上创建多个索引，并独立的维护它们，这些索引可能影响查询的优化。</p>
<p>重复索引：同样列上、同样类型、同样顺序的索引。没有价值。</p>
<p>冗余索引：BTree索引(A,B)、(A)对于列A是冗余的，因为前者亦可单独用于A列。有时扩充已有索引可能降低性能，这是需要冗余索引，例如在已有一个整数列索引，需要扩充一个长的VARCHAR列的时候——如果建立索引(int_col,varchar_col)，并保留(int_col)可能是最好的选择。</p>
<p>冗余索引可能导致插入性能降低，对于上面(int_col,varchar_col)的例子，百万数据的插入性能可能成倍下降（InnoDB）甚至数倍下降（MyISAM）。</p>
<p><span style="background-color: #c0c0c0;">对于从来不会使用的无用索引，应该删除</span>。</p>
<p><span style="text-decoration: underline;"><strong>索引与锁定</strong></span></p>
<p>如果SQL语句（例如for update，注意普通查询不做任何锁定）<span style="background-color: #c0c0c0;">不去touch</span>其不需要的行，则需要锁定的行也很少，这提高了性能，因为：</p>
<ol>
<li>尽管InnoDB具有很高效的行锁，且需要很少的内存，但是行锁定还是有一些成本</li>
<li>锁定更多的行，引起锁争用，降低了并发性</li>
</ol>
<p>InnoDB仅在访问行时锁定它们，并且索引索引可以减少访问和锁定的行数，但是，<span style="background-color: #c0c0c0;">只有InnoDB在引擎级别能够过滤掉不需要的行才能减少锁定</span>，否则，InnoDB把结果集返回到MySQL Server层，结果集中的所有行都被锁定了（MySQL 5.1和以后的版本，InnoDB能够<span style="background-color: #c0c0c0;">在Server层完成过滤后</span>解锁相关行）</p>
<p>即使在使用索引的时候，也可能锁定不必要的行，如果没有索引，在引擎级别可能锁定所有的行（全表扫描）</p>
<div class="blog_h3"><span class="graybg">一个索引案例</span></div>
<p>本节使用一个在线交友网站的例子来简述索引的设计与使用，假设有一张用户信息表，需要支持country, state/region, city, sex, age, eye color等多种条件组合过滤、支持基于最后一次在线时间、排名进行排序。</p>
<p><span style="text-decoration: underline;"><strong>支持多种方式过滤</strong></span></p>
<p>需要考虑哪些列最常出现在WHERE子句中，哪些列的distinct值较多，这会成为建立索引的优选</p>
<p>country、sex列虽然distinct值较少，但是几乎会包含在所有查询中，所以，我们创建一系列以(sex,country)为前缀的索引，尽管这个决定与传统的最佳实现背道而驰，我们有足够的理由：</p>
<ol>
<li>这两列几乎所有查询中都用到，甚至，我们可以设计为每次用户必须选择这两列作为查询条件</li>
<li>通过一定的技巧，可以使这样的索引没有什么负面作用：此技巧就是：如果用户没有指定sex，我们<span style="background-color: #c0c0c0;">可以人工添加 sex in</span> ('m','f')，这个技巧可以保证索引被使用，但是，如果distinct太多，会导致IN 列表过大</li>
</ol>
<p>确认前缀后，需要考虑哪些条件组合会出现在WHERE子句中，并且在没有索引的情况下可能会很慢，明显(sex, country, age) 是一个候选，(sex, country, region, age) 、(sex, country, region, city, age)上也可能需要索引。</p>
<p>如果为它们分别建立索引，则索引可能太多了，需要考虑索引的重用，如果用IN技巧处理region，则(sex, country, age) 可以和(sex, country, region, age)合并为一个索引，但是要注意IN列表过大的问题</p>
<p>对于使用不多的查询列，例如has_pictures, eye_color, hair_color, education，有两个选择：</p>
<ol>
<li>不进行索引，让MySQL进行少量的额外扫描</li>
<li>加入索引并使用IN技巧</li>
</ol>
<p>注意我们把<span style="background-color: #c0c0c0;">age放在索引的最后面</span>，这是因为，<span style="background-color: #c0c0c0;">age通常是一个范围查询</span>，而其他的列要么是相等查询，要么是IN查询——这两个操作符可以常量化索引的左前缀，从而保证索引被优化器尽可能有效的使用。</p>
<p><span style="background-color: #c0c0c0;">IN列表过大</span>可能导致查询速度严重下降，例如下面的语句需要4*3*2=12种组合，WHERE子句需要逐个组合的检查：</p>
<pre class="crayon-plain-tag">WHERE eye_color IN('brown','blue','hazel')
AND hair_color IN('black','red','blonde','brown')
AND sex IN('M','F')</pre>
<p> 12种组合通常不是问题，<span style="background-color: #c0c0c0;">但是如果组合数上千</span>，就要注意了：对于老版本的MySQL，优化器可能需要很长时间的执行、消耗大量的内存；对于新版本的MySQL，则会在超过一定的组合数量后停止优化估算，这会影响索引使用效率。</p>
<p><span style="text-decoration: underline;"><b>避免多个范围查询</b></span></p>
<p>尽管从执行计划上分不出IN (20,21,22)和 &gt;=20 and &lt;=22的区别（都显示为type：range），但是这两种语句对应索引的处理是完全不同的，<span style="background-color: #c0c0c0;">后者会导致MySQL忽视后续的索引</span>。</p>
<p>考虑下面的查询：</p>
<pre class="crayon-plain-tag">WHERE eye_color IN('brown','blue','hazel')
AND hair_color IN('black','red','blonde','brown')
AND sex IN('M','F')
AND last_online &gt; DATE_SUB(NOW(), INTERVAL 7 DAY)  --最近一周登陆过
AND age BETWEEN 18 AND 25</pre>
<p> MySQL只会使用age或者last_online两者中的一个。如果age无法常量化（值列表过大），则无法把last_online放到索引的尾部，则必须使用某种变通的方法，例如：使用JOB来处理一个active字段，如果最近一周没有登陆，则设置为0，在用户登陆时设置为1，然后把索引改成类似(active, sex, country, age)的结构，即可满足需求。</p>
<p>未来版本的MySQL可能支持在单一索引上使用多个范围查询，这样的话，IN技巧就没有价值了。</p>
<p><span style="text-decoration: underline;"><b>优化排序</b></span></p>
<p>对于小结果集的排序，filesort就足够了。</p>
<p>对于大结果集，例如上百万数据，则可能需要为排序建立特殊索引：</p>
<pre class="crayon-plain-tag">-- 索引(sex, rating)可以供下面的查询使用：
SELECT  FROM profiles WHERE sex='M' ORDER BY rating LIMIT 10;</pre>
<p><span style="text-decoration: underline;"><strong>大分页问题</strong></span></p>
<p>如果用户请求离开始处很远的分页信息，例如：</p>
<pre class="crayon-plain-tag">SELECT  FROM profiles WHERE sex='M' ORDER BY rating LIMIT 100000, 10;
-- MySQL必须扫描大量的，而这些最终都是需要扔掉的</pre>
<p> 这将难以避免的导致性能问题，因为高offset导致太多的时间消耗在扫描没有意义的数据上，反正常化、预计算、缓存可能是有效的应对策略，最好的方式是限制用户能够访问的页数——谁会真正关心10000页后面的数据呢？</p>
<p>另外一种解决大分页的问题的策略是<span style="background-color: #c0c0c0;">延迟连接（Deferred Join）</span>:</p>
<pre class="crayon-plain-tag">SELECT  FROM profiles INNER JOIN (
    -- 主键扫描，避免了MySQL收集其最终要扔掉的数据
    SELECT  FROM profiles
    WHERE x.sex='M' ORDER BY rating LIMIT 100000, 10
) AS x USING();</pre>
<div class="blog_h3"><span class="graybg">索引和表维护</span></div>
<p><span style="text-decoration: underline;"><strong>寻找和修复表破坏（Corruption）</strong></span></p>
<p>对于一张表来说，最严重的事情是破坏，对于MyISAM通常发生于Crash之后。索引破坏可能由于硬件故障、MySQL或者OS的BUG。</p>
<p>被破坏的索引可能返回不正确的结果，在<span style="background-color: #c0c0c0;">没有重复值的时候报duplicate-key错误</span>，甚至锁死和崩溃。</p>
<p>可以运行CHECK TABLE来检查表是否被破坏，该命令可以捕获大部分表和索引错误。使用<span style="background-color: #c0c0c0;">REPAIR TABLE</span>命令可以修复表的错误，某些引擎不支持该命令，这时可以使用NoOp的ALTER命令来修复，例如：ALTER TABLE  tab  ENGINE=INNODB。</p>
<p>InnoDB通常不会出现表破坏，除非出现硬件问题，例如内存或磁盘、或者数据文件被外部改动。使用innodb_force_recovery参数可以进入强制恢复模式，或者使用Percona InnoDB Data Recovery Toolkit从被破坏的数据文件中抽取数据。</p>
<p><span style="text-decoration: underline;"><strong>更新索引统计信息</strong></span></p>
<p>当存储引擎给出一个<span style="background-color: #c0c0c0;">非精确的查询检查行数，或者查询计划过于复杂无法估算行数</span>，优化器<span style="background-color: #c0c0c0;">会使用索引统计信息来估算行数</span>。MySQL优化器是基于<span style="background-color: #c0c0c0;">成本</span>的，主要度量依据是<span style="background-color: #c0c0c0;">查询需要访问的数据量</span>。如果索引统计信息不存在，或者过期，可能导致优化器做出错误决定。<span style="background-color: #c0c0c0;">ANALYZE TABLE</span>可以触发生成新的索引统计信息。</p>
<p>MyISAM把索引统计信息存放于磁盘，ANALYZE TABLE会导致表锁定及全表扫描</p>
<p>InnoDB从MySQL5.5开始存放在内存中，使用随机的索引采样来获取统计信息。采样的数据页数通过innodb_stats_sample_pages来设定，默认值为8，增大此值可能提高统计精确度，InnoDB在以下情况下<span style="background-color: #c0c0c0;">会自动执行索引统计</span>：</p>
<ol>
<li>表第一次被打开时</li>
<li>运行ANALYZE TABLE时</li>
<li>表的尺寸发生重大变化时，例如变化了1/16，或者插入了20亿行数据</li>
<li>查询INFORMATION_SCHEMA中的某些表、执行 SHOW TABLE STATUS 、SHOW INDEX时，这可能导致性能下降，可以通过设置innodb_stats_on_metadata禁用</li>
</ol>
<p>MySQL 5.6中，选项innodb_analyze_is_persistent可以使索引统计持久化到系统表中，这有利于系统预热、查询计划的稳定性。</p>
<p><span style="text-decoration: underline;"><b>减少索引和数据碎片</b></span></p>
<p> BTree索引可能因为Page Split变得碎片化（non-filled、nonsequential），从而影响性能（range scan、full index scan可能慢数倍，特别是使用覆盖索引的场景）</p>
<p>表数据同样可能碎片化，包括如下类型：</p>
<ol>
<li>行碎片化：单行被分为多片存储于多个物理位置。即使结果集只需要一行，也会影响性能</li>
<li>Intra-row碎片化：当逻辑上连续的页或者行，在磁盘上不是连续排列时发生。这会影响全表扫描、聚簇索引范围扫描的性能</li>
<li>自由空间碎片化：当数据页中包含大量空白空间时。这会导致服务器读取很多不需要的数据</li>
</ol>
<p>MyISAM会发生各种碎片化，但是InnoDB则不会发生短行（short rows）的碎片化，它会移动行并写在一起。</p>
<p>通过OPTIMIZE TABLE或者dump/reload数据可以整理碎片。对于<span style="background-color: #c0c0c0;">InnoDB的索引碎片的整理，可以通过删除/重建索引完成</span>，对于不支持OPTIMIZE TABLE的存储引擎，可以做NoOp的ALTER TABLE操作。</p>
<div class="blog_h2"><span class="graybg">查询性能优化</span></div>
<div class="blog_h3"><span class="graybg">查询缓慢的原因</span></div>
<p>查询是一个任务，并且被MySQL拆分为<strong><span style="color: #008080;"><a href="/high-performance-mysql-study-note#show-profile-for-single-query"><span style="color: #008080;">子任务</span></a></span></strong>，要优化查询，必须<span style="background-color: #c0c0c0;">消除某些子任务、减少子任务的发生次数、或者加快子任务的执行速度</span>。</p>
<p>基本上，一个查询需要的处理经过解析（Parsed）、计划（Planned）、执行（Executed）等步骤，其中执行是最重要的一步，包含很多<span style="background-color: #c0c0c0;">存储引擎调用</span>（为了获取rows）以及<span style="background-color: #c0c0c0;">Post-retrieval处理</span>（例如分组、排序）。MySQL需要在网络、CPU、特别是磁盘I/O（如果数据不在内存）中花费时间，对于某些存储引擎，可能需要很多上下文切换、系统调用。</p>
<div class="blog_h3"><span class="graybg">优化数据访问</span></div>
<p>最基本的查询缓慢的原因是，处理了太多的数据（绝大部分是不需要的，只是筛选），分析缓慢查询通常按以下的步骤进行：</p>
<ol>
<li>检查应用程序是否<span style="background-color: #c0c0c0;">获取了不需要的数据</span>，例如：<br />访问太多行：使用limit语句限制返回的行数<br />在联表查询中返回不必要的表的列<br />返回所有列，这可能无法使用覆盖索引，并导致更多的CPU、内存和I/O<br />重复返回同样的数据</li>
<li>检查MySQL是否<span style="background-color: #c0c0c0;">分析了不必要的行</span>，在MySQL中，<span style="background-color: #c0c0c0;">最简洁、粗略的查询成本估算度量是：响应时间、返回的行数、检查的行数</span>。这些信息均会记录在<span style="background-color: #c0c0c0;">slow query log</span>中。<br /><strong>响应时间（Response time）</strong>由两部分组成：service time——MySQL真正用来处理查询的时间，queue time——等待某些资源（I/O的完成、行锁的获取）的时间，这两部分时间并不好区分<br /><strong>检查行数/返回行<strong>数</strong></strong>：最理想的是只检查需要返回的行，现实中则很难实现，例如对于JOIN查询，需要访问两个以上表的多行，并生成结果集，这种情况下检查行通常要比返回行多很多。检查行过多也不一定意味着低效查询，因为较短的行访问起来交快，检查多点也没事<br /><strong>检查行的方式</strong>：有时，思考只返回一行数据的查询，有利于分析查询成本。执行计划结果的type字段反映了<span style="background-color: #c0c0c0;">检查行的方式，包括：全表扫描（full table scan）、索引扫描（index scan）、范围扫描（range scan）、唯一索引扫描（unique index lookup）、常量（constants），</span>后面的访问方式比前面的速度快。如果访问方式不佳，最好是添加适当的索引。MySQL可能以三种方式应用WHERE子句，效果从好多差为：<br />a）在存储引擎层，把过滤条件应用到索引查找操作，并消除不匹配的行<br />b）使用覆盖索引（Extra：Using index）在从索引中获取每一个行后，过滤掉不匹配的行，这发生Server层，但不需要表行的访问<br />c）从表中取得行，然后过滤掉不匹配的行（Extra：Using Where），这发生在Server层，并且需要表行的访问</li>
</ol>
<div class="blog_h3"><span class="graybg">重构查询的方法</span></div>
<p><span style="text-decoration: underline;"><strong>复杂查询vs多个查询</strong></span></p>
<p>在以前网络带宽比较缺乏的时代，倾向于在单个复杂查询中完成工作，但是现在没有这个必要了，MySQL设计为允许快速连接/断开、快速响应简单查询（在普通硬件上MySQL可以每秒响应超过十万个简单查询）。</p>
<p>网络上传输数据相比起在MySQL内存中完成数据处理，速度还是非常慢的，因此，尽可能的使用少的查询还是一个好主意。</p>
<p><span style="text-decoration: underline;"><strong>查询分块（Chopping Up a Query）</strong></span></p>
<p>把查询分为完全一样的“小块”，每次影响少量的数据，这在某些场景下也很有效，例如清除旧的数据。定期删除旧数据的JOB如果在一个<span style="background-color: #c0c0c0;">巨大的DELETE语句中完成，将会导致很多行被长期锁定、事务日志被充满、阻塞其他小的语句<span style="background-color: #ffffff;">。适当的使用LIMIT即可很好的改良。此外，在</span></span>单个批次的DELETE让线程睡眠一会<span style="background-color: #c0c0c0;"><span style="background-color: #ffffff;">也是个好主意，避免负载过于集中，影响系统其他业务的运行。</span></span></p>
<p><span style="text-decoration: underline;"><strong>连接分解（Join Decomposition）</strong></span></p>
<p>很多高性能应用使用连接分解技术，即使用多个单表查询来代替一个连接查询，例如：</p>
<pre class="crayon-plain-tag">SELECT * FROM tag
    JOIN tag_post ON tag_post.tag_id=tag.id
    JOIN post ON tag_post.post_id=post.id
WHERE tag.tag='mysql';
-- 上面的查询可以分解为：
SELECT * FROM tag WHERE tag='mysql';
SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id in (123,456,567,9098,8904);</pre>
<p> 咋一看似乎是多此一举，其实这种查询重构具有明显的性能优势：</p>
<ol>
<li>缓存更加有效。某些应用具有缓存单表（映射为Map）数据的能力，如果缓存有效，3个查询中可能有些不需要执行。在使用<span style="background-color: #c0c0c0;">Hibernate的场景</span>中，查询缓存也会更加有效，因为这3张表中，<span style="background-color: #c0c0c0;">如果只有一张表容易变化，那么JOIN查询的缓存将很快失效，而查询重构后，只有1个查询缓存容易失效</span>。</li>
<li>单独执行查询，又是可以减少锁竞争</li>
<li>在应用中进行JOIN，更容易进行Scale，因为可以把表放在不同的服务器上</li>
<li>查询本身可以更加高效，上面的例子中，IN比起JOIN更好</li>
<li>可以减少冗余的行访问，在应用中进行JOIN，意味着每个行只需要获取一次，而JOIN属于反规范化，通常会重复访问很多数据。同样的，这样的重构也减少网络流量和内存消耗</li>
<li>某种程度上，可以认为这种技术是一个手工实现的Hash Join，作为MySQL嵌套循环算法（nested loops algorithm）的替代，Hash Join更加高效</li>
</ol>
<div class="blog_h3"><span class="graybg">查询执行基础知识</span></div>
<p>当客户端发送一个查询给MySQL服务器时，会发生以下事件序列： </p>
<ol>
<li>客户端把SQL语句送到服务器</li>
<li>服务器检查查询缓存，如果命中，则从缓存中获取结果集；否则进入下一步</li>
<li>服务器解析、预处理、优化SQL，并生成执行计划</li>
<li>查询执行引擎通过进行存储引擎API调用，完成计划的执行</li>
<li>服务器返回数据给客户端</li>
</ol>
<p><span style="text-decoration: underline;"><strong> MySQL Client/Server协议</strong></span></p>
<p><span style="background-color: #c0c0c0;">协议是半双工的</span>，任何时刻，MySQL服务器要么在接收数据，要么在发送数据，而不能同时进行。这样的设计使通信简单而快速，但是也有弱点：在接收完消息之前，无法做任何事情。</p>
<p>客户端把整个查询在单个数据报中发送，因此，如果使用很大的查询语句，max_allowed_packet参数就很重要。</p>
<p>相比之下，服务端通常在多个数据报中把响应发过来，客户端在接收完毕之前无法取消，除非强行断开连接，因此必要的LIMIT很重要。</p>
<p>大多数客户端库允许你要么抓取所有结果集并存放在内存，要么逐条抓取（游标），前者是默认行为，在抓取完毕之前，<span style="background-color: #c0c0c0;">查询会处于“Sending data”状态</span>，并不会释放锁和其它资源。</p>
<p><strong>查询状态</strong></p>
<p>每一个MySQL连接（或者说线程）具有一个说明当前其正在做什么的状态字段，使用SHOW FULL PROCESSLIST 命令即可看到当前状态，常见的状态如下表：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">状态</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Sleep</td>
<td>线程正在等待来自客户端的新查询</td>
</tr>
<tr>
<td>Query</td>
<td>线程正在执行查询或者把结果集发送给客户端 </td>
</tr>
<tr>
<td>Locked</td>
<td>线程正在等待Server层授予表锁。注意：基于存储引擎实现的索引，例如InnoDB的行锁，不会导致线程进入Locked状态</td>
</tr>
<tr>
<td>Analyzing and  statistics</td>
<td>线程正在检查存储引擎统计信息，并优化查询</td>
</tr>
<tr>
<td>Copying to tmp table</td>
<td>
<p>线程正在处理查询，把结果集拷贝到<span style="background-color: #c0c0c0;">临时表</span>，可以是因为需要<span style="background-color: #c0c0c0;">GROUP BY，或者filesort、或者UNION</span><br />Copying to tmp table on disk则表示MySQL将<span style="background-color: #c0c0c0;">内存临时表</span>转为<span style="background-color: #c0c0c0;">磁盘临时表</span></p>
</td>
</tr>
<tr>
<td>Sorting result</td>
<td>线程正在进行排序操作</td>
</tr>
<tr>
<td>Sending data</td>
<td>
<p>可能意味着几种状况：</p>
<ol>
<li>线程正在查询的不同Stage之间传递数据</li>
<li>线程正在生成结果集</li>
<li>线程正在把结果集发送给客户端</li>
</ol>
</td>
</tr>
</tbody>
</table>
<p><span style="text-decoration: underline;"><strong>查询缓存</strong></span></p>
<p>在解析查询之前，MySQL就会<span style="background-color: #c0c0c0;">检查查询缓存</span>（如果查询缓存启用的话），这个查找操作是<span style="background-color: #c0c0c0;">大小写敏感的HASH操作</span>。只有语句完全一致，才可能命中缓存。</p>
<p><strong><span style="text-decoration: underline;">查询优化处理</span></strong></p>
<p>该步骤完成执行计划的生成，包含几个子步骤：parsing、preprocessing、optimization</p>
<p><strong>解析器和预处理器</strong></p>
<p>解析器负责把语句转换为parse tree形式，检查语法的合法性。</p>
<p>预处理器对parse tree进行额外的语义检查，例如表和列的存在性、检查访问权限</p>
<p><strong>查询优化器</strong></p>
<p>MySQL使用基于成本的优化器。由于以下原因，有时优化器不能够得到最优执行计划：</p>
<ol>
<li>统计信息错误。Server层依赖于存储引擎提供的统计信息，这些信息可能精确，或者仅仅是大概的数字。例如，由于MVCC，InnoDB不能维护精确的表行数统计信息</li>
<li>成本度量并不是和实际执行成本等价。有时侯读取更多页的计划反而会高效，如果这些页顺序的分布在磁盘上，或者已经被缓存在内存中——优化器并不知道这些信息</li>
<li>MySQL的最优化和我们的理解有所不同，我们通常认为最优化意味着最短的执行时间，而MySQL则认为意味着最低的Cost</li>
<li>MySQL不会考虑当前正在并发执行的SQL语句，这些语句可能影响当前语句的性能</li>
<li>MySQL并不总是采用基于成本的优化，有时采用基于规则的方式，例如：如果语句中存在一个全文MATCH()子句，则会自动尝试使用全文索引</li>
<li>优化器不会考虑不再控制范围内的成本，例如执行存储函数、用户定义函数</li>
<li>优化器不能估算每一个可能的执行计划，这可能导致丢失最优计划</li>
</ol>
<p>MySQL查询优化器是相当复杂的组件，优化可以分为：<span style="background-color: #c0c0c0;">静态优化、动态优化</span>两种：</p>
<ol>
<li>静态优化仅仅通过分析parse tree即可完成，它与WHERE子句中传入的值无关，即时相同的语句使用不同的值执行，优化依旧有效，可以称为“编译时优化”</li>
<li>动态优化则需要根据多种上下文信息来完成，例如WHERE子句中传入的值、索引中具有多少distinct值。每次查询都需要重新优化，可以称为“运行时优化”</li>
</ol>
<p>对于预编译语句、存储过程，MySQL可以只进行一次静态优化，而在每次执行时进行动态优化</p>
<p>以下是常见的MySQL优化：</p>
<ol>
<li>重排连接（Reordering joins）：不一定需要按照SQL中指定的顺序来JOIN，这是一个重要的优化内容</li>
<li>将OUTER JOIN转换为INNER JOIN：MySQL能够识别不必要的OUTER JOIN并自动转换</li>
<li>应用代数等价转换：例如(5=5 AND a&gt;5)会自动转换为a&gt;5</li>
<li>COUNT(), MIN(), MAX()的优化：例如，如果需要寻找BTree最<span style="background-color: #c0c0c0;">左侧列</span>的<span style="background-color: #c0c0c0;">MIN值</span>，只需要请求索引中的<span style="background-color: #c0c0c0;">第一行</span>即可；寻找MAX值则请求最后一行。如果进行了这样的优化，在执行计划里可以看到“<span style="background-color: #c0c0c0;">Select tables optimized away</span>”。此外没有WHERE子句的COUNT(*)会被MyISAM引擎直接优化掉（因为所有表的总数均存放在数据字典）</li>
<li>常量化：如果MySQL发现某些表达式可以简化为常量，会进行优化。例如用户定义@变量在没有发生变化的时候，会被转换为常量表达式，算术表达式也会被转换为常量。此外，一些你可能认为不会常量化的场景下，MySQL也会进行常量化优化：<br />
<pre class="crayon-plain-tag">EXPLAIN SELECT film.film_id, film_actor.actor_id
FROM sakila.film
    INNER JOIN sakila.film_actor USING(film_id) -- 常量化
WHERE film.film_id = 1; -- 常量，只有一行匹配

--- 结果如下，被优化为两个简单查询：
+----+-------------+------------+-------+----------------+-------+------+
| id | select_type | table     | type  | key           | ref   | rows |
+----+-------------+------------+-------+----------------+-------+------+
| 1  | SIMPLE      | film       | const | PRIMARY       | const | 1    |
| 1  | SIMPLE      | film_actor | ref   | idx_fk_film_id | const | 10   |
+----+-------------+------------+-------+----------------+-------+------+</pre>
</li>
<li>覆盖索引：当SELECT子句中所有列被索引覆盖，则不会去寻找行数据</li>
<li>子查询优化：MySQL可以把某些子查询转换为效果等同的形式，将单独查询转换为索引查找</li>
<li>提前结束（Early termination）：MySQL会在<span style="background-color: #c0c0c0;">满足查询要求后尽快结束处理</span>，例如：<br />a）LIMIT语句<br />b）WHERE id = -1发生在只有正数的主键上<br />c）Distinct/not-existsy优化，针对某些DISTINCT、 NOT EXISTS()、LEFT JOIN语句，示例如下：<br />
<pre class="crayon-plain-tag">SELECT film.film_id
FROM sakila.film
    LEFT OUTER JOIN sakila.film_actor USING(film_id)
WHERE film_actor.film_id IS NULL;
 -- 一旦发现右表字段不为空，则立即结束对此电影的处理（通常电影都有很多演员）</pre>
</li>
<li>等同性传播（Equality propagation）：MySQL<span style="background-color: #c0c0c0;">可以识别查询中两列的等同性，例如JOIN的两列</span>，并且把<span style="background-color: #c0c0c0;">WHERE子句在等同列直接进行传播</span>，示例如下：<br />
<pre class="crayon-plain-tag">SELECT film.film_id
FROM sakila.film
    INNER JOIN sakila.film_actor USING(film_id) -- USING强制file_actor.file_id与file表的PK相等
    -- WHERE 子句自动传播给file_actor表的file_id，减少了扫描范围
WHERE film.film_id &gt; 500;  -- WHERE子句限制条件</pre>
</li>
<li>IN()列表比较：MySQL会<span style="background-color: #c0c0c0;">自动排序IN()列表的值，并执行优化的二分查找</span>（binary search）</li>
</ol>
<p><strong>表和索引统计</strong></p>
<p>统计信息是由存储引擎来维护的，像Archive这样的引擎甚至不保存统计信息。Server层（优化器所在）询问存储引擎以下统计信息：</p>
<ol>
<li>表或者索引的总页数</li>
<li>表或者索引的基数（cardinality）</li>
<li>行或者键的长度</li>
<li>键分布信息</li>
</ol>
<p>优化器利用这些信息来协助制定何种执行计划</p>
<p><strong>连接（JOIN）执行策略</strong></p>
<p>MySQL比传统理解更多的使用术语join，它把所有查询看作join——不仅从两张表中匹配行的查询，子查询、单表查询都被看作join：</p>
<ol>
<li>对于FROM中的子查询，首先单独执行它，结果放入临时表，然后将其视为普通表</li>
<li>UNION则被看作多个单端的查询，结果存入临时表，再读取</li>
<li>RIGHT OUTER JOIN被转换为等价的LEFT OUTER JOIN执行</li>
</ol>
<p>MySQL的连接执行策略在目前非常简单：<span style="background-color: #c0c0c0;">每一个JOIN被看作nested-loop join</span>，下面的SQL与对应的伪代码形象的说明这种策略：</p>
<pre class="crayon-plain-tag">SELECT tbl1.col1, tbl2.col2
FROM tbl1 INNER JOIN tbl2 USING(col3)
WHERE tbl1.col1 IN(5,6);
--- 伪代码
outer_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = outer_iter.next
while outer_row  -- 对于左表的每一行，右表的匹配行在嵌套循环里与之结合
    inner_iter = iterator over tbl2 where col3 = outer_row.col3
    inner_row = inner_iter.next
    while inner_row
        output [ outer_row.col1, inner_row.col2 ]
        inner_row = inner_iter.next
    end
    outer_row = outer_iter.next
end

----- 下面是外连接的例子：
SELECT tbl1.col1, tbl2.col2
FROM tbl1 LEFT OUTER JOIN tbl2 USING(col3)
WHERE tbl1.col1 IN(5,6);
--- 伪代码
outer_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = outer_iter.next
while outer_row
    inner_iter = iterator over tbl2 where col3 = outer_row.col3
    inner_row = inner_iter.next
    if inner_row
        while inner_row
            output [ outer_row.col1, inner_row.col2 ]
            inner_row = inner_iter.next
        end
    else  -- 如果右表没有匹配的，则设置一个空行与之匹配
        output [ outer_row.col1, NULL ]
    end
    outer_row = outer_iter.next
end</pre>
<p><strong>执行计划</strong></p>
<p>和很多数据库一样，MySQL不生成字节码来执行查询。查询计划实际上是一个树状的指令，查询执行引擎可以依次执行并最终获得结果：</p>
<p><img class="aligncenter" style="width: 90%;" src="https://blog.gmem.cc/wp-content/uploads/2012/02/MySQL-30.png" alt="" /></p>
<p><strong>连接优化器（The join optimizer）</strong></p>
<p>MySQL优化器最重要的部分是是连接优化器，其决定连接多表的先后顺序。在Oracle的概念里，先执行查询的表称为后面表的<span style="background-color: #c0c0c0;">驱动表</span>。考虑下面的查询：</p>
<pre class="crayon-plain-tag">SELECT film.film_id, film.title, film.release_year, actor.actor_id,
    actor.first_name, actor.last_name
    FROM sakila.film
    INNER JOIN sakila.film_actor USING(film_id)
    INNER JOIN sakila.actor USING(actor_id);

-- MySQL的执行计划如下
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor   -- 和声明的顺序相反，从最后一个表开始驱动
         type: ALL
possible_keys: PRIMARY
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 200     -- 如果使用SELECT STRAIGHT_JOIN 语句强制按照声明顺序来连接，这行是951，需要检查的行更多
        Extra:
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor -- 后续表通过PK引用扫描，需要扫描的数量决定于第一个表
         type: ref
possible_keys: PRIMARY,idx_fk_film_id
          key: PRIMARY
      key_len: 2
          ref: sakila.actor.actor_id
         rows: 1
        Extra: Using index
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: film
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 2
          ref: sakila.film_actor.film_id
         rows: 1
        Extra:</pre>
<p> 优化器会自动选择合适的顺序，除非你指定STRAIGHT_JOIN关键字（很少有必要）。<span style="background-color: #c0c0c0;">N个表进行连接查询</span>时，会导致需要检查的连接顺序达到<span style="background-color: #c0c0c0;">N阶乘个</span>（这称为可能执行计划的search space）,例如，<span style="background-color: #c0c0c0;">10个表的连接需要3,628,800个不同的检查</span>，这将导致优化极为缓慢，因此，MySQL会在达到一定条件后停止检查，这由参数optimizer_search_depth控制。</p>
<p><strong>排序优化</strong></p>
<p>排序结果集可能是很耗时的操作，应该尽量在较少的行上进行排序。当MySQL无法使用索引进行排序时，Server层必须自行完成排序（<span style="background-color: #c0c0c0;">通过磁盘或者内存，但都称为filesort</span>），如果<span style="background-color: #c0c0c0;">排序缓冲</span>能装得下结果集，则MySQL会在内存中完成排序。</p>
<p>基本上，MySQL具有两种排序算法：</p>
<ol>
<li>两阶段算法：这是老的算法，读取行指针、ORDER BY列，排序，然后读取排完序的列表，重新读取行，生成结果集。由于此算法需要两次读取行，这导致了很多的随机I/O，特别是对于MyISAM。</li>
<li>新算法：读取查询所需的所有列，根据 ORDER BY列排序，并根据排序结果生成结果集。在MySQL 4.1以上支持此算法，该算法把很多随机I/O变为顺序I/O，但是<span style="background-color: #c0c0c0;">需要更多的空间</span>。这导致排序缓冲容易被填满</li>
</ol>
<p>如果<span style="background-color: #c0c0c0;">查询所需所有列SIZE * ORDER BY列数 &lt;= max_length_for_sort_data</span>，则MySQL自动使用新算法。</p>
<p>MySQL排序所需的临时存储空间：<span style="background-color: #c0c0c0;">为每个元组提供fixed-size的空间，该尺寸足够存放最大可能的元组</span>（对于字符串还需要考虑字符集，例如对于UTF-8字符集，100长度的字符串需要300字节的空间）</p>
<p>联表查询时，如果<span style="background-color: #c0c0c0;">ORDER BY仅引用join order中的第一个表</span>，则MySQL可以根据此单表排序，然后进行JOIN处理，在执行计划里会显示：<span style="background-color: #c0c0c0;">Extra:Using filesort</span>。否则，必须先JOIN，然后再临时表里进行排序，执行计划会显示<span style="background-color: #c0c0c0;">Extra:Using temporary; Using filesort</span>。</p>
<p>LIMIT通常发生在排序完成之后，但是在MySQL5.6版本，可能会进行一些优化，在排序前丢弃一些不需要的行</p>
<p><strong>返回结果集给客户端</strong></p>
<p>即使不需要返回结果集，MySQL也会对客户端进行响应，例如通知影响的行数<span style="background-color: #c0c0c0;">。MySQL使用增量的方式向客户端发送数据</span>，当其生成第一条结果数据时，即可以并应该向客户端发送数据。这可以避免MySQL在内存中存放过多的数据。</p>
<div class="blog_h3"><span class="graybg">MySQL查询优化器的限制</span></div>
<p><span style="text-decoration: underline;"><strong>相关性子查询（Correlated Subqueries）</strong></span></p>
<p>MySQL有时把子查询<span style="background-color: #c0c0c0;">优化</span>的特别<span style="background-color: #c0c0c0;">差</span>，特别是<span style="background-color: #c0c0c0;"> WHERE  col IN (SELECT ...)</span>这样的子查询：</p>
<pre class="crayon-plain-tag">-- 很自然的子查询：查询演员1参演的所有电影
SELECT * FROM sakila.film
WHERE film_id IN(
    SELECT film_id FROM sakila.film_actor WHERE actor_id = 1);

-- 你可能期望MySQL这样优化：
SELECT GROUP_CONCAT(film_id) FROM sakila.film_actor WHERE actor_id = 1;
SELECT * FROM sakila.film
WHERE film_id IN(1,23,25,106,140,166,277);

-- 恰恰相反，MySQL尝试从outer表film推入一个关联性：
SELECT * FROM sakila.film
WHERE EXISTS (
    SELECT * FROM sakila.film_actor WHERE actor_id = 1
    -- 自作主张推入的关联性
    -- 执行计划可以看到：DEPENDENT SUBQUERY
    -- 弱国outer表很大，这将导致非常严重的性能问题
    AND film_actor.film_id = film.film_id);

-- 建议如此重写此查询：
SELECT film.* FROM sakila.film
INNER JOIN sakila.film_actor USING(film_id)
    WHERE actor_id = 1;
-- 或者使用GROUP_CONCAT()人工生成IN列表</pre>
<p> 但不是说不能用子查询，有时候子查询可能很快，一切以基准测试为依据。</p>
<p><span style="text-decoration: underline;"><strong>等同性传播</strong></span></p>
<p>当IN列表很大时，等同性传播可能拖慢优化速度</p>
<p><span style="text-decoration: underline;"><strong>并行执行</strong></span></p>
<p>MySQL不支持多处理器并行执行单个查询</p>
<p><span style="text-decoration: underline;"><strong>Hash Join</strong></span></p>
<p>MySQL没有内置的Hash Join支持</p>
<div class="blog_h3"><span class="graybg">查询优化器提示</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">提示 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>HIGH_PRIORITY<br />LOW_PRIORITY</td>
<td>
<p>提示MySQL，相对于在同一个表上执行的查询，当前语句优先级如何。HIGH_PRIORITY让其进入待执行列表的最前面，LOW_PRIORITY则是放到最后面</p>
<p>通常可以用在MyISAM上（表锁），绝不要用在InnoDB上</p>
</td>
</tr>
<tr>
<td>DELAYED</td>
<td>与INSERT、REPLACE一起使用，可以使语句立即返回，待插入行则被放入缓冲，在表空闲时被批量插入。某些引擎没有实现此特性</td>
</tr>
<tr>
<td>STRAIGHT_JOIN</td>
<td>可以仅跟着SELECT，让MySQL根据语句指定的顺序进行JOIN</td>
</tr>
<tr>
<td>SQL_SMALL_RESULT<br />SQL_BIG_RESULT</td>
<td>用于SELECT语句，提示优化器如何、何时在GROUP BY、DISTINCT查询中使用临时表及排序。SQL_BIG_RESULT提示优化器结果集可能很大，最好使用磁盘临时表</td>
</tr>
<tr>
<td>SQL_BUFFER_RESULT</td>
<td>提示MySQL把结果集放入临时表并尽快释放锁</td>
</tr>
<tr>
<td>SQL_CACHE<br />SQL_NO_CACHE</td>
<td>提示结果集是否可以缓存</td>
</tr>
<tr>
<td>FOR UPDATE<br />LOCK IN SHARE MODE</td>
<td>对于支持行锁的引擎，可以对匹配行进行锁定</td>
</tr>
<tr>
<td>USE INDEX<br />IGNORE INDEX<br />FORCE INDEX</td>
<td>提示优化器使用或者忽略某个索引，在5.0以后，可以使用FOR ORDER BY 、FOR GROUP BY来影响排序与分组</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">特殊类型查询的优化建议</span></div>
<p><span style="text-decoration: underline;"><strong>COUNT(*)优化</strong></span></p>
<p>要获取行数时，总是使用COUNT(*)</p>
<p>MyISAM只有在无WHERE子句的COUNT(*)时才有高性能</p>
<p><strong>简单优化</strong></p>
<p>对于MyISAM，有时可以利用其COUNT(*) 特性：</p>
<pre class="crayon-plain-tag">SELECT COUNT(*) FROM world.City WHERE ID &gt; 5;  -- 需要检查1000行
SELECT COUNT(*) FROM world.City WHERE ID &lt;=5;  -- 只需检查5行

-- 优化，使用总数减去&lt;=5，即可得到&gt;5的数目
SELECT (SELECT COUNT(*) FROM world.City) - COUNT(*)
FROM world.City WHERE ID &lt;= 5;</pre>
<p><strong>近似计数</strong></p>
<p>使用EXPLAIN语句，可以得到大概需要检查的行数</p>
<p><strong>复杂优化</strong></p>
<p>可利用概要表（summary tables）、外部缓存（memcached等）来进行总数统计</p>
<p><strong><span style="text-decoration: underline;">优化表连接</span></strong></p>
<ol>
<li>确保连接列（ON或者USING子句）具有必要的索引</li>
<li>尽量保证GROUP BY、ORDER BY仅使用来自单个表的列</li>
</ol>
<p><span style="text-decoration: underline;"><strong>优化子查询</strong></span></p>
<p>通常，尽量使用JOIN代替子查询。但是对于MySQL5.6或者MariaDB等MySQL变体，此规则不适用</p>
<p><span style="text-decoration: underline;"><strong>优化GROUP BY和DISTINCT</strong></span></p>
<p>MySQL可以使用临时表或者filesort来处理GROUP BY，通常，<span style="background-color: #c0c0c0;">使用主键分组具有更高的效率</span>，例如：</p>
<pre class="crayon-plain-tag">SELECT actor.first_name, actor.last_name, COUNT(*)
FROM sakila.film_actor
     INNER JOIN sakila.actor USING(actor_id)
GROUP BY film_actor.actor_id; -- first_name and last_name are dependent on the actor_id</pre>
<p> MySQL自动根据GROUP BY指定的列进行排序，如果不想排序引起filesort，可以指定ORDER BY NULL</p>
<p><span style="text-decoration: underline;"><strong>优化LIMIT和OFFSET</strong></span></p>
<p>分页查询时最常见的问题是<span style="background-color: #c0c0c0;">高OFFSET值导致</span>的，LIMIT 10000,20导致生成10020行，然后扔掉前面的10000行，这是非常昂贵的，这个问题有几个解决思路：</p>
<ol>
<li>禁止访问过大的页</li>
<li>使用提前计算的摘要表</li>
<li>和只包含主键、ORDER BY列的冗余表进行JOIN</li>
<li>使用Sphinx</li>
<li>使用某种能够记录行位置信息的“书签”：<br />
<pre class="crayon-plain-tag">-- 获取前20行
SELECT * FROM sakila.rental
ORDER BY rental_id DESC LIMIT 20;
-- 获取第16049到16030行
SELECT * FROM sakila.rental
WHERE rental_id &lt; 16030  -- 如果此主键总是递增的，无论OFFSET多高，均不会影响性能
ORDER BY rental_id DESC LIMIT 20;</pre>
</li>
<li>使用覆盖索引来进行OFFSET，然后再JOIN需要的其他列：<br />
<pre class="crayon-plain-tag">SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5;
-- 如果film表非常大，可以优化为：
SELECT film.film_id, film.description
FROM sakila.film
    --这是一个Deferred join，让MySQL在索引中检索尽量少的数据，而不去访问行
    INNER JOIN (
        SELECT film_id FROM sakila.film
        ORDER BY title LIMIT 50, 5  
    ) AS lim USING(film_id);</pre>
</li>
</ol>
<p><span style="text-decoration: underline;"> <strong>优化UNION</strong></span></p>
<p>UNION总会使用到临时表。<span style="background-color: #c0c0c0;">尽量使用UNION ALL而不是UNION</span>，后者会自动增加DISTINCT，导致查询效率变低。</p>
<div class="blog_h2"><span class="graybg">MySQL高级特性</span></div>
<div class="blog_h3"><span class="graybg">分区表（Partitioned Tables）</span></div>
<p>所谓分区表是指有<span style="background-color: #c0c0c0;">多个物理子表（这些子表使用一样的存储引擎）组成的单个逻辑表</span>。可以把<span style="background-color: #c0c0c0;">分区表看作索引的一个粗略形式</span>——Index以很低的成本获取相邻数据，相邻数据要么可以顺序的读取，要么在内存中匹配到；分区表则可以快速的判断出需要的数据在哪个分区里</p>
<p>MySQL的索引是按分区定义的，这与Oracle不同。<span style="background-color: #c0c0c0;">PARTITION BY子句定义了如何分区</span>的方式。</p>
<p>分区表可以减少表的数据访问、集中存储相关行，在以下场景下，分区表特别具有益处：</p>
<ol>
<li>当表<span style="background-color: #c0c0c0;">非常大，不能纳入内存</span>，或者对于<span style="background-color: #c0c0c0;">“热点行”集中在尾部</span>的表（例如日志类的表）</li>
<li>分区表更加容易维护，例如，可以通过<span style="background-color: #c0c0c0;">drop整个分区的方式来删除历史数据</span>，这样做速度很快。可以按分区来优化、检查、修复</li>
<li>分区数据可以物理分布在多个磁盘上，这样可以更有效的使用多磁盘</li>
<li>在某些工作负载下，可以避免性能瓶颈，例如InnoDB的per-index互斥、ext3文件系统的per-inode锁定</li>
<li>可以按分区来备份和恢复</li>
</ol>
<p>MySQL分区表具有一些限制，例如：</p>
<ol>
<li>每个表最多有1024个分区</li>
<li>MySQL5.1的分区表达式必须是整数，MySQL5.5在某些情况下可以根据列值进行分区</li>
<li>主键or唯一索引必须包含分区表达式中出现的所有列</li>
<li>不能使用外键约束</li>
</ol>
<p><span style="text-decoration: underline;"><strong>分区工作原理</strong></span></p>
<p>对于存储引擎来说，表分区就是普通的表；对于用户来说，表分区由Handler Objects表示，无法直接访问。分区表按以下方式实现逻辑操作：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;">操作 </td>
<td style="text-align: center;"> 实现方式</td>
</tr>
</thead>
<tbody>
<tr>
<td>SELECT</td>
<td>partitioning layer会打开并锁定所有分区，查询优化器会判断是否某些分区可以被忽略掉，然后partitioning layer通过Handler API调用管理分区的存储引擎完成查询</td>
</tr>
<tr>
<td>INSERT</td>
<td>partitioning layer会打开并锁定所有分区，然后决定哪个分区接受此数据，并插入到分区</td>
</tr>
<tr>
<td>DELETE</td>
<td>partitioning layer会打开并锁定所有分区，然后判断哪个分区包含此数据，并从分区删除</td>
</tr>
<tr>
<td>UPDATE</td>
<td>partitioning layer会打开并锁定所有分区，然后判断哪个分区包含此数据，读取，修改，判断哪个分区接受新数据，然后插入目标分区，删除源分区的数据</td>
</tr>
</tbody>
</table>
<p>注意：partitioning layer的锁定行为与存储引擎有关，与对普通表运行这些语句类似。</p>
<p><span style="text-decoration: underline;"><b>分区的类型</b></span></p>
<p>MySQL支持数种分区方式，其中最常用的是range分区——针对列(s)定义一个range值或者函数，例如：</p>
<pre class="crayon-plain-tag">CREATE TABLE sales (
    order_date DATETIME NOT NULL,
    -- Other columns omitted
) ENGINE=InnoDB PARTITION BY RANGE(YEAR(order_date)) (
    -- 按年分区，其他任何返回确定性的整数的函数均可使用
    PARTITION p_2010 VALUES LESS THAN (2010),
    PARTITION p_2011 VALUES LESS THAN (2011),
    PARTITION p_2012 VALUES LESS THAN (2012),
    PARTITION p_catchall VALUES LESS THAN MAXVALUE 
);</pre>
<p> 其它分区方式包括：<span style="background-color: #c0c0c0;">key、hash、list</span>。在MySQL5.5+可以使用<span style="background-color: #c0c0c0;">RANGE COLUMNS分区方式</span>，可以直接使用date-based列进行分区。下面列出一些使用分区的场景：</p>
<ol>
<li>基于hash的子分区（Subpartitioning）可以减少热点行的per-index互斥竞争</li>
<li>基于key分区来减少InnoDB互斥竞争</li>
<li>基于取模函数的range分区，仅保留需要的一部分数据</li>
<li>在一个使用自增长主键的场景下，如果想通过表分区来使最近热点数据clustered在一起，如果想根据date-based列作为分区，其必须作为主键的一部分，这与自增长主键相悖。可以根据表达式<span style="background-color: #c0c0c0;">HASH(id DIV 1000000)</span>来进行分区，这样每100万行会自动形成一个分区，而且最近的数据因为HASH值一样，自动cluster在一起</li>
</ol>
<p><span style="text-decoration: underline;"><strong>如何使用分区</strong></span></p>
<p>考虑如下场景：</p>
<ol>
<li>需要查询一张包含若干年数据的巨大的表，数据量有<span style="background-color: #c0c0c0;">10TB</span>，使用传统<span style="background-color: #c0c0c0;">机械磁盘</span></li>
<li>需要对<span style="background-color: #c0c0c0;">最近几月</span>的数据进行统计分析，数据量<span style="background-color: #c0c0c0;">达1亿行</span></li>
</ol>
<p>该场景下面临的问题：</p>
<ol>
<li><span style="background-color: #c0c0c0;">表太大</span>了，不能扫描整个表</li>
<li>几乎不能使用索引，因为维护成本、空间消耗太大，类似Infobright的系统完全抛弃的BTree索引</li>
<li>根据索引的情况，可能出现<span style="background-color: #c0c0c0;">大量的碎片、聚簇很差的数据</span>，大量<span style="background-color: #c0c0c0;">随机I/O可能导致致命性能问题</span></li>
</ol>
<p>只有两个选项是可行的：</p>
<ol>
<li>查询必须是针对表的portion进行<span style="background-color: #c0c0c0;">顺序扫描</span></li>
<li>期望的表、索引portion<span style="background-color: #c0c0c0;">完整的在内存中匹配</span></li>
</ol>
<p>有两个针对大数据量的策略：</p>
<ol>
<li><span style="background-color: #c0c0c0;">扫描数据但不索引之</span>：仅使用表分区作为导航至期望行的手段，只要WHERE子句仅仅跨越较少的分区，性能可以不错</li>
<li><span style="background-color: #c0c0c0;">索引数据，隔离热点数据</span>：如果除了一小部分热点数据以外，很少使用。可以把热点数据分到足够小的区中，以便可以把数据连同其索引适合内存</li>
</ol>
<p><span style="text-decoration: underline;"><strong>可能的陷阱</strong></span></p>
<ol>
<li><span style="background-color: #c0c0c0;">NULL值可能与表pruning相悖</span>：如果分区函数可能返回NULL，那么<span style="background-color: #c0c0c0;">对应的数据将被存放到定义的第一个分区</span>中。如果第一分区很大，特别是使用扫描但不索引的策略时，性能可能低下。变通办法是<span style="background-color: #c0c0c0;">创建一个dummy第一分区</span>，只要不存放非法数据，这个分区将是空的，检查的代价也就很小了，注意，在MySQL5.5+使用PARTITION BY RANGE COLUMNS不需要此变通</li>
<li><span style="background-color: #c0c0c0;">PARTITION BY 和索引不匹配</span>：假设根据C1分区，而建立C2索引，那么根据C2索引查询需要检查每个分区的索引树，除非索引的所有非叶子节点在内存中，否则比不进行索引扫描更慢，因此，应当<span style="background-color: #c0c0c0;">避免在非分区列上进行索引</span></li>
<li><span style="background-color: #c0c0c0;">分区的选择可能成本很高</span>：不同分区方式具有不同的实现，因此性能表现也不会一样。特别是对于range分区，MySQL需要在分区列表里逐个寻找，如果分区表非常多，可能导致性能低下，这个问题在一行行插入数据时特别明显。解决此问题应该限制分区表的数量，通常<span style="background-color: #c0c0c0;">100个分区</span>在大多数情况下工作良好</li>
<li>打开和锁定分区可能成本很高：打开和锁定表发生在pruning之前，此成本是不可去除的，对于<span style="background-color: #c0c0c0;">简单操作，例如基于主键的单行查询，影响较大</span></li>
<li>维护可能成本很高：诸如创建和DROP表分区的操作很快，但是REORGANIZE PARTITION之类的操作则可能相当耗时，因为其是基于逐行拷贝的方式进行的</li>
</ol>
<p>MySQL5.5+以上版本的分区表技术比较成熟</p>
<p><span style="text-decoration: underline;"><strong>查询优化</strong></span></p>
<p>分区表优化的关键是利用分区函数减少需要访问的分区数量，因此，仅当<span style="background-color: #c0c0c0;">尽可能在WHERE子句中指定partitioned key</span>，使用<span style="background-color: #c0c0c0;">EXPLAIN PARTITIONS</span>可以检查优化器是否在修剪分区：</p>
<pre class="crayon-plain-tag">EXPLAIN PARTITIONS SELECT * FROM sales_by_day WHERE day &gt; '2011-01-01'
*************************** 1. row ***************************
         id: 1
select_type: SIMPLE
      table: sales_by_day
 partitions: p_2011,p_2012

//注意：修剪只能发生在分区函数对应的列（即使基于表达式分区）上，而不能处理表达式的结果：
EXPLAIN PARTITIONS SELECT * FROM sales_by_day WHERE YEAR(day) = 2010
//可以把上面的语句转换为等价形式，以利用修剪
EXPLAIN PARTITIONS SELECT * FROM sales_by_day WHERE day BETWEEN '2010-01-01' AND '2010-12-31'</pre>
<div class="blog_h3"><span class="graybg">视图</span></div>
<p>MySQL支持两种视图实现方式：</p>
<ol>
<li>TEMPTABLE：即根据视图的定义生成临时表，然后在临时表上进行查询。如果视图定义包含<span style="background-color: #c0c0c0;">GROUP BY、 DISTINCT、UNION、聚合函数、子查询等构造，会使用此方式</span></li>
<li>MERGE：即在查询时把视图定义合并到查询语句中，MySQL会尽可能的使用此方式</li>
</ol>
<p>MySQL视图的限制：</p>
<ol>
<li>不支持视图上的触发器</li>
<li>不支持物化视图，可以使用Flexviews实现类似功能</li>
<li>不支持索引的视图，可以使用Flexviews实现类似功能</li>
</ol>
<p><span style="text-decoration: underline;"><strong>可更新视图</strong></span></p>
<p>可更新视图允许使用UPDATE, DELETE, INSERT 语句来修改潜在的表数据，如果<span style="background-color: #c0c0c0;">视图包含GROUP BY, UNION, 或者聚合函数，则不支持更新</span>。修改数据的SQL可以<span style="background-color: #c0c0c0;">包含JOIN，但是被修改的列必须在一个表内</span>。</p>
<pre class="crayon-plain-tag">CREATE VIEW Oceania AS
SELECT * FROM Country WHERE Continent = 'Oceania'
WITH CHECK OPTION; -- 强制任何基于此视图更新的数据匹配视图定义的WHERE约束
-- 因此，下面这样的更新会导致错误
UPDATE Oceania SET Continent = 'Atlantis';</pre>
<div class="blog_h3"><span class="graybg">外键约束</span></div>
<p>InnoDB是唯一支持外键的引擎。</p>
<p>外键可能导致关联表被锁定：例如插入一条数据到子表时，其<span style="background-color: #c0c0c0;">外键引用的父表的对应行也被锁定</span>。这种现象可能导致意外的锁定甚至死锁。</p>
<p>外键可能引起重大的性能开销，某些场景下可以考虑以下代替方案：</p>
<ol>
<li> 在应用程序中控制数据约束</li>
<li>使用枚举值来代替外键来进行列表值约束</li>
<li>使用触发器来实现级联操作</li>
</ol>
<div class="blog_h3"><span class="graybg">在数据库中存储代码</span></div>
<p>MySQL支持触发器、存储过程、存储函数、以及周期性任务中的events。</p>
<div class="blog_h3"><span class="graybg">游标</span></div>
<p>MySQL提供<span style="background-color: #c0c0c0;">只读、单向、服务器端的游标功能</span>，供存储过程或者低级别Client API使用。</p>
<div class="blog_h3"><span class="graybg">预编译语句</span></div>
<p>预编译语句对于需要重复执行的语句可以提高性能，因为：</p>
<ol>
<li>服务器只需要解析语句一次</li>
<li>服务器只需要执行某些优化步骤一次，并缓存这一部分</li>
<li>基于二进制协议传送参数比ASCII方式更加高效（减少网络带宽、客户端内存消耗），特别是BLOB、TEXT字段</li>
<li>MySQL把参数直接存储在服务器缓冲中,减少在服务器内存拷贝值的开销</li>
</ol>
<p>此外，预编译语句也有利于安全，其避免了SQL注入的可能</p>
<p><span style="text-decoration: underline;"><strong>预编译语句的优化过程</strong></span></p>
<p>准备阶段：解析SQL，消除否定表达式、重写子查询</p>
<p>首次执行：如果可能，简化嵌套连接为OUTER JOINS或者INNER JOINS</p>
<p>每次执行：</p>
<ol>
<li>修剪分区</li>
<li>如果可能，消除COUNT(), MIN(), MAX()</li>
<li>移除常量子表达式</li>
<li>检测constant tables</li>
<li>传播等同性</li>
<li>分析和优化ref, range, index_merge</li>
<li>优化JOIN的顺序</li>
</ol>
<div class="blog_h3"><span class="graybg">用户定义函数</span></div>
<p>所谓UDF于存储函数不同，UDF可以基于任何语言编写，通过C进行调用，其性能较高。</p>
<div class="blog_h3"><span class="graybg">插件</span></div>
<p>MySQL支持插件机制，下面是一个简短的插件列表：</p>
<ol>
<li>Procedure 插件：可以对结果集进行后处理</li>
<li>Daemon 插件：作为一个进程与MySQL一起运行，可以进行诸如监听网络连接、执行定期任务等工作。Percona Server的Handler <br />Socket plugin就是一个例子，它监听端口并允许使用NoSQL方式来访问MySQL</li>
<li>INFORMATION_SCHEMA 插件：支持提供INFORMATION_SCHEMA表</li>
<li>Full-text 解析插件：用于支持全文检索</li>
<li>Audit插件：在SQL语句的预定义点接收事件，可以进行记录日志</li>
<li>验证插件：支持例如PAM、LDAP的身份验证扩展</li>
</ol>
<div class="blog_h3"><span class="graybg">字符集</span></div>
<p>所谓字符集（Character Sets）是指<span style="background-color: #c0c0c0;">二进制编码到符号集的映射</span>。排序规则（Collation）是指对指定字符集下不同字符的比较规则。</p>
<p>MySQL字符集设置默认继承规则：</p>
<ol>
<li>创建数据库时，从服务器character_set_server设置继承</li>
<li>创建表时，从数据库设置继承</li>
<li>创建列时，从表设置继承</li>
</ol>
<p>客户端和服务器通信时，可能使用不同的字符集，服务器根据需要进行转换：</p>
<ol>
<li>服务器假设客户端使用character_set_client指定的字符集来发送语句（statement）</li>
<li>服务器接收到语句后，使用character_set_connection来转换字符集,也用character_set_connection来决定如何把数字转换为字符串</li>
<li>当服务器返回结果集或者错误码时，使用character_set_result来转换</li>
</ol>
<p><span style="text-decoration: underline;"><strong>选择字符集合排序规则</strong></span></p>
<p>MySQL 4.1+支持多种字符集合排序规则，包括使用UTF-8编码的多字节Unicode字符。<span style="background-color: #c0c0c0;">排序规则的后缀：_cs, _ci, _bin</span>用来标注使用<span style="background-color: #c0c0c0;">大小写敏感、大小写不敏感、或者根据二进制值</span>排序。</p>
<div class="blog_h3"><span class="graybg">全文检索</span></div>
<p>MySQL 5.6.4+以后的InnoDB支持全文检索，目前对中文的支持不是很好</p>
<div class="blog_h3"><span class="graybg">分布式（XA）事务</span></div>
<p>MySQL5.0+以上版本，部分支持两阶段提交的XA事务，其可作为XA事务参与者（participants），但是不能作为协调者（coordinator）。</p>
<div class="blog_h3"><span class="graybg">查询缓存</span></div>
<p>许多数据库系统能够<span style="background-color: #c0c0c0;">缓存查询的执行计划</span>，MySQL除此之外还能<span style="background-color: #c0c0c0;">直接缓存SELECT的结果集</span>，此即所谓Query Cache。</p>
<p>相关表一旦发生修改，查询缓存即失效。</p>
<p>随着服务器性能的提高，查询缓存往往成为整个服务器上的<span style="background-color: #c0c0c0;">单点竞争热点</span>，因此，可以<span style="background-color: #c0c0c0;">考虑默认禁止查询缓存</span>。如果的确有必要，可以<span style="background-color: #c0c0c0;">设置不超过数十MB的缓存</span>。</p>
<p><span style="text-decoration: underline;"><strong>MySQL如何检查缓存命中</strong></span></p>
<p>检查策略很简单，缓存类似一个HashMap，其key就是查询语句（不做任何处理）的Hash Code。</p>
<p>MySQL<span style="background-color: #c0c0c0;">不会缓存结果集不是确定性的查询</span>，例如带有<span style="background-color: #c0c0c0;">NOW() 、CURRENT_DATE()</span>函数的查询语句，包含<span style="background-color: #c0c0c0;">用户定义函数、存储函数、用户变量、临时表、mysql数据库中的表的查询语句也不会缓存</span>。考虑下面的例子：</p>
<pre class="crayon-plain-tag">... DATE_SUB(CURRENT_DATE, INTERVAL 1 DAY) -- 不能缓存
... DATE_SUB('2014-07-14’, INTERVAL 1 DAY) -- 能缓存</pre>
<p>MySQL<span style="background-color: #c0c0c0;">5.1以前不支持预编译语句的查询缓存</span>。</p>
<p>查询缓存在某些时候能够提高性能，但是<span style="background-color: #c0c0c0;">对读与写均增加额外的消耗</span>：</p>
<ol>
<li>读操作之前必须检查缓存</li>
<li>如果查询是可缓存的，且尚未缓存，那么生成缓存需要一些额外的资源</li>
<li>对于写操作，其必须使相应的缓存条目失效，如果<span style="background-color: #c0c0c0;">缓存碎片严重、缓存特别大</span>，这将严重影响性能</li>
</ol>
<p>对于InnoDB，一旦<span style="background-color: #c0c0c0;">写操作开始，就会失效相应缓存</span>，即使事务尚未提交，并且，<span style="background-color: #c0c0c0;">事务提交前，相应表示不可缓存的</span>。</p>
<p><span style="text-decoration: underline;"><b>查询缓存如何使用内存</b></span></p>
<p>MySQL完全把<span style="background-color: #c0c0c0;">查询缓存置于内存中</span>。查询缓存支持使用可变长度的内存块，每个块知道其类型（存放查询结果/查询语句使用的表/查询文本...）、大小、包含多少数据，并且持有指向前/后物理块/逻辑块的指针。</p>
<p>MySQL启动时，即为查询缓存分配对应的内存。每一次进行缓存时，申请缓存内存中的一个块，其最小大小为query_cache_min_res_unit字节。块的分配属于相对较慢的操作，因为MySQL需要检查空闲块列表，并找到一个足够大的。</p>
<p><span style="text-decoration: underline;"><strong>何时使用查询缓存</strong></span></p>
<p>最适合缓存的查询是<span style="background-color: #c0c0c0;">生成耗时大、结果集小的查询</span>，例如针对大表的COUNT(*)之类的聚合查询。</p>
<p>对于写负载很大的系统应当禁用查询缓存。</p>
<div class="blog_h2"><span class="graybg">优化服务器设置</span></div>
<p>MySQL配置没有特定的公式，只能根据<span style="background-color: #c0c0c0;">实际情况去优化</span>————包括<span style="background-color: #c0c0c0;">负载、数据、应用的需求、以及硬件</span>，MySQL有大量的设置可以改变，但是不应当<span style="background-color: #c0c0c0;">随意的修改、设置很多参数</span>，这样可能导致内存耗尽、导致MySQL使用swap文件。应当<span style="background-color: #c0c0c0;">调整好基本参数（例如InnoDB缓冲池大小、日志文件大小）</span>，并<span style="background-color: #c0c0c0;">把精力放在Schema优化、索引、查询的设计上</span>。如果某些参数需要优化，其必定会在查询响应时间上有所体现。</p>
<div class="blog_h3"><span class="graybg">MySQL配置如何工作</span></div>
<p>从哪里获取配置文件：通过命令行参数指定文件的位置，在Linux下，通常位于/etc/my.cnf、/etc/mysql/my.cnf ：</p>
<pre class="crayon-plain-tag">which mysqld
/usr/sbin/mysqld --verbose --help | grep -A 1 'Default options'
#查找配置文件的位置</pre>
<p><span style="text-decoration: underline;"><strong>语法、作用范围、动态性</strong></span></p>
<p>配置参数均为小写，单词使用下划线或者短横线连接，下面的两个设置（或命令行参数）是等价的：</p>
<pre class="crayon-plain-tag">/usr/sbin/mysqld --auto-increment-offset=5
/usr/sbin/mysqld --auto_increment_offset=5</pre>
<p> 配置参数可能有不同的作用域范围，有些事服务器<span style="background-color: #c0c0c0;">全局范围</span>的，有些是<span style="background-color: #c0c0c0;">连接范围</span>的，有些则是<span style="background-color: #c0c0c0;">针对一个对象的</span>。某些连接范围的参数具有全局等价参数，后者可以看作是其默认值：</p>
<ol>
<li>query_cache_size是全局参数</li>
<li>sort_buffer_size具有全局默认值，每个SESSION可以设置自己的值</li>
<li>join_buffer_size具有全局默认值，可以在SESSION上设置，并且，单个查询可以为每个JOIN设置一个join buffer</li>
</ol>
<p>有些参数允许不停机的情况下<span style="background-color: #c0c0c0;">动态修改（对已经创建的SESSION无效）</span>，这些修改在重启后会消失，例如下面的命令设置SESSION或者GLOBAL的sort_buffer_size：</p>
<pre class="crayon-plain-tag">SET sort_buffer_size = ;
SET GLOBAL sort_buffer_size = ;
SET @@sort_buffer_size := ;
SET @@session.sort_buffer_size := DEFAULT;   -- 设置为GLOBAL默认值
SET @@global.sort_buffer_size := ;</pre>
<p><span style="text-decoration: underline;"><strong>动态参数设置的副作用</strong></span></p>
<p>动态的设置参数可能具有边际效应，例如导致刷空缓冲中的脏块，常见的具有边际效应的动态参数如下表：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">参数 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 200px;">key_buffer_size</td>
<td>
<p>设置此值将导致分配指定数量的内存供key buffer（key cache，索引缓存）,但是在使用之前，OS不会提交这些内存给MySQL。</p>
<p>MySQL支持<span style="background-color: #c0c0c0;">创建多个key buffer</span></p>
</td>
</tr>
<tr>
<td>table_cache_size</td>
<td>设置此值后，某个线程<span style="background-color: #c0c0c0;">下次打开表时</span>发生效果，如果设置的值大于缓存中表的数目，则插入缓存；反之，则把不用的表从缓存中删除</td>
</tr>
<tr>
<td>thread_cache_size</td>
<td>下一个Connection关闭后产生效果，如果超过线程缓存限制，则会销毁Connction对应线程而不是缓存之</td>
</tr>
<tr>
<td>query_cache_size</td>
<td>修改此值后，MySQL立即清除所有的查询缓存，调整缓存的大小，并重新初始化缓存。由于MySQL是串行删除缓存的，所以可能导致服务器停顿较长时间</td>
</tr>
<tr>
<td>read_buffer_size</td>
<td>直到查询需要读取缓冲时，MySQL才会分配整块的、指定大小的内存</td>
</tr>
<tr>
<td>read_rnd_buffer_size</td>
<td>直到查询需要读取缓冲时，MySQL才会分配足够其使用的内存</td>
</tr>
<tr>
<td>sort_buffer_size</td>
<td>直到查询需要进行排序时，MySQL才会分配整块的、指定大小的内存</td>
</tr>
</tbody>
</table>
<p><span style="background-color: #c0c0c0;">不要把per-connection的参数全局默认值太高</span>，例如sort_buffer_size，否则将造成巨大的浪费，应当在需要时设置，并在使用完后恢复默认：</p>
<pre class="crayon-plain-tag">SET @@session.sort_buffer_size := ;
-- Execute the query...
SET @@session.sort_buffer_size := DEFAULT;</pre>
<div class="blog_h3"><span class="graybg">创建MySQL配置文件</span></div>
<p>MySQL默认的示例文件中具有很多被注释的配置项，需要注意这些配置项的解释<span style="background-color: #c0c0c0;">可能不是合理的、完整的，甚至是不正确的</span>，这些示例文件对于现代硬件、工作负载来说，已经过期了。</p>
<p>最合适的做法是，<span style="background-color: #c0c0c0;">从头编写一个配置文件</span>，而不是以默认示例配置文件为基础进行修改，下面是一个推荐的配置模板：</p>
<pre class="crayon-plain-tag">[mysqld]
# GENERAL
datadir                    = /var/lib/mysql                  #数据文件位置
socket                     = /var/lib/mysql/mysql.sock       #Socket文件位置，最好不要使用默认值
pid_file                   = /var/lib/mysql/mysql.pid        #PID文件位置，最好不要使用默认值
user                       = mysql                           #运行MySQL的OS用户
port                       = 3306
storage_engine             = InnoDB                          #默认存储引擎，大部分时候InnoDB是最好的选择
# INNODB                                                     #InnoDB正常运行最基本的配置是缓冲池、日志文件大小，默认值均过小了
innodb_buffer_pool_size    =                          #有些建议是设置为75%-80%服务器内存，这不科学
innodb_log_file_size       = 
innodb_file_per_table      = 1                               #每张表一个文件，出于可管理性、灵活性的考虑
innodb_flush_method        = O_DIRECT                        #仅对Unix有意义
# MyISAM
key_buffer_size            = 
# LOGGING
log_error                  = /var/lib/mysql/mysql-error.log
log_slow_queries           = /var/lib/mysql/mysql-slow.log
# OTHER
tmp_table_size             = 32M
max_heap_table_size        = 32M
query_cache_type           = 0
query_cache_size           = 0
max_connections            = 
thread_cache_size          = 
table_cache_size           = 
open_files_limit           = 65535                           #打开文件的限制，现代OS打开文件几乎不消耗什么资源，如果设置的过小，可能导致too many open files 错误
[client]
socket                     = /var/lib/mysql/mysql.sock
port                       = 3306</pre>
<p><span style="text-decoration: underline;"><strong> innodb_buffer_pool_size的正确设置方式</strong></span></p>
<ol>
<li>从服务器内存总量开始计算</li>
<li>减去操作系统、其它程序所需要的内存</li>
<li>减去MySQL其它组件需要的内存，例如每个查询操作需要的缓冲区</li>
<li>减去InnoDB日志文件所需要的内存，以便OS有足够内存来缓存之。最好留有一定的空间供二进制日志缓存使用，特别是使用延迟复制的场景，因为可能需要读取旧的master二进制日志</li>
<li>减去MySQL中其它缓冲、缓存需要空间，例如MyISAM的key cache、查询缓存</li>
<li>将剩余的值除以105%，以去除InnoDB管理buffer pool所需的内存</li>
<li>向下取整，作为目标值</li>
</ol>
<p> 举例：服务器192G内存，作为MySQL专用服务器，只使用InnoDB引擎，不使用查询缓存、没有过多的连接数，InnoDB日志总大小为4G，则InnoDB缓冲池大小估算过程可以如下：</p>
<ol>
<li>考虑2GB或者5%的内存供OS、MySQL其它组件使用</li>
<li>减去4GB供InnoDB日志使用</li>
<li>剩余177GB，向下取整，设置为168GB</li>
</ol>
<p>如果使用MyISAM并且需要缓存其索引，则会有所不同；在Windows下，MySQL存在大内存管理的缺陷，特别是MySQL5.5以前。</p>
<p>最好是设置一个<span style="background-color: #c0c0c0;">安全、较大的值，然后运行服务一段时间</span>，根据工作负载的需要调整，因为MySQL的连接本身占用内存很少，通常在256KB左右，但是如果查询使用了<span style="background-color: #c0c0c0;">临时表、排序、存储过程</span>等，则可能使用很大的内存。</p>
<div class="blog_h3"><span class="graybg">配置内存使用</span></div>
<p>MySQL的内存使用可以分为可控、不可控两类，后者包括：MySQL实例需要的内存、解析查询、管理内部状态需要的内存。内存配置的步骤与上一节类似，可以按以下步骤：</p>
<ol>
<li>确定MySQL可以使用的内存上限</li>
<li>判断per-connection的内存用量，例如：排序缓冲、临时表</li>
<li>判断OS和其它程序需要的内存</li>
<li>其余的内存分配给MySQL缓存，例如InnoDB的buffer pool</li>
</ol>
<p><span style="text-decoration: underline;"><strong>MySQL可以使用多少内存？</strong></span></p>
<p>对于32位的Linux内核，限制进程使用的寻址空间在2.5-2.7GB左右，超过寻址空间限制来使用内存可能导致崩溃。</p>
<p>不同OS对单进程的内存限制不一样，栈大小也需要考虑。</p>
<p>即使是64bit系统，某些限制仍然存在，例如，MyISAM的key buffer在MySQL5.0和以前的版本最多设置到4GB</p>
<p><span style="text-decoration: underline;"><strong>每个连接需要的内存</strong></span></p>
<p>MySQL只需要<span style="background-color: #c0c0c0;">很少的内存来保持connection (thread)的开启状态</span>，也需要一个<span style="background-color: #c0c0c0;">基本数量的内存来执行任何SQL语句</span>。需要确定执行查询时所需要的内存峰值并进行相应的设置，否则查询可能很慢或者失败。</p>
<p>一般不需要考虑最糟糕的峰值占用，例如对于100个连接，设置myisam_sort_buffer_size为256M，那么最糟糕的情况下需要25GB的内存，但是这种情况发生的可能性不大。合理的做法是在真实的Workload下观测服务器中MySQL进程的内存消耗。</p>
<p><span style="text-decoration: underline;"><strong>为OS保留内存</strong></span></p>
<p>OS内存不足的一个指征是频繁的使用Swapping（paging）虚拟内存到磁盘，通常应该至少保留2GB或者5%给OS。</p>
<p><span style="text-decoration: underline;"><strong>为缓存分配内存</strong></span></p>
<p>如果服务器供MySQL专用，那么<span style="background-color: #c0c0c0;">OS保留内存、查询处理所需内存以外的所有内存，均可分配给缓存使用</span>。MySQL缓存是需要内存最大的部分，它使用缓存来避免磁盘访问。对于大部分场景，下面是最重要的缓存类型：</p>
<ol>
<li>InnoDB缓冲池</li>
<li>InnoDB日志文件、MyISAM数据的操作系统缓存</li>
<li>MyISAM的key（索引）缓存</li>
<li>查询缓存</li>
<li>一些不能实际配置的缓存，例如二进制日志、表定义文件的缓存</li>
</ol>
<p>其它类型的缓存使用内存的量很少，不必过分关注。</p>
<p>如果只使用MyISAM，可以完全禁用InnoDB；反之，只需要给MyISAM配置最少的资源（InnoDB内部会使用MyISAM表做一些操作）。</p>
<p><span style="text-decoration: underline;"><strong>InnoDB缓冲池</strong></span></p>
<p>如果主要使用InnoDB表，那么 InnoDB缓冲池将比其它任何组件需要更多的内存。InnoDB<span style="background-color: #c0c0c0;">严重依赖</span>该缓冲池——它负责<span style="background-color: #c0c0c0;">缓存索引、保持行数据、自适应Hash索引、插入缓冲、锁、以及其它内部结构</span>。InnoDB使用<span style="background-color: #c0c0c0;">缓冲池实现延迟写入（delay writes）</span>，可以把多个写操作合并执行。</p>
<p>使用innotop之类的工具可以监视该缓冲池的使用，注意没有必要设置超过需要的大值。过大的缓冲池会<span style="background-color: #c0c0c0;">导致过长的关闭（如果缓冲中有很多脏页，那么关闭时必须写入数据文件，即使强制关闭，在启动时也少不了恢复时间）、预热（warmup）时间</span>。</p>
<p>减少关闭时间：在运行时设置innodb_max_dirty_pages_pct为一个较小值，等待flush线程刷空缓冲池，当脏页数较小（状态变量：Innodb_buffer_pool_pages_dirty）时关闭MySQL。</p>
<p>innodb_max_dirty_pages_pct并不保证在缓冲池中存储更少的脏页，而是控制MySQL停止延迟(lazy)行为的阈值——当脏页占比超过阈值时，MySQL的flush线程会尽快的刷出脏页，保证占比低于阈值。此外当事务日志的空间不足时，会出现“激进刷空”模式。</p>
<p>当大缓冲池搭配慢速磁盘时，服务器可能需要很长的时间来预热，Percona Server提供了一个在重启后重新载入数据页的功能来减少预热时间，MySQL 5.6+亦有类似功能。</p>
<p><span style="text-decoration: underline;"><strong>MyISAM键缓存</strong></span></p>
<p>MyISAM的key caches也称为key buffers，默认为一个，可以创建多个。与InnoDB不同，<span style="background-color: #c0c0c0;">MyISAM只缓存索引，不缓存数据</span>。如果只使用MyISAM，应当分配足够的内存给键缓存。</p>
<p>最重要的配置参数是key_buffer_size，在MySQL5.0-，单个键缓存具有4GB的最大值限制。没必要分配比索引总大小更大的内存：</p>
<pre class="crayon-plain-tag">-- 计算索引总大小
SELECT SUM(INDEX_LENGTH) FROM INFORMATION_SCHEMA.TABLES WHERE ENGINE='MYISAM';</pre>
<p> 默认情况下，只有一个键缓存，下面的语句示意如何创建新的键缓存并把表映射到其上（未明确映射的表，映射到默认缓存）：</p>
<pre class="crayon-plain-tag">#配置文件添加两个新的键缓存
key_buffer_1.key_buffer_size = 1G
key_buffer_2.key_buffer_size = 1G
#现在有3个键缓存了

#把t1、t2表的索引缓存到key_buffer_1
mysql&gt; CACHE INDEX t1, t2 IN key_buffer_1;

#使用init_file选项和下面的命令预先加载索引
LOAD INDEX INTO CACHE t1, t2;</pre>
<p> 使用SHOW STATUS 、SHOW  VARIABLES可以监控键缓存的使用，计算公式为：</p>
<p style="padding-left: 30px;"><span style="background-color: #c0c0c0;">100 - ( (Key_blocks_unused * key_cache_block_size) * 100 / key_buffer_size )</span></p>
<p>如果运行一段时间后，服务器<span style="background-color: #c0c0c0;">没有用满键缓存，可以考虑降低设置</span>的值。</p>
<p>关于键缓存命中率(hit ratio)：数字没有实际意义，不同工作场景下对命中率的要求不同。根据经验，每秒缓存miss更有价值：假设磁盘每秒支持100随机读，那么5次/秒的miss不会造成问题，80次/秒则可能造成问题。<span style="background-color: #c0c0c0;">Key_reads / Uptime</span>可以计算自服务启动以来的miss/s，下面的语句则可以计算最近的miss/s：</p>
<pre class="crayon-plain-tag">-- 每10秒统计
mysqladmin extended-status -r -i 10 | grep Key_reads</pre>
<p> 需要注意的时，MyISAM使用OS缓存来处理数据文件，数据文件通常比索引大，因此，<span style="background-color: #c0c0c0;">通常需要保留比key buffer更大的内存给OS</span>。在完全不使用MyISAM表时，可以设置key_buffer_size=32M左右，因为有时候内部GROUP BY之类操作可能需要MyISAM临时表。</p>
<p><strong>MyISAM key block size</strong></p>
<p>键的块大小很重要，这与MyISAM与OS缓存、文件系统的交互方式有关，错误的设置可能导致read-around wirte——OS在写入数据前必须读出一定的数据，假设系统page size是4KB，而key block size设置为1KB，下面的场景演示了read-around write：</p>
<ol>
<li>MyISAM请求磁盘上的1KB key block</li>
<li>OS从磁盘读取4KB数据并缓存之，然后把其中需要的1KB传递给MyISAM</li>
<li>OS为了丢弃上面的数据，以便缓存其它数据</li>
<li>MyISAM修改1KB数据，要求OS写回磁盘</li>
<li>OS再次读取4KB，合并MyISAM写入的1KB，然后把整个4KB写回磁盘</li>
</ol>
<p>设置<span style="background-color: #c0c0c0;">myisam_block_size</span>与系统page size一致，可以避免read-around write问题。</p>
<p><span style="text-decoration: underline;"><strong>线程缓存</strong></span></p>
<p>Thread cahce缓存没有和Connection关联的线程。Connection到达时，MySQL将其与某个缓存的Thread关联，或者创建新Thread，后者相对较慢。Connection关闭时，把Thread放回缓存，或者销毁。</p>
<p>参数thread_cache_size可以指定线程缓存的大小，观察Threads_created变量，保证其小于10/s即可。Threads_connected变量用于观察当前连接数。</p>
<p>此参数设置的过小并不会节省很多内存，因为缓存/休眠的线程通常仅占用256KB左右的内存。设置过大（例如几千）则可能导致另外的问题，因为有些OS不能很好的处理大量线程，即使大部分都处于休眠状态。</p>
<p><strong><span style="text-decoration: underline;">表缓存</span></strong></p>
<p>与线程缓存类似，但是存放的是代表了表的对象。对象的具体属性与存储引擎相关。</p>
<p>在MySQL5.1+，表缓存分为两个部分：</p>
<ol>
<li>table_open_cache：打开的表的缓存。这个是每个线程单独的缓存</li>
<li>table_definition_cache：已解析的表定义（.frm文件），足够存入所有表定义即可</li>
</ol>
<p><span style="text-decoration: underline;"><strong>InnoDB数据字典</strong></span></p>
<p>InnoDB具有自己的per-table缓存，通常称作表定义缓存或者数据字典，目前不支持手工配置其大小。</p>
<p>如果使用innodb_file_per_table，那么同时打开的*.ibd（数据文件）文件的数量有限制，可以 通过innodb_open_files来控制。</p>
<div class="blog_h3"><span class="graybg">配置I/O行为</span></div>
<p>一些选项控制MySQL如何把数据同步到磁盘、如何进行数据恢复。这些设置体现了<span style="background-color: #c0c0c0;">数据安全性与性能之间的权衡</span>，一般来说，保证数据立即、一致的写入磁盘是需要较大代价的。</p>
<p><span style="text-decoration: underline;"><strong>InnoDB的I/O配置</strong></span></p>
<p>允许对InnoDB的数据恢复（recovers，InnoDB启动后总是会运行数据恢复处理）、打开和刷出（flush）数据的行为进行控制，从而很大程度上的影响恢复和整体性能。InnoDB具有一个复杂的buffer、file链来提高性能和保证ACID属性，链条中的每个环节都是可配置的，这个链条如下图所示：</p>
<p><img src="https://blog.gmem.cc/wp-content/uploads/2012/02/MySQL.1.png" alt="" /></p>
<p><strong>InnoDB的事务日志</strong></p>
<p>事务日志的意义在于<span style="background-color: #c0c0c0;">降低提交的成本</span>——把事务记录到文件（顺序I/O）而不是把buffer pool刷出到磁盘（事务对应索引、数据的修改往往映射到表空间的随机位置，从而导致random I/O）。需要注意的是，对于<span style="background-color: #c0c0c0;">SSD，随机I/O的劣势不像机械磁盘那么严重</span>。</p>
<p>一旦<span style="background-color: #c0c0c0;">事务日志记录到文件，即实现了持久化</span>，即使修改内容没有回写到数据文件中，如果突然宕机，MySQL会在启动时重做日志内容并恢复已提交的事务。</p>
<p>当然，InnoDB最终还是需要把修改内容回写到数据文件，因为<span style="background-color: #c0c0c0;">事务日志具有固定的大小</span>。InnoDB使用循环写入的方式使用日志文件，但是必须确保<span style="background-color: #c0c0c0;">不能覆盖未回写到数据文件的日志</span>。</p>
<p>InnoDB使用<span style="background-color: #c0c0c0;">一个后台线程负责智能回写数据文件</span>。其智能表现在：把写操作进行分组，以尽量实现顺序I/O。后台写入机制使I/O系统与查询负载解耦。</p>
<p>总体的InnoDB事务日志的大小由以下两个参数控制，对写性能具有很大的影响：</p>
<ol>
<li>innodb_log_file_size：默认5MB，单个日志文件的大小</li>
<li>innodb_log_files_in_group：默认2个，日志文件的个数</li>
</ol>
<p>默认值对于高性能工作负载来说太小了，应该<span style="background-color: #c0c0c0;">设置为上百MB，甚至达到GB级别</span>。通常不需要修改日志文件的个数，修改大小的步骤如下：</p>
<ol>
<li>完全的（不能强行关闭，否则日志文件中会存在尚未刷出到数据文件的事务）关闭MySQL</li>
<li>移除旧的日志文件，例如ib_logfile0</li>
<li>重新配置日志文件大小，并重启MySQL</li>
</ol>
<p>权衡日志文件的理想大小：</p>
<ol>
<li>过小的问题：InnoDB需要更多的Checkpoints，导致更多的日志写操作，<span style="background-color: #c0c0c0;">极端情况下，写查询需要等待日志刷出到磁盘，以腾出日志文件空间</span>。</li>
<li>过大的问题：当进行恢复时，InnoDB需要做很多的工作，从而可能需要很多的时间。较新版本的MySQL在这一点上性能有所提高。注意恢复时间还与被修改的数据量、数据分布情况有关。</li>
</ol>
<p><span style="background-color: #c0c0c0;">日志缓冲（log buffer）</span>：当InnoDB修改任何数据时，其会写入事务日志到内存的日志缓冲里，此缓冲<span style="background-color: #c0c0c0;">默认大小为1MB</span>，当以下三种情况之一发生时：<span style="background-color: #c0c0c0;">缓冲满了、事务提交时，或者过了一秒</span>，InnoDB把缓冲刷出到日志文件中。参数innodb_log_buffer_size控制此缓冲的大小。建议的大小范围是1-8MB，除非需要写入大量的BLOB，否则没必要设置的更大。通过SHOW INNODB STATUS查看状态变量<span style="background-color: #c0c0c0;">Innodb_os_log_written</span>，观察10-100秒，检查每秒写入数来判断日志缓冲是否足够大，例如：对于1MB的缓冲，每秒写入的峰值不超过100KB说明缓冲足够了。</p>
<p>Innodb_os_log_written也可以用<span style="background-color: #c0c0c0;">来衡量日志文件是否足够大</span>，例如峰值每秒写入100KB，那么256MB的<span style="background-color: #c0c0c0;">日志文件</span>基本<span style="background-color: #c0c0c0;">够一小时</span>使用了。</p>
<p>日志缓冲只有被刷出，事务才能持久化，设置innodb_flush_log_at_trx_commit可以控制日志缓冲刷出的频率：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 50px; text-align: center;"> 值</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td><span style="background-color: #c0c0c0;">每秒</span>把log buffer写入到log file（在大部分OS，这只是把数据从InnoDB内存移动到系统缓存，仍然在内存中），并刷出log file到持久化存储（Blocked I/O直到写入完成）。但是在事务提交时不做任何操作</td>
</tr>
<tr>
<td>1</td>
<td>
<p><span style="background-color: #c0c0c0;">每次事务提交时</span>把log buffer写入到log file，并刷出log file到持久化存储。这是默认的、最安全的设置，不会丢失任何事务。设置为该值会导致trx/s大大减小，在高速机械磁盘上，只能达到几百次事务/秒</p>
<p>对于高性能事务性应用，应当设置为1，并且把日志文件放在具有电池支持的写缓存RAID磁盘上，这样快且安全。</p>
</td>
</tr>
<tr>
<td>2</td>
<td>每次事务提交时写入log buffer到log file，但是<span style="background-color: #c0c0c0;">不flush（存放在OS的缓存中）</span>。InnoDB每秒进行一次flush调度。与0相比，MySQL崩溃时2不会丢失数据，整个服务器宕机则可能丢失数据</td>
</tr>
</tbody>
</table>
<p><strong>InnoDB如何打开和刷出日志和数据文件</strong></p>
<p>参数innodb_flush_method用于配置InnoDB与文件系统的交互方式（包括读和写，同时影响日志文件、数据文件）：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> 值</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 180px;">fdatasync</td>
<td>
<p>非Windows系统的默认值。InnoDB使用fsync()代替fdatasync()来刷出数据、日志文件。fsync与fdatasync的区别是后者仅写数据，前者同时写文件元数据</p>
<p>使用fsync的缺点是，OS至少会在自己的缓存内缓冲一部分数据，可能（依赖于OS与文件系统）导致不必要的双重缓冲</p>
</td>
</tr>
<tr>
<td>O_DIRECT</td>
<td>
<p>InnoDB使用O_DIRECT标记或者directio()来读写数据文件（对日志文件没影响），某些Unix类系统不支持（GNU/Linux、FreeBSD、Solaris均支持）。</p>
<p>该设置还是使用fsync()，但是指示OS不进行缓存或者预读取（read-ahead），从而避免双重缓冲的问题。该设置不能禁止RAID卡的预读取功能</p>
</td>
</tr>
<tr>
<td>ALL_O_DIRECT</td>
<td>与O_DIRECT类似，但是同时对日志文件起作用。在Percona Server、MariaDB上被支持。</td>
</tr>
<tr>
<td>O_DSYNC</td>
<td>对日志文件起作用，使写操作同步化——完成后才返回</td>
</tr>
<tr>
<td>async_unbuffered</td>
<td>Windows下的默认值，使用Windows原生的异步（重叠）I/O仅需读写</td>
</tr>
<tr>
<td>unbuffered</td>
<td>仅WIndows，与async_unbuffered类似，但是不使用原生异步I/O</td>
</tr>
<tr>
<td>normal</td>
<td>仅WIndows，导致InnoDB不使用异步I/O或者非缓冲I/O</td>
</tr>
<tr>
<td>nosync and littlesync</td>
<td>仅开发时使用</td>
</tr>
</tbody>
</table>
<p><strong>InnoDB表空间</strong></p>
<p>InnoDB把数据存放在表空间中，表空间是一种虚拟文件系统，可以跨越多个磁盘文件。除了数据和索引外，undo log、insert buffer、doublewrite buffer、一些其他的内部数据结构也存放于其中。使用下面的参数配置表空间：</p>
<pre class="crayon-plain-tag">#表空间的目录
innodb_data_home_dir  = /var/lib/mysql/
#表空间文件名和大小，autoextend表示空间用完了自动增长（最好设置最大大小）
innodb_data_file_path = ibdata1:1G;ibdata2:1G;ibdata3:1G:autoextend;ibdata4:1G:autoextend:max:2G</pre>
<p>这样的设置：innodb_data_file_path = /disk1/ibdata1:1G;/disk2/ibdata2:1G并不能分担负载到多个磁盘，因为InnoDB是逐个填满数据文件的，可以通过RAID来分担负载。</p>
<p>使用innodb_file_per_table可以为每个表创建一个名为tablename.ibd的数据文件，有利于把表分散到多个磁盘。这种方式下，对于小表可能存在额外的空间浪费，因为InnoDB页的大小是16KB，如果一个表的数据只有1KB，那么它至少需要占用16KB的空间。此外，innodb_file_per_table可能导致低下的DROP性能，原因如下：</p>
<ol>
<li>DROP表意味着删除文件。在某些文件系统（ext3）可能很慢。可以把ibd文件链接到零字节文件，然后手工删除之，而不是等待MySQL删除</li>
<li>每个表在InnoDB内具有自己的表空间。MySQL移除表空间时，需要锁定并扫描buffer pool来确定哪些页属于该表空间，如果buffer pool很大，该操作会相当耗时。Percona Server提供了innodb_lazy_drop_table参数来解决此问题</li>
</ol>
<p>总之，建议使用innodb_lazy_drop_table配合具有容量上限的表空间，在出现问题时参考上面两条。</p>
<p>不建议使用裸分区（未格式化的）来存储表空间。因为性能提升不大。</p>
<p><strong>其它I/O配置项</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">参数 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>sync_binlog</td>
<td>
<p>控制MySQL刷出二进制日志到磁盘的方式。0表示由OS决定何时刷出；非0表示刷出前发生多少次二进制日志写操作（自动提交时每个语句对应一个写，否则每个事务对应一个写）。通常设置为0或1。</p>
<p>设置为0时，MySQL崩溃可能导致<span style="background-color: #c0c0c0;">二进制日志与事务数据不同步</span>，从而<span style="background-color: #c0c0c0;">使数据复制、按时间点恢复</span>无法完成。另一方面，设置为1在获取安全性的同时会导致较高的代价——同步二进制日志、事务日志使MySQL必须在两个单独的磁盘位置刷出数据，可能需要磁盘寻道。</p>
<p>在具有不断电写缓存的RAID卷上存放二进制日志能够极大的提高性能。</p>
</td>
</tr>
<tr>
<td>expire_logs_days</td>
<td>自动删除过期的二进制日志。</td>
</tr>
</tbody>
</table>
<p><span style="text-decoration: underline;"><strong>MyISAM的I/O配置</strong></span></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">参数 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>delay_key_write</td>
<td>
<p>控制延迟索引写入</p>
<p>OFF：MyISAM在每次写操作时都刷出key buffer中修改的块到磁盘。除非表被LOCK TABLES锁定<br />ON：启用延迟索引写入，但仅对使用DELAY_KEY_WRITE选项创建的表有效<br />ALL：对所有表启用延迟索引写入<br />延迟索引写入在某些时候有价值，但是不会很大的提高性能。在小数据尺寸、高读索引命中、低写索引命中时最有意义。<br />延迟索引写入具有以下缺点：</p>
<ol>
<li>服务器崩溃时数据块没写出到磁盘时，到导致索引破坏</li>
<li>如果很多写入被延迟，则关闭表需要消耗较多时间（等待写入刷出），在MySQL5.0可能导致较长时间的表缓存锁定</li>
<li>未刷出的脏数据块占用key buffer，可能导致无法从磁盘读入新块，查询因此停顿直到有足够的key buffer空间</li>
</ol>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>myisam_recover_options</td>
<td>
<p>控制MyISAM如何从错误中恢复</p>
<p>DEFAULT：指示MySQL修复所有标记为崩溃或者未完全关闭的表<br />BACKUP：保存一个数据文件的备份为.BAK文件便于后续检查<br />FORCE：即使有的行从.MYD文件中丢失，也进行恢复<br />QUICK：除非有删除块，否则跳过恢复</p>
<p>可以使用上述多个选项，使用逗号分隔</p>
</td>
</tr>
<tr>
<td>myisam_use_mmap</td>
<td>
<p>允许通过系统page cache访问.MYD文件</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">配置并发</span></div>
<p><span style="text-decoration: underline;"><strong>InnoDB并发配置</strong></span></p>
<p>InnoDB为高并发场景设计。最近数年InnoDB有很大进步单非完美，某些InnoDB性能指标在高并发时会下降，而有效的处理方法仅仅是降低并发或者升级MySQL版本。在MySQL5.1-高并发是灾难性场景——所有操作在全局互斥对象（例如buffer pool mutex）上列队，导致服务器常常处于挂起状态。</p>
<p>InnoDB具有其自己的线程调度器，来控制<span style="background-color: #c0c0c0;">线程如何进入其“内核”进行数据访问、可以进行哪些操作</span>。</p>
<p><span style="background-color: #c0c0c0;">innodb_thread_concurrency</span>可以用于<span style="background-color: #c0c0c0;">老版本MySQL的线程并发数控制</span>，可以设置为磁盘数*CPU数*2。如果线程数满了，<span style="background-color: #c0c0c0;">后续到达的线程首先休眠innodb_thread_sleep_delay</span>（默认10ms，这个值对于大量小查询场景可能过大了）再次尝试，仍然失败则进入等待队列。</p>
<p>一旦线程进入内核，其持有<span style="background-color: #c0c0c0;">若干票据（tickets）允许其免费再次返回内核（不经过并发检查）</span>。<span style="background-color: #c0c0c0;">innodb_concurrency_tickets</span>控制票据数，这个参数是per-query而非per-transaction的，查询结束后剩余的票据即丢弃，一般不需要修改此参数，除非有很多长时运行（long-running）的查询。</p>
<p><span style="background-color: #c0c0c0;">innodb_commit_concurrency</span>控制同时有多少线程能够进行提交操作。如果innodb_thread_concurrency已经设置的很低，很多线程仍然在等待，可以尝试设置该参数。</p>
<p>另外一个有价值的解决方案是<span style="background-color: #c0c0c0;">使用线程池来限制并发</span>，MariaDB中已经实现，Oracle也为MySQL5.5提供了商业插件。</p>
<p><span style="text-decoration: underline;"><strong>MyISAM并发配置</strong></span></p>
<p>在讨论MyISAM并发设置之前，有必要理解其如何插入和删除数据：</p>
<ol>
<li>删除：不需要重新排序整个表，只是把被删除的行打个标记，在表上形成一个“洞”</li>
<li>插入：MyISAM会倾向于尝试填充删除的洞，以重用空间，如果无法填充或者没有洞，则附加到表的结尾</li>
</ol>
<p><span style="background-color: #c0c0c0;">concurrent_insert</span>参数控制MyISAM的并发插入行为：0表示不允许并发插入，每个INSERT锁定整张表；1表示允许并发插入，只要表中没有洞；2为MySQL5.0+引入的值，强制并发插入附加到表的尾部，即使存在洞（如果没有读线程存在，则会尝试填充洞）</p>
<div class="blog_h3"><span class="graybg">基于工作负载的配置</span></div>
<p><span style="text-decoration: underline;"><strong>优化BLOB和TEXT负载</strong></span></p>
<p>MySQL对BLOB字段的处理和其他类型不同（本节把BLOB和TEXT统称为BLOB，因为其本质上是一样的数据类型），特别是：<span style="background-color: #c0c0c0;">MySQL不能为BLOB值使用内存临时表</span>，因此一个使用临时表的、包含BLOB的查询，不管表有多小，都会导致基于文件的临时表。解决此问题有两个方法：</p>
<ol>
<li>使用SUBSTRING()函数将BLOB转换为VARCHAR</li>
<li>提高临时表的性能，例如将其存放在内存文件系统中（例如tmpfs）。<span style="background-color: #c0c0c0;">参数tmpdir控制临时表的存放位置</span>。</li>
</ol>
<p>对于基于BLOB、TEXT等长列，如果内容大于768字节，InnoDB可能在<span style="background-color: #c0c0c0;">行外部开辟存储空间（external storage space）</span>存放其剩余的部分，将以16KB的页为单位，并且每个长列使用独立外部存储空间。此行为可能导致很大的空间浪费。</p>
<p><span style="text-decoration: underline;"><strong>优化文件排序</strong></span></p>
<p>MySQL5.6针对具有LIMIT子句的查询进行了优化，改变了其使用sort buffer的方式。</p>
<div class="blog_h3"><span class="graybg">InnoDB高级设置</span></div>
<p>新版本的InnoDB引擎具有更多的特性和更好的性能，如果使用MySQL5.1+，可以设置<span style="background-color: #c0c0c0;">ignore_builtin_innodb</span>来忽视内置的InnoDB，然后配置plugin_load来把InnoDB作为插件配置。新版本的InnoDB包含若干可以提高性能、安全性的参数：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>innodb</td>
<td>设置为FORCE，则InnoDB无法启动的情况下，MySQL启动会失败</td>
</tr>
<tr>
<td>innodb_autoinc_lock_mode</td>
<td>控制InnoDB生成自增长PK的方式，如果很多事务在等待autoincrement lock，则需要考虑此设置</td>
</tr>
<tr>
<td>innodb_buffer_pool_instances</td>
<td>
<p>在MySQL5.5+，可以把buffer pool分成多个segments，这是高并发、多核心CPU的情况下提供MySQL可扩展性（scalability）的最重要的方法。多缓冲池把把工作负载分区，全局性的互斥量不会有太多的争用。可以设置为8个。</p>
<p>Percona Server使用另外一种方式降低争用——细化锁定的粒度。特别是Percona Server5.5，同时支持多个buffer pool和细粒度互斥锁</p>
</td>
</tr>
<tr>
<td>innodb_io_capacity</td>
<td>
<p>InnoDB曾经硬编码的假设：其运行在支持100 I/O的单磁盘上。但是对于高速磁盘，例如SSD，这个假设太低了。</p>
<p>该值设置的越大，每次刷出脏页的数量也就越多。SSD可以配置到2000以上。</p>
</td>
</tr>
<tr>
<td>innodb_read_io_threads<br />innodb_write_io_threads</td>
<td>这两个参数控制InnoDB后台读写I/O线程数，MySQL5.5默认设置为4读4写，对大多数服务器足够了。但是对于具有很多磁盘的高并发场景，可以增加这两个值，或者简单的设置为与物理磁盘的盘面转轴数相等</td>
</tr>
<tr>
<td>innodb_strict_mode</td>
<td>在某些情况下，使InnoDB抛出错误而不是发出警告</td>
</tr>
<tr>
<td>innodb_old_blocks_time</td>
<td>控制一个页从LRU年轻区转移到年老区需要经过的最少毫秒数，默认0，可以设置为1000。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">操作系统和硬件优化</span></div>
<div class="blog_h3"><span class="graybg">CPU的选择</span></div>
<p><span style="text-decoration: underline;"><strong>多个CPU还是更快的CPU？</strong></span></p>
<p>更快的CPU减低响应时间，更多的CPU则增加吞吐量。</p>
<p>现代服务器通常有多个CPU插槽，每个CPU会有多个核心（独立执行单元），每个核心可能有多个“硬件线程”（现代OS对超线程的支持很好）。本章所说的<span style="background-color: #c0c0c0;">CPU速度指单个执行单元的速度</span>，CPU数则指<span style="background-color: #c0c0c0;">OS看到的CPU数（即：CPU数*核心数*线程数）</span>。</p>
<p>对于<span style="background-color: #c0c0c0;">计算密集型（CPU-bound）工作负载，更快的CPU常常比更多的CPU有优势</span>。由于MySQL不能把单个查询fork到多个CPU上执行，所以对于计算密集型查询，只有提升其速度才能缩短响应时间。<span style="background-color: #c0c0c0;">数据复制（replication）也得益于更快的CPU</span>。</p>
<p>如果需要同时运行<span style="background-color: #c0c0c0;">很多查询</span>，则多个CPU更有优势，目前版本的MySQL可以<span style="background-color: #c0c0c0;">很好的利用16或者24个CPU</span>。</p>
<p>即使不存在很多查询同时执行，MySQL依然可以使用<span style="background-color: #c0c0c0;">空闲CPU来进行后台工作</span>，包括：InnoDB buffer purging、网络操作等。</p>
<p>另外一个判断更快还是更多的方式是检查<span style="background-color: #c0c0c0;">查询实际做了什么</span>。从硬件级别来看，一个查询要么在执行，要么在等待（例如<span style="background-color: #c0c0c0;">在run queue等待</span>，即所有process是runnable而所有CPU均忙；<span style="background-color: #c0c0c0;">等待latch或者lock</span>；<span style="background-color: #c0c0c0;">等待磁盘或者网络</span>）。如果查询在等待latch或者lock，那么最好是有更快的CPU；如果查询在run queue中，则更多更快CPU均可；如果在等待InnoDB log buffer mutex，则说明需要增强I/O能力。</p>
<div class="blog_h3"><span class="graybg">平衡内存与磁盘资源</span></div>
<p>需要大量内存的最重要原因不是需要在内存存放如此多的数据，而是为了避免磁盘I/O。</p>
<p>计算机具有金字塔状的缓存层次，越是上层的，越小越贵：</p>
<p><img class="aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2012/02/MySQL.2.png" alt="" /></p>
<p>每个层次的缓存均应当缓存热点数据以便快速访问。应用程序特定的缓存（例如InnoDB的buffer pool）通常比OS的缓存更加高效，因为它知道自己需要缓存哪些数据。</p>
<p><span style="text-decoration: underline;"><strong>随机I/O对比顺序I/O</strong></span></p>
<p><span style="background-color: #c0c0c0;">随机I/O更多的从缓存中获益</span>——缓存减少了昂贵的磁盘寻道时间，<span style="background-color: #c0c0c0;">顺序I/O通常没必要缓存，除非目标数据完整的fit in 内存</span>。具体分析如下：</p>
<ol>
<li>顺序I/O本省就比随机I/O快，不管<span style="background-color: #c0c0c0;">磁盘还是内存</span>。例如，典型的机械磁盘可能支持每秒100次随机I/O、50MB的顺序I/O，那么对于100字节的row来说，随机读取每秒100行，顺序读取则达到500000行，差距达到5000倍！对于内存，每秒随机I/O可以达到25万次，顺序I/O则可以达到5百万行。可以看到，对于随机I/O，内存比磁盘快2500倍，顺序I/O则仅有10倍。</li>
<li>存储引擎能更快的处理顺序I/O。随机读取通常意味着存储引擎需要进行索引操作——对应了BTree结构导航和值比较，而顺序读通常只需要遍历一个较简单的数据结构。此外，随机读通常用于获取单独的行，但是<span style="background-color: #c0c0c0;">读取的是整个16KB页</span>——可能包括若干行的数据，这里面就存在浪费，因为大部分都是不需要的数据，顺序读则通常需要整个页上的所有数据。</li>
</ol>
<p><span style="background-color: #c0c0c0;">添加内存是解决随机读问题的最好办法</span>。</p>
<p><span style="text-decoration: underline;"><strong>缓存，读取和写入</strong></span></p>
<p>如果有足够的内存，那么可以让读取变成纯粹的内存操作——在服务器预热完成后。而写入则不然，或迟或早，写入必须被持久化到磁盘中。<span style="background-color: #c0c0c0;">延迟的写入</span>可以增加性能，除此之外，写入数据的分组有利于提高性能：</p>
<ol>
<li>Many writes，one flush：单行数据在刷出磁盘之前，可能在内存中被修改多次</li>
<li>I/O merging：内存中发生的多个不同的数据修改可以合并为一个写操作</li>
</ol>
<p>以上两点也是为何很多数据库系统使用写前日志（write-ahead logging）的原因，所谓写前日志就是指零散的数据更改不直接写回到数据文件，而是先写入到一个顺序的日志文件中，后台线程负责把日志中的更改写到数据文件，它可以进行必要的优化。</p>
<p><span style="text-decoration: underline;"><strong>你的工作集（Working Set）是什么？</strong></span></p>
<p>所谓工作集，是指工作真正需要的那一部分数据。在MySQL中，可以认为工作集是最频繁使用的页的集合，包括数据和索引。工作集应该以缓存单元（cache unit）度量，对于InnoDB，一个缓存单元为16KB（默认，页的大小），因此，即使只读取1条数据，也会导致整个页被载入buffer pool，这可能导致很大的浪费。聚簇索引把相关的数据放在一起，尽量的减少这种浪费。</p>
<p><span style="text-decoration: underline;"><strong>寻找一个合适的内存/硬盘比率</strong></span></p>
<p>通过性能基准测试，来确认一个可接受的缓存丢失率（cache miss rate）。缓存丢失率和CPU占用紧密相关，例如。如果一段时间内CPU处于used达到99%，而处于I/O等待为1%，那么缓存丢失率是可接受的。</p>
<p>加大内存和缓存丢失率并不是线性的关系，这和工作负载有关，例如，10GB内存对应了10%缓存丢失率，那么要减小到1%丢失率可能需要500G而不是50G内存。</p>
<p><span style="text-decoration: underline;"><strong>选择硬盘</strong></span></p>
<p>传统机械硬盘读取数据需要三个步骤：</p>
<ol>
<li>把读取磁头移动到磁盘表面的适当位置</li>
<li>等待磁盘转动，直到需要的数据到达磁头下面</li>
<li>等待磁盘转过所有需要的数据</li>
</ol>
<p>其中1、2步消耗的时间叫access time，<span style="background-color: #c0c0c0;">小的随机访问主要的消耗时间是access time</span>。3步消耗的时间主要取决于<span style="background-color: #c0c0c0;">传输速度，大的顺序读取的主要消耗时间</span>主要在第3步。</p>
<p>多种因素影响磁盘的选择，考虑一个流行新闻网站——需要很多小的随机读，需要考虑以下因子：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;"> 因素</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>存储容量(Storage capacity)</td>
<td>现代磁盘大部分足够大，如果不够，把小磁盘组成RAID </td>
</tr>
<tr>
<td>传输速度(Transfer speed)</td>
<td>现代磁盘的传输速度都非常快，速度取决于<span style="background-color: #c0c0c0;">转轴速度和磁盘数据密度、以及与主机的接口</span>（很多现代磁盘读取数据的速度大于接口传输速度）。对于在线网站来说，速度通常不是问题，因为主要是小的随机读</td>
</tr>
<tr>
<td>访问时间(Access time)</td>
<td>这是<span style="background-color: #c0c0c0;">影响随机读取速度最大的因素</span></td>
</tr>
<tr>
<td>转轴速度(Spindle rotation speed)</td>
<td>包括7200、10000、15000转，对顺序读取、随机读取均有较大的影响</td>
</tr>
<tr>
<td>物理尺寸</td>
<td>其它参数相同的情况下，尺寸越小，磁头移动耗时也越小</td>
</tr>
</tbody>
</table>
<p> InnoDB很容易扩展到多个磁盘。MyISAM则不是。</p>
<div class="blog_h3"><span class="graybg">固态（Flash）存储技术</span></div>
<p>亦称非易失随机存取存储器（NVRAM），与传统硬盘的结构非常不同。主要可以分为<span style="background-color: #c0c0c0;">两类：SSD、PCIe cards</span>，前者通过实现SATA来模拟标准硬盘，后者则使用特殊驱动，作为块设备。固态存储具有以下特点：</p>
<ol>
<li>相对于机械硬盘，具有<span style="background-color: #c0c0c0;">很好的随机读、写性能</span>。通常读性能相对于写更好一些</li>
<li><span style="background-color: #c0c0c0;">更好的顺序读、写性能</span>。某些入门的固态存储，顺序读写速度可能不如高速传统硬盘</li>
<li><span style="background-color: #c0c0c0;">更好的并发支持</span></li>
</ol>
<p>其中1和3对于数据库来说是最重要的提升。很多反规范化设计的Schema就是为了避免随机I/O。</p>
<p>在未来，RDBMS将因为固态存储技术发生深刻的改变，过去几十年RDBMS已经针对机械磁盘做了大量优化，对于固态存储则没有。</p>
<p><span style="text-decoration: underline;"><strong>固态存储概览</strong></span></p>
<p>固态存储最重要的一个特征是，可以多次快速的读取小的单元，而写的时候则有复杂的问题需要处理——<span style="background-color: #c0c0c0;">除非擦除整个块（例如512KB），不能重新写入</span>一个cell。经过多次擦除，最终数据块将坏掉，为了避免这种损坏，固态硬盘必须能够重新定位数据页并进行垃圾回收——所谓<span style="background-color: #c0c0c0;">磨损均衡（wear leveling）</span>。</p>
<p>由于固态硬盘重新定位、垃圾回收的特性，磁盘使用占比越大， 其效率会越低，对于100GB的文件，其位于160GB、320GB固态硬盘上，写效率是不一样的。</p>
<p><span style="text-decoration: underline;"><strong>闪存技术</strong></span></p>
<p>分为两种类型：</p>
<ol>
<li>single-level cell (SLC)：每个cell存放一个bit，速度快，耐用，但是数据密度低。能够支持10万次循环写入。实际应用中能达到20年寿命</li>
<li>multi-level cell (MLC)：每个cell存放2个甚至3个bit，单位成本低，速度相对慢，不耐用。好的设备能够支持1万次循环写入。有些厂商对技术进行改进，称为企业级MLC（eMLC）</li>
</ol>
<p><span style="text-decoration: underline;"><strong>SSD组建RAID</strong></span></p>
<p>建议使用SATA的SSD组成的RAID以避免单块磁盘的故障导致数据丢失。一些就的RAID控制器不能很好的支持SSD，仍然做诸如buffering、写入重排序之类浪费时间的操作。</p>
<p><span style="text-decoration: underline;"><strong>MySQL针对固态存储的优化</strong></span></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 优化内容</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>InnoDB的I/O能力配置</td>
<td>
<p>增大innodb_io_capacity，可以<span style="background-color: #c0c0c0;">设置到2000-20000</span>，根据SSD的IOPS（每秒输入输出数）确定</p>
<p>SSD比机械硬盘支持更高的并发访问，所以可以<span style="background-color: #c0c0c0;">修改读写I/O线程数到10-15</span></p>
</td>
</tr>
<tr>
<td>增大InnoDB的日志文件大小</td>
<td>可以设置为4GB或者更大。Percona Server、MySQL 5.6支持<span style="background-color: #c0c0c0;">4GB以上的日志文件</span>，大日志文件可以提高和稳定性能，且对于SSD不会在宕机重启后因大量随机I/O导致的漫长恢复（crash recovery）等待</td>
</tr>
<tr>
<td>把某些文件从Flash移动到RAID</td>
<td>
<p>除了把InnoDB日志改大以外，而可以将其存储位置移动出SSD，存放到具有电池支持的写缓存的RAID中，因为InnoDB日志主要是顺序写入，不会从SSD获益太多</p>
<p>处于类似的原因，可以把二进制日志、ibdata1文件（包含双重写入缓冲、插入缓冲）移动到RAID。</p>
</td>
</tr>
<tr>
<td>禁止预读取（read-ahead）</td>
<td>
<p>有些时候预读取的成本比收益更大，特别是对于SSD。MySQL5.5提供一个参数来设置</p>
</td>
</tr>
<tr>
<td>配置InnoDB的flushing算法</td>
<td>
<p>标准InnoDB没有提供针对SSD的有价值的选项，但是Percona XtraDB（包括PerconaServer、MariaDB）可以：</p>
<p>设置<span style="background-color: #c0c0c0;">innodb_adaptive_checkpoint=keep_average</span>（默认值estimate），设置<span style="background-color: #c0c0c0;">innodb_flush_neighbor_pages=0</span>。</p>
</td>
</tr>
<tr>
<td>潜在的禁用doublewrite buffer</td>
<td>除了考虑把双重缓冲移出SSD，可以考虑禁用它——某些设备声明支持16KB原子写入（一般要求O_DIRECT和XFS文件系统），这导致双重写入缓冲多余。这可能让MySQL的性能提高50%，但是可能不是100%安全。</td>
</tr>
<tr>
<td>限制插入缓冲的大小</td>
<td>
<p>insert buffer（新版本InnoDB称为change buffer）用于减少针对<span style="background-color: #c0c0c0;">不在内存中的非唯一索引页的随机I/O（当对应行被update时）</span>。</p>
<p>在使用普通硬盘的某些情况下，<span style="background-color: #c0c0c0;">工作集比内存大很多时，增大插入缓冲可能减少两个数量级的随机I/O</span>。</p>
<p>但是对于SSD，这没有必要了，因为其随机I/O快了很多，可以将其限制其最大尺寸为较小的值。MySQL 5.6支持此设置。</p>
</td>
</tr>
<tr>
<td>调整InnoDB页大小和checksum</td>
<td>
<p>MySQL5.6允许调整默认的16KB页大小、checksum算法</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Replica的硬件选择</span></div>
<p>如果出于failover的目的，Replica应当与Master一样高的配置；如果仅仅为了提高整体读取能力，则可以使用廉价的方案。</p>
<div class="blog_h3"><span class="graybg">RAID性能优化</span></div>
<p>RAID可以提供<span style="background-color: #c0c0c0;">冗余、存储容量、缓存和速度的优势</span>。不同的RAID对数据库需求的满足情况如下：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 50px; text-align: center;">RAID </td>
<td style="width: 200px; text-align: center;">特性</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>RAID0</td>
<td>
<p>廉价、高速、危险，不具备冗余</p>
<p>磁盘数：N<br />读写：快读快写</p>
</td>
<td>最廉价、最高性能的RAID方案。不具有冗余特性，适用于不太关心（其数据可以从其它地方很容易的重新获取）的Replica。很简单，易于软件实现。</td>
</tr>
<tr>
<td>RAID1</td>
<td>
<p>高速读、简单、安全</p>
<p>磁盘数：2(通常)<br />读写：快读慢写</p>
</td>
<td>在很多场景下提供更好的读性能，它把数据复制到多块硬盘上，具有很好的冗余性，其读性能比RAID0稍好。适合于日志或者类似的场景，因为顺序I/O不需要底层磁盘性能很高。很简单，易于软件实现。</td>
</tr>
<tr>
<td>RAID5</td>
<td>
<p>安全、速度、成本的折衷 </p>
<p>磁盘数：N+1<br />读写：快读，写依据场景</p>
</td>
<td>
<p>使用分布式奇偶校验块（distributed parity blocks）来把数据分散在多块磁盘上，其中一块磁盘损坏，可以通过奇偶校验重建数据。这是比较经济的RAID，因为只需要浪费阵列中单块磁盘的存储空间。</p>
<p>RAID5的<span style="background-color: #c0c0c0;">随机写成本较高</span>，因为<span style="background-color: #c0c0c0;">每个写操作需要2次读+2次写</span>（包括计算和存储校验位）。如果是<span style="background-color: #c0c0c0;">顺序写，或者阵列中包含很多磁盘，则写性能会有一些提高</span>。RAID5的<span style="background-color: #c0c0c0;">随机读、顺序读性能均较好</span>。</p>
<p>RAID5适合以读为主的工作负载，可以用来存放数据、日志。</p>
<p>RAID5在磁盘出现损坏时，替换新盘会导致严重的性能下降，因为它需要读取所有磁盘来完成数据重建。</p>
</td>
</tr>
<tr>
<td>RAID10</td>
<td>
<p>昂贵、快速、安全</p>
<p>磁盘数：2N<br />读写：快读快写</p>
</td>
<td>由若干镜像磁盘对组成。<span style="background-color: #c0c0c0;">对于读、写性能都有很大的提升</span>。如果一块磁盘坏掉，性能可能下降高达50%。<span style="background-color: #c0c0c0;">成本高</span>。</td>
</tr>
<tr>
<td>RAID50</td>
<td>
<p>适合存放大量数据</p>
<p>磁盘数：2(N+1)<br />读写：快读快写</p>
</td>
<td>相当于RAID5和RAID0的结合。每个RAID5磁盘组需要3+块硬盘。<span style="background-color: #c0c0c0;">读写均较快</span>。适用于海量数据存储（数据仓库、特大OLTP）。</td>
</tr>
</tbody>
</table>
<p><span style="text-decoration: underline;"><strong>RAID故障、恢复和监控</strong></span><br />多块磁盘同时坏掉的可能性往往被低估。实时上，RAID并不能减少对备份的需求。</p>
<p><span style="text-decoration: underline;"><strong>RAID缓存</strong></span></p>
<p>RAID缓存置于RAID控制器之中，作为磁盘和主机之间的数据交换缓冲。RAID控制器可能因为以下原因使用缓存：</p>
<ol>
<li>Caching Reads：控制器从磁盘上读取数据返回给OS后，可以将其缓存起来，这种用法没有太大意义。因为控制器不知道哪些是热点数据，而且缓存容量很小</li>
<li>Caching read-ahead data：如果控制器检测到顺序读，其可能预读取可能马上需要使用的数据。对于InnoDB没有价值，因为InnoDB自己管理预读取</li>
<li>Caching writes：控制器可以把写请求缓存起来，安排在后续步骤中进行写入。这有两个好处：立即像OS返回Success信息；可以重排写入以达到高效目的</li>
<li>Internal operations：某些RAID操作，特别是RAID5写操作非常复杂，控制器需要内部存储来进行计算</li>
</ol>
<p>RAID缓存是稀缺资源，应当合理分配。某些控制器允许你分配缓存使用，通常将更多的缓存分配给写操作能够很好的提高性能。对于RAID1、0、10，可以分配100%给写缓存，对于RAID5，则需要保留一些供内部使用。</p>
<p>某些RAID控制器允许设置写入延迟时间，根据实际工作负载设置。</p>
<p><span style="background-color: #c0c0c0;">缺少battery backup unit (BBU)的写缓存可能造成数据损坏</span>。此外，<span style="background-color: #c0c0c0;">某些磁盘本身具有写缓存功能，这是没有电池保护的</span>，这样的磁盘可能执行虚假的fsync()操作，可能需要禁止这种磁盘缓存。</p>
<div class="blog_h3"><span class="graybg">使用多磁盘分卷</span></div>
<p>MySQL创建多种类型的文件：</p>
<ol>
<li>数据和索引文件</li>
<li>事务日志文件</li>
<li>二进制日志文件</li>
<li>其它日志文件：错误日志、查询日志、缓慢查询日志等</li>
<li>临时文件和表</li>
</ol>
<p>MySQL没有复杂的表空间管理功能，默认情况下，它只是把单个Schema的文件统一存放在一个目录下。</p>
<p>出于性能的考虑来把<span style="background-color: #c0c0c0;">日志和数据分到不同的分卷上是没有必要</span>的，除非你有很多磁盘（20+）或者使用SSD。把数据和二进制日志分到不同卷上的真正价值在于避免因为崩溃（对于没有电池保护的写缓存）导致两种数据均丢失。</p>
<div class="blog_h3"><span class="graybg">网络配置</span></div>
<p>通过skip_name_resolve禁止DNS查找，使用IP地址，防止因为DNS缓慢导致的高延迟。</p>
<p>back_log控制入站TCP连接队列的容量，对于频繁打开、关闭连接的Web应用，默认值50可能太小。将其设置到上千并不会有什么坏处，但是要注意OS的配置，例如somaxconn默认为128、tcp_max_syn_backlog需要增大。</p>
<p>对网络结构、跃点数也需要加以关注，跃点数会增加延迟，网络结构不好可能限制了带宽。对于本地网络，至少应当保证1G带宽，在交换机主干网，应当保证10G带宽。</p>
<div class="blog_h3"><span class="graybg">交换文件（Swapping）</span></div>
<p>物理内存不够时会发生Swapping，这对MySQL性能有严重的影响。</p>
<div class="blog_h2"><span class="graybg">数据复制（Replication）</span></div>
<p>MySQL内置的数据复制机制是构建大型高性能MySQL应用的基础。Replication允许将一台或者多台服务器配置为某个服务器的Replicas，以保持数据同步。这种机制是<span style="background-color: #c0c0c0;">高性能、高可用性、可扩展性（scalability）、灾难恢复、备份、分析、数据仓库</span>等任务的中心。</p>
<div class="blog_h3"><span class="graybg">Replication纵览</span></div>
<p>Replication要解决的基本问题是把数据在多台数据库之间保持同步。多台replica连接到同一台master，replica和master的角色可以相互转换。</p>
<p>MySQL支持两种类型的Replication：</p>
<ol>
<li>基于语句（statement-based）的复制，亦称逻辑复制，从MySQL3.23即开始存在</li>
<li>基于行的复制，MySQL5.1引入</li>
</ol>
<p>这两种复制均是通过<span style="background-color: #c0c0c0;">录制master二进制日志的改变，并在replica中进行replay实现同步</span>，并且都是<span style="background-color: #c0c0c0;">异步的（同步时间没有保证）</span>。</p>
<p><span style="background-color: #c0c0c0;">新版本的MySQL可以作为老版本的Replica</span>，反之则可能存在问题。</p>
<p>Replication对master不会添加额外的压力，只要master开启二进制日志功能即可（开启这个功能对master性能有影响，但是这是数据备份、基于时间点恢复数据的基础）。Replica读取master日志会产生少许的网络I/O压力，特别是读取很老的日志时。</p>
<p><span style="text-decoration: underline;"><strong>Replication解决的问题</strong></span></p>
<p>以下是常见的Replication用法：</p>
<ol>
<li>数据分布（Data distribution）：可以进行异地数据备份，甚至是时断时开的网路状况下</li>
<li>负载均衡（Load balancing）：可以把读请求分布到多台服务器上</li>
<li>备份（Backups）：可以协助数据备份，但是数据复制并不是备份的代替技术</li>
<li>高可用性和故障转移：避免单点故障</li>
</ol>
<p><span style="text-decoration: underline;"><strong>Replication的工作方式</strong></span></p>
<p>大体上说，数据复制包含三个步骤：</p>
<ol>
<li>master录制数据的变化（事件）到二进制日志</li>
<li>replica复制事件到其自己的转播日志（relay log）</li>
<li>replica回放转播日志中的事件，将数据变更应用到自己的数据文件</li>
</ol>
<p>更细致的描述如下图：</p>
<p> <img class="aligncenter  wp-image-3294" src="https://blog.gmem.cc/wp-content/uploads/2012/02/replicas.jpg" alt="replicas" width="604" height="408" /></p>
<p>需要注意<span style="background-color: #c0c0c0;">回放是在replica的单个线程上进行的</span>，而<span style="background-color: #c0c0c0;">变更则可能是在master上并发出现的</span>，这可能导致性能瓶颈。</p>
<div class="blog_h3"><span class="graybg">建立Replication</span></div>
<p>建立步骤根据场景不同有很多变种，对于<span style="background-color: #c0c0c0;">新安装的</span>主从MySQL服务器，步骤大概如下：</p>
<ol>
<li> 在各服务器上创建数据复制账户</li>
<li>配置master、replica</li>
<li>指示replica连接到master并进行数据复制</li>
</ol>
<p><span style="text-decoration: underline;"><strong>创建数据复制账户</strong></span></p>
<p>MySQL具有一些和数据复制相关的特殊权限（privileges），下面是授权示意脚本：</p>
<pre class="crayon-plain-tag">-- 在master、replica上创建一个名为repl的账户，并授权
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.*
TO repl@'192.168.0.%' IDENTIFIED BY 'p4ssword',; -- 显示用户在192.168.0本地网段</pre>
<p><span style="text-decoration: underline;"><strong>Master和Replica的配置</strong></span></p>
<p>需要开启master的二进制日志，并赋予server_id：</p>
<pre class="crayon-plain-tag">log_bin   = mysql-bin ;日志文件的名称，在MySQL命令行运行SET SQL_LOG_BIN=0可以临时禁止二进制日志
;不能使用默认值1，在某些MySQL版本会导致冲突
server_id = 10
;完成配置后重启，运行命令SHOW MASTER STATUS;检查是否正常：
;File名称可能不同
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 | 98       |              |                  |
+------------------+----------+--------------+------------------+</pre>
<p>在replica上需要类似的配置：</p>
<pre class="crayon-plain-tag">log_bin           = mysql-bin
server_id         = 2
relay_log         = /var/lib/mysql/mysql-relay-bin ;转播日志的名称
log_slave_updates = 1 ;把复制得到的事件存放到replica自己的二进制日志中
read_only         = 1 ;使replica处于只读模式，不允许普通用户创建表、修改数据</pre>
<p><span style="text-decoration: underline;"><strong> 启动复制</strong></span></p>
<pre class="crayon-plain-tag">CHANGE MASTER TO MASTER_HOST='host name or ip',
MASTER_USER='repl',
MASTER_PASSWORD='p4ssword',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=0;</pre>
<p>检查确认replica的配置正常：</p>
<pre class="crayon-plain-tag">mysql&gt; SHOW SLAVE STATUS
*************************** 1. row ***************************
       Slave_IO_State:     //表示复制操作没有在进行
          Master_Host: server1
          Master_User: repl
          Master_Port: 3306
        Connect_Retry: 60
      Master_Log_File: mysql-bin.000001
  Read_Master_Log_Pos: 4   //replica知道第一个有意义的事件的位置是4
       Relay_Log_File: mysql-relay-bin.000001
        Relay_Log_Pos: 4
Relay_Master_Log_File: mysql-bin.000001
     Slave_IO_Running: No //表示复制操作没有在进行
    Slave_SQL_Running: No //表示复制操作没有在进行
                     ...omitted...
Seconds_Behind_Master: NULL

mysql&gt; START SLAVE;
mysql&gt; SHOW SLAVE STATUS

*************************** 1. row ***************************
       Slave_IO_State: Waiting for master to send event  //IO线程正在等待master的新事件，这意味着所有事件已经被获取
          Master_Host: server1
          Master_User: repl
          Master_Port: 3306
        Connect_Retry: 60
      Master_Log_File: mysql-bin.000001
  Read_Master_Log_Pos: 164 //日志位置改变，意味着某些日志已经被获取并回放
       Relay_Log_File: mysql-relay-bin.000001
        Relay_Log_Pos: 164
Relay_Master_Log_File: mysql-bin.000001
     Slave_IO_Running: Yes  //注意IO、SQL线程都处于运行状态
    Slave_SQL_Running: Yes
                     ...omitted...
Seconds_Behind_Master: 0

//在master上可以看到有replica创建的线程（连接）
mysql&gt; SHOW PROCESSLIST
*************************** 1. row ***************************
     Id: 55
   User: repl
   Host: replica1.webcluster_1:54813
     db: NULL
Command: Binlog Dump
   Time: 610237
  State: Has sent all binlog to slave; waiting for binlog to be updated
   Info: NULL
//在replica上可以看到一个IO线程、一个SQL线程

mysql&gt; SHOW PROCESSLIST\G
*************************** 1. row ***************************
     Id: 1
   User: system user
   Host:
     db: NULL
Command: Connect
   Time: 611116
  State: Waiting for master to send event
   Info: NULL
*************************** 2. row ***************************
     Id: 2
   User: system user
   Host:
     db: NULL
Command: Connect
   Time: 33   //SQL线程已经空闲33秒，意味着33秒内没有事件回放
  State: Has read all relay log; waiting for the slave I/O thread to update it
   Info: NULL  //会显示其正在执行的语句</pre>
<p><span style="text-decoration: underline;"><strong>从其它服务器上初始化replica</strong></span></p>
<p>更常见的常见是，master已经运行了一段时间后，创建replica进行数据复制，这种情况下replica和master处于不同步状态，需要三个条件才能完成replica的初始化：</p>
<ol>
<li>master某个时间点的快照</li>
<li>master的当前日志文件，以及上述快照时间点在日志文件里的偏移量</li>
<li>master从上述快照时间点到当前的二进制日志文件</li>
</ol>
<p>从其它服务器上克隆一个replica有以下方法：</p>
<ol>
<li>冷拷贝：关闭master，拷贝其文件到replica；启动master，其将会启动一个新二进制日志，使用CHANGE MASTER TO命令从新二进制日志启动replica</li>
<li>热拷贝：如果仅使用MyISAM，可以使用mysqlhotcopy或者rsync在服务启动的情况下拷贝文件</li>
<li>使用mysqldump：如果仅使用InnoDB，可以使用下面的命令把master的所有东西dump出来，全部加在到replica，并把replica的日志坐标移动到master二进制日志对应处：<br />
<pre class="crayon-plain-tag">#-single-transaction导致读取事务开始点的所有数据
mysqldump --single-transaction --all-databases --master-data=1 --host=server1 |mysql --host=server2</pre>
</li>
<li>使用快照或者备份：如果知道对应的二进制日志坐标，可以通过把备份、快照还原到replica，然后使用该坐标运行CHANGE MASTER TO命令。支持的快照例如：LVM snapshots, SAN snapshots, EBS snapshots等。如果使用备份，那么备份时间点之后的二进制日志均需要存在</li>
<li>使用Percona XtraBackup</li>
<li>从其它replica复制数据</li>
</ol>
<p><span style="text-decoration: underline;"><strong>推荐的Replication配置</strong></span></p>
<pre class="crayon-plain-tag">;对于master最重要的配置，每次事务提交时同步二进制日志，防止崩溃导致的数据丢失
sync_binlog=1
;如果使用InnoDB的master，应该
innodb_flush_logs_at_trx_commit=1 ;日志提交刷出日志
innodb_support_xa=1 ;MySQL5.0+
innodb_safe_binlog  ;仅MySQL4.1效果与上一条类似
log_bin=/var/lib/mysql/mysql-bin ;最好明确指定路径和基名(base name)


;对于replica
relay_log=/path/to/logs/relay-bin
skip_slave_start ;阻止replica在崩溃后自动启动
read_only  ;除了具有SUPER权限的线程、replication SQL thread，不能修改库上的非临时表
sync_master_info = 1
sync_relay_log = 1
sync_relay_log_info = 1</pre>
<div class="blog_h3"><span class="graybg">Replication的技术细节</span></div>
<p>MySQL5.0以上支持两种复制模式，<span style="background-color: #c0c0c0;">默认使用基于语句的模式，如果出现无法录制的语句，则临时切换到基于行的模式</span>。</p>
<p><span style="text-decoration: underline;"><strong>基于语句的复制</strong></span></p>
<p>MySQL 5.0-仅支持基于语句的复制，则在RDBMS的世界里很少见。这种方式是通过<span style="background-color: #c0c0c0;">录制并回放master中修改了数据的SQL语句</span>实现的。</p>
<p>优点：占用带宽小，易于理解和配合mysqlbinlog使用</p>
<p>缺点：语句中可能存在上下文相关的函数，例如CURRENT_USER()，这可能导致问题</p>
<p><span style="text-decoration: underline;"><strong>基于行的复制</strong></span></p>
<p>与其它RDBMS的实现类似，录制实实在在的行数据改变。主要的优势是它能<span style="background-color: #c0c0c0;">正确的执行所有语句</span>。缺点则是SQL语句不在日志中，很难弄清到底干了什么。</p>
<p><span style="text-decoration: underline;"><strong>Replication相关的文件</strong></span></p>
<p>除了上述的二进制日志文件，传播日志文件，还有以下文件和数据复制相关</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">文件名 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>mysql-bin.index</td>
<td>具有和二进制日志一样的前缀，附加.index后缀。跟踪磁盘上的二进制日志文件。</td>
</tr>
<tr>
<td style="width: 150px;">mysql-relay-bin.index</td>
<td>与上一个类似，跟踪磁盘上的转播日志文件</td>
</tr>
<tr>
<td style="width: 150px;">master.info</td>
<td>包含replica连接master需要的信息。包含明码的密码</td>
</tr>
<tr>
<td style="width: 150px;">relay-log.info</td>
<td>包含replica当前二进制日志、转播日志的坐标</td>
</tr>
</tbody>
</table>
<p><span style="text-decoration: underline;"><strong>向其它replica转发Replication Events</strong></span></p>
<p>log_slave_updates选项允许把当前replica作为其它replica的master使用。其工作原理示意图如下：</p>
<p><img class="aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2012/02/replica-2.jpg" alt="" width="90%" /></p>
<p>当log_slave_updates启用时，第一个replica<span style="background-color: #c0c0c0;">接收到master的日志并回放后，会将其记录到自己的二进制日志里</span>，这样，第二个replica就可以收到这个事件，进行类似的回放操作了。需要注意的时，master、第一个replica的日志position并不相同，不要做这种假设。</p>
<p><span style="text-decoration: underline;"><strong>Replication Filters</strong></span></p>
<p>复制过滤器允许仅复制master的部分数据，包含两种类别：</p>
<ol>
<li>在master上过滤二进制日志事件的过滤器：包括binlog_do_db 、binlog_ignore_db，不应当使用</li>
<li>在replica上过滤转播日志事件的过滤器： 若干replicate_*选项过滤SQL线程从转播日志读取的事件。可以忽视/复制一个/多个数据库、重写一个数据库到另外一个、根据LIKE匹配语法忽视/复制表</li>
</ol>
<div class="blog_h3"><span class="graybg">Replication拓扑结构</span></div>
<p>MySQL支持多种复制拓扑结构，基本的规则是：</p>
<ol>
<li>每个replica只能有一个master</li>
<li>每个replica必须具有惟一的server ID</li>
<li>每个master可以具有多个replica</li>
<li>通过设置log_slave_updates，replica可以传播来自master的事件，从而作为其它replica的master</li>
</ol>
<p><span style="text-decoration: underline;"><strong>Master and Multiple Replicas</strong></span></p>
<p>这种拓扑和一主一从的结构没有本质区别，因为replica之间不进行任何通信：</p>
<p><img class="aligncenter  wp-image-3333" src="https://blog.gmem.cc/wp-content/uploads/2012/02/topolog-1.jpg" alt="topolog-1" width="258" height="226" /></p>
<p>在<span style="background-color: #c0c0c0;">有很多读请求、较少写请求</span>时，这种拓扑非常有效。下面列出其常见应用场景：</p>
<ol>
<li>不同replica作为不同的角色使用，例如使用不同的索引、不同的存储引擎</li>
<li>把一个replica作为备用master</li>
<li>把一个replica放到远程数据中心作为灾备</li>
<li>延时（Time-delay）某个replica作为灾备</li>
<li>将某个replica作为备份用，或者培训、开发用</li>
</ol>
<p>该拓扑被广泛使用的一个原因是避免了复杂性。</p>
<p><span style="text-decoration: underline;"><strong>Master-Master in Active-Active Mode</strong></span></p>
<p>又称为dual-master、bidirectional replication。两个服务器各自配置为对方的master和replica：</p>
<p><img class="aligncenter  wp-image-3336" src="https://blog.gmem.cc/wp-content/uploads/2012/02/topolog-2.jpg" alt="topolog-2" width="210" height="106" /></p>
<p>Active-Active模式有其用途，但是仅仅是在特殊的场景下，例如对于地理分布的办公室，每个办公室都需要本地可修改的数据副本的场景。</p>
<p>该模式最大的问题是冲突修改的处理，例如两个数据库同时修改了一行数据，或者在具有AUTO_INCREMENT的表上同时插入数据。MySQL5.0+提供<span style="background-color: #c0c0c0;">auto_increment_increment、auto_increment_offset</span>解决自增长主键冲突问题。</p>
<p><span style="text-decoration: underline;"><strong>Master-Master in Active-Passive Mode</strong></span></p>
<p>该模式是<span style="background-color: #c0c0c0;">非常强大的设计容错（fault-tolerant）和高可用性系统</span>的拓扑。与Active-Active的区别在于，<span style="background-color: #c0c0c0;">其中一个服务器是被动的（passive）、只读的</span>：</p>
<p> <img class="aligncenter  wp-image-3337" src="https://blog.gmem.cc/wp-content/uploads/2012/02/topolog-3.jpg" alt="topolog-3" width="209" height="111" /></p>
<p>该配置允许<span style="background-color: #c0c0c0;">轻松的进行Active、Passive角色的转换</span>，因为两台服务器的配置是相同的，这让故障转移很方便。该模式亦<span style="background-color: #c0c0c0;">支持不停机（downtime）</span>维护、表优化、升级OS、硬件，考虑下面的场景：</p>
<ol>
<li>ALTER TABLE锁定整个表，阻塞读写操作，该操作可能需要很长时间完成，从而打断服务的运行</li>
<li>可以停止Active上的replication threads，这样Active不会从Passive上录制和回放任何事件</li>
<li>然后，在Passive上可以进行ALTER TABLE操作</li>
<li>完成后，把Passive切换为Active，应用程序连接到新的Active</li>
<li>新的Passive读取日志，并回放ALTER TABLE操作</li>
</ol>
<p>使用active-passive master-master拓扑可以解决很多其他问题，已经回避MySQL的限制。</p>
<p>在两台服务器上同时进行以下配置，即可实现该模式：</p>
<ol>
<li>确保两台服务器具有相同的数据</li>
<li>启用二进制日志，设置唯一的Server ID，创建replication账号</li>
<li><span style="background-color: #c0c0c0;">启用replica 更新日志（log_slave_updates）</span>，这对于故障转移、自动恢复（failback）非常重要</li>
<li>可选的，配置Passive为ead-only，防止冲突的修改</li>
<li>分别启动两台服务器的MySQL实例</li>
<li>分别配置为对方的replica，从新创建的二进制日志开始</li>
</ol>
<p>当Active上发生一个数据变化时，会发生以下事件序列：</p>
<ol>
<li>变化作为事件写入Active的二进制日志</li>
<li>Passive读取到该事件，并存入自己的转播日志</li>
<li>Passive执行转播日志，并记录到自己的二进制日志（由于设置了log_slave_updates）</li>
<li>Active读取到该事件，由于发现事件的Server ID与自己相同，它<span style="background-color: #c0c0c0;">忽略这个事件</span></li>
</ol>
<p><span style="text-decoration: underline;"><span style="color: #008080;"><a href="#switching-roles-of-active-passive-topology"><span style="color: #008080; text-decoration: underline;">后续章节</span></a></span></span>包含主从角色切换的详细配置。</p>
<p><span style="text-decoration: underline;"><strong>Master-Master with Replicas</strong></span></p>
<p>可以为每一个master添加1个或者多个replicas：</p>
<p><img class="aligncenter  wp-image-3341" src="https://blog.gmem.cc/wp-content/uploads/2012/02/topolog-4.jpg" alt="topolog-4" width="246" height="197" /></p>
<p>该拓扑的优点是提供额外的冗余，提供更好的读性能。在地理分布的数据复制场景，该拓扑排除了单点故障。</p>
<p><strong><span style="text-decoration: underline;">Ring Replication</span></strong></p>
<p>其实上面几种具有连个master的拓扑，只是环形复制的特例。环形复制依赖于环上的每一个点，因此大大增加了整个系统失败的可能性：</p>
<p><img class="aligncenter  wp-image-3342" src="https://blog.gmem.cc/wp-content/uploads/2012/02/topolog-5.jpg" alt="topolog-5" width="294" height="224" /></p>
<p>通常没有必要使用该拓扑</p>
<p><strong><span style="text-decoration: underline;">Master, Distribution Master, and Replicas</span></strong></p>
<p>每个Replica都要在master上<span style="background-color: #c0c0c0;">创建一个线程来使用binlog dump命令读取二进制日志并发送给replica</span>，Replica很多时可能给master带来不可忽视的负载：</p>
<ol>
<li>每个Replica的线程独立运行互不影响，这会导致很多重复的工作</li>
<li>如果Replica很多，且存在一个很大的二进制日志事件（例如LOAD DATA INFILE），master可能内存溢出并崩溃</li>
<li>如果每个Replica在读取二进制日志的不同部分，可能导致很多的磁盘操作，影响master的性能</li>
</ol>
<p>为了把负载从master上移除，可以使用分布式master（distribution master）——它的唯一作用是读取master的日志并服务于replica：</p>
<p><img class="aligncenter  wp-image-3343" src="https://blog.gmem.cc/wp-content/uploads/2012/02/topolog-6.jpg" alt="topolog-6" width="278" height="323" /></p>
<p>为了溢出实际执行查询的资源消耗，可以把分布式master的存储引擎改为Blackhole。</p>
<p>如何确定需要一个分布式master来减轻master的负载？一个大概的规则是，如果master已经全速运行了，可能不能再为它添加replica；另外如果master只有很少的写操作，则通常不需要分布式master。对于网络带宽敏感的场景，使用slave_compressed_protocol可以减轻master的网络压力。</p>
<p>分布式replica可以作为复制过滤器的集中执行点。</p>
<p>Blackhole引擎的表根本没有数据，因此其速度很快，但是Blackhole引擎具有一些BUG。</p>
<p><span style="text-decoration: underline;"><strong>定制化的数据复制解决方案</strong></span></p>
<p><strong>选择性复制</strong></p>
<p>复制master的一部分到某个replica上，某些情况下这与水平分区（horizontal data partitioning）概念上有相似，不同的是，在这里，master上具有全部的数据，写查询永远在master进行即可，读查询则根据需要可以在replica或者master上进行。</p>
<p>考虑需要把公司不同部门的数据分发在不同的replica上，可以在master上为每个部门创建一个数据库：sales, marketing, procurement…，每个replica则配置replicate_wild_do_table选项，仅录制和回放其感兴趣的库的数据，例如对于销售部门：</p>
<pre class="crayon-plain-tag">replicate_wild_do_table = sales.%</pre>
<p><strong>分离功能</strong></p>
<p>很多应用具有混合OLTP/OLAP的特征，这两类业务是截然不同的，前者多是小的事务性操作，后者则是大的缓慢的读查询且没有数据实时性的要求，他们需要不同的MySQL配置、索引、存储引擎甚至硬件。</p>
<p>一个通用的做法是把OLTP数据库的数据复制到一个OLAP的数据库</p>
<p><strong>数据归档</strong></p>
<p>可以在replica上备份数据，而从master上永久移除之。</p>
<p><strong>创建一个日志服务器（log server）</strong></p>
<p>日志服务器没有数据，它的唯一目的就是让回放、过滤二进制事件变得容易，它有利于在<span style="background-color: #c0c0c0;">崩溃后重启应用、指定时间点（point-in-time）的数据恢复</span>。</p>
<p>假设你有若干日志文件（somelog-bin.000001, somelog-bin.000002……）需要分析，只需要建立一个没有数据的数据库，然后设置：</p>
<pre class="crayon-plain-tag">log_bin       = /var/log/mysql/somelog-bin
log_bin_index = /var/log/mysql/somelog-bin.index ;注意把所有日志文件，每个一行的存入该文件，脚本：/bin/ls −1 /var/log/mysql/somelog-bin.[0-9]* &gt; /var/log/mysql/somelog-bin.index</pre>
<p> 让这个服务器把那些日志当作是他自己的，然后启动日志服务器，使用SHOW MASTER LOGS来验证它已经识别了那些日志。</p>
<p>日志服务器不需要执行日志，它只会让其它服务器来读取它的日志。</p>
<p>对于数据恢复来说，日志服务器比mysqlbinlog更有优势，因为：</p>
<ol>
<li>数据复制其实就是应用二进制日志的过程，这种方式已经经过无数的生产环境的验证，相当稳定。而mysqlbinlog的工作方式有所不同，可能不能完整的重现二进制日志中的变化</li>
<li>日志服务器更快，因为它避免了从日志抽取SQL并到mysql中执行的过程</li>
<li>可以很容易的看到处理进度</li>
<li>容错性好，可以跳过复制失败的语句</li>
<li>很容易的过滤复制事件</li>
<li>如果日志是基于行的格式，mysqlbinlog可能无法读取二进制日志</li>
</ol>
<div class="blog_h3"><span class="graybg">Replication的管理和维护</span></div>
<p><span style="text-decoration: underline;"><strong>监控Replication的状态</strong></span></p>
<pre class="crayon-plain-tag">mysql&gt; SHOW MASTER STATUS; #显示master当前日志的路径和配置
mysql&gt; SHOW MASTER LOGS;   #显示存在于磁盘上的二进制日志
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000220 | 425605    |
| mysql-bin.000221 | 1134128   |
| mysql-bin.000222 | 13653     |
| mysql-bin.000223 | 13634     |
+------------------+-----------+
#显示日志中的事件
mysql&gt; SHOW BINLOG EVENTS IN 'mysql-bin.000223' FROM 13634\G
*************************** 1. row ***************************
Log_name: mysql-bin.000223
Pos: 13634
Event_type: Query
Server_id: 1
End_log_pos: 13723
Info: use `test`; CREATE TABLE test.t(a int)</pre>
<p><span style="text-decoration: underline;"><strong>度量Replication延迟</strong></span></p>
<p>SHOW SLAVE STATUS中的Seconds_behind_master理论上能够显示replica相对于master的延迟，但是往往不可靠。最好的方法是使用<span style="background-color: #c0c0c0;">心跳记录（heartbeat record）</span>，即每秒在master上更新一次的记录。</p>
<p><span style="text-decoration: underline;"><strong>确定replica是否与master处于同步状态</strong></span></p>
<p>验证replica的同步性应当是日常工作的一部分，特别是replica作为备份使用的时候。Percona Toolkit包含一个叫<span style="background-color: #c0c0c0;">pt-table-checksum</span>的工具可以用于根据校验和来检验同步性。</p>
<p><span style="text-decoration: underline;"><strong>replica的重新同步</strong></span></p>
<p>传统做法是，停止replica，重新从master克隆数据，缺点是不方便，特别是数据量大的时候。Percona Toolkit包含一个叫<span style="background-color: #c0c0c0;">pt-table-sync</span>的工具可以提供帮助。</p>
<p><span style="text-decoration: underline;"><strong>修改master</strong></span></p>
<p>由于某些原因，例如服务器升级、master出现故障，可能需要把replica提升为master，并通知给所有replica。</p>
<p><strong>Planned Promotions</strong></p>
<p>整体步骤如下：</p>
<ol>
<li>停止旧的master的写操作</li>
<li>等待replicas完成复制</li>
<li>把一个replica配置为master</li>
<li>把replica、写请求指向新的master，然后启用新master的写操作</li>
</ol>
<p>更深入的讲，以下操作可能需要：</p>
<ol>
<li>停止向发送当前master写请求，用可能需要强制客户端退出</li>
<li>使用FLUSH TABLES WITH READ LOCK停止master的所有写活动。或者通过read_only把master设为只读模式。注意，设置read_only不能阻止当前事务的提交，最好是kill所有活动的事务</li>
<li>选择一个replica作为新master，要确保它已经完全与旧master同步（已经执行完所有从旧master提取的转播日志）</li>
<li>可选的，验证新master与旧master的数据完全一样</li>
<li>在新master上执行STOP SLAVE、CHANGE MASTER TO MASTER_HOST=''、RESET SLAVE，这可以让新master从旧master断开连接，并且丢弃master.info中的信息</li>
<li>按照“推荐的Replication配置”来设置新master</li>
<li>执行SHOW MASTER STATUS来获取新master的二进制日志坐标</li>
<li>确保其它replica也与旧master同步</li>
<li>关闭旧master</li>
<li>在MySQL5.1+，如果需要的话激活新master上的事件</li>
<li>让客户端连接到新master</li>
<li>在各replica上执行CHANGE MASTER TO，指向新master，使用第6步获得的二进制日志坐标</li>
</ol>
<p><strong>Unplanned promotions</strong></p>
<p>如果master崩溃了，你不得不提升一个replica来替换它，这种场景相对较为复杂。可能存在数据丢失，因为master上的某些事件可能没有被任何replica复制，甚至master上执行了一个回滚，而replica尚未执行，如果以后能获取崩溃master的数据，可能可以手工恢复。</p>
<p>下面是提升步骤：</p>
<ol>
<li>确认哪个replica具有<span style="background-color: #c0c0c0;">最新的数据，它将作为新的master</span>。在每个replica上执行SHOW SLAVE STATUS，选择Master_Log_File/Read_Master_Log_Pos最新的一个</li>
<li>让所有replicas完成转播日志的执行</li>
<li>在新master上执行STOP SLAVE、CHANGE MASTER TO MASTER_HOST=''、RESET SLAVE，这可以让新master从旧master断开连接，并且丢弃master.info中的信息</li>
<li>执行SHOW MASTER STATUS来获取新master的二进制日志坐标</li>
<li>比较每个replica的Master_Log_File/Read_Master_Log_Pos与新master的差异，以计算这些replica相对于新master的二进制日志坐标。这里<span style="background-color: #c0c0c0;">假设log_bin 、log_slave_updates在所有replica上开启</span>，以确保能把所有replica恢复到一致的状态</li>
<li>执行上一节的10-12步</li>
</ol>
<p><strong>如何定位日志坐标</strong></p>
<p>如果某个replica的日志坐标与新master不同，则必须计算出该replica<span style="background-color: #c0c0c0;">相对于新master二进制日志的当前坐标</span>。并且将此坐标在命令CHANGE MASTER TO中使用。<span style="background-color: #c0c0c0;">除了使用SHOW SLAVE STATUS命令，亦可通过mysqlbinlog</span>来获取replica最新的最后一条语句，并找到新master二进制日志里同样的语句的位置。</p>
<p>考虑一个具体的例子，如下图所示：</p>
<p><img class="aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2012/02/replica-3.jpg" alt="" width="439" height="453" /></p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/high-performance-mysql-study-note">High Performance MySQL学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/high-performance-mysql-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
