<?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; MySQL</title>
	<atom:link href="https://blog.gmem.cc/category/work/database/mysql/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Thu, 30 Apr 2026 03:05:57 +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>Galera学习笔记</title>
		<link>https://blog.gmem.cc/galera-study-note</link>
		<comments>https://blog.gmem.cc/galera-study-note#comments</comments>
		<pubDate>Tue, 14 Jan 2020 11:24:25 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Galera]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=35345</guid>
		<description><![CDATA[<p>MariaDB对比MySQL MariaDB提供了兼容MySQL的数据库解决方案，它本身是MySQL的一个Fork。 复制 MySQL中复制（Replication）是异步的、单向的。其中一个服务器作为Master，其它的作为Slave。所谓主主模式，是两个服务器分别配置为对方的Slave。MariaDB提供主主复制、主从复制。 MySQL 的默认二进制日志格式是基于行的，而在 MariaDB 中，默认的二进制日志格式是混合式的。 MariaDB支持二进制日志压缩（log_bin_compress）。 集群 MySQL提供shared-nothing的集群支持，通过自动分区的方式将数据分发到不同节点。在内部MySQL使用同步的、两阶段的提交，确保数据被写入到多个分片。 MariaDB使用Galera Cluster实现多主，从10.1开始MariaDB内置Galera，只需要配置参数即可启用集群模式。 存储引擎 MariaDB支持更多的存储引擎：XtraDB（10.2-的默认引擎，InnoDB增强版）、InnoDB（10.2+默认引擎）、MariaDB ColumnStore、Aria、Archive、Blackhole、Cassandra Storage Engine、Connect、CSV、FederatedX、Memory、Merge、Mroonga、MyISAM、MyRocks、QQGraph、Sequence Storage Engine、SphinxSE、Spider、TokuDB 其它特性 不可见列 <a class="read-more" href="https://blog.gmem.cc/galera-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/galera-study-note">Galera学习笔记</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">MariaDB对比MySQL</span></div>
<p>MariaDB提供了兼容MySQL的数据库解决方案，它本身是MySQL的一个Fork。</p>
<div class="blog_h2"><span class="graybg">复制</span></div>
<p>MySQL中<span style="background-color: #c0c0c0;">复制（Replication）是异步的、单向的</span>。其中一个服务器作为Master，其它的作为Slave。所谓主主模式，是两个服务器分别配置为对方的Slave。MariaDB提供主主复制、主从复制。</p>
<p>MySQL 的默认二进制日志格式是基于行的，而在 MariaDB 中，默认的二进制日志格式是混合式的。</p>
<p>MariaDB支持二进制日志压缩（log_bin_compress）。</p>
<div class="blog_h2"><span class="graybg">集群</span></div>
<p>MySQL提供shared-nothing的集群支持，通过自动分区的方式将数据分发到不同节点。在内部MySQL使用<span style="background-color: #c0c0c0;">同步的、两阶段的提交，确保数据被写入到多个分片</span>。</p>
<p>MariaDB使用Galera Cluster实现多主，从10.1开始MariaDB内置Galera，只需要配置参数即可启用集群模式。</p>
<div class="blog_h2"><span class="graybg">存储引擎</span></div>
<p>MariaDB支持更多的存储引擎：XtraDB（10.2-的默认引擎，InnoDB增强版）、InnoDB（10.2+默认引擎）、MariaDB ColumnStore、Aria、Archive、Blackhole、Cassandra Storage Engine、Connect、CSV、FederatedX、Memory、Merge、Mroonga、MyISAM、MyRocks、QQGraph、Sequence Storage Engine、SphinxSE、Spider、TokuDB</p>
<div class="blog_h2"><span class="graybg">其它特性</span></div>
<div class="blog_h3"><span class="graybg">不可见列</span></div>
<p>MariaDB 上可用，MySQL 不支持该功能。这个功能允许创建不再 SELECT * 语句中出现的列，而在进行插入时，如果它们的名字没有出现在 INSERT 语句中，就不需要为这些列提供值。</p>
<div class="blog_h3"><span class="graybg">JSON数据类型</span></div>
<p>从 5.7 版本开始，MySQL 支持由 RFC 7159 定义的原生 JSON 数据类型，可以高效地访问 JSON 文档中的数据。</p>
<p>MariaDB 没有提供这一增强功能，认为 JSON 数据类型不是 SQL 标准的一部分。但为了支持从 MySQL 复制数据，MariaDB 为 JSON 定义了一个别名，实际上就是一个 LONGTEXT 列。</p>
<div class="blog_h3"><span class="graybg">线程池</span></div>
<p><span style="background-color: #c0c0c0;">MariaDB 支持连接线程池，这对于短查询和 CPU 密集型的工作负载（OLTP）来说非常有用</span>。<span style="background-color: #c0c0c0;">在 MySQL 的社区版本中，线程数是固定</span>的，因而限制了这种灵活性。MySQL 计划在企业版中增加线程池功能。</p>
<div class="blog_h1"><span class="graybg">Galera基础</span></div>
<p>MariaDB Galera Cluster提供MariaDB多主集群，仅仅对XtraDB/InnoDB存储引擎提供支持。</p>
<p>从MariaDB 10.1开始，<a href="https://github.com/codership/mysql-wsrep">MySQL-wsrep</a>补丁被合并到MariDB，这意味着使用标准的MariaDB+<a href="https://github.com/codership/galera/">Galera wsrep提供者库</a>即可构建Galera集群。该补丁由<a href="https://groups.google.com/forum/?fromgroups#!forum/codership-team">Codership</a>开发，提供<a href="https://galeracluster.com/library/documentation/architecture.html">wsrep API</a>支持。</p>
<div class="blog_h2"><span class="graybg">简介</span></div>
<div class="blog_h3"><span class="graybg">特性</span></div>
<p>Galera的主要特性包括：</p>
<ol>
<li>几乎同步的（virtually synchronous ）的复制</li>
<li>Active-Active多主拓扑支持，不需要failover</li>
<li>针对集群的任何节点进行读写操作</li>
<li>自动的集群成员控制，失败的节点能够自动的移出集群</li>
<li>自动节点加入</li>
<li>真正的（从底层）并行复制</li>
<li>不需要读写分离</li>
<li>Slave支持多线程以提升性能</li>
</ol>
<div class="blog_h3"><span class="graybg">优势</span></div>
<p>基于Galera的集群的主要优势包括：</p>
<ol>
<li>没有Slave的延迟</li>
<li>不会丢失事务</li>
<li>读操作的可扩容性</li>
<li>更小的客户端延迟</li>
</ol>
<div class="blog_h3"><span class="graybg">场景</span></div>
<p>Galera的使用场景包括：</p>
<ol>
<li>从Master读写：Galera支持这种传统场景，但是和MariaDB传统主从复制相比，任何一个<span style="background-color: #c0c0c0;">节点都可以随时成为Master</span>，而其它节点仅仅是客户端将其作为Slave看待而以。由于Slave可以并行的应用writeset，集群的吞吐量会快的多，此外复制的延迟也被消除</li>
<li>广域网集群：可以支持这种网络下的几乎同步复制，可能会存在延迟，取决于RTT。这些延迟仅仅影响commit操作</li>
<li>灾难恢复：广域网集群的子用例。这种情况下，位于某个数据中心的节点仅被动接受复制，不处理任何客户端事务。由于Galera的几乎同步的复制，它不会丢失数据，并可以在主节点宕机后立即升级为新的主节点</li>
<li>延迟消除：让客户端访问靠近自己的节点，可以消除读操作的延迟。延迟仅仅在写操作时才发生</li>
</ol>
<div class="blog_h2"><span class="graybg">版本</span></div>
<p>MariaDB、Galera库、wsrep API版本对应关系如下：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">MariaDB</td>
<td style="text-align: center;">Galera/wsrep提供者库</td>
<td style="text-align: center;">wsrep API</td>
</tr>
</thead>
<tbody>
<tr>
<td>MariaDB 10.4+</td>
<td>4</td>
<td>26</td>
</tr>
<tr>
<td>MariaDB 10.3-</td>
<td>3</td>
<td>25</td>
</tr>
</tbody>
</table>
<p>使用包名galera-4安装Galera 4 wsrep provider。版本26.4.6对应MariaDB的版本是10.5.7 / 10.4.16</p>
<p>使用包名galera安装Galera 3 wsrep provider。版本25.3.31对应MariaDB的版本是10.3.26 / 10.2.35 / 10.1.48</p>
<div class="blog_h2"><span class="graybg">通用复制库</span></div>
<p>Galera复制功能实现为一个共享库，可以连接到任何实现了wsrep API的事务处理系统。它是由一系列组件组成的协议栈，提供<span style="background-color: #c0c0c0;">准备、复制、应用事务writeset</span>的功能。</p>
<p>在Galera 4很多组件被重新设计。</p>
<div class="blog_h3"><span class="graybg">wsrep API</span></div>
<p>即写入集复制API（writeset replication API），它定义了Galera Replication和MariaDB之间的接口，以及各自的职责。 </p>
<div class="blog_h3"><span class="graybg">wsrep hooks</span></div>
<p>在RDBMS引擎中的wsrep集成点。</p>
<div class="blog_h3"><span class="graybg">Galera Provider</span></div>
<p>为Galera库实现wsrep API。</p>
<p>尽管galera provider负责在提交时，在所有节点认证writeset，但是<span style="background-color: #c0c0c0;">writeset不需要被立即apply到节点</span>。<span style="background-color: #c0c0c0;">实际上writeset被放置在节点的接收队列上，由节点的某个galera slave thread最终apply到数据库中</span>。</p>
<p>Galera slave thread的数量可以通过系统变量<pre class="crayon-plain-tag">wsrep_slave_threads</pre>配置。</p>
<p>Galera slave thread们负责自行判断哪些writeset可以被安全的并行apply，但是，如果<span style="background-color: #c0c0c0;">节点经常出现数据一致性问题，可以将线程数量设置为1</span>。</p>
<div class="blog_h3"><span class="graybg">certification layer</span></div>
<p>负责准备writeset，执行认证测试。</p>
<div class="blog_h3"><span class="graybg">replication</span></div>
<p>管理复制协议，提供整体上的排序能力（ordering capabilities）</p>
<div class="blog_h3"><span class="graybg">GCS framework</span></div>
<p>为Group Communication系统（GCS）提供插件化架构。很多GCS实现可以被适配。内置实现是vsbes、gemini </p>
<div class="blog_h2"><span class="graybg">状态转移</span></div>
<p>所谓状态转移是指从现有节点复制数据，以使得新节点/宕机后恢复的节点到达同步状态的操作。</p>
<div class="blog_h3"><span class="graybg">状态快照转移</span></div>
<p>在一次State Snapshot Transfers / SST中，集群通过从一个节点拷贝完整的数据集，来创建新节点。</p>
<p>当新节点加入集群后，它会触发一次SST。</p>
<div class="blog_h3"><span class="graybg">增量状态转移</span></div>
<p>在一次Incremental State Transfers /IST中，集群从正常节点，拷贝某个节点缺失的writeset。</p>
<p>如果节点离开集群时间比较短，则IST通常比SST更块。</p>
<div class="blog_h2"><span class="graybg">原理</span></div>
<p>在Galera集群中，服务器在提交（Commit）期间进行事务复制。其做法是，<span style="background-color: #c0c0c0;">将事务关联的写入集（writeset）广播到所有节点</span>。 </p>
<div class="blog_h3"><span class="graybg">同步/异步复制</span></div>
<p>同步复制能够保证：如果一个节点上的数据发生变更，那么这些变更会同时发生在其它节点上。</p>
<p>异步复制不提供上述保证，这意味着，在第一个节点上发生变更后，复制完成之前该节点宕机，<span style="background-color: #c0c0c0;">可能会导致数据丢失</span>。</p>
<p>同步复制的优势：</p>
<ol>
<li>使用<span style="background-color: #c0c0c0;">同步复制的集群，总是能保证HA和数据一致性</span>。节点崩溃不会导致数据丢失</li>
<li>同步复制允许<span style="background-color: #c0c0c0;">事务在所有节点上并行的执行</span>。而不是进行Replay</li>
<li>因果律保证：如果先在A节点发生事务，然后在B节点执行查询，那么该查询一定能看到A节点那个事务的结果</li>
</ol>
<p>典型的同步复制利用<span style="background-color: #c0c0c0;">两阶段提交或者分布式锁</span>来实现，非常缓慢。再加上实现的复杂度，导致MySQL、PostgreSQL等传统RDBMS都仅仅支持异步复制，或者所谓半同步复制（主库在执行完客户端提交的事务后不是立刻返回给客户端，而是等待至少一个从库接收到并写到Relay log中才返回给客户端）。</p>
<div class="blog_h3"><span class="graybg">基于认证的复制</span></div>
<p>Galera使用基于认证的复制（Certification-based replication），实现几乎完全同步（virtually synchronous）的数据复制。</p>
<p>Galera的基本思想是乐观执行（ optimistic execution）：<span style="background-color: #c0c0c0;">假设不存在冲突，正常执行事务，直到达到提交点（commit point）</span>，在提交之前，通过认证来检查冲突</p>
<p>Galera的核心机制包括：</p>
<ol>
<li>分组通信（group communication）：定义了数据库节点之间的通信pattern，保证复制数据的一致性</li>
<li>写入集（writeset）：将若干数据库写操作合并为一个消息，避免了每次数据库操作都引发节点之间的协作。<span style="background-color: #c0c0c0;">writeset中包含了事务的所有必要信息</span></li>
<li>数据库状态机（database state machine）：只读事务仅在节点本地执行。非只读事务首先在单个节点本地执行，然后广播writeset到其它节点，认证并apply到数据库</li>
<li>全局事务排序（global transaction reordering）：在复制期间，<span style="background-color: #c0c0c0;">Galera为每个事务分配一个全局的序号即seqno，每个节点将以相同的顺序接收（和应用）事务</span>。当事务到达提交点时，节点会检查上一次成功的事务的seqno。这两次事务seqno的差值，提示了尚未apply的事务数量。节点会对这些没处理的事务进行主键冲突检查，发现冲突则触发认证失败</li>
</ol>
<div class="blog_h3"><span class="graybg">复制流程</span></div>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2021/01/certificationbasedreplication.png"><img class="size-full wp-image-35431 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2021/01/certificationbasedreplication.png" alt="certificationbasedreplication" width="800" height="480" /></a></p>
<p>Galera执行数据复制的流程如下：</p>
<ol>
<li>
<p>当客户端发出一个 commit 指令，在<span style="background-color: #c0c0c0;">事务在服务器端被正式（数据已经写入，只差）提交之前，所有对数据库的更改都会被writeset收集起来</span>，并且<span style="background-color: #c0c0c0;">将 writeset 纪录广播</span>给其他节点</p>
</li>
<li>
<p><span style="background-color: #c0c0c0;">writeset 将在每个节点（包括发起writeset的原始节点）上、基于主键进行决定性认证测试（deterministic certification test,）</span>，测试结果决定节点能否 apply writeset</p>
</li>
<li>
<p>如果认证测试失败：节点丢弃writeset，集群<span style="background-color: #c0c0c0;">回滚原始事务</span></p>
</li>
<li>如果认证测试成功：<span style="background-color: #c0c0c0;">事务被正式提交，其余节点则apply writeset</span></li>
</ol>
<p>只有所有节点都认证成功，达成共识，那么发起事务的原始节点就可以正式提交并给客户端响应。在这个时间点，用户的写入集已经同步到了所有节点，不会因为原始节点故障而导致数据丢失</p>
<div class="blog_h2"><span class="graybg">注意点</span></div>
<p>关于从普通MariaDB转到Galera的TIPS，参考：<a href="https://mariadb.com/kb/en/tips-on-converting-to-galera/">https://mariadb.com/kb/en/tips-on-converting-to-galera/</a></p>
<div class="blog_h3"><span class="graybg">仅支持InnoDB</span></div>
<p>对任何其它引擎的表的写入，包括系统表（mysql.*，使用MyISAM引擎）都不会复制到其它节点。<span style="background-color: #c0c0c0;">例外情况是DDL语句，这些语句虽然修改mysql.*表，但是却会被复制</span>。</p>
<p>对MyISAM复制目前提供试验性支持，设置系统变量：wsrep_replicate_myisam。</p>
<p>FLUSH PRIVILEGES 不会被复制。</p>
<div class="blog_h3"><span class="graybg">不支持明确锁定</span></div>
<p>LOCK TABLES、FLUSH TABLES (table list) WITH READ LOCK等语句不被支持，可以通过适当的事务控制来达到相同效果。</p>
<p>全局性的锁操作，例如FLUSH TABLES WITH READ LOCK则可以被支持。</p>
<div class="blog_h3"><span class="graybg">必须有主键</span></div>
<p>所有表必须具有主键（支持复合主键）。针对无主键表的DELETE操作是不支持的。没有主键的表，可能在不同节点上的顺序不同。</p>
<div class="blog_h3"><span class="graybg">查询日志必须写到文件</span></div>
<p>普通查询日志、缓慢查询日志，不支持写入到表中。如果启用这些日志，你必须设置log_out=FILE</p>
<div class="blog_h3"><span class="graybg">不支持XA事务</span></div>
<div class="blog_h3"><span class="graybg">事务尺寸的限制</span></div>
<p>尽管Galera没有明确限制，但是writeset被存储在内存中，这隐含了对事务尺寸的限制。</p>
<p>为了避免非常大的事务严重影响节点性能。系统变量的默认值设置为：</p>
<ol>
<li>wsrep_max_ws_rows  事务最大影响行数，128K</li>
<li>wsrep_max_ws_size 事务最大尺寸（写入集大小）2GB</li>
</ol>
<p>此外根据社区的反馈，大尺寸事务可能有<span style="background-color: #c0c0c0;">额外的不可忽略的开销</span>。你写入100K的行，可能需要额外200-300MB甚至数G内存。</p>
<div class="blog_h3"><span class="graybg">牵涉DDL时锁语义改变</span></div>
<p>你的DML语句操作一个锁，而另外一个DDL已经发起。正常情况下MySQL会等待元数据锁，但是Galera则会立即执行DML。甚至仅有单个节点的情况下，也会发生这种情况。</p>
<div class="blog_h3"><span class="graybg">自增列空隙</span></div>
<p>不要假设自增长的列是一个个顺序增长的。Galera为了保证没有冲突，为每个节点的自增长键都设置了空虚。</p>
<div class="blog_h3"><span class="graybg">网络分区</span></div>
<p>当出现网络分区，并且当前节点不再大多数节点所在分区。命令会导致ER_UNKNOWN_COM_ERROR：WSREP has not yet prepared node for application use / Unknown command。这个报错是为了放置数据不一致。网络分区状态可以通过检查<pre class="crayon-plain-tag">wsrep_ready</pre>变量发现。</p>
<p>少部分节点所在的分区，节点可能会<span style="background-color: #c0c0c0;">丢弃所有客户端连接</span>，这个行为可能比较意外 —— 因为客户端可能是空闲的，甚至不知道发生什么连接就忽然断了。</p>
<p>这种被分区的节点重新连接到集群后，可能有大量数据需要同步。在同步完成之前，它仍然会报告unknown command。</p>
<div class="blog_h3"><span class="graybg">不要修改binlog格式</span></div>
<p>在运行时修改binlog格式，不但会导致复制失败，还会导致其它节点崩溃。</p>
<div class="blog_h3"><span class="graybg">性能问题</span></div>
<p>根据Galera的设计，<span style="background-color: #c0c0c0;">集群的整体性能取决于最慢的节点</span>。</p>
<p>此外，即使<span style="background-color: #c0c0c0;">只有一个节点。性能也可能比非集群模式的MariaDB低很多</span>，特别是对于大事务。</p>
<div class="blog_h3"><span class="graybg">查询缓存问题</span></div>
<p>在MariaDB Galera Cluster 5.5.40, MariaDB Galera Cluster 10.0.14以及MariaDB 10.1.2之前，需要设置query_cache_size=0以禁用查询缓存。</p>
<div class="blog_h3"><span class="graybg">异步复制模式</span></div>
<p>启用异步复制模式的情况下，不支持在Slave节点上进行并行复制（slave-parallel-threads 大于1）。</p>
<div class="blog_h3"><span class="graybg">表结构不一致</span></div>
<p>节点的表结构可能不一致，特别是在执行rolling schema upgrade期间。</p>
<div class="blog_h3"><span class="graybg">读写分离</span></div>
<p>读写分离不是必须的，但是仍然建议做读写分离，未来底层架构可能变化。</p>
<div class="blog_h1"><span class="graybg">变量清单</span></div>
<div class="blog_h2"><span class="graybg">状态变量</span></div>
<p>所有状态变量具有一致的前缀：</p>
<pre class="crayon-plain-tag">SHOW STATUS LIKE 'wsrep%';</pre>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class=" blog_h3">wsrep_applier_thread_count</td>
<td>当前applier线程的数量，这种线程负责将writeset写入到数据库</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_apply_oooe</strong></td>
<td>writeset被乱序的apply的频繁程度，可以提示并行处理的效率</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_apply_oool</strong></td>
<td>具有高序列号的writeset，在具有低序列号的writeset之前被apply —— 这种情况发生的频繁程度，可以提示处理缓慢的writeset</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_apply_window</strong></td>
<td>并发的被apply的writeset的最高、最低序列号的平均差值</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cert_deps_distance</td>
<td>可能被并行apply的writeset序列号的平均差值，提示潜在的并行度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cert_index_size</td>
<td>认证索引中的条目数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cert_interval</td>
<td>在一个事务被复制期间，平均接收到的新事务的数量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_cluster_conf_id</strong></td>
<td>集群成员关系发生变化的总次数</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_cluster_size</strong></td>
<td>集群成员当前数量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_cluster_state_uuid</strong></td>
<td>集群当前状态的唯一标识。如果和wsrep_local_state_uuid相同，意味着本节点和集群同步（in sync）</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_cluster_status</strong></td>
<td>
<p>集群组件状态，可能值：</p>
<ol>
<li>PRIMARY：primary group configuration, quorum present</li>
<li>NON_PRIMARY：non-primary group configuration, quorum lost</li>
<li>DISCONNECTED：not connected to group, retrying</li>
</ol>
</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cluster_weight</td>
<td>当前集群成员们的总weight，即当前Primary Component中的节点的pc.weight的求和</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_commit_oooe</strong></td>
<td>事务被乱序commit的频繁程度</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_commit_window</strong></td>
<td>并发的被commit的最大、最小seqno的平均差值</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_connected</strong></td>
<td>MariaDB是否连接到wsrep provider，值ON/OFF</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_desync_count</td>
<td>需要节点临时失去同步状态的、进行中的操作数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_evs_delayed</td>
<td>当前节点在哪些节点上注册到了delayed list</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_evs_evict_list</td>
<td>从集群驱除的节点的UUIDs。在重启mysqld进程之前，被驱逐的节点无法重新加入集群</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_evs_repl_latency</td>
<td>
<p>提示组通信（group communication）的复制（replication）延迟。即从消息发出到消息接收的时延（秒）</p>
<p>由于复制是组操作（group operation），因此该参数提示了最慢的ACK和最长的RTT时间</p>
</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_evs_state</td>
<td>显示EVS协议的内部状态</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_flow_control_paused</td>
<td>由于流控而导致复制暂停的时长，从上一个FLUSH STATUS命令开始记时</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_flow_control_paused_ns</td>
<td>总计处于暂体状态的纳秒数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_flow_control_recv</td>
<td>从最近一次状态查询以来，FC_PAUSE事件的接收总数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_flow_control_sent</td>
<td>从最近一次状态查询以来，FC_PAUSE事件的发送总数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_gcomm_uuid</td>
<td>节点的UUID（用于组通信）</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_incoming_addresses</td>
<td>在cluster component中的incoming服务器地址列表</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_last_committed</td>
<td>最近一次事务的seqno</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_bf_aborts</strong></td>
<td>被slave transcation中断的本地事务的总数，中断由于认证失败（存在冲突）导致</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_cached_downto</td>
<td>writeset缓存（GCache）中最小的seqno值</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_cert_failures</strong></td>
<td>在认证测试中失败（存在冲突）的本地事务总数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_commits</td>
<td>在本节点上提交的本地事务总数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_index</td>
<td>集群中本节点的索引，索引zero-based</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_recv_queue</strong></td>
<td>当前接收队列长度，即等待被apply的writeset数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_recv_queue_avg</td>
<td>自最近一次状态查询以来，接收队列的平均长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_recv_queue_max</td>
<td>自最近一次FLUSH STATUS以来，接收队列的最大长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_recv_queue_min</td>
<td>自最近一次FLUSH STATUS以来，接收队列的最小长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_replays</td>
<td>由于asymmetric lock granularity导致replay的事务的总量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_send_queue</strong></td>
<td>当前发送队列的长度，即等待被发送的writeset数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_send_queue_avg</td>
<td>自最近一次状态查询以来，发送队列的平均长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_send_queue_max</td>
<td>自最近一次FLUSH STATUS以来，发送队列的最大长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_send_queue_min</td>
<td>自最近一次FLUSH STATUS以来，发送队列的最小长度</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_state</strong></td>
<td>内部Galera Cluster FSM state</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_state_comment</strong></td>
<td>
<p>上个字段的可读文本</p>
<p>如果节点是Primary Component的成员，则值可能是：</p>
<p style="padding-left: 30px;">Joining, Waiting on SST, Joined, Synced, Donor</p>
<p>如果节点不是PC成员，则值是：Initialized</p>
<p>节点状态转换示意图：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2021/01/galerafsm.png"><img class="size-full wp-image-35459 alignleft" src="https://blog.gmem.cc/wp-content/uploads/2021/01/galerafsm.png" alt="galerafsm" width="315" height="338" /></a></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<ol>
<li>节点启动，并连接到Primary Component</li>
<li>节点发起状态转移请求，开始缓存writeset</li>
<li>节点接收SST，获得了所有集群数据，开始apply writeset</li>
<li>节点赶上集群进度。现在它的slave queue是空的，开始启用FC，保持slave queue为空。节点设置wsrep_ready=1，它可以处理事务了</li>
<li>节点接收其它节点发来的SST请求，配合进行状态转移。缓存所有它无法apply的writeset（某些SST会阻塞donor）</li>
</ol>
</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_state_uuid</strong></td>
<td>节点的UUID状态，如果和wsrep_cluster_state_uuid相同则本节点保持了同步</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_open_connections</td>
<td>在wsrep provider中打开的连接数量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_open_transactions</strong></td>
<td>
<p>在wsrep provider中已经注册的、本地运行中的事务的数量</p>
<p>这个变量提示了生成writeset的事务的数量，只读事务不在统计中</p>
</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_protocol_version</td>
<td>wsrep协议版本</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_provider_name</td>
<td>wsrep提供者名称</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_provider_vendor</td>
<td>wsrep提供者vendor</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_provider_version</td>
<td>wsrep提供者版本</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_ready</strong></td>
<td>wsrep提供者是否就绪</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_received</strong></td>
<td>从其它节点接收到的writeset数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_received_bytes</td>
<td>从其它节点接收到的writeset字节数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_repl_data_bytes</td>
<td>复制的数据字节数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_repl_keys</td>
<td>复制的键数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_repl_keys_bytes</td>
<td>复制的键字节数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_repl_other_bytes</td>
<td>复制的其它bits数量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_replicated</strong></td>
<td>复制到其它节点的writeset数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_replicated_bytes</td>
<td>复制到其它节点的writeset字节数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_rollbacker_thread_count</td>
<td>rollbacker线程数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_thread_count</td>
<td>wsrep线程（applier/rollbacker）总数</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">系统变量</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">wsrep_auto_increment_control</td>
<td>
<p>如果设置为1（默认），自动根据集群规模调整auto_increment_increment/auto_increment_offset </p>
<p>集群规模变化时也会调整</p>
<p>避免由于auto_increment导致的复制冲突</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_causal_reads</strong></td>
<td>
<p>默认OFF。如果设置为ON，确保集群范围内的隔离级别：读取已提交</p>
<p>如果master比slave apply的速度快很多，意味着失去同步。该选项设置为<span style="background-color: #c0c0c0;">ON，则让slave在事件处理完毕之前，不去处理查询</span></p>
<p>设置为ON，会导致更大的读延迟</p>
<p>已经废弃：用 wsrep_sync_wait=1 代替</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_certification_rules</td>
<td>
<p>集群中使用的认证规则（certification rules ），可选值：</p>
<ol>
<li>strict，可能导致更多的认证失败</li>
<li>optimized，允许更多的并发，减少认证失败</li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_certify_nonPK</td>
<td>默认ON。如果设置为ON，允许复制没有PK的表，但是可能导致未定义的行为</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_cluster_address</strong></td>
<td>
<p>节点启动时连接的地址，格式：</p>
<p style="padding-left: 30px;">&lt;schema&gt;://&lt;cluster_address&gt;[?option1=value1[&amp;option2=value2]]</p>
<p>示例：</p>
<p style="padding-left: 30px;">gcomm://192.168.0.1:1234?gmcast.listen_addr=0.0.0.0:2345</p>
<p>建议指定所有可能的节点地址：</p>
<p style="padding-left: 30px;">gcomm://&lt;node1 or ip:port&gt;,&lt;node2 or ip2:port&gt;,&lt;node3 or ip3:port&gt;</p>
<p><span style="background-color: #c0c0c0;">如果指定空白（gcomm://）则节点会发起一个新的集群</span></p>
<p>option指的是wsrep provider选项。这些选项的默认值来自 wsrep_provider_options ，<span style="background-color: #c0c0c0;">这里可以覆盖之</span></p>
<p>某些配置下，该变量可以在运行时修改，这会导致节点断开现有连接，并连接到其它集群</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_cluster_name</strong></td>
<td>集群的名称，节点<span style="background-color: #c0c0c0;">不能连接到和不同名称的集群</span></td>
</tr>
<tr>
<td class="blog_h3">wsrep_convert_LOCK_to_trx</td>
<td>
<p>将LOCK/UNLOCK TABLES语句转换为BEGIN/COMMIT，用于适配老的应用程序（免修改）</p>
<p>谨慎使用，可能导致巨大的writeset</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_data_home_dir </strong></td>
<td>wsrep provider存储内部文件的目录 </td>
</tr>
<tr>
<td class="blog_h3">wsrep_dbug_option </td>
<td>向wsrep provider传递调试选项</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_debug</strong> </td>
<td>
<p>wsrep调试日志级别。NONE（默认）, SERVER, TRANSACTION, STREAMING, CLIENT</p>
<p>开启日志，可以记录冲突的事务</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_desync</td>
<td>
<p>默认OFF。当节点接收太多writeset，无法即使消化（apply），则事务被存放到接收队列中。如果接收队列积蓄的writeset太多（由于wsrep选项gcs.fc_limit指定），则节点enage流控</p>
<p>如果将此字段设置为ON，则针对desync的节点禁用流控（flow control）。desync的节点会继续慢慢悠悠的处理队列中的writeset，这可能导致积累越来越多</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_dirty_reads</strong></td>
<td>可session，默认OFF。默认情况下，如果节点没有和群组（集群）同步（状态wsrep_ready=OFF），则它会拒绝除了SET/SHOW之外的任何查询请求。如果将此选项设置为ON，那么不会导致数据变化的查询请求可以被节点所接受</td>
</tr>
<tr>
<td class="blog_h3">wsrep_drupal_282555_workaround</td>
<td>默认OFF。如果启用，这启用针对缺陷<a href="https://www.drupal.org/node/282555">Drupal/MySQL/InnoDB bug #282555</a>的workaround。该缺陷可能导致向AUTO_INCREMENT列插入DEFAULT值时产生重复KEY </td>
</tr>
<tr>
<td class="blog_h3">wsrep_forced_binlog_format</td>
<td>
<p>覆盖任何session级别binlog格式设置</p>
<p>取值：STATEMENT, ROW, MIXED, NONE</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_gtid_domain_id</td>
<td>用于wsrep GTID（Global Transaction ID）模式的GTID domain ID</td>
</tr>
<tr>
<td class="blog_h3">wsrep_gtid_mode</td>
<td>
<p>wsrep GTID mode尝试在所有节点上，保持writeset的GTID的一致性。在加入新节点时，通过SST将GTID状态复制到节点</p>
<p>如果打算使用MariaDB Replication，启用wsrep GTID mode可能有用</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_gtid_seq_no</td>
<td>仅session，设置WSREP GTID seqno</td>
</tr>
<tr>
<td class="blog_h3">wsrep_ignore_apply_errors</td>
<td>
<p>apply错误应该被忽略，还是报告给provider：</p>
<p style="padding-left: 30px;">0 不跳过任何错误<br />1 忽略某些DDL错误（DROP DATABASE, DROP TABLE, DROP INDEX, ALTER TABLE）<br />2 忽略DML错误（仅仅忽略DELETE错误）<br />4 忽略所有DDL错误</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_log_conflicts</td>
<td>默认OFF。如果设置为ON，冲突的MDL以及InnoDB所的详细信息被记录 </td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_max_ws_rows</strong></td>
<td>
<p>一个writeset允许的最大行数</p>
<p>为了保证向后兼容，默认值0，表示允许任意大小 </p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_max_ws_size</strong></td>
<td>一个writeset允许的最大bytes</td>
</tr>
<tr>
<td class="blog_h3">wsrep_mysql_replication_bundle</td>
<td>
<p>可以被分组到一起处理的复制事件的数量。一个试验性的实现允许辅助一个slave节点处理commit时延</p>
<p>默认0，不支持分组</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_node_address</strong></td>
<td>节点的网络地址。默认0.0.0.0:4567</td>
</tr>
<tr>
<td class="blog_h3">wsrep_node_incoming_address</td>
<td>监听客户端连接的地址</td>
</tr>
<tr>
<td class="blog_h3">wsrep_node_name</td>
<td>节点的名称，可以用在wsrep_sst_donor中</td>
</tr>
<tr>
<td class="blog_h3">wsrep_notify_cmd</td>
<td>每当节点状态或集群成员关系发生变化时调用的命令 </td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_on</strong></td>
<td>
<p>可session，默认OFF。是否启用wsrep复制，如果设置为OFF则无法加载wsrep provider，不能加入集群</p>
<p>如果在session级别设置为OFF，则该会话的操作不会被复制，但是其它会话、applier线程仍然正常工作</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_OSU_method</td>
<td>
<p>指定在线Schema更新方法（schema upgrade method） ：</p>
<ol>
<li>TOI（默认）：Total Order Isolation。在每个节点上，DDL以相同的顺序执行，保证数据一致性。数据库被影响的部分在全集群范围锁定</li>
<li>RSU：Rolling Schema Upgrade。DDL仅仅在本地节点处理，用户需要手工在其它节点处理</li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_patch_version</td>
<td>wsrep补丁版本</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_provider</strong></td>
<td>
<p>wsrep provider库的位置，可能位置：</p>
<ol>
<li>Ubuntu：/usr/lib/libgalera_smm.so</li>
<li>CentOS：/usr/lib64/libgalera_smm.so</li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_provider_options</td>
<td>传递给wsrep provider的、分号（;）分隔的选项列表 </td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_recover</strong></td>
<td>如果在服务器启动时设置为ON，则节点会尝试恢复最近一次apply的writeset的seqno，并让服务器退出</td>
</tr>
<tr>
<td class="blog_h3">wsrep_reject_queries</td>
<td>
<p>用于进入维护状态，节点会继续apply writeset，但是客户端查询会导致Error 1047: Unknown command错误。取值：</p>
<ol>
<li>NONE（默认）：查询正常处理</li>
<li>ALL：所有客户端查询请求被拒绝，已经存在的客户端连接继续维护</li>
<li>ALL_KILL：所有查询被拒绝，同时关闭所有连接</li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_replicate_myisam</strong></td>
<td>是否对MyISAM表的DML可以被复制。试验性功能</td>
</tr>
<tr>
<td class="blog_h3">wsrep_restart_slave</td>
<td>默认OFF。如果设置为ON，则节点重新加入集群后replication slave自动重启</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_retry_autocommit</strong></td>
<td>
<p>在报告客户端以错误之前，因为集群范围冲突而导致无法提交的auto-commit请求重试的次数</p>
<p>默认1。设置为0则不会重试</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_slave_FK_checks</td>
<td>默认ON。如果设置为ON，则applier slave thread会执行外键约束检查</td>
</tr>
<tr>
<td class="blog_h3">wsrep_slave_UK_checks</td>
<td>默认OFF。如果设置为ON，则applier slave thread会执行辅助索引的唯一性检查</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_slave_threads</strong></td>
<td>默认1。用于并行apply writeset的slave thread的总数。这些线程能够自行决定writeset 能否被安全的并行apply，但是，如果你发现集群中频繁出现不一致性问题，考虑设置为1</td>
</tr>
<tr>
<td class="blog_h3">wsrep_sr_store </td>
<td>streaming replication fragments的存储方式</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_sst_auth</strong></td>
<td>SST时使用的身份验证信息</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_sst_donor</strong></td>
<td>SST的偏好的源节点 </td>
</tr>
<tr>
<td class="blog_h3">wsrep_sst_donor_rejects_queries</td>
<td>默认OFF。如果设置为ON则供给（donor）节点在SST期间拒绝查询请求并返回UNKNOWN COMMAND</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_sst_method</strong></td>
<td>默认rsync。SST方法 </td>
</tr>
<tr>
<td class="blog_h3">wsrep_sst_receive_address</td>
<td>供给节点（donor）连接到此地址，来发送状态转移更新</td>
</tr>
<tr>
<td class="blog_h3">wsrep_strict_ddl</td>
<td>默认OFF。如果设置为ON，则禁止对不支持Galera Replication的表（即非InnoDB）的DDL</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_sync_wait</strong></td>
<td>
<p>在该参数指定的操作类型执行之前，执行causality checks 以确保操作在完全同步的前提下执行</p>
<p>在causality check期间，所有查询请求被阻塞，以保证节点应用了所有到发起check那个时间点的所有更新。一旦check结束，操作开始在节点上执行</p>
<p>操作类型：</p>
<p style="padding-left: 30px;">0 - 禁用等待同步（默认）<br />1 - READ， 即SELECT、BEGIN/START TRANSACTION / SHOW。等价于wsrep_causal_read=1<br />2 - UPDATE and DELETE<br />3 - READ, UPDATE and DELETE;<br />4 - INSERT and REPLACE;<br />5 - READ, INSERT and REPLACE;<br />6 - UPDATE, DELETE, INSERT and REPLACE;<br />7 - READ, UPDATE, DELETE, INSERT and REPLACE; <br />8 - SHOW <br />9 - READ and SHOW<br />10 - UPDATE, DELETE and SHOW<br />11 - READ, UPDATE, DELETE and SHOW<br />12 - INSERT, REPLACE and SHOW<br />13 - READ, INSERT, REPLACE and SHOW<br />14 - UPDATE, DELETE, INSERT, REPLACE and SHOW <br />15 - READ, UPDATE, DELETE, INSERT, REPLACE and SHOW</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_trx_fragment_size </td>
<td>
<p>对于流式复制（streaming replication），事务分片（ transaction fragments）的大小</p>
<p>单位由wsrep_trx_fragment_unit指定</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_trx_fragment_unit)</td>
<td>
<p>事务分片的单位：</p>
<p style="padding-left: 30px;">bytes  事务的binlog事件缓冲的字节数<br />rows  事务影响的行数<br />statements  多语句事务中执行的SQL数量</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">安装配置</span></div>
<div class="blog_h2"><span class="graybg">普通安装</span></div>
<p>为了安装MariaDB Galera Cluster，需要以下包：</p>
<ol>
<li>支持Galera的MariDB</li>
<li>Galera wsrep提供者库</li>
</ol>
<p>为了支持基于SST的备份，可能需要额外的包。</p>
<div class="blog_h3"><span class="graybg">安装MariaDB</span></div>
<p>添加源配置：</p>
<pre class="crayon-plain-tag"># MariaDB 10.5 CentOS repository list - created 2021-01-15 07:44 UTC
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.5/centos8-amd64
module_hotfixes=1
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1</pre>
<p>执行安装：</p>
<pre class="crayon-plain-tag">dnf install MariaDB-server
systemctl start mariadb</pre>
<div class="blog_h3"><span class="graybg">安装Galera wsrep提供者</span></div>
<pre class="crayon-plain-tag">dnf install galera4 </pre>
<div class="blog_h3"><span class="graybg">启动新集群 </span></div>
<p>集群的第一个节点需要以下面的命令行选项来自举（bootstrap）：</p>
<pre class="crayon-plain-tag">mysqld --wsrep-new-cluster</pre>
<p>这个选项告诉MariaDB，不需要连接到既有集群。对于一个现有的节点，以该选项重启，会导致创建新的识别集群身份的UUID，并且它不会再连接到老集群。</p>
<p>对于使用Systemd的系统，应该使用下面的命令创建新集群：</p>
<pre class="crayon-plain-tag">galera_new_cluster</pre>
<div class="blog_h3"><span class="graybg">添加新节点 </span></div>
<p>要添加新节点，需要通过wsrep_cluster_address选项指定集群地址，如果第一个节点的地址是192.168.0.1，则可以用下面的配置加入新节点：</p>
<pre class="crayon-plain-tag">[mariadb]
; 支持IP地址或DNS名称
wsrep_cluster_address=gcomm://192.168.0.1</pre>
<p>使用上述配置，新节点将会连接到一个既有集群节点，并随后发现所有的节点。<span style="background-color: #c0c0c0;">最好在此配置中，指定所有集群成员的地址</span>，免得因为单点的故障 导致无法加入集群。</p>
<p>当所有成员加入到集群，状态不一致的集群可能通过IST/SST来达到一致性。</p>
<div class="blog_h3"><span class="graybg">重启集群</span></div>
<p>当集群所有节点关闭后，需要依次重启节点。</p>
<p>如果第一个节点以常规方式启动， 则它会尝试连接到wsrep_cluster_address中的其它节点，这必然失败。因此，集群完全关闭之后，需要对第一个节点进行bootstrap操作。</p>
<p><span style="background-color: #c0c0c0;">第一个节点，必须是数据最新的节点</span>。如果节点检测到它可能<span style="background-color: #c0c0c0;">不是数据最新（如果它不是最后一个关闭的，或者它是崩掉的）</span>的节点，它会拒绝bootstrap。</p>
<p>查看所有节点的grastate.dat文件，其中seqno字段的值最大的，就是数据最新的节点。如果节点是崩掉的且seqno=-1，则可以使用下面的命令恢复seqno：</p>
<pre class="crayon-plain-tag">mysqld --wsrep_recover</pre>
<p>确定数据最新节点后，可以修改它的数据目录下的grastate.dat文件，设置：</p>
<pre class="crayon-plain-tag">safe_to_bootstrap=1</pre>
<p>使用Systemd的情况下，节点的seqno可以通过命令：<pre class="crayon-plain-tag">galera_recovery</pre>恢复。 galera_recovery脚本调用mysqld时，错误日志会写入到/tmp/wsrep_recovery.XXXXXX。错误日志会有类似下面的内容：</p>
<pre class="crayon-plain-tag">#                                 集群group id                         seqno
[Note] WSREP: Recovered position: 7bff636d-50c7-11eb-81c8-6f8dd75e7fb4:1202</pre>
<p>启用Galera时，MariaDB的Systemd服务会在启动数据库之前自动运行galera_recovery。</p>
<div class="blog_h3"><span class="graybg">状态监控</span></div>
<p>和Galera相关的状态变量，都放在wsrep_前缀下：</p>
<pre class="crayon-plain-tag">SHOW GLOBAL STATUS LIKE 'wsrep_%';</pre>
<div class="blog_h2"><span class="graybg">容器化 </span></div>
<p>参考<a href="#k8s">在K8S中运行</a>一节。</p>
<div class="blog_h2"><span class="graybg">主要配置项说明</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">配置</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2"><strong><em>基础配置</em></strong></td>
</tr>
<tr>
<td class=" blog_h3">wsrep_provider</td>
<td>wsrep提供者库的位置</td>
</tr>
<tr>
<td class="blog_h3">wsrep_cluster_name</td>
<td>Galera集群名称</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cluster_address</td>
<td>
<p>集群地址配置</p>
<p>格式：&lt;schema&gt;://&lt;cluster_address&gt;[?option1=value1[&amp;option2=value2]]</p>
</td>
</tr>
<tr>
<td class=" blog_h3">binlog_format</td>
<td>二进制日志格式，默认ROW</td>
</tr>
<tr>
<td class=" blog_h3">default_storage_engine</td>
<td>要设置为InnoDB</td>
</tr>
<tr>
<td class=" blog_h3">innodb_autoinc_lock_mode</td>
<td>生成AUTO_INCREMENT值时的锁模式，要设置为2（interleaved lock mode）</td>
</tr>
<tr>
<td class=" blog_h3">innodb_doublewrite</td>
<td>
<p>保持默认值1，也就是说InnoDB在写入数据文件之前，首先将数据存储到一个双重写入缓冲（InnoDB Doublewrite Buffer），以提升容错能力</p>
<p>双重写入缓冲用于从不完全写入的page中恢复，如果InnoDB在将页写入磁盘时断电，会出现不完全写入的问题</p>
</td>
</tr>
<tr>
<td class=" blog_h3">query_cache_size</td>
<td>对于5.5.40-  10.0.14- 以及10.1.2，需要设置为0</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_on</td>
<td>设置为ON，启用wsrep复制，10.1.2+</td>
</tr>
<tr>
<td colspan="2"><em><strong>性能配置</strong></em></td>
</tr>
<tr>
<td class="blog_h3">innodb_flush_log_at_trx_commit</td>
<td>可以设置为0，尽管在标准的MariaDB中设置为0会有丢数据的风险，但是在Galera中要安全，因为<span style="background-color: #c0c0c0;">数据的不一致性总是可以从另外一个节点恢复</span></td>
</tr>
<tr>
<td colspan="2"><strong><em>复制行为控制</em></strong></td>
</tr>
<tr>
<td class="blog_h3">log_slave_updates</td>
<td>
<p>设置为ON，则节点将接收到的writeset写入到binlog，默认不写入</p>
<p>如果你希望节点作为传统MariaDB Replication的Master，开启此选项</p>
</td>
</tr>
<tr>
<td class="blog_h3">binlog_do_db</td>
<td>影响到此选项列出的数据库的DML操作，被写入binlog</td>
</tr>
<tr>
<td class="blog_h3">binlog_ignore_db</td>
<td>影响到此选项列出的数据库的DML操作，不写入binlog</td>
</tr>
<tr>
<td class="blog_h3">replicate_wild_do_table</td>
<td>只有该选项匹配的表的DML才被应用到此slave</td>
</tr>
<tr>
<td class="blog_h3">replicate_wild_ignore_table</td>
<td>该选项匹配的表的DML被当前slave丢弃</td>
</tr>
<tr>
<td class="blog_h3">wsrep_sst_auth</td>
<td>SST的 username:password配置，mysqldump SST需要</td>
</tr>
<tr>
<td colspan="2"><strong><em>端口配置</em></strong></td>
</tr>
<tr>
<td class="blog_h3">wsrep_node_address</td>
<td>wsrep复制使用的地址端口，默认0.0.0.04567</td>
</tr>
<tr>
<td class="blog_h3">ist.recv_addr</td>
<td>IST端口，默认4568</td>
</tr>
<tr>
<td class="blog_h3">wsrep_sst_receive_address</td>
<td>SST端口，默认4444。对于任何除mysqldump之外的状态快照转移方式，都使用此端口</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">状态转移</span></div>
<p>当<span style="background-color: #c0c0c0;">新加入一个节点</span>到Galera集群时，此节点需要从集群节点获取数据，这个复制过程被称作状态转移（State Transfer），Galera支持两种状态传输方式：</p>
<ol>
<li>State Snapshot Transfers (SST) 传输一个节点的完整状态（即所有数据），全量复制</li>
<li>Incremental State Transfers (IST) 只传输缺失的数据，增量复制</li>
</ol>
<div class="blog_h1"><span class="graybg">IST</span></div>
<p>使用 IST 的方式时，集群会判断加入集群的节点所缺失的数据，而不必传输完整的数据集。使用 IST 必须满足特定的条件：</p>
<ol>
<li>加入节点的 state UUID 必须和此集群的相同</li>
<li>加入节点所缺失的数据都在 donor 节点的 writeset cache 中存在</li>
</ol>
<p>如果上述条件满足，<span style="background-color: #c0c0c0;">供给节点会发送加入节点上缺失的事务，按照顺序replay它们，直到加入节点赶上集群进度</span>。</p>
<p><span style="background-color: #c0c0c0;">IST不会阻塞供给节点</span></p>
<p>使用 IST 最重要的参数是 donor 节点的 gcache.size 大小，这个参数表示分配多少空间用于缓存 writeset。缓存的空间越多，能够使用 IST 的几率就越大</p>
<div class="blog_h1"><span class="graybg">SST</span></div>
<p>所谓状态快照转移（SST），就是指集群通过将某个up-to-date节点的数据集，完整的拷贝到另外一个节点的过程/操作。</p>
<div class="blog_h2"><span class="graybg">SST类型</span></div>
<p>从概念上来看，存在两种状态转移方式。</p>
<div class="blog_h3"><span class="graybg">逻辑</span></div>
<p>即mysqldump SST，该SST方法，实际上是调用mysqldump，从源节点获取数据库的逻辑dump文件。</p>
<p>要使用mysqldump SST，新加入的节点必须完全初始化，并且准备好接受连接。</p>
<p>该SST方法会阻塞源节点，在状态转移期间，无法对源节点进行修改。该SST方法是最慢的一种，在高负载集群中可能导致问题。</p>
<div class="blog_h3"><span class="graybg">物理</span></div>
<p>这类SST方法将数据文件从源节点拷贝到新加入的节点。节点需要在状态转移之后再完成初始化。mariabackup SST等属于这一类别。</p>
<p>这类方法比mysqldump SST快很多，但是具有一些限制条件：</p>
<ol>
<li>只能在服务器启动时使用</li>
<li>新节点必须和源节点配置很相似，例如 innodb_file_per_table取值必须相同</li>
</ol>
<p>这类方法中的某些，例如mariabackup SST，是非阻塞的，也就是说，在状态转移期间源节点可以继续处理请求。</p>
<div class="blog_h2"><span class="graybg">选择SST方法</span></div>
<p>通过设置全局系统变量，可以修改使用的SST方法：</p>
<pre class="crayon-plain-tag">SET GLOBAL wsrep_sst_method='mariabackup';</pre>
<p>SST方法也可以在配置文件中修改：</p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_method = mariabackup</pre>
<p>注意：<span style="background-color: #c0c0c0;">源节点和新节点必须使用相同的SST方法</span>。 建议所有节点使用一致的SST方法。</p>
<div class="blog_h2"><span class="graybg"><a id="sst-methods"></a>内置SST方法</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">SST方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">mariabackup</td>
<td>
<p>利用工具mariabackup执行SST，这是两种非阻塞的SST方法之一</p>
<p>如果希望在SST期间，能够在源节点上执行查询，推荐使用该方法</p>
<p>该方法需要在节点上安装socat</p>
</td>
</tr>
<tr>
<td class="blog_h3">rsync</td>
<td>
<p>默认SST方法，使用rsync来创建源节点的数据快照。在执行SST期间，源节点使用使用<span style="background-color: #c0c0c0;">读锁锁定</span></p>
<p>这是<span style="background-color: #c0c0c0;">速度最快的SST方法，特别是对于大数据集</span>（因为仅仅拷贝文件）</p>
<p>如果不希望SST期间，源节点上可以执行查询，推荐使用该方法</p>
</td>
</tr>
<tr>
<td class="blog_h3">mysqldump</td>
<td>在源节点上执行mysqldump，然后通过管道发送给连接新节点的mysql客户端</td>
</tr>
<tr>
<td class="blog_h3">xtrabackup-v2</td>
<td>
<p>使用Percona XtraBackup 执行SST，这是两种非阻塞的SST方法之一</p>
<p>需要安装额外的软件</p>
</td>
</tr>
<tr>
<td class="blog_h3">xtrabackup</td>
<td>已经被xtrabackup-v2代替</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">身份验证</span></div>
<p>除了rsync SST之外，都需要配置基于用户名:密码的身份验证：</p>
<pre class="crayon-plain-tag">SET GLOBAL wsrep_sst_auth = 'mariabackup:password';</pre>
<p>或者修改配置文件：</p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_auth = mariabackup:password</pre>
<p>某些身份认证插件，例如unix_socket或gssapi，不需要密码，这种情况下只需要指定用户名： </p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_auth = mariabackup:</pre>
<div class="blog_h2"><span class="graybg">最小集群规模</span></div>
<p>为了防止脑裂的出现，推荐最小集群规模为3。</p>
<p>另外一个需要3节点的原因是，当SST阻塞了源节点时，仍然可以有一个节点能响应查询。</p>
<div class="blog_h2"><span class="graybg">SST失败</span></div>
<p>如果状态转移失败，新节点一般是不可用的。</p>
<p>如果使用mysqldump SST，可能还需要手工恢复某些MariaDB管理表。</p>
<div class="blog_h2"><span class="graybg">mariabackup SST</span></div>
<p>本节介绍如何使用这种SST方法。</p>
<div class="blog_h3"><span class="graybg">设置SST方法</span></div>
<p>修改配置文件：</p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_method = mariabackup </pre>
<p>或者通过命令行设置：</p>
<pre class="crayon-plain-tag">SET GLOBAL wsrep_sst_method='mariabackup';</pre>
<div class="blog_h3"><span class="graybg">配置身份验证</span></div>
<p>该SST方法需要在所有供给（donor）节点上进行<span style="background-color: #c0c0c0;">本地身份验证</span>。</p>
<p>首先需要创建专用的数据库账号： </p>
<pre class="crayon-plain-tag">CREATE USER 'mariabackup'@'localhost' IDENTIFIED BY 'mypassword';
GRANT RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'mariabackup'@'localhost';</pre>
<p>然后修改配置文件或执行命令行设置：</p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_auth = mariabackup:mypassword</pre><br />
<pre class="crayon-plain-tag">SET GLOBAL wsrep_sst_auth = 'mariabackup:mypassword';</pre>
<div class="blog_h3"><span class="graybg">免密码认证</span></div>
<p>可以使用unix_socket身份认证插件，这样就不需要指定密码： </p>
<pre class="crayon-plain-tag">CREATE USER 'mysql'@'localhost' IDENTIFIED VIA unix_socket;
GRANT RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'mysql'@'localhost';</pre>
<div class="blog_h3"><span class="graybg">选择供给节点 </span></div>
<p>当mariadbbackup在供给（donor）节点创建备份时，它需要在备份最后创建一个全局的锁：</p>
<ol>
<li>在10.3-，锁通过  <pre class="crayon-plain-tag">FLUSH TABLES WITH READ LOCK</pre>实现</li>
<li>在10.4+，锁通过 <pre class="crayon-plain-tag">BACKUP STAGE BLOCK_COMMIT</pre>实现</li>
</ol>
<p>如果集群中某个节点正作为“主”节点来使用 —— 也就是说应用程序连接到它来写入数据，那么该节点不应该用作SST源，因为全局锁会干扰应用程序的写操作。</p>
<p>可以通过下面的系统变量，来设置偏好使用哪些节点作为源：</p>
<pre class="crayon-plain-tag">[mariadb]
; 如果集群由 node1, node2, node3, node4, node5构成。node1作为主使用
; 那么node2可以这样配置：
wsrep_sst_donor=node3,node4,node5,
; 结尾的逗号表示，如果偏好的节点不可用，允许选择任何节点作为源</pre>
<div class="blog_h3"><span class="graybg">配置日志</span></div>
<p>mariabackup SST具有独立的日志，不和数据库日志在一起：</p>
<pre class="crayon-plain-tag">[sst]
sst-log-archive=1
sst-log-archive-dir=/var/log/mysql/sst/
; 记录到syslog中
sst-syslog=1</pre>
<div class="blog_h1"><span class="graybg">负载均衡</span></div>
<p><a href="https://fromdual.com/galera-load-balancer-documentation">Galera Load Balancer</a>（GLB）是一个简单的、特地为Galera设计负载均衡器。该LB支持多种负载均衡策略。</p>
<p>节点可以被<span style="background-color: #c0c0c0;">设置不同的权重，优先使用高权重的节点</span>，根据选择的策略，<span style="background-color: #c0c0c0;">低权重节点可能被忽略</span>（除非高权重节点挂掉）。</p>
<p>一个轻量级的守护进程glbd负责接收客户端连接，并执行LB算法，重定向客户端请求到适当的节点。</p>
<div class="blog_h2"><span class="graybg">特性</span></div>
<ol>
<li>可以在运行时配置后端节点列表</li>
<li>支持drain节点，也就是不分配新的连接给它，但是不会kill服务器 —— 让它优雅关闭</li>
<li>使用epool提升性能</li>
<li>支持多线程，可以利用多核心CPU</li>
<li>支持可选的watchdog来监控后端并调整路由表</li>
</ol>
<div class="blog_h2"><span class="graybg">策略</span></div>
<p>GLB支持以下负载均衡策略：</p>
<ol>
<li>least connected： 最少连接，新连接并分发给连接最少的节点，需要考虑节点权重</li>
<li>round-robin：依次循环调度给每个节点</li>
<li>single：所有连接调度给最高权重的节点，除非此节点挂了，或者引入一个新的、更高权重的节点</li>
<li>random：随机调度</li>
<li>source tracking：来自同一源地址的连接，总是被调度给同一节点</li>
</ol>
<div class="blog_h1"><span class="graybg">仲裁节点</span></div>
<p>推荐的部署模式是3节点以上，奇数节点。</p>
<p>如果部署第3个节点（例如只有两个数据中心）的成本过高，可以考虑引入仲裁节点（Galera Arbitrator）以防止脑裂。仲裁节点：</p>
<ol>
<li>参与投票，来决定Primary Component。当两节点集群出现网络分区时，能够看到仲裁节点的那个构成Primary Component</li>
<li>不参与复制。<span style="background-color: #c0c0c0;">尽管仲裁节点不参与复制，但是它会接收所有数据（但是不存储），这意味着你不能将其放置在网络连接差的地方，否则可能导致很差的集群性能</span></li>
</ol>
<p><span style="background-color: #c0c0c0;">仲裁节点还有另外一个用途——用于备份</span>，它可以请求数据库状态的一致性快照。</p>
<div class="blog_h2"><span class="graybg">启动仲裁节点</span></div>
<p>由独立的守护进程garbd来运行仲裁节点：</p>
<pre class="crayon-plain-tag">garbd --group=example_cluster \
     --address="gcomm://192.168.1.1,192.168.1.2,192.168.1.3" \
     --option="socket.ssl_key=/etc/ssl/galera/server-key.pem;socket.ssl_cert=/etc/ssl/galera/server-cert.pem;socket.ssl_ca=/etc/ssl/galera/ca-cert.pem;socket.ssl_cipher=AES128-SHA256""</pre>
<p>配置信息也可以放在独立文件中：</p>
<pre class="crayon-plain-tag">group = example_cluster
address = gcomm://192.168.1.1,192.168.1.2,192.168.1.3</pre><br />
<pre class="crayon-plain-tag">garbd --cfg /path/to/arbitrator.config</pre>
<div class="blog_h1"><span class="graybg">备份和恢复</span></div>
<p>由于Galera集群的复制特性，执行备份时我们只需要在一个节点上进行。而在恢复时，也仅仅需要在一个节点上执行，剩下节点的同步，可以依靠状态转移自动完成。</p>
<div class="blog_h2"><span class="graybg">备份节点</span></div>
<p>任何时候，都可以针对某个Galera节点，使用常规的MariaDB备份工具进行备份。</p>
<p>上文中的<a href="sst-methods">内置SST方法</a>，对应了各种备份工具，混合使用多种备份方法，能够提高额外的数据安全性。这里挑选几个工具说明用法：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 120px; text-align: center;">备份工具</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>mysqldump</td>
<td>
<p>逻辑备份工具，也就是说它是面向SQL的，适合在不同版本的数据库之间进行数据迁移</p>
<p>备份时可以使用<pre class="crayon-plain-tag">--single-transaction</pre>选项，这样可以得到InnoDB表的一致性备份</p>
<p>mysqldump实际上使用SELECT * FROM table的方式来加载数据，这样会有个问题，数据量大的时候，会导致InnoDB缓冲池反复刷新，导致节点性能严重下降，进而导致整个Galera集群性能下降 —— Galera集群<span style="background-color: #c0c0c0;">性能取决于最慢的那个节点</span>。如果设置<pre class="crayon-plain-tag">wsrep_desync=OFF</pre>，让备份中的节点临时的不再同步，则集群其他节点的性能不会受到影响</p>
<pre class="crayon-plain-tag"># 备份所有数据库
mysqldump -u root -p pswd –all-databases &gt; data.sql
# 仅仅备份gmem数据库的表结构
mysqldump -u root -p pswd –no-data gmem &gt; schema.sql


# 恢复数据库，可能需要先清空
mysql&gt; drop database gmem;

mysql -u root -p pswd &lt;  schema.sql</pre>
</td>
</tr>
<tr>
<td>xtrabackup</td>
<td>
<p>物理备份工具，针对数据块，支持全量备份、增量备份。该工具来自Percona，包含一个C语言编写的xtrabackup、Perl语言编写的innobackupex，支持备份InnoDB、XtraDB、MyIASM等引擎的表
<p>在备份期间，<span style="background-color: #c0c0c0;">xtrabackup不会锁定（热备份）数据库。对于100G+的大数据库，它的恢复性能要比mysqldump好的多</span></p>
<p>基于xtrabackup的备份/恢复流程包括三个步骤：备份、准备、恢复</p>
<p>使用--backup子命令，可以进行备份。</p>
<pre class="crayon-plain-tag">#                   数据目录                  备份存放目录
xtrabackup --backup --datadir=/var/lib/mysql/ --target-dir=/data/backups/mysql/</pre>
<p>命令将会启动一个文件拷贝线程，拷贝ibdata文件，一个日志扫描线程，复制事务日志：</p>
<pre class="crayon-plain-tag">xtrabackup: Transaction log of lsn (&lt;SLN&gt;) to (&lt;LSN&gt;) was copied.</pre>
<p>备份完毕后，备份存放目录下，会出现类似下面的文件：</p>
<pre class="crayon-plain-tag"># 假设使用innodb_file_per_table选项
/data/backups/mysql/ibdata1
/data/backups/mysql/test
/data/backups/mysql/test/tbl1.ibd
/data/backups/mysql/xtrabackup_checkpoints
/data/backups/mysql/xtrabackup_logfile</pre>
<p>--backup子命令得到的备份，需要进行--prepare操作后，才能实现针对某个特定时间点的、数据文件的一致性。这是由于，不同数据文件是在不同时间点拷贝的，在这个拷贝过程中，先前的数据文件可能又存在新的修改。使用这些数据文件去启动数据库，InnoDB可能会检测到数据损坏并拒绝启动</p>
<p>--prepare子命令调用方式如下：</p>
<pre class="crayon-plain-tag">xtrabackup --prepare --target-dir=/data/backups/mysql/

# 结束时会打印
101107 16:40:15  InnoDB: Shutdown completed; log sequence number &lt;LSN&gt;</pre>
<p>此时的备份是满足数据一致性，可以用于恢复。直接拷贝数据文件即可：</p>
<pre class="crayon-plain-tag">rsync -avrP /data/backup/ /var/lib/mysql/
chown -R mysql:mysql /var/lib/mysql</pre>
<p>注意：xtrabackup仅仅备份InnoDB数据文件，你需要额外备份MySQL系统数据库、MyISAM数据、表定义文件（.frm）等。或者使用innobackupex让这一切都自动化：</p>
<pre class="crayon-plain-tag"># 备份      数据库帐号密码                      备份根目录            不创建基于时间戳的子目录
innobackupex --user=DBUSER --password=DBUSERPASS /path/to/BACKUP-DIR/ --no-timestamp
#            指定MySQL配置文件
innobackupex --defaults-file=/tmp/other-my.cnf --user=DBUSER --password=DBUSERPASS /path/to/BACKUP-DIR/

# 准备                   允许使用多少内存
innobackupex --apply-log --use-memory=100M  /path/to/BACKUP-DIR

# 恢复
innobackupex --copy-back /path/to/BACKUP-DIR</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">备份集群</span></div>
<p><span style="background-color: #ffffff;">由于</span><span style="background-color: #c0c0c0;">所有Galera节点具有相同的数据</span>，因此你可以使用常规的MariaDB备份工具进行备份，但是这种方式存在缺点 —— 缺少全局事务ID信息（GTID）。使用这种备份来恢复，不能让节点进入良好定义的状态。此外，某些备份工具会阻塞节点，这也需要注意。
<p>因此，理想的备份方式是发起一次SST：</p>
<ol>
<li>节点会在一个良好定义的点发起备份</li>
<li>节点会将GTID关联到备份</li>
<li>备份期间，节点从集群desync，避免影响性能 —— 备份过程可能阻塞节点</li>
<li>集群知道节点正在进行备份，因此不会将其选为donor</li>
</ol>
<p>我们需要利用仲裁节点来发起备份：</p>
<pre class="crayon-plain-tag"># garbd可以以函数方式执行，调用一个命令
#                                   如果在集群节点上发起，则4567已被占用，另外选择一个端口
garbd --address gcomm://192.168.1.2?gmcast.listen_addr=tcp://0.0.0.0:4444 \
#   机器名称               供给节点名称           在供给节点上执行的脚本的后缀
  --group example_cluster --donor galera-3 --sst backup_mysqldump</pre>
<p>你也可以通过配置文件指定各种选项：</p>
<pre class="crayon-plain-tag">; 填写集群的wsrep_cluster_name
group='example_cluster'
; 填写集群的wsrep_cluster_address
address="gcomm://172.31.30.39:4567,172.31.18.53:4567,172.31.26.106:4567"
; 填写集群的wsrep_provider_options。listen_addr添加garbd进程的监听端口
options="gmcast.listen_addr=tcp://0.0.0.0:4444"
; 指定哪个节点执行备份
donor="galera-3"
; 日志文件路径
log='/var/log/garbd.log'
; 备份节点上的备份脚本的后缀，对于下面的后缀，脚本名是wsrep_sst_backup_mysqldump
sst='backup_mysqldump'</pre>
<p>下面是一个脚本的示例，它使用了bash + mysqldump，但是任何脚本语言、备份工具都是可以的： </p>
<pre class="crayon-plain-tag">#!/bin/bash

# SET VARIABLES
db_user='admin_backup'
db_passwd='Rover123!'

backup_dir='/backup'
backup_sub_dir='temp'

today=`date +"%Y%m%d"`
# Dump文件路径
backup_today="galera-mysqldump-$today.sql"
# GTID文件路径
gtid_file="gtid-$today.dat"


# 加载此脚本，它提供GTID变量
. /usr/bin/wsrep_sst_common


# 备份配置文件
cp /etc/my.cnf $backup_dir/$backup_sub_dir/
cp /etc/garb.cnf $backup_dir/$backup_sub_dir/
# 备份GTID
echo "GTID: ${WSREP_SST_OPT_GTID}" &gt; $backup_dir/$backup_sub_dir/$gtid_file


# 进行数据库Dump
mysqldump --user="$db_user" --password="$db_passwd" \
          --flush-logs --all-databases \
          &gt; $backup_dir/$backup_sub_dir/$backup_today

# 打包
cd $backup_sub_dir
tar -czf $backup_dir/$backup_today.tgz * --transform "s,^,$backup_today/," </pre>
<div class="blog_h2"><span class="graybg">恢复集群</span></div>
<p>将备份恢复到一个新集群，或者既有集群，可以使用相同的步骤：</p>
<ol>
<li>选择一个节点，作为恢复节点</li>
<li>停止其它节点</li>
<li>关闭恢复节点的Galera Replication，进入Standalone模式：<br />
<pre class="crayon-plain-tag">SET GLOBAL wsrep_provider = 'none';

SHOW STATUS LIKE 'wsrep_connected';

+-----------------+-------+
 
| Variable_name   | Value |
 
+-----------------+-------+
 
| wsrep_connected | OFF   |
 
+-----------------+-------+</pre>
</li>
<li>调用备份工具，执行恢复</li>
<li>在恢复节点上重新开启Galera Replication</li>
<li>启动被停止的节点重新加入集群，并通过SST重新获得同步</li>
</ol>
<div class="blog_h1"><span class="graybg">wsrep提供者</span></div>
<div class="blog_h2"><span class="graybg">GCache</span></div>
<p>用于存放writeset的特殊缓存，叫作writeset cache或GCache。GCache支持三种存储：</p>
<ol>
<li>Permanent In-Memory Store：直接使用操作系统的内存，适合有很多空余内存的系统，默认情况是禁用的</li>
<li>Permanent Ring-Buffer File：在缓存初始化时就预先分配的一块磁盘空间，默认情况下，它的大小是 128Mb</li>
<li>On-Demand Page Store：根据需要在运行时动态分配内存映射页文件，默认情况下是 128Mb，但如果需要更大的 write-set，它可以动态的增长。当 page file 不再使用时，Galera Cluster 会删除这些文件，可以对 page file 的总大小进行限制</li>
</ol>
<p>Galera Cluster 的缓存分配算法会按上面的顺序来分配缓存空间，gcache.dir 参数可以指定缓存保存在磁盘的位置。</p>
<div class="blog_h2"><span class="graybg">复制流控</span></div>
<p>Galera允许一个节点根据需要来暂停/恢复复制，这叫作Flow Control。FC可以节点在apply事务方面，过于落后其它节点。</p>
<p>我们知道，所有节点收到writeset并进行认证之后，原始的接收请求的节点就可以在本地完成提交并应答客户端了。此时，其它节点不一定也apply/commit了事务（取决于配置）。尚未apply/commit的事务（writeset）会存放在接收队列中。</p>
<p>如果某个节点速度缓慢，那么它的接收队列会不断积压，到达一定程度之后，就会触发FC：</p>
<ol>
<li>暂停复制</li>
<li>处理接收队列的积压</li>
<li>当队列到达可管理的大小之后，再恢复复制</li>
</ol>
<div class="blog_h2"><span class="graybg">隔离级别</span></div>
<p>在Galera中，需要去分单节点内的、集群范围的事务隔离级别。单节点内的事务隔离级别不必多提，参考MariaDB提供的配置项即可。</p>
<p>集群范围的事务隔离，受到复制协议的影响，不同节点上发起的事务，可能不会完全等同的隔离。</p>
<div class="blog_h2"><span class="graybg">Component</span></div>
<p>集群中能互相保持心跳通信的节点的集合叫作Component，两个Component之间存在网络分区，无法相互通信。理想情况下，集群只有一个Component。</p>
<div class="blog_h3"><span class="graybg">Primary Component</span></div>
<p>大部分节点组成的Component叫作Primary Component —— <span style="background-color: #c0c0c0;">PC必须满足大多数原则，在三节点集群中，至少两个节点才能组成PC</span>。</p>
<p><span style="background-color: #c0c0c0;">只有位于PC中的节点，才能继续修改数据库状态</span>。</p>
<p>Galera Cluster 会周期性的检查每个节点的连接是否正常， evs.inactive_check_peroid 参数可以设置检查的周期。如果一个节点的失效时间超过了 evs.suspect_timeout 的值，那么节点将被标记为 suspect。如果 Primary Component 中的所有节点都将某个节点标记为 suspect ，那么此节点被移出集群。</p>
<p>如果一个节点的失效时间超过了 evs.inactive_timeout 的值，此节点将直接被移出集群而不需要协商。故障的节点将不可读不可写。</p>
<div class="blog_h3"><span class="graybg">节点权重</span></div>
<p>在计算是否构成Primary Component / Quorum时，节点是可以被加权的。节点的权重通过pc.weight配置。例如下面的配置：</p>
<pre class="crayon-plain-tag">node1: pc.weight = 2
node2: pc.weight = 1
node3: pc.weight = 0</pre>
<p>即使同时杀掉node2和node3，node1仍然构成PC </p>
<div class="blog_h2"><span class="graybg">提供者选项</span></div>
<p>提供者选项在系统变量 wsrep_provider_options 中配置，选项之间用;隔开。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class=" blog_h3">base_dir</td>
<td>数据目录</td>
</tr>
<tr>
<td class=" blog_h3">base_host</td>
<td>默认127.0.0.1。内部使用，不要设置</td>
</tr>
<tr>
<td class=" blog_h3">base_port</td>
<td>默认4567。内部使用，不要设置</td>
</tr>
<tr>
<td class=" blog_h3">cert.log_conflicts</td>
<td>默认no。是否记录认证失败的详细信息</td>
</tr>
<tr>
<td class=" blog_h3">cert.optimistic_pa</td>
<td>
<p>默认yes。控制在slave上并行apply的行为</p>
<p>如果设置，允许认证算法（ certification algorithm）所确定（determined）的全范围并行化（full range of parallelization ）</p>
<p>如果不设置，并行apply窗口（parallel applying window）不会超过在master上看到的窗口。并且，一旦它所看到的master上操作被commit之后，立即开始apply</p>
</td>
</tr>
<tr>
<td class=" blog_h3">debug</td>
<td>默认no。是否启用调试</td>
</tr>
<tr>
<td class=" blog_h3">evs.auto_evict</td>
<td>
<p><span style="background-color: #c0c0c0;">触发自动驱除</span>协议（Auto Eviction protocol）之前，允许一个<span style="background-color: #c0c0c0;">落后（delayed）节点的entry的数量</span></p>
<p>每当接收到某个节点的一个delayed response，就在delayed list中添加一个entry</p>
<p>默认0，表示禁用</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.debug_log_mask</td>
<td>
<p>开启EVS调试日志，要求wsrep_debug=on</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.delay_margin</td>
<td>
<p>在此节点<span style="background-color: #c0c0c0;">加入一个entry到delayed list之前</span>，<span style="background-color: #c0c0c0;">响应时间的延迟</span>可达到多少。必须设置的比节点之间的RTT大</p>
<p>其实就是，evs.delay_margin决定了多慢的情况下进行一次计数，evs.auto_evict决定了计数多少次后发起驱除</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.delayed_keep_period</td>
<td>如果一个节点被当前节点加入自己的delayed list，那么该节点要持续多长时间不再缓慢，才能从延迟列表移除</td>
</tr>
<tr>
<td class=" blog_h3">evs.evict</td>
<td>
<p>如果设置为当前节点的gcomm UUID，则当前节点从集群移除</p>
<p>如果设置为空，则当前节点的eviction list被清空</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.inactive_check_period</td>
<td>检测不活动成员（peer）（即响应延迟的节点）的周期。超过此周期之后，节点才可能被加入到delayed list，并在之后进行驱除</td>
</tr>
<tr>
<td class=" blog_h3">evs.inactive_timeout</td>
<td>节点被断定为挂掉之前，inactive的最大持续时间</td>
</tr>
<tr>
<td class=" blog_h3">evs.info_log_mask</td>
<td>记录额外EVS信息的日志</td>
</tr>
<tr>
<td class=" blog_h3">evs.install_timeout</td>
<td>等待install message确认的超时时间</td>
</tr>
<tr>
<td class=" blog_h3">evs.join_retrans_period</td>
<td>组成集群成员关系时，EVS join消息重传的频率</td>
</tr>
<tr>
<td class=" blog_h3">evs.keepalive_period</td>
<td>没有其它流量时，多就发送一次心跳</td>
</tr>
<tr>
<td class=" blog_h3">evs.max_install_timeouts</td>
<td>默认3。install message允许的超时总次数</td>
</tr>
<tr>
<td class=" blog_h3">evs.send_window</td>
<td>
<p>单次允许复制的packet的数量。必须比evs.user_send_window大</p>
<p>仅仅影响数据包，在WAN环境下可以设置的比默认值（4）大的多，例如512</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.stats_report_period</td>
<td>报告EVS统计信息的周期</td>
</tr>
<tr>
<td class=" blog_h3">evs.suspect_timeout</td>
<td>在此超时之后，节点被怀疑已经挂了。如果所有节点怀疑它挂了，那么在evs.install_timeout之前节点会被移除集群</td>
</tr>
<tr>
<td class=" blog_h3">evs.use_aggregate</td>
<td>默认true。如果为true则在可能的情况下小的packet会被聚合为大的</td>
</tr>
<tr>
<td class=" blog_h3">evs.user_send_window</td>
<td>
<p>单次允许复制的packet的数量。必须比evs.send_window小，推荐 1/2</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.version</td>
<td>EVS协议版本</td>
</tr>
<tr>
<td class=" blog_h3">evs.view_forget_timeout</td>
<td>从view history中移除之前需要经过的时间</td>
</tr>
<tr>
<td class=" blog_h3">gcache.dir</td>
<td>
<p>GCache存放的目录</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gcache.keep_pages_size</td>
<td>缓存页的总数量</td>
</tr>
<tr>
<td class=" blog_h3">gcache.mem_size</td>
<td>GCache缓存使用内存大小</td>
</tr>
<tr>
<td class=" blog_h3">gcache.name</td>
<td>GCache缓存ring buffer文件的名字</td>
</tr>
<tr>
<td class=" blog_h3"><strong>gcache.page_size</strong></td>
<td>GCache缓存页面映射文件的大小</td>
</tr>
<tr>
<td class=" blog_h3">gcache.recover</td>
<td>在节点启动时，是否进行GCache恢复。如果能够恢复GCache，则节点加入集群后可以提供IST —— 这在集群完全重启的时候有用</td>
</tr>
<tr>
<td class=" blog_h3"><strong>gcache.size</strong></td>
<td>缓存ring buffer大小，即用于缓存writeset的空间大小，启动时分配</td>
</tr>
<tr>
<td class=" blog_h3">gcomm.thread_prio</td>
<td>Gcomm线程策略和优先级</td>
</tr>
<tr>
<td class=" blog_h3">gcs.fc_debug</td>
<td>默认0，大于0时会输出SST流控信息</td>
</tr>
<tr>
<td class=" blog_h3">gcs.fc_factor</td>
<td>默认1.0，接收队列中writeset数量跌到gcs.fc_limit的多少比例后，恢复复制</td>
</tr>
<tr>
<td class=" blog_h3">gcs.fc_limit</td>
<td>默认16。接收队列中writeset超过此数量后，复制暂停。在master-slave用法下，可以大大增加</td>
</tr>
<tr>
<td class=" blog_h3">gcs.fc_master_slave</td>
<td>默认no。是否假设集群仅仅有一个master —— 就是所有客户端都连接到一个节点进行写查询</td>
</tr>
<tr>
<td class=" blog_h3">gcs.max_packet_size</td>
<td>最大packet大小，超过此大小则writeset被分片</td>
</tr>
<tr>
<td class=" blog_h3">gcs.max_throttle</td>
<td>
<p>在状态转移时，为了避免内存消耗过大，可以进行复制限流</p>
<p>默认0.25。设置为0则为了完成状态转移，可以暂停复制</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gcs.recv_q_hard_limit</td>
<td>
<p>接收队列大小的硬限制。超过此大小则服务器终止运作</p>
<p>推荐设置为内存的 1/2 + swap大小</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gcs.recv_q_soft_limit</td>
<td>
<p>接收队列软限制。相对于gcs.recv_q_hard_limit的因子，默认0.25</p>
<p>到达软限制后，即开始限流。限流的速度随着队列积压，线性增加</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gcs.sync_donor</td>
<td>
<p>默认no。是否集群其它成员需要和donor保持同步</p>
<p>如果设置为yes，则当donor因为状态转移而阻塞时整个集群被阻塞</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.listen_addr</td>
<td>默认tcp:0.0.0.0:4567。Galera监听来自其它节点的连接的地址</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.mcast_addr</td>
<td>
<p>默认空。如果设置，则使用UDP组播进行复制，所有节点必须设置一致</p>
<p>示例：gmcast.mcast_addr=239.192.0.11</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.mcast_ttl</td>
<td>组播的TTL值</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.peer_timeout</td>
<td>initiating message relaying的连接超时</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.segment</td>
<td>
<p>定义节点属于的段（segment）。默认情况下，所有节点都在段0。通常将同一数据中心的节点放在同一个段</p>
<p>Galera协议数据，仅仅会重定向到同一段中的单个节点，然后由该节点再repay给段中其它节点</p>
<p>可以降低跨数据中心的流量</p>
<p>取值0-255之间</p>
</td>
</tr>
<tr>
<td class=" blog_h3">ist.recv_addr</td>
<td>监听IST的地址</td>
</tr>
<tr>
<td class=" blog_h3">pc.ignore_quorum</td>
<td>
<p>默认false。是否忽略quorum计算</p>
<p>如果设置为true，如果master和其它slaves产生了网络分区，它仍然继续工作。但是会导致master-slave模式下slave不能自动重新连接到master</p>
</td>
</tr>
<tr>
<td class=" blog_h3">pc.ignore_sb</td>
<td>默认false。是否允许脑裂情况下继续处理数据修改，在multi-master模式下会导致数据不一致</td>
</tr>
<tr>
<td class=" blog_h3">pc.recovery</td>
<td>默认true。如果设置为true，则Primary Component状态被存储在磁盘中。当集群完全崩溃后，可以进行自动恢复，后续的集群优雅完全重启会要求从一个新的Primary Component自举</td>
</tr>
<tr>
<td class=" blog_h3">pc.wait_prim</td>
<td>默认true。如果设置为true，节点会在pc.wait_prim_timeout之内等待Primary Component。用于启动non-primary component并使通过pc.bootstrap使之称为primary</td>
</tr>
<tr>
<td class=" blog_h3">pc.wait_prim_timeout</td>
<td>等待Primary Component的超时</td>
</tr>
<tr>
<td class=" blog_h3">pc.weight</td>
<td>节点的权重，用于quorum计算</td>
</tr>
<tr>
<td class=" blog_h3">protonet.backend</td>
<td>传输后端，目前仅仅支持ASIO</td>
</tr>
<tr>
<td class=" blog_h3">repl.causal_read_timeout</td>
<td>causal reads（读取已提交）超时</td>
</tr>
<tr>
<td class=" blog_h3">repl.commit_order</td>
<td>
<p>是否、何时允许乱序commit：</p>
<p style="padding-left: 30px;">0 BYPASS  不监控commit顺序<br />1 OOOC 对于所有事务允许乱序提交<br />2 LOCAL_OOOC 仅仅允许本地事务的乱序提交<br />3 NO_OOOC 不允许乱序提交</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"><a id="k8s"></a>在K8S中运行</span></div>
<div class="blog_h2"><span class="graybg">Orange方案</span></div>
<p>Orange开发了一个Galera Operator，简化K8S中的Galera的部署和运维。支持的特性包括：</p>
<ol>
<li>创建、销毁集群</li>
<li>集群规模括缩容</li>
<li>垂直括缩：修改容器CPU/内存、持久卷大小</li>
<li>节点崩溃后自动故障转移</li>
<li>备份Galera集群到外部S3存储</li>
<li>从外部备份恢复Galera集群</li>
</ol>
<p>&nbsp;</p>
<div class="blog_h2"><span class="graybg">severalnines方案</span></div>
<p>来自 <a href="https://github.com/severalnines/galera-docker-mariadb">severalnines / galera-docker-mariadb</a>。此方案在K8S中运行Maria DB 10.1，支持Galera，需要同时运行一个Etcd集群。</p>
<div class="blog_h3"><span class="graybg">entrypoint.sh</span></div>
<p>逻辑比较乱，主要是通过查询Etcd来获得集群节点列表、各节点的seqno信息：</p>
<pre class="crayon-plain-tag">#!/bin/bash
set -e

# if command starts with an option, prepend mysqld
if [ "${1:0:1}" = '-' ]; then
  CMDARG="$@"
fi

[ -z "$TTL" ] &amp;&amp; TTL=10

if [ -z "$CLUSTER_NAME" ]; then
  echo &gt;&amp;2 'Error:  You need to specify CLUSTER_NAME'
  exit 1
fi

DATADIR="$("mysqld" --verbose --help 2&gt;/dev/null | awk '$1 == "datadir" { print $2; exit }')"
echo &gt;&amp;2 "Content of $DATADIR:"
ls -al $DATADIR

if [ ! -s "$DATADIR/grastate.dat" ]; then
  # 没有grastate.dat文件，意味着集群尚未初始化
  INITIALIZED=1
  if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
    echo &gt;&amp;2 'error: database is uninitialized and password option is not specified '
    echo &gt;&amp;2 '  You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD'
    exit 1
  fi
  mkdir -p "$DATADIR"
  chown -R mysql:mysql "$DATADIR"

  # 初始化MariaDB
  echo 'Running mysql_install_db'
  mysql_install_db --user=mysql --datadir="$DATADIR" --rpm
  echo 'Finished mysql_install_db'

  # 在后台启动MariaDB
  mysqld --user=mysql --datadir="$DATADIR" --skip-networking &amp;
  pid="$!"

  mysql=(mysql --protocol=socket -uroot)

  # 等待MariaDB初始化完毕
  for i in {30..0}; do
    if echo 'SELECT 1' | "${mysql[@]}" &amp;&gt;/dev/null; then
      break
    fi
    echo 'MySQL init process in progress...'
    sleep 1
  done
  if [ "$i" = 0 ]; then
    echo &gt;&amp;2 'MySQL init process failed.'
    exit 1
  fi

  # sed is for https://bugs.mysql.com/bug.php?id=20545
  mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql
  if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
    MYSQL_ROOT_PASSWORD="$(pwmake 128)"
    echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"
  fi

  # 在本地（禁止复制）创建root xtrabackup等用户
  "${mysql[@]}" &lt;&lt;-EOSQL
	  -- What's done in this file shouldn't be replicated
	  --  or products like mysql-fabric won't work
	  SET @@SESSION.SQL_LOG_BIN=0;
	  DELETE FROM mysql.user ;
	  CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
	  GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
	  CREATE USER 'xtrabackup'@'localhost' IDENTIFIED BY '$XTRABACKUP_PASSWORD';
	  GRANT RELOAD,LOCK TABLES,REPLICATION CLIENT ON *.* TO 'xtrabackup'@'localhost';
	  GRANT REPLICATION CLIENT ON *.* TO monitor@'%' IDENTIFIED BY 'monitor';
	  DROP DATABASE IF EXISTS test ;
	  FLUSH PRIVILEGES ;
EOSQL
  if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then
    mysql+=(-p"${MYSQL_ROOT_PASSWORD}")
  fi

  # 创建额外的数据库、用户
  if [ "$MYSQL_DATABASE" ]; then
    echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}"
    mysql+=("$MYSQL_DATABASE")
  fi

  if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
    echo "CREATE USER '"$MYSQL_USER"'@'%' IDENTIFIED BY '"$MYSQL_PASSWORD"' ;" | "${mysql[@]}"

    if [ "$MYSQL_DATABASE" ]; then
      echo "GRANT ALL ON \`"$MYSQL_DATABASE"\`.* TO '"$MYSQL_USER"'@'%' ;" | "${mysql[@]}"
    fi

    echo 'FLUSH PRIVILEGES ;' | "${mysql[@]}"
  fi

  if [ ! -z "$MYSQL_ONETIME_PASSWORD" ]; then
    "${mysql[@]}" &lt;&lt;-EOSQL
		ALTER USER 'root'@'%' PASSWORD EXPIRE;
EOSQL
  fi
  if ! kill -s TERM "$pid" || ! wait "$pid"; then
    echo &gt;&amp;2 'MySQL init process failed.'
    exit 1
  fi

  echo
  echo 'MySQL init process done. Ready for start up.'
  echo
fi
chown -R mysql:mysql "$DATADIR"

function join {
  local IFS="$1"
  shift
  echo "$*"
}


# 将当前MariaDB实例加入到Galera集群，通过访问Etcd获取必要信息
if [ -z "$DISCOVERY_SERVICE" ]; then
  cluster_join=$CLUSTER_JOIN
else
  echo
  echo '&gt;&gt; Registering in the discovery service'

  etcd_hosts=$(echo $DISCOVERY_SERVICE | tr ',' ' ')
  flag=1

  echo
  # 寻找健康Etcd节点
  for i in $etcd_hosts; do
    echo "&gt;&gt; Connecting to https://${i}/health"
    curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key https://${i}/health || continue
    if curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key https://$i/health | jq -e 'contains({ "health": "true"})'; then
      healthy_etcd=$i
      flag=0
      break
    else
      echo &gt;&amp;2 "&gt;&gt; Node $i is unhealty. Proceed to the next node."
    fi
  done

  # 如果没有Etcd节点则放弃
  if [ $flag -ne 0 ]; then
    echo "&gt;&gt; Couldn't reach healthy etcd nodes."
    exit 1
  fi

  echo
  echo "&gt;&gt; Selected healthy etcd: $healthy_etcd"

  if [ ! -z "$healthy_etcd" ]; then
    URL="https://$healthy_etcd/v2/keys/galera/$CLUSTER_NAME"

    set +e
    echo &gt;&amp;2 "&gt;&gt; Waiting for $TTL seconds to read non-expired keys.."
    # 防止刚刚死去的Galera节点的注册信息仍然存在
    sleep $TTL

    # 获取活动Galera节点列表
    echo &gt;&amp;2 "&gt;&gt; Retrieving list of keys for $CLUSTER_NAME"
    addr=$(curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key $URL | jq -r '.node.nodes[]?.key' | awk -F'/' '{print $(NF)}')
    cluster_join=$(join , $addr)

    ipaddr=$(hostname -i | awk {'print $1'})
    [ -z $ipaddr ] &amp;&amp; ipaddr=$(hostname -I | awk {'print $1'})

    echo
    if [ -z $cluster_join ]; then
      # 没有活动Galera节点。集群尚未初始化，或者集群完全宕机后重启（需要bootstrap）
      echo &gt;&amp;2 "&gt;&gt; KV store is empty. This is a the first node to come up."
      echo
      echo &gt;&amp;2 "&gt;&gt; Registering $ipaddr in https://$healthy_etcd"
      # 写入当前节点信息到Etcd
      curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key $URL/$ipaddr/ipaddress -X PUT -d "value=$ipaddr"
    else
      # 存在活动的Galera节点，看看有没有Synced的
      curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key ${URL}?recursive=true\&amp;sorted=true &gt;/tmp/out
      running_nodes=$(cat /tmp/out | jq -r '.node.nodes[].nodes[]? | select(.key | contains ("wsrep_local_state_comment")) | select(.value == "Synced") | .key' | awk -F'/' '{print $(NF-1)}' | tr "\n" ' ' | sed -e 's/[[:space:]]*$//')
      echo
      echo "&gt;&gt; Running nodes: [${running_nodes}]"

      if [ -z "$running_nodes" ]; then
        # 没有Synced节点，必须恢复本节点的seqno，如果本节点最大，则可以自举
        TMP=/var/lib/mysql/$(hostname).err
        echo &gt;&amp;2 "&gt;&gt; There is no node in synced state."
        echo &gt;&amp;2 "&gt;&gt; It's unsafe to bootstrap unless the sequence number is the latest."
        echo &gt;&amp;2 "&gt;&gt; Determining the Galera last committed seqno using --wsrep-recover.."
        echo

        # 恢复seqno
        mysqld_safe --wsrep-cluster-address=gcomm:// --wsrep-recover
        cat $TMP
        seqno=$(cat $TMP | tr ' ' "\n" | grep -e '[a-z0-9]*-[a-z0-9]*:[0-9]' | head -1 | cut -d ":" -f 2)
        # 此容器是新启动的，才会设置INITIALIZED。对于新节点，seqno设置为0
        if [ $INITIALIZED -eq 1 ]; then
          echo &gt;&amp;2 "&gt;&gt; This is a new container, thus setting seqno to 0."
          seqno=0
        fi

        echo
        if [ ! -z $seqno ]; then
          # 将当前节点的seqno写入到Etcd
          echo &gt;&amp;2 "&gt;&gt; Reporting seqno:$seqno to ${healthy_etcd}."
          WAIT=$(($TTL * 2))
          curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key $URL/$ipaddr/seqno -X PUT -d "value=$seqno&amp;ttl=$WAIT"
        else
          seqno=$(cat $TMP | tr ' ' "\n" | grep -e '[a-z0-9]*-[a-z0-9]*:[0-9]' | head -1)
          echo &gt;&amp;2 "&gt;&gt; Unable to determine Galera sequence number."
          exit 1
        fi
        rm $TMP

        echo
        echo &gt;&amp;2 "&gt;&gt; Sleeping for $TTL seconds to wait for other nodes to report."
        sleep $TTL

        echo
        echo &gt;&amp;2 "&gt;&gt; Retrieving list of seqno for $CLUSTER_NAME"
        bootstrap_flag=1

        # Retrieve seqno from etcd
        curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key ${URL}?recursive=true\&amp;sorted=true &gt;/tmp/out
        cluster_seqno=$(cat /tmp/out | jq -r '.node.nodes[].nodes[]? | select(.key | contains ("seqno")) | .value' | tr "\n" ' ' | sed -e 's/[[:space:]]*$//')

        for i in $cluster_seqno; do
          if [ $i -gt $seqno ]; then
            # 如果存在其它节点的seqno更大，本节点不处理bootstrap
            bootstrap_flag=0
            echo &gt;&amp;2 "&gt;&gt; Found another node holding a greater seqno ($i/$seqno)"
          fi
        done

        echo
        if [ $bootstrap_flag -eq 1 ]; then
          # 本节点seqno最大，处理bootstrap
          # Find the earliest node to report if there is no higher seqno
          # node_to_bootstrap=$(cat /tmp/out | jq -c '.node.nodes[].nodes[]?' | grep seqno | tr ',:\"' ' ' | sort -k 11 | head -1 | awk -F'/' '{print $(NF-1)}')
          ## The earliest node to report if there is no higher seqno is computed wrongly: issue #6
          node_to_bootstrap=$(cat /tmp/out | jq -c '.node.nodes[].nodes[]?' | grep seqno | tr ',:"' ' ' | sort -k5,5r -k11 | head -1 | awk -F'/' '{print $(NF-1)}')
          if [ "$node_to_bootstrap" == "$ipaddr" ]; then
            echo &gt;&amp;2 "&gt;&gt; This node is safe to bootstrap."
            cluster_join=
          else
            echo &gt;&amp;2 "&gt;&gt; Based on timestamp, $node_to_bootstrap is the chosen node to bootstrap."
            echo &gt;&amp;2 "&gt;&gt; Wait again for $TTL seconds to look for a bootstrapped node."
            sleep $TTL
            curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key ${URL}?recursive=true\&amp;sorted=true &gt;/tmp/out

            # Look for a synced node again
            running_nodes2=$(cat /tmp/out | jq -r '.node.nodes[].nodes[]? | select(.key | contains ("wsrep_local_state_comment")) | select(.value == "Synced") | .key' | awk -F'/' '{print $(NF-1)}' | tr "\n" ' ' | sed -e 's/[[:space:]]*$//')

            echo
            echo &gt;&amp;2 "&gt;&gt; Running nodes: [${running_nodes2}]"

            if [ ! -z "$running_nodes2" ]; then
              cluster_join=$(join , $running_nodes2)
            else
              echo
              echo &gt;&amp;2 "&gt;&gt; Unable to find a bootstrapped node to join."
              echo &gt;&amp;2 "&gt;&gt; Exiting."
              exit 1
            fi
          fi
        else
          # seqno不是最大的，不处理bootstrap
          echo &gt;&amp;2 "&gt;&gt; Refusing to start for now because there is a node holding higher seqno."
          echo &gt;&amp;2 "&gt;&gt; Wait again for $TTL seconds to look for a bootstrapped node."
          sleep $TTL

          # Look for a synced node again
          curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key ${URL}?recursive=true\&amp;sorted=true &gt;/tmp/out
          running_nodes3=$(cat /tmp/out | jq -r '.node.nodes[].nodes[]? | select(.key | contains ("wsrep_local_state_comment")) | select(.value == "Synced") | .key' | awk -F'/' '{print $(NF-1)}' | tr "\n" ' ' | sed -e 's/[[:space:]]*$//')

          echo
          echo &gt;&amp;2 "&gt;&gt; Running nodes: [${running_nodes3}]"

          if [ ! -z "$running_nodes2" ]; then
            cluster_join=$(join , $running_nodes3)
          else
            echo
            echo &gt;&amp;2 "&gt;&gt; Unable to find a bootstrapped node to join."
            echo &gt;&amp;2 "&gt;&gt; Exiting."
            exit 1
          fi
        fi
      else
        # if there is a Synced node, join the address
        cluster_join=$(join , $running_nodes)
      fi
    fi
    set -e

    echo
    echo &gt;&amp;2 "&gt;&gt; Cluster address is gcomm://$cluster_join"
  else
    echo
    echo &gt;&amp;2 '&gt;&gt; No healthy etcd host detected. Refused to start.'
    exit 1
  fi
fi

echo
echo &gt;&amp;2 "&gt;&gt; Starting reporting script in the background"
nohup /report_status.sh root $MYSQL_ROOT_PASSWORD $CLUSTER_NAME $TTL $DISCOVERY_SERVICE &amp;

# set IP address based on the primary interface
sed -i "s|WSREP_NODE_ADDRESS|$ipaddr|g" /etc/my.cnf

echo
echo &gt;&amp;2 "&gt;&gt; Starting mysqld process"
if [ -z $cluster_join ]; then
  export _WSREP_NEW_CLUSTER='--wsrep-new-cluster'
  # set safe_to_bootstrap = 1
  GRASTATE=$DATADIR/grastate.dat
  [ -f $GRASTATE ] &amp;&amp; sed -i "s|safe_to_bootstrap.*|safe_to_bootstrap: 1|g" $GRASTATE
else
  export _WSREP_NEW_CLUSTER=''
fi

exec mysqld --wsrep_cluster_name=$CLUSTER_NAME --wsrep-cluster-address="gcomm://$cluster_join" --wsrep_sst_auth="xtrabackup:$XTRABACKUP_PASSWORD" $_WSREP_NEW_CLUSTER $CMDARG</pre>
<div class="blog_h3"><span class="graybg">report_status.sh</span></div>
<p>该脚本会每TTL报告一次当前Galera容器的状态。</p>
<div class="blog_h3"><span class="graybg">配置</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">环境变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>MYSQL_ROOT_PASSWORD</td>
<td>
<p>MySQL数据库的密码</p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>XTRABACKUP_PASSWORD</td>
<td>
<p>此方案使用的SST方法是XtraBackup SST，它使用用户xtrabackup@localhost，如果需要定制密码，设置该变量</p>
</td>
</tr>
<tr>
<td>CLUSTER_NAME</td>
<td>Galera集群的名称</td>
</tr>
<tr>
<td>DISCOVERY_SERVICE</td>
<td>
<p>如果需要启用发现服务（目前仅仅支持Etcd），需要配置该变量，形式IP:PORT，多个地址需要逗号分隔</p>
<p>容器会访问发现服务，使用CLUSTER_NAME来查询，如果发现已经存在对应的Galera集群，则加入；否则创建新的Galera集群</p>
</td>
</tr>
<tr>
<td>CLUSTER_JOIN</td>
<td>
<p>如果不启用发现服务，则需要设置此变量：</p>
<ol>
<li>如果变量为空，则创建新集群</li>
<li>如果不为空，则连接到此地址所指向的、既有集群的节点</li>
</ol>
</td>
</tr>
<tr>
<td>TTL</td>
<td>
<p>如果节点是alive的（wsrep_cluster_state_comment=Synced），每TTL - 2秒，report_status.sh会报告自身的状态</p>
<p>如果节点宕掉了，那么Etcd中的key无法刷新，因为过期而删除，这样构建Galera通信地址URI时就会跳过宕掉的节点</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">服务发现</span></div>
<p>如果Galera集群名字为my_wsrep_cluster，则此方案会在Etcd中存储以下数据：</p>
<pre class="crayon-plain-tag">// url -s "http://192.168.55.111:2379/v2/keys/galera/my_wsrep_cluster?recursive=true"
{
    "action": "get",
    "node": {
        "createdIndex": 10049,
        "dir": true,
        "key": "/galera/my_wsrep_cluster",
        "modifiedIndex": 10049,
        "nodes": [
            {
                "createdIndex": 10067,
                "dir": true,
                // 每个节点在 /galera/$CLUSTER_NAME/下有个键
                "key": "/galera/my_wsrep_cluster/10.255.0.6",
                "modifiedIndex": 10067,
                "nodes": [
                    {
                        "createdIndex": 10075,
                        "expiration": "2016-11-29T10:55:35.37622336Z",
                        // 存储节点seqno
                        "key": "/galera/my_wsrep_cluster/10.255.0.6/wsrep_last_committed",
                        "modifiedIndex": 10075,
                        "ttl": 10,
                        "value": "0"
                    },
                    {
                        "createdIndex": 10073,
                        "expiration": "2016-11-29T10:55:34.788170259Z",
                        // 存储节点状态
                        "key": "/galera/my_wsrep_cluster/10.255.0.6/wsrep_local_state_comment",
                        "modifiedIndex": 10073,
                        "ttl": 10,
                        "value": "Synced"
                    }
                ]
            }...
        ]
    }
}</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/galera-study-note">Galera学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/galera-study-note/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>
		<item>
		<title>MySQL知识集锦</title>
		<link>https://blog.gmem.cc/mysql-faq</link>
		<comments>https://blog.gmem.cc/mysql-faq#comments</comments>
		<pubDate>Mon, 11 Apr 2011 09:31:22 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1611</guid>
		<description><![CDATA[<p>常见术语  术语 说明  change buffer 变更缓冲。由于insert、delete、purge等操作导致相应的索引变化需要写入，这些变化往往意味着随机I/O，为避免性能问题，后台线程周期性的执行索引变更的写入 dirty page 在InnoDB缓冲池中，已经被修改的，但是尚未刷出到数据文件的页，相对的叫clean page double buffer 双重缓冲是InnoDB引入的文件刷出技术，在把页写入到数据文件之前，首先将其写到一个连续的、称为双重缓冲的区域。只有在这一写入完毕后，InnoDB才把页写到数据文件的相应位置。如果在写数据文件期间出现OS、存储子系统或者MySQL进程的崩溃，InnoDB可以很好的从双重缓冲恢复数据 extent 表空间中的一组页，对于默认的16KB页，一个extend包含64页。对于4/8/16KB的页，extent的大小始终为1MB Record Lock 记录锁，是InnoDB行锁的一种，锁定一个索引记录（Index record）。记录锁总是锁定索引而不是数据行本身，即便表没有任何索引，InnoDB也会创建一个隐藏的聚簇索引，并用该索引执行记录的锁定 gap lock 间隙锁，是InnoDB行锁的一种。在2个索引记录之间的间隙上的锁定，或者在第一个索引记录之前、最后一个索引记录之后的间隙上的锁定 <a class="read-more" href="https://blog.gmem.cc/mysql-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/mysql-faq">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_h1"><span class="graybg">常见术语</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;"> 术语</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>change buffer</td>
<td>
<p>变更缓冲。由于insert、delete、purge等操作导致相应的索引变化需要写入，这些变化往往意味着随机I/O，为避免性能问题，后台线程周期性的执行索引变更的写入</p>
</td>
</tr>
<tr>
<td>dirty page</td>
<td>
<p>在InnoDB缓冲池中，已经被修改的，但是尚未刷出到数据文件的页，相对的叫clean page</p>
</td>
</tr>
<tr>
<td>double buffer</td>
<td>
<p>双重缓冲是InnoDB引入的文件刷出技术，在把页写入到数据文件之前，首先将其写到一个连续的、称为双重缓冲的区域。只有在这一写入完毕后，InnoDB才把页写到数据文件的相应位置。如果在写数据文件期间出现OS、存储子系统或者MySQL进程的崩溃，InnoDB可以很好的从双重缓冲恢复数据</p>
</td>
</tr>
<tr>
<td>extent</td>
<td>
<p>表空间中的一组页，对于默认的16KB页，一个extend包含64页。对于4/8/16KB的页，extent的大小始终为1MB</p>
</td>
</tr>
<tr>
<td>Record Lock</td>
<td>
<p>记录锁，是InnoDB行锁的一种，锁定一个索引记录（Index record）。记录锁<span style="background-color: #c0c0c0;">总是锁定索引</span>而不是数据行本身，即便表没有任何索引，InnoDB也会<span style="background-color: #c0c0c0;">创建一个隐藏的聚簇索引</span>，并用该索引执行记录的锁定</p>
</td>
</tr>
<tr>
<td>gap lock</td>
<td>
<p>间隙锁，是InnoDB行锁的一种。在2个索引记录之间的间隙上的锁定，或者在第一个索引记录之前、最后一个索引记录之后的间隙上的锁定</p>
<p>扫描普通索引时，InnoDB会对当前索引值与下一个索引值之间的间隙进行锁定</p>
<p>如果间隙锁导致了性能问题，可以设置隔离级别为READ-COMMITTED或者开启innodb_locks_unsafe_for_binlog，这相当于禁用索引扫描/搜索的间隙锁，间隙锁将仅仅被用于外键约束检查或者重复键检查</p>
</td>
</tr>
<tr>
<td>Next-key lock</td>
<td>
<p>下一键锁。默认情况下InnoDB工作在隔离级别 REPEATABLE READ下，并且系统变量innodb_locks_unsafe_for_binlog=OFF，这种设置下InnoDB使用下一键锁来进行索引扫描/搜索，同时避免幻影读问题。</p>
<p>下一键锁<span style="background-color: #c0c0c0;">联合使用了Record lock以及Gap lock</span>，在<span style="background-color: #c0c0c0;">锁定住索引记录</span>的同时，该索引记录<span style="background-color: #c0c0c0;">前面的gap</span>也被锁定。因此，如果一个会话持有索引记录R的共享或者独占锁，那么其它会话无法插入一个直接位于R之前（按索引顺序）的记录</p>
<p>下面的例子说明下一键锁的效果：</p>
<pre class="crayon-plain-tag">select id, col from tab;  -- 测试表，id为聚簇索引，col为普通辅助索引
+----+------+
| id | col  |
+----+------+
| 10 |   10 |
| 15 |   15 |
| 16 |   16 |
| 20 |   20 |
+----+------+


-- session 1
start transaction;
-- 下面两行都可以用来测试，都获得写锁
select * from tab where id=10 for update;
update tab set col=10 where id=10;
-- session 2
start transaction;
update tab set col=10 where id=10; -- 超时，记录锁
insert tab (id,col) values (5, 5); 
-- 成功，聚簇索引、唯一性索引不引入上一键锁，仅使用记录锁

-- session 1
start transaction;
-- 下面改用辅助索引执行检索
-- 如果col没有创建索引，这将导致锁整个表
update tab set col=10 where col=10; -- Next-Key范围：(-∞,10]
-- session 2
start transaction;
insert tab (id,col) values (5, 5); -- 超时，处于下一键锁范围
update tab set id=15 where col=15; -- 成功
insert tab (id,col) values (13, 13); -- 超时，辅助索引下一键值的间隙锁(10,15)
update tab set col=13 where col=13; -- 成功，记录不存在的更新不会导致锁等待


-- session 1
start transaction;
update tab set col=15 where col=15;  -- Next-Key范围(10,15]
-- session 2
start transaction;
insert tab (id,col) values (13, 13); -- 超时，处于下一键锁范围


-- session 1
start transaction;
update tab set col=20 where col=20; -- Next-Key范围：(16,20]
-- session 2
start transaction;
update tab set col=16 where col=16; -- 成功，不在Next-Key范围
insert tab (id,col) values (17,17); -- 超时，处于下一键锁范围
insert tab (id,col) values (22,22); -- 超时，辅助索引的下一键值的间隙锁

-- session 1
start transaction;
-- 由于此行的插入尚未提交，不会使用下一键锁
insert tab (id,col) values (22,22); -- 下一键范围：(20, +∞)
-- session 2
start transaction;
insert tab (id,col) values (22,22); -- 超时
update tab set col=20 where col=20; -- 成功
insert tab (id,col) values (21,21); -- 成功
insert tab (id,col) values (23,23); -- 成功
insert tab (id,col) values (25,25); -- 成功</pre>
</td>
</tr>
<tr>
<td>metadata lock</td>
<td>
<p>5.5.3+引入元数据锁定机制，来管理对数据库对象（而不是表中的数据）的并发访问，并保证数据一致性（防止结构性变更）。除了表以外，元数据锁定还应用到schema、存储对象（procedures、functions、triggers、scheduled events）。下面是元数据锁工作方式的示例：
<pre class="crayon-plain-tag">-- 事务1
start transaction;
select * from tab1;
select * from tab2;
-- 事务1得到tab1、tab2的元数据锁，在事务提交前，这些锁不会释放

-- 事务2
-- 其它事务如果在元数据锁定表上执行DDL或者写锁，会被阻塞
start transaction;  
drop table tab1;         -- 失败
alter table tab2;        -- 失败
lock table tab1 write;   -- 失败</pre>
<p> 在5.5.3版本之前，元数据锁的等价机制会在语句级别得到并释放结构性锁</p>
</td>
</tr>
<tr>
<td>log</td>
<td>
<p>在MySQL中，日志可能指：</p>
<ol>
<li>重做日志（redo log），在InnoDB的上下文中，日志典型的是指位于<span style="background-color: #c0c0c0;">ib_logfile*</span>文件的重做日志</li>
<li>撤销日志（undo log），在InnoDB的上下文中，日志还可能指是指位于系统表空间或者撤销表空间的撤销日志</li>
<li>错误日志（error log），记录启动时的诊断信息或者运行时错误</li>
<li>二进制日志（binary log），用于支持主从复制，或者基于时间点的恢复</li>
<li>一般查询日志（general query log），用于诊断应用程序问题</li>
<li>缓慢查询日志（slow query log ），用于记录执行缓慢的语句</li>
</ol>
</td>
</tr>
<tr>
<td>log buffer</td>
<td>主要针对InnoDB重做日志，这是一个内存区域，其中包含将要写入到ib_logfile*文件的数据。其大小由innodb_log_buffer_size控制</td>
</tr>
<tr>
<td>log file</td>
<td>主要针对InnoDB重做日志，即ib_logfile*文件</td>
</tr>
<tr>
<td>log group</td>
<td>主要针对InnoDB重做日志，日志组，通常由ib_logfile0、ib_logfile1等组成</td>
</tr>
<tr>
<td>page</td>
<td>
<p>页，用来表示在任何时候，InnoDB在磁盘（数据文件）和内存（缓冲池）之间传递数据的最小单位。每个页存放1-N行数据，如果1行数据都放不下，InnoDB会创建额外的指针结构，确保行的相关信息被存放在一个页中</p>
<p>当InnoDB批量读写页以提高吞吐量时，每次执行一个extent</p>
<p>在5.6之前，InnoDB页大小固定为16KB，此后则可以是4KB、8KB或者16KB，由innodb_page_size控制</p>
</td>
</tr>
<tr>
<td>purge</td>
<td>
<p>由专用线程周期性执行的垃圾清理工作：</p>
<ol>
<li>从索引中移除无用的值</li>
<li>物理的移除由先前delete语句删除的的数据行</li>
</ol>
</td>
</tr>
<tr>
<td>redo log</td>
<td>
<p>即InnoDB的重做日志，它是一种基于磁盘的数据结构，用于在崩溃恢复时纠正被不完整事务写入的数据。在常规操作中，重做日志编码并存储对InnoDB表数据的写请求（这些请求可能来自SQL语句或者低级别的API）为redo，在宕机恢复时，尚未来得及写入到数据文件的写操作被回放，并写入到数据文件。</p>
<p>在5.6.3之前，重做日志文件最大4GB，此后可达512GB</p>
<p>重做日志的磁盘布局受到innodb_log_file_size、innodb_log_group_home_dir、 innodb_log_files_in_group的控制，最后一个很少使用</p>
</td>
</tr>
<tr>
<td>row lock</td>
<td>锁定一行数据，防止其他事务对其执行不兼容的访问，表中的其他行可以被自由访问。行锁由InnoDB的DML操作自动发起</td>
</tr>
<tr>
<td>savepoint</td>
<td>保存点，用于帮助实现嵌套事务</td>
</tr>
<tr>
<td>segment</td>
<td>
<p>段，InnoDB表空间中的一部分，如果用目录类比表空间，那么段相当于文件，段可以增长、新建。</p>
<p>对于每表一文件模式，表数据在一个段中，而每一个关联的索引在各自的段中；对于所有表一文件模式，所有表的段都位于系统表空间中。系统表空间还会包括一个或者多个回滚段，供撤销日志使用</p>
<p>随着数据的插入和删除，段会自动增长和收缩。当段需要额外的空间时，它以extent为单位（1MB）增加；反之，当一个extent中的数据都不需要时，这个extent的空间被释放</p>
</td>
</tr>
<tr>
<td>Temporary Tables</td>
<td>
<p>某些情况下MySQL服务器会在创建内部使用的临时表，以便处理查询。临时表可能基于memory引擎被存放在内存中；或者基于MyISAM引擎并存放在磁盘中。服务器往往最初把临时表存放在内存中，当表过大时，转移到磁盘中</p>
<p>临时表的使用场景包括：</p>
<ol>
<li>union查询会使用到临时表</li>
<li>某些视图会用到临时表，比如基于TEMPTABLE处理算法的视图，或者使用了union查询、聚合函数的视图</li>
<li>当order by和group by 从句不同时，或者order by、group by 包含来自非首个表（按join queue顺序）的列时，使用临时表</li>
<li>distinct和order by 联用可能需要临时表</li>
<li>使用选项SQL_SMALL_RESULT时，MySQL可能使用内存临时表</li>
<li>基于多表的update语句</li>
<li>GROUP_CONCAT()、COUNT(DISTINCT)</li>
<li>衍生表（来自from从句的子查询）</li>
</ol>
<p>如果需要确定某个查询语句是否使用了临时表，可以通过解释计划的Extra列判断，Using temporary提示使用了临时表，但是注意解释计划可能不对衍生表或者物化临时表显示Using temporary</p>
</td>
</tr>
<tr>
<td>undo</td>
<td>撤销。在事务的整个生命周期内被维护的日志信息，该日志信息记录了所有数据库改变，以便在回滚发生时，这些改变能够被撤销。该日志被存放在位于系统表空间或者独立的undo表空间中的撤销日志中</td>
</tr>
<tr>
<td>undo log</td>
<td>
<p>撤销日志，也称为undo buffer。存放被活动事务修改前的数据的副本，如果其他事务需要读取原始数据，MySQL将从undo log中获取。默认撤销日志位于系统表空间中。从MySQL 5.6开始，可以配置innodb_undo_tablespaces、innodb_undo_directory来将撤销日志分散到1-N个独立的表空间文件中，并可选的存放在独立的硬盘，例如SSD中</p>
<p>撤销日志被划分为不同的部分，包括insert undo buffer和update undo buffer</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常用命令/SQL</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>show full processlist;</td>
<td>显示线程列表。如果显示Copying to tmp table，或者时间太大，要注意语句性能问题</td>
</tr>
<tr>
<td>show variables;</td>
<td>
<p>显示服务器系统变量</p>
<pre class="crayon-plain-tag">-- 显示字符集信息
show variables like 'character_set_%';
-- 显示MySQL版本信息
show variables like "%version%";
-- 显示当前会话的自动提交状态
show variables like 'autocommit';</pre>
</td>
</tr>
<tr>
<td>show status;</td>
<td>
<p>显示服务器状态变量，连同session范围的计数器
<pre class="crayon-plain-tag">-- 显示innodb锁统计信息
show status like 'innodb_row_lock_%';
-- 显示innodb缓冲池信息
show status like 'innodb_buffer_pool_%';
-- 显示最后一次查询的成本
show status like 'Last_query_cost';</pre>
</td>
</tr>
<tr>
<td>flush status;</td>
<td>刷空多种统计性的服务器状态变量</td>
</tr>
<tr>
<td>show global status;</td>
<td>显示服务器启动以来的计数器</td>
</tr>
<tr>
<td>
<p>show innodb status;
<p>show engine innodb status;</p>
</td>
<td>
<p>显示innodb实时状态信息</p>
<p>5.6使用</p>
</td>
</tr>
<tr>
<td>show table status;</td>
<td>
<p>显示当前数据库的表状态，列信息：</p>
<p>Name 表的名称<br />Engine 表的存储引擎<br />Row_format 行格式，对于MyISAM表，可以是Dynamic, Fixed, Compressed。如果具有VARCHAR、BLOB列则是Dynamic，Compressed仅存在于MyISAM压缩表<br />Rows 表的行数，对于MyISAM和大部分引擎，此值是精确的，对于InnoDB则是估算的<br />Avg_row_length 平均每行的Byte数<br />Data_length 整个表数据的Byte数<br />Max_data_length 表持有数据的最大量<br />Index_length 索引消耗的磁盘空间<br />Auto_increment 下一个AUTO_INCREMENT值<br />Create_time 表创建时间<br />Update_time 表数据最后变化时间<br />Check_time 表最后一次使用 CHECK TABLE或myisamchk检查的时间<br />Collation 表的字符集<br />Checksum 整个表的校验和<br />Create_options 表创建时指定的任何其他的选项<br />Comment 包含大量额外信息</p>
</td>
</tr>
<tr>
<td>select @@global.tx_isolation;</td>
<td>显示系统的事务隔离级别</td>
</tr>
<tr>
<td>alter table t_rt_val_his engine=myisam;</td>
<td>修改表的引擎</td>
</tr>
<tr>
<td>show create table t_table;</td>
<td>显示DDL语句</td>
</tr>
<tr>
<td>mysql -uuser -ppasswd dbname &lt; path</td>
<td>
<p>数据库导入命令</p>
<p>-h MySQL主机名或IP<br />-P MySQL监听端口<br />-u用户名<br />-p密码<br />--default-character-set=字符集，应和导出时设置一致，导出未设置，可以尝试latin1</p>
</td>
</tr>
<tr>
<td>
<p>mysqldump -uuser -ppasswd dbname</p>
<p>[tab1 tab2 tab3……] &gt; path</p>
</td>
<td>
<p>数据库导出命令，如果不指定表名，则导出整个库</p>
<p>-h MySQL主机名或IP<br />-P MySQL监听端口<br />-u用户名<br />-p密码<br />--hex-blob 导出HEX格式的blob字段<br />--default-character-set=字符集<br />--routines  包含例程（存储过程、函数）<br />--triggers  包含触发器<br />--add-drop-table  为每个create table语句添加drop语句<br />--add-drop-database  为每个create database语句添加drop语句<br />--add-drop-trigger 为每个create trigger语句添加drop语句<br />--add-locks  为每个表的dump前后添加lock tables/unlock tables语句，当dump文件reload时获得更快的插入速度<br />--all-databases  Dump出所有数据库的所有表<br />--complete-insert  Dump出的插入语句包含完整的列名<br />--compress  如果客户端、服务器都支持，启用压缩传输<br />--databases  指定需要导出的数据库<br />--no-autocommit  不自动提交每条语句，在每个表所有语句尾部提交一次</p>
</td>
</tr>
<tr>
<td>start transaction;<br />    select @a:=sum(salary) from t1;<br />    update t2 SET sum=@a;<br />commit;</td>
<td>
<p>命令行中开启事务</p>
</td>
</tr>
<tr>
<td>
<p>set autocommit=0;</p>
</td>
<td>
<p>在会话（当前连接）中明确禁止自动提交</p>
</td>
</tr>
<tr>
<td>
<p>set profiling = 1;<br />show profiles ;<br />show profile for query 1;</p>
</td>
<td>
<p>启用剖析<br />显示剖析统计信息<br />显示指定查询的各步骤的耗时</p>
<p>参考：<a href="/high-performance-mysql-study-note#profiling-a-single-query">剖析单个查询</a></p>
</td>
</tr>
<tr>
<td>
<p>set session transaction isolation level read committed;</p>
</td>
<td>
<p>在会话（当前连接）中 设置事务隔离级别</p>
</td>
</tr>
<tr>
<td>
<p>check table mytable;<br />repair table mytable;</p>
</td>
<td>
<p>检查表上的错误<br />修复MyISAM表</p>
</td>
</tr>
<tr>
<td>
<p>explain</p>
</td>
<td>
<p>解释执行计划</p>
<p>explain extended select * from tab;<br />show warnings;    --- 可以看到被重构后的SQL语句</p>
<p>explains partitions   --- 检查使用的分区表</p>
</td>
</tr>
<tr>
<td>
<p>mysqld</p>
</td>
<td>
<p>mysqld是MySQL服务器端的主程序，使用mysqld --verbose --help | less 可以查看详细帮助</p>
<p>--defaults-file=/path-to/my.cnf 指定配置文件</p>
</td>
</tr>
<tr>
<td>
<p>create database</p>
</td>
<td>
<p>创建新的数据库，例如： create database newdb;</p>
</td>
</tr>
<tr>
<td> mysql_install_db</td>
<td>
<p>初始化MySQL数据库。5.7之前，会创建一个空密码的本地root用户，5.7之后使用随机密码，密码存放在～/.mysql_secret中。举例：</p>
<p><pre class="crayon-plain-tag">cd mysql

adduser mysql
chown -R mysql:mysql data

scripts/mysql_install_db --basedir=. --datadir=data

bin/mysqld_safe --defaults-file=my.cnf &amp;</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">用户管理命令</span></div>
<div class="blog_h2"><span class="graybg">创建</span></div>
<p>在MySQL中<pre class="crayon-plain-tag">create user</pre> 语句用于创建用户，相应的<pre class="crayon-plain-tag">alter user</pre> 、<pre class="crayon-plain-tag">drop user</pre> 用于修改、删除用户。MySQL在<pre class="crayon-plain-tag">mysql.user</pre> 表中存储用户。</p>
<div class="blog_h3"><span class="graybg">语法格式</span></div>
<pre class="crayon-plain-tag">CREATE USER [IF NOT EXISTS]
-- 目标规格，格式 user [ auth_option ]
-- 其中user必须是'username'@'host'格式，引号可以省略
-- host中可以出现%，用于通配，例如192.168.0.%通配所有192.168.0子网的主机
-- %.gmem.cc则通配所有gmem.cc下的主机
-- auth_option指定身份验证方式，可以是：
-- IDENTIFIED BY 'auth_string' | DENTIFIED BY PASSWORD 'hash_string'
-- IDENTIFIED WITH auth_plugin | IDENTIFIED WITH auth_plugin BY 'auth_string'
-- IDENTIFIED WITH auth_plugin AS 'hash_string'
-- 其中auth_string表示明文，hash_string则为身份验证插件处理后的散列值，皆存放在authentication_string字段
user_specification [, user_specification] ...

-- tsl_option，指定与SSL相关的选项，语法和CREATE USER相同，可以是:
-- SSL | X509 | CIPHER 'cipher' | ISSUER 'issuer' | SUBJECT 'subject'
[REQUIRE {NONE | tsl_option [[AND] tsl_option] ...}]
-- resource_option，指定用户的资源配额，可以是：
-- MAX_QUERIES_PER_HOUR count | MAX_UPDATES_PER_HOUR count
-- MAX_CONNECTIONS_PER_HOUR count | MAX_USER_CONNECTIONS count
[WITH resource_option [resource_option] ...]
-- password_option 密码选项，可以是：
-- PASSWORD EXPIRE | PASSWORD EXPIRE DEFAULT
-- PASSWORD EXPIRE NEVER | PASSWORD EXPIRE INTERVAL N DAY
-- lock_option 账户锁定选项，可以是：
-- ACCOUNT LOCK | ACCOUNT UNLOCK
[password_option | lock_option] ... </pre>
<div class="blog_h3"><span class="graybg">举例</span></div>
<pre class="crayon-plain-tag">-- 创建一个可从所有主机上登录的用户
create user 'newuser'@'%' identified by 'newpasswd';
-- 创建一个可从192.168.0网段登录的用户
create user 'newuser'@'192.168.0.%' identified by 'newpasswd';
-- 创建一个只能从服务器本机登录的用户，不授予密码
create user 'newuser'@'localhost'; </pre>
<div class="blog_h2"><span class="graybg">修改密码</span></div>
<pre class="crayon-plain-tag">SET PASSWORD [FOR user] = PASSWORD('auth_string');
# 举例
set password for gmem = password('gmem');</pre>
<div class="blog_h2"><span class="graybg">授权</span></div>
<p>在MySQL中，<pre class="crayon-plain-tag">grant</pre> 语句用于给MySQL用户授权，相应的<pre class="crayon-plain-tag">revoke</pre> 命令用于收回权限。</p>
<div class="blog_h3"><span class="graybg">语法格式</span></div>
<pre class="crayon-plain-tag">-- 要给其它用户授权，当前用户必须具有GRANT OPTION权限，同时拥有那些即将授出的权限
-- 如果系统变量read_only启用，则需要额外的权限SUPER
GRANT 
-- 权限类型，可以指定多个。括号内的列列表，用于指定特殊的权限级别——列
priv_type [(col_list)] [,priv_type [(col_list)]]...  
ON 
-- 可选的对象类型，可以是：TABLE、FUNCTION、PROCEDURE
[object_type] 
-- 权限级别，授权应用到哪个库的哪些对象，可以是：
-- * | *.* | db_name.* | db_name.tbl_name | tbl_name | db_name.routine_name
priv_level
TO
-- 目标用户，格式 user [ auth_option ]
-- 其中user为'username'@'host'格式
-- auth_option可以是：
-- IDENTIFIED BY 'auth_string' | DENTIFIED BY PASSWORD 'hash_string'
-- IDENTIFIED WITH auth_plugin | IDENTIFIED WITH auth_plugin BY 'auth_string'
-- IDENTIFIED WITH auth_plugin AS 'hash_string'
user_specification [, user_specification] ...

-- tsl_option，指定与SSL相关的选项，语法和CREATE USER相同，可以是:
-- SSL | X509 | CIPHER 'cipher' | ISSUER 'issuer' | SUBJECT 'subject'
[REQUIRE {NONE | tsl_option [[AND] tsl_option] ...}]

-- GRANT OPTION 表示新用户可以向其它人授权
-- resource_option，指定用户的资源配额，可以是：
-- MAX_QUERIES_PER_HOUR count | MAX_UPDATES_PER_HOUR count
-- MAX_CONNECTIONS_PER_HOUR count | MAX_USER_CONNECTIONS count
[WITH {GRANT OPTION | resource_option} ...];</pre>
<div class="blog_h3"><span class="graybg">权限类型（priv_type）</span></div>
<p>MySQL支持以下权限类型：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 30%; text-align: center;">权限</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ALL [PRIVILEGES]</td>
<td>授予所有权限，除了GRANT OPTION</td>
</tr>
<tr>
<td>ALTER</td>
<td>允许执行ALTER TABLE，支持的权限级别：全局、数据库、表</td>
</tr>
<tr>
<td>ALTER ROUTINE</td>
<td>允许修改或删除存储过程（stored routines），支持的权限级别：全局、数据库、存储过程（procedure）</td>
</tr>
<tr>
<td>CREATE</td>
<td>允许创建数据库、表，支持的权限级别：全局、数据库、表</td>
</tr>
<tr>
<td>CREATE ROUTINE</td>
<td>允许创建存储过程，支持的权限级别：全局、数据库</td>
</tr>
<tr>
<td>CREATE TABLESPACE</td>
<td>允许创建、修改、删除表空间和日至文件组，支持的权限级别：全局</td>
</tr>
<tr>
<td>CREATE TEMPORARY TABLES</td>
<td>允许执行CREATE TEMPORARY TABLE，支持的权限级别：全局、数据库</td>
</tr>
<tr>
<td>CREATE USER</td>
<td>运行执行fCREATE USER、DROP USER、RENAME USER、REVOKE ALL PRIVILEGES，支持的权限级别：全局</td>
</tr>
<tr>
<td>CREATE VIEW</td>
<td>允许创建或修改视图，支持的权限级别：全局、数据库、表</td>
</tr>
<tr>
<td>DELETE</td>
<td>允许执行DELETE，支持的权限级别：全局、数据库、表</td>
</tr>
<tr>
<td>DROP</td>
<td>允许删除数据库、表、视图，支持的权限级别：全局、数据库、表</td>
</tr>
<tr>
<td>EVENT</td>
<td>允许使用事件调度机制（例如CREATE EVENT...），支持的权限级别：全局、数据库</td>
</tr>
<tr>
<td>EXECUTE</td>
<td>允许执行存储过程，支持的权限级别：全局、数据库、表</td>
</tr>
<tr>
<td>FILE</td>
<td>允许导致服务器读写文件，支持的权限级别：全局</td>
</tr>
<tr>
<td>GRANT OPTION</td>
<td>允许向其它用户授权，支持的权限级别：全局、数据库、表、存储过程、代理</td>
</tr>
<tr>
<td>INDEX</td>
<td>允许创建或删除索引，支持的权限级别：全局、数据库、表</td>
</tr>
<tr>
<td>INSERT</td>
<td>允许执行INSERT语句，支持的权限级别：全局、数据库、表、列</td>
</tr>
<tr>
<td>LOCK TABLES</td>
<td>允许对具有SELECT权限的表执行LOCK TABLES，支持的权限级别：全局、数据库</td>
</tr>
<tr>
<td>PROCESS</td>
<td>允许通过SHOW PROCESSLIST查看所有进程，支持的权限级别：全局</td>
</tr>
<tr>
<td>PROXY</td>
<td>允许使用用户代理</td>
</tr>
<tr>
<td>REFERENCES</td>
<td>允许创建外键，支持的权限级别：全局、数据库、表、列</td>
</tr>
<tr>
<td>RELOAD</td>
<td>允许执行FLUSH操作，支持的权限级别：全局</td>
</tr>
<tr>
<td>REPLICATION CLIENT</td>
<td>允许用户询问master/slave服务器在哪里，支持的权限级别：全局</td>
</tr>
<tr>
<td>REPLICATION SLAVE</td>
<td>允许从二进制日志中读取来自master的事件记录，支持的权限级别：全局</td>
</tr>
<tr>
<td>SELECT</td>
<td>允许执行SELECT语句，支持的权限级别：全局、数据库、表、列</td>
</tr>
<tr>
<td>SHOW DATABASES</td>
<td>允许执行SHOW DATABASES，支持的权限级别：全局</td>
</tr>
<tr>
<td>SHOW VIEW</td>
<td>允许执行SHOW CREATE VIEW，支持的权限级别：全局、数据库、表</td>
</tr>
<tr>
<td>SHUTDOWN</td>
<td>允许执行命令mysqladmin shutdown，支持的权限级别：全局</td>
</tr>
<tr>
<td>SUPER</td>
<td>允许使用其它管理性操作，例如 CHANGE MASTER TO、KILL、PURGE BINARY LOGS、SET GLOBAL以及执行mysqladmin debug命令，支持的权限级别：全局</td>
</tr>
<tr>
<td>TRIGGER</td>
<td>允许创建、删除表上的触发器，支持的权限级别：全局、数据库、表</td>
</tr>
<tr>
<td>UPDATE</td>
<td>允许执行UPDATE语句，支持的权限级别：全局、数据库、表、列</td>
</tr>
<tr>
<td>USAGE</td>
<td>无特权（no privileges）的同义词</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">举例</span></div>
<pre class="crayon-plain-tag">-- 把数据库database的object对象的全部权限授予newuser，并允许他把这些权限授予别人
grant all privileges on database.object to 'newuser'@'%' with grant option;
-- 把所有数据库的所有权限授予newuser
grant all privileges on *.* to 'newuser'@'%';</pre>
<div class="blog_h1"><span class="graybg">常用函数</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">MySQL函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>CURDATE</td>
<td>
<p>以YYY-MM-DD或YYYYMMDD格式得到当前日期，示例：</p>
<pre class="crayon-plain-tag">-- 今年的第一天
SELECT CONCAT(YEAR(CURDATE()),'-01-01') FROM dual;
-- 得到YYYYMMDD格式的整数
SELECT CURDATE() + 0 FROM dual;</pre>
</td>
</tr>
<tr>
<td>CURTIME</td>
<td>以H:MM:SS或者HHMMSS.uuuuuu格式得到当前时间</td>
</tr>
<tr>
<td>MONTH</td>
<td>
<p>从日期中抽取月份字段，示例：
<pre class="crayon-plain-tag">SELECT MONTH(CURDATE()) FROM dual; </pre>
</td>
</tr>
<tr>
<td>YEAR</td>
<td>从日期中抽取4位数的年度字段</td>
</tr>
<tr>
<td>CONCAT</td>
<td>
<p>连接多个字符串，示例：
<pre class="crayon-plain-tag">SELECT CONCAT('My', 'SQL') FROM dual;
SELECT CONCAT(6.12) FROM dual;</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">MySQL配置与服务器状态</span></div>
<div class="blog_h2"><span class="graybg">系统变量（System Variables）</span></div>
<p>MySQL系统变量包含一系列配置信息，它们都具有默认值，并且大部分可以在运行时使用SET命令动态的修改，在表达式中，亦可引用系统变量。可以通过多种方式来查看这些系统变量的值：
<pre class="crayon-plain-tag">-- 使用mysqladmin命令查看
mysqladmin variables;
-- 通过information_schema数据库查看
select * from information_schema.SESSION_STATUS;
-- 查看包含了编译时默认值和配置文件合并的系统参数列表
mysqld --verbose --help
-- 查看编译时默认值的系统参数列表
mysqld --no-defaults --verbose --help</pre>
<div class="blog_h3"><span class="graybg">可配置系统变量和选项</span></div>
<p>这些变量和选项（以下统称参数）用来定制MySQL服务器的配置，或者优化性能。基于文件进行配置时，应该把参数列在my.cnf的[mysqld]段。下面列出MySQL 5.6版本的参数说明：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 35%; text-align: center;"><strong> 选项</strong></td>
<td style="text-align: center;"><strong>说明 </strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>abort_slave_event_count</td>
<td>默认0。如果设置为正数N，那么slave的SQL线程启动后，能接收来自master的N个事件，之后不再接收，就好像与master之间的网络被断开一样</td>
</tr>
<tr>
<td>archive</td>
<td>默认ON。是否启用ARCHIVE插件</td>
</tr>
<tr>
<td>auto_increment_increment</td>
<td>默认1， 1_65535之间。自增长列的递增值</td>
</tr>
<tr>
<td>auto_increment_offset</td>
<td>
<p>默认1， 1_65535之间，不得大于auto_increment_increment。确定自增长列的值的起始点，仅在auto_increment_increment !=1时有意义。第N行的自增长列的取值按：auto_increment_offset + N × auto_increment_increment计算</p>
<p>注意：当该参数被修改时，新生成的自增长列值的计算，不会依赖于既有的列值，还是固定按上述公式得出</p>
</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>autocommit</strong></span></td>
<td>默认TRUE，<span style="background_color: #c0c0c0;">注意TRUE/FALSE可以用1/0代替</span>，下同。是否在每一个语句执行后自动提交</td>
</tr>
<tr>
<td>back_log</td>
<td>默认80。排队中连接请求的最大数目，当MySQL服务器服务大量短时连接时，可以调整该参数</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>basedir</strong></span></td>
<td>默认$INS_DIR。MySQL安装目录，很多其它目录通常相对于该目录解析</td>
</tr>
<tr>
<td>big_tables</td>
<td>默认FALSE。 通过存储到文件来允许大结果集，可以避免大部分"table full"错误</td>
</tr>
<tr>
<td><strong><span style="background_color: #c0c0c0;">bind_address</span></strong></td>
<td>默认0.0.0.0。 绑定的监听地址</td>
</tr>
<tr>
<td>binlog_cache_size</td>
<td>默认32768。 二进制日志中，用于事务性引擎的事务性缓存的大小。如果经常使用包含大量语句的事务，可以增加该参数的值</td>
</tr>
<tr>
<td>binlog_direct_non_transactional_updates</td>
<td>默认FALSE。 如果使用语句格式的二进制日志，打开此选项后，非事务性引擎执行的更新会直接写入二进制日志。注意，打开此选项必须确保事务性、非事务性表之间没有依赖（反例：insert into myisam select * from innodb），否则会导致slave和master不一致</td>
</tr>
<tr>
<td>binlog_do_db</td>
<td>明确指定master二进制日志针对的数据库名称，其它数据库不被记录日志</td>
</tr>
<tr>
<td>binlog_ignore_db</td>
<td>指定不需要记录二级制日志的数据库名称</td>
</tr>
<tr>
<td>binlog_error_action</td>
<td>当语句由于致命错误无法写入二进制日志时的动作，可以忽略或导致master终止</td>
</tr>
<tr>
<td>binlog_format </td>
<td>默认STATEMENT。 二级制日志的格式， ROW表示基于行的二进制日志；STATEMENT表示基于语句的二进制日志；MIXED表示尽量使用基于语句的格式，除非导致歧义（例如牵涉到时间、用户定义函数）</td>
</tr>
<tr>
<td>binlog_order_commits</td>
<td>默认TRUE。 是否在内部发起与事务相同顺序的commit操作</td>
</tr>
<tr>
<td>binlog_row_event_max_size </td>
<td>默认8192。 基于行的二进制日志，每个事件最大字节数，多个行会尽可能合并到一个事件中，该值必须是256的倍数</td>
</tr>
<tr>
<td>binlog_rows_query_log_events  </td>
<td>默认FALSE。 允许写入查询日志事件到二进制日志中</td>
</tr>
<tr>
<td>binlog_stmt_cache_size</td>
<td>默认32768。 非事务性引擎的语句缓存大小，如果经常使用语句更新大量的行，可以增加该参数的值</td>
</tr>
<tr>
<td>blackhole</td>
<td>默认ON。 是否启用BLACKHOLE插件</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">bulk_insert_buffer_size</span></strong></td>
<td>默认8388608。 用于优化批量插入的tree cache的大小，该参数针对单个线程，增加该参数可以有效的提升批量插入的性能</td>
</tr>
<tr>
<td>character_set_client_handshake</td>
<td>默认TRUE。 是否在握手阶段考虑客户端的编码方式</td>
</tr>
<tr>
<td>character_set_filesystem</td>
<td>默认binary。 文件系统字符集</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>character_set_server</strong></span></td>
<td>默认latin1。服务器默认字符集</td>
</tr>
<tr>
<td>character_sets_dir</td>
<td>默认$INS_DIR/share/charsets/。存放字符集的目录</td>
</tr>
<tr>
<td>chroot </td>
<td>在启动时设置mysqld的根目录 </td>
</tr>
<tr>
<td>collation_server</td>
<td>默认latin1_swedish_ci。 默认排序规则</td>
</tr>
<tr>
<td>concurrent_insert </td>
<td>默认AUTO。 对于MyISAM，是否使用并发插入，可用值NEVER, AUTO, ALWAYS</td>
</tr>
<tr>
<td>connect_timeout </td>
<td>默认10。 服务器等待客户端发送连接数据包(connect packet)的超时秒数，超时后服务器发送Bad handshake给客户端</td>
</tr>
<tr>
<td>console</td>
<td>默认FALSE。 是否保留控制台窗口</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>datadir</strong></span></td>
<td>数据文件的根目录 </td>
</tr>
<tr>
<td>date_format</td>
<td>默认%Y_%m_%d。 日期格式</td>
</tr>
<tr>
<td>datetime_format</td>
<td>默认%Y_%m_%d %H:%i:%s。 日期时间格式</td>
</tr>
<tr>
<td>default_storage_engine</td>
<td>默认InnoDB。 新创建表的默认存储引擎</td>
</tr>
<tr>
<td>default_time_zone</td>
<td>默认时区</td>
</tr>
<tr>
<td>default_tmp_storage_engine</td>
<td>默认InnoDB。 新的明确创建的临时表使用的存储引擎</td>
</tr>
<tr>
<td>des_key_file</td>
<td>从文件加载des_encrypt()的加密密钥 </td>
</tr>
<tr>
<td>disconnect_on_expired_password</td>
<td>默认TRUE。 如果密码过期，断开连接</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>expire_logs_days</strong></span></td>
<td>默认0。 二进制日志过期的天数，如果禁用，启用二进制日志会导致磁盘耗尽</td>
</tr>
<tr>
<td>external_locking</td>
<td>默认FALSE。 使用外部（系统级）锁定，开启后，可以在MySQL处于运行状态时，使用myisamchk检查MyISAM表</td>
</tr>
<tr>
<td>flush</td>
<td>默认FALSE。 在SQL命令之间，刷出MyISAM表到磁盘</td>
</tr>
<tr>
<td>flush_time</td>
<td>默认0。 如果大于0，每隔一段时间后，专用线程刷出所有表到磁盘</td>
</tr>
<tr>
<td>gdb</td>
<td>默认FALSE。 启用调试</td>
</tr>
<tr>
<td>general_log</td>
<td>默认FALSE。 将连接、查询信息都记录到表或者文件</td>
</tr>
<tr>
<td>general_log_file</td>
<td>默认$INS_DIR/data/hostname.log。 上述日志的文件名</td>
</tr>
<tr>
<td>gtid_mode</td>
<td>默认OFF。 是否启用全局事务标识符（Global Transaction Identifiers）</td>
</tr>
<tr>
<td>host_cache_size </td>
<td>默认279。 为避免DNS解析，最多缓存多少主机名</td>
</tr>
<tr>
<td>init_connect</td>
<td>对于每一个新的连接，执行的命令 </td>
</tr>
<tr>
<td>init_file</td>
<td>在服务器启动时，从文件读出并执行命令 </td>
</tr>
<tr>
<td>init_slave</td>
<td>Salve的SQL线程启动时执行的命令</td>
</tr>
<tr>
<td>innodb</td>
<td>默认ON。</td>
</tr>
<tr>
<td>innodb_adaptive_flushing</td>
<td>
<p>默认TRUE。 在检查点刷出脏页，以避免I/O暴增</p>
<p>自适应刷出（adaptive flushing）是InnoDB引入的一项刷出算法，通过引入检查点的概念，使I/O更加平缓，避免暴涨暴跌。该算法不是把所有脏页一次性从InnoDB缓冲池刷出到数据文件，而是周期性的刷出一小批脏页。该算法根据flush操作的频率、重做日志增长的速度决定最优化的刷出比率</p>
</td>
</tr>
<tr>
<td>innodb_adaptive_flushing_lwm </td>
<td>默认10。 上述刷出机制被禁用的最低水位（脏页占InnoDB重做日志缓冲的比例）</td>
</tr>
<tr>
<td>innodb_adaptive_hash_index </td>
<td>默认TRUE。 启用InnoDB自适应哈希索引</td>
</tr>
<tr>
<td>innodb_adaptive_max_sleep_delay </td>
<td>默认150000。 最大休眠微秒数</td>
</tr>
<tr>
<td>innodb_autoextend_increment </td>
<td>默认64。 数据文件自动扩展的尺寸，MB</td>
</tr>
<tr>
<td>innodb_buffer_pool_dump_at_shutdown</td>
<td>默认FALSE。 是否在关闭时Dump出InnoDB缓冲池</td>
</tr>
<tr>
<td>innodb_buffer_pool_dump_now</td>
<td>默认FALSE。 立即Dump出InnoDB缓冲池到@@innodb_buffer_pool_filename</td>
</tr>
<tr>
<td>innodb_buffer_pool_filename</td>
<td>默认ib_buffer_pool。 DumpInnoDB缓冲池时的文件名</td>
</tr>
<tr>
<td>innodb_buffer_pool_instances </td>
<td>默认0。 缓冲池的数量，对于高档服务器，可以增加此值以提高可扩容性</td>
</tr>
<tr>
<td>innodb_buffer_pool_load_abort</td>
<td>默认FALSE。 立即中止正在执行的缓冲池加载（预热）动作</td>
</tr>
<tr>
<td>innodb_buffer_pool_load_at_startup</td>
<td>默认FALSE。 在启动时，从@@innodb_buffer_pool_filename读取缓冲池</td>
</tr>
<tr>
<td>innodb_buffer_pool_load_now</td>
<td>默认FALSE。 立即从@@innodb_buffer_pool_filename读取缓冲池</td>
</tr>
<tr>
<td><strong><span style="background_color: #c0c0c0;">innodb_buffer_pool_size</span></strong></td>
<td>
<p>默认134217728。 用于存储InnoDB<span style="background_color: #c0c0c0;">数据和索引</span>的缓冲池的大小</p>
<p>如果服务器是供MySQL专用的，可以尽量大，在为操作系统、MySQL其它组件预留足够的内存后，所有剩余的内存均可以分配给缓冲池，典型设置： 5-6GB(8GB内存)，20-25GB(32GB内存)，100-120GB(128GB内存)  </p>
</td>
</tr>
<tr>
<td>innodb_buffer_pool_stats</td>
<td>默认ON。 是否启用INNODB_BUFFER_POOL_STATS插件</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>innodb_change_buffer_max_size</strong></span></td>
<td>默认25。 变更缓冲（磁盘文件）相对InnoDB缓冲池的最大比例。如果服务器增删改压力非常重，可以增加该参数的值</td>
</tr>
<tr>
<td>innodb_change_buffering</td>
<td>默认all。 使用变更缓冲的操作类型，可选值inserts、deletes、purges、changes、none、all</td>
</tr>
<tr>
<td>innodb_commit_concurrency</td>
<td>默认0。 可以同时执行commit的线程数量，0表示不限制。在运行时不能在0和非0之间改变改变该参数</td>
</tr>
<tr>
<td>innodb_compression_failure_threshold_pct</td>
<td>
<p>默认 5。 当压缩失败的比率超过此阈值，InnoDB开始为新的压缩页预留空闲空间，预留空间的大小被动态调整，但不超过innodb_compression_pad_pct_max</p>
<p>关于OLTP场景使用压缩以节约磁盘空间：InnoDB压缩传统上主要用于只读或者几乎只读的场景。但是随着SSD这样快而贵的存储设备的出现，InnoDB压缩可以降低高流量、交互性网站的存储需求、IOPS</p>
<p>关于压缩失败：当InnoDB压缩表被执行DML操作时，如果对一个压缩页的更新时，会导致再次压缩，如果压缩后页空间不够，将导致MySQL执行页拆分，而新拆分的两个页又需要分别压缩，这是一个成本较高的场景。检查information_schema.innodb_cmp中compress_ops/compress_ops_ok的比值可以推测压缩失败出现的频度</p>
</td>
</tr>
<tr>
<td>innodb_compression_level</td>
<td>默认6 。 压缩级别，用于时空交换，范围0_9</td>
</tr>
<tr>
<td>innodb_compression_pad_pct_max</td>
<td>默认50。 每个压缩页预留空白的最大占比</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>innodb_concurrency_tickets</strong></span></td>
<td>
<p>默认5000 。同时进入InnoDB的线程的数量具有限制，超过限制后的到达的线程将排队，一旦线程进入后，将得到该参数指定的票据数量，每次线程进入InnoDB都消耗一个票据，耗尽后将继续排队</p>
<p>该参数如果设置为较小的值，那么只牵涉到较少行的线程可以公平的与大量更新的线程竞争，其缺点是后者可能需要多次循环排队才能完成整个事务。show engine innodb status可以看到事务持有的票据数</p>
</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>innodb_data_file_path</strong></span></td>
<td>默认ibdata1:10M:autoextend。指定系统表空间中InnoDB数据文件的文件名（目录innodb_data_home_dir）和大小。autoextend指定是否自动扩展，注意<span style="background_color: #c0c0c0;">系统表空间永远不会收缩</span></td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>innodb_data_home_dir</strong></span></td>
<td>系统表空间中的InnoDB数据文件的统一存放目录。该参数不会影响innodb_file_per_table启用时每个表的数据文件位置</td>
</tr>
<tr>
<td>innodb_disable_sort_file_cache </td>
<td>默认FALSE。 禁用合并排序（merge_sort）临时文件的操作系统级缓存，效果上相当于使用O_DIRECT打开这样的临时文件</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>innodb_doublewrite </strong></span></td>
<td>默认TRUE。 是否在写入数据文件之前，写入到双重缓冲</td>
</tr>
<tr>
<td>innodb_fast_shutdown </td>
<td>默认1。 InnoDB的关闭模式：设置为0则在关闭前执行完整的purge和change buffer合并；设置为1则跳过这些操作并关闭；设置为2则刷出innoDB并冷关闭，就像MySQL宕机一样，这会导致未提交的事务丢失</td>
</tr>
<tr>
<td>innodb_file_format </td>
<td>默认Antelope。 仅在innodb_file_per_table启用时有意义。选择Barracuda才能支持InnoDB压缩特性</td>
</tr>
<tr>
<td>innodb_file_io_threads </td>
<td>默认4。 InnoDB负责I/O操作的线程个数</td>
</tr>
<tr>
<td>innodb_file_per_table </td>
<td>
<p>默认ON。如果启用，InnoDB将新创建的表的数据、索引存放在独立的.ibd文件中，而不是存放在系统表空间。当表被drop或者truncate时，存储空间会被回收。如果要支持压缩，必须启用该选项。启用该选项后ALTER操作会导致表从系统表空间移出 </p>
<p>开启该选项的好处：</p>
<ol>
<li>truncate命令快</li>
<li>可以optimize表</li>
<li>可以使用多块磁盘</li>
<li>可以高效的进行大字段存储，Row format=dynamic时LOB被单独存储</li>
<li>可以单表移动或备份</li>
<li>可以支持数据压缩</li>
<li>崩溃恢复可能更快</li>
</ol>
<p>开启该选项的缺点：</p>
<ol>
<li>更多的fsync()调用</li>
<li>更多的文件描述符被打开，太多表（10000+）不推荐打开</li>
<li>向后兼容性问题</li>
</ol>
</td>
</tr>
<tr>
<td>innodb_flush_log_at_timeout </td>
<td>默认1。 每隔N秒刷出InnoDB重做日志到文件系统。如果OS崩溃或者断电，肯定导致N秒的数据丢失</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>innodb_flush_log_at_trx_commit </strong></span></td>
<td>
<p>默认1。 控制性能与完全的ACID保证之间的权衡。修改该参数能够提高性能，但是可能在崩溃时丢失数据</p>
<ol>
<li>值0：保证大概每1秒（不能完全保证，因为线程调度的不确定性）重做日志被写入文件并刷出。但是事务提交时，日志缓冲不会被写入到文件。mysqld崩溃时可能丢失1秒的数据</li>
<li>值1：保证完整的ACID兼容，每当事务提交时，重做日志缓冲被写入到日志文件，并且日志文件被刷出到磁盘（Blocked I/O直到写入完成）。该值会导致trx/s大大减小，在高速机械磁盘上，只能达到几百次trx/s</li>
<li>值2：保证每次事务提交时，日志缓冲均被写入到文件，但是不保证文件被刷出到磁盘（可能位于OS缓存）。如果OS崩溃或者断电，可能丢失1秒的数据。设置为该值，并使用具有电池支持的写缓存的RAID磁盘，配合使用innodb_flush_method=O_DIRECT，可能在保证安全性的前提下提高性能</li>
</ol>
<p>从5.6.6开始，日志文件刷出间隔由innodb_flush_log_at_timeout控制</p>
<p>不管该参数设置为多少，InnoDB崩溃恢复机制都会运行，事务要么被完整的应用，要么被彻底删除</p>
</td>
</tr>
<tr>
<td><strong><span style="background_color: #c0c0c0;">innodb_flush_method</span></strong></td>
<td>
<p>定义刷出数据到InnoDB数据文件、日志文件的方式，仅仅在Linux/Unix系统上可配置：</p>
<ol>
<li>fsync：执行fsync()系统调用来刷出数据和日志文件，这是默认设置</li>
<li>O_DSYNC：使用O_SYNC打开和刷出日志；使用fsync()刷出数据文件。一般不用，在很多Unix变种上存在问题</li>
<li>O_DIRECT：使用O_DIRECT来打开数据文件，使用fsync()刷出数据和日志文件</li>
<li>O_DIRECT_NO_FSYNC：在刷出I/O时使用O_DIRECT，但是跳过fsync()，只适用于特定文件系统</li>
</ol>
<p>O_DIRECT选项用于跳过OS缓存直接写入</p>
</td>
</tr>
<tr>
<td>innodb_flush_neighbors </td>
<td>
<p>默认1。 指示在刷出InnoDB缓冲池中一页的时候，是否把同一extent中的脏页一起刷出：</p>
<ol>
<li>值0：不刷出任何相邻脏页</li>
<li>值1：刷出位于缓冲池中同一extent中的连续脏页</li>
<li>值2：刷出位于缓冲池中同一extent中的脏页</li>
</ol>
<p>对于传统HDD，开启此选项有利于减低I/O负载；对于SSD，由于不存在寻道问题，可以关闭该选项</p>
</td>
</tr>
<tr>
<td>innodb_flushing_avg_loops </td>
<td>默认30。 控制InnoDB自适应刷出机制相应工作负载变化的速度，设置的越大，响应速度越慢</td>
</tr>
<tr>
<td>innodb_force_load_corrupted </td>
<td>默认FALSE。 强制加载被污染的表，如果某些表因为数据损坏而无法访问，需要手工处理，可以打开此标记，处理完毕后再关闭</td>
</tr>
<tr>
<td>innodb_force_recovery </td>
<td>默认0。 可以设置0_6，只在非常验证的诊断场景下才需要更改</td>
</tr>
<tr>
<td><span style="background_color: #c0c0c0;"><strong>innodb_io_capacity  </strong></span></td>
<td>
<p>默认200。 设置InnoDB后台任务（从缓冲池刷出页、合并变更缓冲里的数据等）能执行的I/O活动的上限。该参数供所有缓冲池实例使用，多个缓冲池会平分这一参数</p>
<p>该参数可以设置的与系统的IOPS相近，但是在满足需要的前提下，应当尽量的低，如果设置的过高（例如100万），数据可能过快的移除、插入缓冲池，而导致缓冲池的缓存价值变低</p>
<p>对于5400/7200转的老旧硬盘，有可能需要设置到100。当前的默认值适应大部分现代设备和较高的I/O速率</p>
<p>对于具有多磁盘、SSD等能够支持高IOPS存储设备的系统，提高该参数可能获益</p>
</td>
</tr>
<tr>
<td>innodb_io_capacity_max </td>
<td>紧急情况下InnoDB后台任务最大I/O能力，如果不设置，默认值是innodb_io_capacity的2倍，并且大于2000</td>
</tr>
<tr>
<td><strong><span style="background_color: #c0c0c0;">innodb_lock_wait_timeout </span></strong></td>
<td>
<p>默认50。 在InnoDB事务放弃前，等待行锁的最长时间，默认50秒。放弃后，会打印Lock wait timeout exceeded信息，并且导致当前语句（而不是整个事务）回滚，innodb_rollback_on_timeout参数会在此场景下导致整个事务回滚</p>
<p>对于高交互性应用或者OLTP，该参数可能需要调低</p>
<p>注意：</p>
<ol>
<li>该参数只会影响InnoDB的行级锁</li>
<li>该参数对死锁没有作用，InnoDB会立即检测到死锁，并回滚其中一个事务</li>
</ol>
</td>
</tr>
<tr>
<td><strong><span style="background_color: #c0c0c0;">innodb_log_buffer_size </span></strong></td>
<td>
<p>默认8388608，范围在256KB到4GB之间。InnoDB重做日志的内存缓冲大小，InnoDB事务在修改任何数据时，总是先写入到该缓冲，只在<span style="background_color: #c0c0c0;">缓冲已满、事务提交、或者过了N秒后</span>，才会把该缓冲刷出到重做日志文件中。</p>
<p>如果存在大量的、增删改大量行或者LOB的事务，可以增加此参数，以减少磁盘I/O。</p>
</td>
</tr>
<tr>
<td>innodb_log_compressed_pages </td>
<td>默认TRUE。 指定是否重压缩的页镜像被存放在重做日志中</td>
</tr>
<tr>
<td><strong><span style="background_color: #c0c0c0;">innodb_log_file_size </span></strong></td>
<td>
<p>默认50331648。 InnoDB重做日志组中，单个文件的大小，innodb_log_file_size *innodb_log_files_in_group不得超过512G。</p>
<p>1MB到innodb_buffer_pool_size/innodb_log_files_in_group是合理的取值范围。该值设置的越大，InnoDB缓冲池需要的刷出操作越少，因而磁盘I/O也越少，对于高并发负载，可以设置到GB级别</p>
<p>尽管5.5以后，重做日志文件的大小对崩溃恢复速度的影响变小，但是，<span style="background_color: #c0c0c0;">该参数越大，崩溃恢复时间仍然越长</span></p>
<p>注意修改此值要准守一定的步骤，在高性能MySQL学习笔记中有详细说明</p>
</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_log_files_in_group </span></strong></td>
<td>默认2。 InnoDB重做日志组包含的文件个数，InnoDB采取循环写入的方式使用这些文件</td>
</tr>
<tr>
<td>innodb_log_group_home_dir</td>
<td>InnoDB重做日志的存放目录</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>innodb_lru_scan_depth </strong></span></td>
<td>默认1024。 影响InnoDB缓冲池的刷出操作，用于调优I/O敏感的工作负载。该参数指定了页面清理线程遍历（每秒执行一次）缓冲池LRU列表最大的深度。如果在某个工作负载下还具有空闲的I/O能力，可以增加该参数；如果具有大缓冲池，并且写敏感工作负载使I/O能力饱和，则可以减少该值</td>
</tr>
<tr>
<td><strong><span style="background_color: #c0c0c0;">innodb_max_dirty_pages_pct </span></strong></td>
<td>
<p>默认75。 InnoDB会把缓冲池中的页刷出到数据文件，以确保脏页占比不超过此参数指定的比例。该参数不会影响刷出操作的速率</p>
<p>该参数设置的很大时，如果重做日志空间不足，可能导致激烈的刷出操作</p>
</td>
</tr>
<tr>
<td>innodb_max_dirty_pages_pct_lwm</td>
<td>默认0。 脏页低水位，超过该比例后，预刷出（preflushing）机制被启用，以控制脏页比率。默认禁用</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_max_purge_lag</span></strong></td>
<td>
<p>默认0。 当清理操作迟滞时，如何延迟insert/update/delete的执行，默认值表示不延迟。</p>
<p>InnoDB事务系统维护一个transactiond的列表，这些transaction执行了update/delete操作而导致某些索引记录被标记为删除。该列表的长度相当于清理迟滞（purge lag）的值，当purge lag大于innodb_max_purge_lag时所有insert/update/delete操作将被延迟执行。迟滞值会显示在InnoDB状态输出：</p>
<pre class="crayon-plain-tag">Purge done for trx's n:o &lt; 0 290315608 undo n:o &lt; 0 17
#迟滞值为20
History list length 20</pre>
<p>对于小事务（100bytes）典型的设置可能是100万，为了避免过分的延迟，可以设置innodb_max_purge_lag_delay</p>
</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_max_purge_lag_delay</span></strong></td>
<td>默认0。 最大延迟毫秒数，参考上一个参数</td>
</tr>
<tr>
<td>innodb_monitor_disable </td>
<td rowspan="2">启禁information_schema.innodb_metrics中一个或者多个计数器</td>
</tr>
<tr>
<td>innodb_monitor_enable </td>
</tr>
<tr>
<td>innodb_monitor_reset  </td>
<td rowspan="2">重置information_schema.innodb_metrics中的计数器</td>
</tr>
<tr>
<td>innodb_monitor_reset_all</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_old_blocks_pct</span></strong></td>
<td>默认37。 InnoDB缓冲池中供旧块（old block）子列表使用的比例，默认3/8，通常和innodb_old_blocks_time联用</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>innodb_old_blocks_time</strong></span></td>
<td>
<p>默认1000。 非0值可以用来预防InnoDB缓冲池被仅仅短时间引用的数据（例如全表扫描）充满，增加此值可以增强前述预防功能。</p>
<p>该参数指定一个毫秒数的阈值，插入缓冲池old sublist的块，必须至少等待此阈值后，被访问时才被转移到new sublist中；如果设置为0，一旦old sublist中的块被访问，就插入到new sublist中</p>
<p>old/new块是InnoDB缓冲池改进的LRU算法引入的概念，该算法致力于尽量让hot pages（经常被访问的）驻留在缓冲池中。某些全表扫描、预读导致加入缓冲池的块，可能后续永远不会被使用，也就没有缓存价值，因此，InnoDB默认把新读进去的块存放在LRU列表的后面3/8部分，此部分即old list，存放在其中的块即old blocks</p>
</td>
</tr>
<tr>
<td>innodb_open_files</td>
<td>默认0。 限制InnoDB最多同时打开的.ibd文件的数量，只在使用多个表空间（例如file per table）有意义</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_page_size</span></strong></td>
<td>
<p>默认16384。 所有InnoDB表空间使用的页的大小。</p>
<p>较小的页更适合存在代码small write的OLTP场景，因为大页包含多个的多行可能出现争用；较小的页亦适用于SSD，因为SSD通常使用小的块，例如4KB。让InnoDB页大小与硬盘的块大小接近，可以避免对未更改数据的重新写入</p>
<p>默认值16KB适用于广泛的应用场景，特别是牵涉到表扫描、批量更新的DML操作的场景</p>
</td>
</tr>
<tr>
<td>innodb_print_all_deadlocks </td>
<td>
<p>默认FALSE。 启用后，所有的死锁信息记录在mysqld的错误日志中，否则只用通过InnoDB引擎状态查看最后的死锁。</p>
<p>偶发的死锁不需要特别关注，因为InnoDB会立即检测并回滚事务。如果应用程序没有合适的错误处理、重试逻辑，可以打开此选项进行检测。一般可能需要调整代码中锁的获取顺序，避免死锁</p>
</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_purge_batch_size </span></strong></td>
<td>默认300。 重做日志中的记录数到达多少时，触发清理操作，这会导致InnoDB缓冲池中的脏页被刷出到数据文件，通常和innodb_purge_threads联用</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_purge_threads </span></strong></td>
<td>默认1。 专用于InnoDB清理操作的后台线程的数量，最大值32</td>
</tr>
<tr>
<td>innodb_random_read_ahead </td>
<td>默认FALSE。 启用随机预读（random read-ahead ）来优化InnoDB的I/O</td>
</tr>
<tr>
<td>innodb_read_ahead_threshold </td>
<td>
<p>默认56，范围0-64。 控制线性预读（linear read-ahead）的敏感性。InnoDB会立即预读当前extent内至少innodb_read_ahead_threshold个页（注意默认一个extent是64页），并发起一个异步读，来读取整个extent后续的页</p>
<p>预读不一定有价值，状态变量Innodb_buffer_pool_read_ahead_evicted/Innodb_buffer_pool_read_ahead的比值可以看到哪些页在预读后没有被使用就清除了。此外，InnoDB状态输出中可以看到预读和预读立即清除的速率：</p>
<pre class="crayon-plain-tag">----------------------
BUFFER POOL AND MEMORY
----------------------
Pages read ahead 0.00/s, 
evicted without access 0.00/s, 
Random read ahead 0.00/s</pre>
</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_read_io_threads</span></strong></td>
<td>默认4。 执行读操作的InnoDB线程数量</td>
</tr>
<tr>
<td>innodb_read_only </td>
<td>默认FALSE。 以只读模式运行，可以运用在数据仓库中，让多个MySQL实例共享一份数据文件</td>
</tr>
<tr>
<td>innodb_replication_delay</td>
<td>默认0。 当innodb_thread_concurrency到达时，Slave上的复制线程延迟的毫秒数</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_rollback_on_timeout </span></strong></td>
<td>默认FALSE。 5.6版本的默认情况下，事务超时只会导致最后一个语句被回滚，指定该选项可以让整个事务回滚</td>
</tr>
<tr>
<td>innodb_rollback_segments </td>
<td>默认128。 指定系统表空间中有多少个回滚段，该参数会被innodb_undo_logs覆盖</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_sort_buffer_size </span></strong></td>
<td>默认1048576。 当创建InnoDB索引的时候，可以使用多大的缓冲区来对数据进行排序。索引创建完毕后，此缓冲被释放，该参数不会影响后续的索引维护操作</td>
</tr>
<tr>
<td>innodb_spin_wait_delay</td>
<td>默认6。 轮询自旋锁时最大的延迟，由于硬件和OS的不同，该参数不能映射到具体的时间间隔</td>
</tr>
<tr>
<td>innodb_stats_auto_recalc </td>
<td>默认TRUE。 当表的数据发生足够量的改变时，导致InnoDB自动重新计算索引统计信息</td>
</tr>
<tr>
<td>innodb_stats_method </td>
<td>
<p>默认nulls_equal。 收集统计信息时，如何处理InnoDB索引中的NULL值：
<ol>
<li>nulls_equal：认为所有NULL值相等，并构成一个单独的value group</li>
<li>nulls_unequal：认为NULL值各不相等，单独形成value group</li>
<li>nulls_ignored：忽略NULL值的统计价值</li>
</ol>
<p>生成表统计信息的方式，影响查询优化器对执行计划的选择</p>
</td>
</tr>
<tr>
<td>innodb_stats_on_metadata </td>
<td>默认FALSE。 如果启用，InnoDB在元数据语句（metadata statements，例如show table status;show index）被执行时或者查询information_schema.tables|statistics时，自动更新统计信息</td>
</tr>
<tr>
<td>innodb_stats_persistent </td>
<td>默认TRUE。 InnoDO索引统计信息是否持久化保存。create|alter表时使用stat persistent从句可以按单个表的设置此参数</td>
</tr>
<tr>
<td>innodb_stats_persistent_sample_pages</td>
<td>默认20。 进行索引信息统计时，采样的索引页的数量。增加此值会提高统计信息的精确度，但是导致analyze table的I/O成本变大。对innodb_stats_persistent=true的表有效</td>
</tr>
<tr>
<td>innodb_stats_transient_sample_pages </td>
<td>默认8。 类似上面，对innodb_stats_persistent=false的表有效</td>
</tr>
<tr>
<td>innodb_strict_mode </td>
<td>默认FALSE。 如果打开，在某些情况下返回错误，而不是警告</td>
</tr>
<tr>
<td>innodb_support_xa </td>
<td>默认TRUE。 启用两阶段提交的XA分布式事务支持。在master上不要关闭</td>
</tr>
<tr>
<td>innodb_sync_spin_loops</td>
<td>自旋锁的最大循环次数，修改此值可以在无谓的线程上下文切换以及浪费CPU时间之间进行平衡</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_thread_concurrency</span></strong></td>
<td>
<p>默认0。 最大并发线程数，即InnoDB持有的，负责事务处理的最大操作系统线程数。一旦到达限制，其它线程按FIFO排队，因等待锁而阻塞的线程不计算为并发线程</p>
<p>值范围0-1000，0表示无限制，InnoDB会根据需要创建足够多的线程，0亦导致InnoDB状态输出的queries inside InnoDB、queries in queue counters段落被禁用</p>
<p>该参数合理的值依赖于具体的工作负载，特别是MySQL需要和其它应用程序共享CPU的时候。如果设置为某个值后性能很差，可以实时的修改为0</p>
<p>下面是一些帮助确定合理值的经验：</p>
<ol>
<li>如果当前工作负载并发的用户线程小于64，设置为0</li>
<li>如果工作负载持续沉重或偶尔卡住，初始设置为128，然后96/80/64的递减，直到找到最佳性能点。比如工作负载通常有40-50用户，但是周期性的增加到60/70甚至200，如果发现在80用户时性能稳定，继续增加时则性能衰退，可能innodb_thread_concurrency=80是较好的点</li>
<li>如果不希望InnoDB的用户线程操作指定的虚拟处理器数量，那么可以设置该参数为那个数量；如果要隔离MySQL对其它应用的影响，可以将其绑定到虚拟处理器</li>
<li>该参数过大，可能导致性能衰退，原因是过多的线程争用系统资源</li>
</ol>
</td>
</tr>
<tr>
<td>innodb_thread_sleep_delay </td>
<td>
<p>默认10000。 InnoDB加入队列前休眠的微秒数</p>
<p>对于5.6.3+，可以设置innodb_adaptive_max_sleep_delay为一个允许的最大休眠时间，并让InnoDB自适应的调整</p>
</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>innodb_undo_directory</strong></span></td>
<td>默认 .。 InnoDB为撤销日志提供独立表空间的目录，典型的做法是把撤销日志存放在独立的磁盘上。该参数与innodb_undo_logs、innodb_undo_tablespaces联用。默认值.表示和其它日志文件放在一个目录</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_undo_logs</span></strong></td>
<td>
<p>默认128。 撤销日志（或者说回滚段）的数量，每个撤销日志最多可以持有1024个事务。该参数覆盖innodb_rollback_segments。</p>
<p>当观察到针对撤销日志的争用时，可以调整该值，状态变量Innodb_available_undo_logs反应了目前可用的撤销日志的数量</p>
</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>innodb_undo_tablespaces</strong></span></td>
<td>
<p>默认0。 撤销日志表空间的数量，撤销日志会被分配到这些表空间中。注意，系统表空间总是包含一个撤销日志表空间，后者默认用来存放所有撤销日志</p>
<p>额外的撤销表空间被创建在innodb_undo_directory目录中，命令为undoN，每个表空间的默认大小10MB</p>
<p>该参数必须在InnoDB初始前就设置，如果在运行过程中修改为更大的值，会导致MySQL启动失败</p>
</td>
</tr>
<tr>
<td>innodb_use_native_aio </td>
<td>
<p>默认TRUE。 仅用于Linux，是否使用操作系统的异步I/O子系统，在5.5+原先仅存在与Windows的异步I/O被添加到Linux版本中。该选项提高了I/O-bound系统的性能</p>
<p>开启此选项时，如果具有大量的InnoDB的I/O线程，可能导致超过Linux系统的限制，从而出现：<pre class="crayon-plain-tag">EAGAIN: The specified maxevents exceeds the user's limit of available events.</pre> 错误，此时可以调整<pre class="crayon-plain-tag">/proc/sys/fs/aio-max-nr</pre> 中的限制值</p>
</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">innodb_write_io_threads</span></strong></td>
<td>默认4。 执行写操作的InnoDB线程数量</td>
</tr>
<tr>
<td>interactive_timeout </td>
<td>默认28800。 交互式客户端超时时间，</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>join_buffer_size </strong></span></td>
<td>
<p>默认262144，32bit系统最大支持4GB-1。 用于普通索引扫描（plain index scans）、范围索引扫描或者不使用索引而进行全表扫描的join操作的缓冲区的最小值</p>
<p>加快join的最好方式是创建合适的索引，如果不能增加索引，才考虑调整此参数的值。每个缓冲只用于两个表的join，因此某些没有用到索引的复杂多表join查询，可能需要用到多个这样的缓冲</p>
<p>不应该将此参数的全局值设置的过大，最好只在执行大join的会话级别设置</p>
</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">key_buffer_size </span></strong></td>
<td>
<p>默认8388608，32bit系统最大支持4GB-1。 MyISAM表的索引块（Index blocks）被缓冲，并由所有线程共享，这个缓冲池被称为Key buffer或者Key cache。该参数指定此缓冲池的大小</p>
<p>增加此参数的值，能够提高所有读操作、很多写操作的性能，对于专用MyISAM的服务器，可以设置到物理内存的25%。如果设置的过高，可能导致OS执行内存换页，导致极差的性能，由于MySQL依赖于OS提供的文件系统缓存来读取数据，必须为OS文件系统缓存预留内存</p>
<p>状态变量：Key_read_requests、Key_reads、 Key_write_requests、Key_writes用于监测此键缓存的性能，Key_reads/Key_read_requests正常情况下应当小于0.01</p>
<p>公式：<pre class="crayon-plain-tag">1 - ((Key_blocks_unused * key_cache_block_size) / key_buffer_size)</pre> 用于计算键缓存当前使用比</p>
</td>
</tr>
<tr>
<td>key_cache_age_threshold </td>
<td>默认300。 影响MyISAM键缓存的内部数据移动，设置的越小，数据从hot子列表转移到warm子列表的速度越快</td>
</tr>
<tr>
<td>key_cache_block_size </td>
<td>默认1024。 MyISAM键缓存的数据块的大小</td>
</tr>
<tr>
<td>key_cache_division_limit </td>
<td>默认100。 MyISAM键缓存hot/warm的比例，该参数指定了warm子列表的百分比</td>
</tr>
<tr>
<td>large_pages </td>
<td>默认FALSE。 是否启用大页（large page）支持</td>
</tr>
<tr>
<td>large_pages_size</td>
<td>大内存页的大小，目前仅Linux支持</td>
</tr>
<tr>
<td>lock_wait_timeout </td>
<td>
<p>默认31536000。 尝试获取元数据锁（metadata locks）的超时时间。该超时应用到所有使用元数据锁的语句，包括在表、视图上执行的DDL、DML；存储函数；lock tables；flush tables with read lock</p>
<p>该超时不会应用到针对mysql库中对象的隐式访问，例如grant/revoke语句，但是对系统表的显示访问，例如select/update，会使用到该超时</p>
<p>该超时会单独应用到每个元数据锁的获取</p>
</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">log_bin</span></strong></td>
<td>默认FALSE。是否启用二进制日志的记录</td>
</tr>
<tr>
<td>log_bin_basename</td>
<td>默认datadir + '/' + hostname + '-bin'。二进制日志的路径和basename</td>
</tr>
<tr>
<td>log_bin_index</td>
<td>二进制日志的索引文件名</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>log_error</strong></span></td>
<td>默认hostname.err。记录启动消息和错误日志的文件名</td>
</tr>
<tr>
<td>log_output </td>
<td>默认FILE。 指定一般查询日志、缓慢查询日志的输出类型，可以选择FILE/TABLE/NONE，如果设置为TABLE，将在mysql库建立general_log、slow_log表以存放日志</td>
</tr>
<tr>
<td>log_queries_not_using_indexes </td>
<td>默认FALSE。 如果启用缓慢查询日志的同时启用该参数，那么没有使用索引的查询、虽然使用索引单进行了全表扫描的查询会被记录</td>
</tr>
<tr>
<td>log_raw </td>
<td>默认FALSE。 开启后在记录查询日志时，使用明文密码</td>
</tr>
<tr>
<td>log_short_format</td>
<td>默认FALSE。 记录更少的内容到二进制日志或者查询日志</td>
</tr>
<tr>
<td>log_slave_updates</td>
<td>默认FALSE。 Slave是否把从Master接收到的更新记录到自己的二进制日志中，如果开启，需要Slave启用二进制日志</td>
</tr>
<tr>
<td>log_slow_admin_statements</td>
<td>默认FALSE。 是否把管理性的缓慢查询记录到缓慢查询日志中，这些查询包括ALTER TABLE, ANALYZE TABLE, CHECK TABLE, CREATE INDEX, DROP INDEX, OPTIMIZE TABLE,REPAIR TABLE</td>
</tr>
<tr>
<td>log_slow_slave_statements</td>
<td>默认FALSE。 是否把Slave上执行缓慢的查询记录到Slave的缓慢查询日志中</td>
</tr>
<tr>
<td>log_throttle_queries_not_using_indexes </td>
<td>默认0。 当log_queries_not_using_indexes启用的时候，该参数限制每分钟进入缓慢日志的最大查询个数</td>
</tr>
<tr>
<td>log_warnings </td>
<td>默认1。 是否打印额外的警告信息到错误日志中</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">long_query_time </span></strong></td>
<td>默认10。 判断缓慢查询的阈值，单位秒。如果一个查询比执行时间超过此值，状态变量Slow_queries被增加，如果缓慢查询日志被启用，该查询会被写入日志</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">low_priority_updates </span></strong></td>
<td>
<p>默认FALSE。 如果设置为TRUE，那么所有insert、update、delete、lock table write语句都会等待，直到所有未决的select、lock table for read操作完成</p>
<p>该参数只对那些仅仅使用表级锁定的引擎有效，例如MyISAM, MEMORY, MERGE</p>
</td>
</tr>
<tr>
<td>lower_case_table_names </td>
<td>
<p>默认0。 只读属性，指示底层文件系统是否大小写敏感，0表示敏感</p>
<p>如果从Windows上Dump出数据库到Linux下，应该把该值设置为1，否则可能导致Table '***' doesn't exist的错误</p>
</td>
</tr>
<tr>
<td>master_info_file </td>
<td>默认master.info。 Slave记录Master信息的文件名</td>
</tr>
<tr>
<td>master_info_repository </td>
<td>默认FILE。 指示Slave记录Master的状态和连接信息到表而是文件</td>
</tr>
<tr>
<td>master_retry_count </td>
<td>默认86400。 在Slave放弃到Master的连接尝试前，重试的次数</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">max_allowed_packet </span></strong></td>
<td>
<p>默认4194304，最大1GB，必须设置为1024的倍数。 客户端把整个查询在单个数据报中发送，如果使用很大的查询语句（例如包含大的LOB字段），就必须调整该参数。数据报缓冲的初始值会被设置为net_buffer_length，最大可以增长到max_allowed_packet</p>
<p>在数据复制场景中，集群中每台服务器应当设置一样的值</p>
<p>客户端应当同时设置max_allowed_packet，尽管客户端Library中给出的默认值是1GB，但是不同的客户端程序可能覆盖了这以默认值，例如mysql、mysqldump这两个程序分别使用16MB、24MB作为默认值</p>
</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>max_binlog_cache_size </strong></span></td>
<td>默认18446744073709547520。 如果一个事务要求超过该参数指定的内存，MySQL发出一个<pre class="crayon-plain-tag">Multi-statement transaction required more than 'max_binlog_cache_size' bytes of storage</pre> 错误，适当的最大值应该设置为4GB，因为目前MySQL只能支持最大4GB的二进制日志缓冲</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">max_binlog_size </span></strong></td>
<td>默认1073741824。 如果一个对二进制日志的写操作导致当前日志超过max_binlog_size，则MySQL轮换日志（关闭当前日志，打开下一个日志）</td>
</tr>
<tr>
<td>max_binlog_stmt_cache_size </td>
<td>默认18446744073709547520。 二进制日志非事务性的语句缓存的最大大小</td>
</tr>
<tr>
<td>max_connect_errors </td>
<td>默认100。 如果某台主机连续这么多次连接是否，该主机（在重启前）将被禁止后续的连接尝试</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">max_connections </span></strong></td>
<td>默认151。 最大允许的同时连接数，如果经常出现Too Many Connections的错误提示则可能需要增加此值</td>
</tr>
<tr>
<td>max_error_count </td>
<td>默认64。 show errors和show warnings语句能够显示的最大错误、警告的数量</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">max_heap_table_size </span></strong></td>
<td>
<p>默认16777216。 用户创建的内存表的占用内存的最大数量，设置该参数对既有的内存表没有作用，除非这些内存表被某些DDL语句操控，例如create/truncate/alter</p>
<p>该参数常常和tmp_table_size联用，以限制MySQL内部内存表的尺寸</p>
</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>max_join_size </strong></span></td>
<td>
<p>默认18446744073709551615。 可以用于禁止执行这样的查询：需要检查超过max_join_size行的单表查询；组合数超过max_join_size的链表查询；可能需要进行超过max_join_size次磁盘寻道的查询</p>
<p>通过设置该参数，可以捕获到没有合理使用键的、因而可能需要很长执行时间的查询</p>
<p>设置该参数为非默认值，将导致sql_big_selects变为0；设置sql_big_selects为1，则该参数的值被忽略</p>
</td>
</tr>
<tr>
<td>max_length_for_sort_data </td>
<td>默认1024。 导致filesort算法被激活的索引值列表深度的阈值</td>
</tr>
<tr>
<td>max_prepared_stmt_count </td>
<td>默认16382。 限制服务器上预编译语句的最大数量，设置为0则禁用预编译语句</td>
</tr>
<tr>
<td>max_relay_log_size</td>
<td>默认0。 如果Slave的中继日志超过该参数，那么将导致日志轮换——当前日志文件被关闭并打开新文件</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">max_sort_length </span></strong></td>
<td>
<p>默认1024。 排序时，使用的字段的最大长度，这导致前max_sort_length个字符相等的字段被认为是相等的</p>
<p>对于5.6.9+，MySQL不对integer/decimal/float等数据类型应用该参数</p>
</td>
</tr>
<tr>
<td>max_sp_recursion_depth</td>
<td>默认0。 存储过程最多被递归调用的深度</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">max_user_connections </span></strong></td>
<td>默认0。 每个MySQL账户最多同时创建的连接数</td>
</tr>
<tr>
<td>max_write_lock_count </td>
<td>默认18446744073709551615。 在此数量的写锁之后，允许某些未决的读锁请求被处理</td>
</tr>
<tr>
<td>memlock </td>
<td>默认FALSE。 将mysqld锁定在内存中，方式OS将其交换到磁盘</td>
</tr>
<tr>
<td>metadata_locks_cache_size </td>
<td>默认1024。 元数据锁缓存的大小，该缓存被MySQL用来避免同步对象的创建和销毁。对于创建同步对象代价较大的OS，例如Windows XP，该参数有用</td>
</tr>
<tr>
<td>min_examined_row_limit </td>
<td>默认0。 检查行数小于此值的查询，不会被记录到缓慢查询日志</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">myisam_block_size </span></strong></td>
<td>默认1024。 MyISAM索引页的块大小</td>
</tr>
<tr>
<td>myisam_max_sort_file_size </td>
<td>默认9223372036853727232。 当重建MyISAM索引时，MySQL允许使用临时文件的最大尺寸</td>
</tr>
<tr>
<td>myisam_mmap_size </td>
<td>默认18446744073709551615。 用于MyISAM压缩文件的内存映射文件的最大内存占用，如果使用了大量MyISAM压缩表，可以减少此值以避免潜在的换页问题</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">myisam_repair_threads</span></strong></td>
<td>默认1。 如果设置的值超过1，当Repair by sorting时，MyISAM索引会被并行的创建</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">myisam_sort_buffer_size </span></strong></td>
<td>默认8388608。 用来排序MyISAM索引的缓冲区的大小，此排序会在repair table/create index/alter table时发生。该类操作的出现几率较小，提高此参数有利于增强性能</td>
</tr>
<tr>
<td>myisam_stats_method </td>
<td>默认nulls_unequal。 统计索引值分布时，处理MyISAM表null值的方法，该参数会影响优化器的行为</td>
</tr>
<tr>
<td>myisam_use_mmap </td>
<td>默认FALSE。 读写MyISAM表时，是否使用内存映射文件</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">net_buffer_length </span></strong></td>
<td>
<p>默认16384。 每个线程会关联一个connection buffer和一个result buffer，其初始值都是net_buffer_length，最大值则是max_allowed_packet。在每个语句执行完毕后result buffer都会收缩到net_buffer_length</p>
<p>该参数通常不需要修改，但是如果服务器内存特别小，可以将其设置为客户端发送的语句的期望大小</p>
</td>
</tr>
<tr>
<td>net_read_timeout </td>
<td>默认30。 服务器在读取客户端数据时，最大的超时时间</td>
</tr>
<tr>
<td>net_write_timeout </td>
<td>默认60。 服务器在向客户端写数据时，最大的超时时间 </td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">open_files_limit </span></strong></td>
<td>默认1024。 操作系统运行mysqld打开的文件的最大数量，如果设置的过小，可能导致too many open files错误，由于现代OS中打开文件几乎不需要消耗什么资源，可以设置为65535</td>
</tr>
<tr>
<td>partition </td>
<td>默认ON。 开启或者禁用用户定义的表分区功能</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">pid_file</span></strong></td>
<td>默认$INS_DIR/data/hostname.pid。 进程标识符文件的名字</td>
</tr>
<tr>
<td>plugin_dir</td>
<td>默认$INS_DIR/lib/plugin/。插件目录</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">port  </span></strong></td>
<td>默认3306。 mysqld的TCP/IP监听端口</td>
</tr>
<tr>
<td>port_open_timeout </td>
<td>默认0。 端口打开的超时时间，某些系统可能对端口重用的时间间隔有限定</td>
</tr>
<tr>
<td>preload_buffer_size </td>
<td>默认32768。 预加载索引时分配的缓冲的大小</td>
</tr>
<tr>
<td>profiling_history_size </td>
<td>默认15，最大值100。 当启用profiling时，保留profiling信息的历史语句的最大数量</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">query_cache_limit </span></strong></td>
<td>默认1048576。 单个查询能够使用的查询缓冲的大小，如果查询的结果超过此限制，将不被缓存</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">query_cache_min_res_unit </span></strong></td>
<td>默认4096。 每一次对查询结果进行缓存时，需要申请缓存内存中的一个块，该参数指定其最小大小，块的分配属于相对较慢的操作，因为MySQL需要检查空闲块列表，并找到一个足够大的</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">query_cache_size </span></strong></td>
<td>默认1048576。 设置全局的查询缓存的大小。修改此值后，MySQL立即清除所有的查询缓存，调整缓存的大小，并重新初始化缓存，由于MySQL是串行删除缓存的，所以可能导致服务器停顿较长时间。当一个查询语句执行完毕，其结果会被MySQL缓存，起来，如果后续到到来的SQL一模一样，则直接返回结果，不进入优化和执行阶段。但是，一旦缓存相关的任何表发生了变化，则所有缓存均必须被<span style="background-color: #c0c0c0;">串行的删除</span>，如果查询缓存特别大，则可能导致插入、更新的效率很低</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">query_cache_type </span></strong></td>
<td>默认0。 查询缓存的使用方式：0表示关闭；1表示使用（除非select sql_no_cache）；2表示按需（select sql_cache）</td>
</tr>
<tr>
<td>query_cache_wlock_invalidate</td>
<td>默认FALSE。 默认的，当一个客户端获得一个MyISAM表的写锁时，其它线程发起的、结果存在与查询缓存的语句不会被阻塞。如果设置该参数为true，将导致MyISAM写锁获得时将该表的所有查询缓存禁用，这导致所有后续对该表的请求等待锁的释放</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">query_prealloc_size </span></strong></td>
<td>
<p>默认8192。 用于查询解析和执行的缓冲的大小，该缓冲不会在一个语句执行后被释放。</p>
<p>如果需要运行复杂的查询，增大该值可能提高性能，因为它避免了在语句执行过程中的服务器段内存分配</p>
</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">read_buffer_size </span></strong></td>
<td>
<p>默认131072。 每一个对MyISAM表进行顺序扫描（sequential scan）的线程，MySQL为它扫描的每个表分配read_buffer_size的缓冲，如果需要做很多顺序扫描（或者扫描速度较慢），可能需要增加该值，该缓冲直到需要时才会分配</p>
<p>该值必须设置为4KB的倍数</p>
<p>对于所有引擎，下面的场景下该参数亦有效：</p>
<ol>
<li>当利用order by 对临时文件（而非临时表）中的行排序时，读缓冲可用于临时文件的索引缓存</li>
<li>当对分区表进行批量插入时</li>
<li>当缓存嵌套查询的中间结果时</li>
</ol>
<p>对于memory引擎，该参数还用于判断memory block的大小</p>
</td>
</tr>
<tr>
<td>read_only </td>
<td>默认FALSE。 除了具有super权限的用户，禁止任何写操作</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">read_rnd_buffer_size </span></strong></td>
<td>
<p>默认262144。随机读缓冲区的大小，该参数用于MyISAM表的数据读取，以及其它引擎的Multi-Range读操作的优化</p>
<p>对于基于键排序（key-sorting）的MyISAM查询，结果行将通过此缓冲来读取，以避免不必要的随机I/O</p>
<p>如果表很大或者没有存储在引擎的缓存中，基于辅助索引（secondary index，非聚簇索引）的范围扫描可能导致很多的随机磁盘访问。多范围读优化（Multi-Range Read Optimization，MRR）机制是MySQL用来减少此随机I/O的算法。该算法先仅仅扫描索引，然后收集相关行的主键并排序，最后依据排序后的主键来获取表数据，这样就可以减少一定的随机I/O。当使用到覆盖索引时，MRR不会被使用。当使用MRR时，在执行计划的Extra列可以看到Using MRR</p>
<p>在使用MRR时，read_rnd_buffer_size指示了主键缓冲区的大小，这限制了单次（single pass）能处理的数据量</p>
<p>该参数针对每个线程分配，因此不要设置过大的全局值，仅仅在必要时会话级别设置</p>
</td>
</tr>
<tr>
<td>relay_log</td>
<td>中继日志的名字格式，默认host_name-relay-bin</td>
</tr>
<tr>
<td>relay_log_index</td>
<td>存放中继日志索引信息的文件，默认host_name-relay-bin.index</td>
</tr>
<tr>
<td>relay_log_info_file </td>
<td>默认relay_log.info。 Slave存放关于中继日志信息的文件</td>
</tr>
<tr>
<td>relay_log_info_repository </td>
<td>默认FILE。 中继日志信息的保存形式，可以指定为表或者文件</td>
</tr>
<tr>
<td>relay_log_purge </td>
<td>默认TRUE。 是否在中继日志不需要时就进行清理</td>
</tr>
<tr>
<td>relay_log_recovery </td>
<td>默认FALSE。 是否在服务器启动后，立即进行中继日志的恢复</td>
</tr>
<tr>
<td>relay_log_space_limit </td>
<td>默认0。 所有中继日志使用的空间总额限制</td>
</tr>
<tr>
<td>safe_user_create </td>
<td>默认FALSE。 可以防止用户创建新的MySQL用户</td>
</tr>
<tr>
<td>server_id </td>
<td>默认0。 主从复制时，使用的服务器ID</td>
</tr>
<tr>
<td>server_uuid</td>
<td>5.6+添加的，在用户提供的server_id的基础上新的全局唯一服务器标识符，和主从复制有关</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>skip_external_locking</strong></span></td>
<td>当MySQL使用外部锁定（OS锁定）时，该参数为OFF。外部锁定仅对MyISAM引擎有意义</td>
</tr>
<tr>
<td>skip_grant_tables</td>
<td>默认FALSE。 使MySQL不适用授权系统来启动，因此任何人可以不受限制的访问所有数据库</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">skip_name_resolve</span></strong></td>
<td>
<p>默认FALSE。 当检查客户端连接时，不适用DNS系统，如果设置为TRUE，grant tables中的Host列必须是localhost或者IP地址</p>
</td>
</tr>
<tr>
<td>skip_networking</td>
<td>默认FALSE。 不进行网络监听，所有和mysqld的交互必须通过命名管道、UNIX套接字或者Windows 的共享内存完成。可以用于限制仅允许本机访问</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">skip_slave_start</span></strong></td>
<td>默认FALSE。 通知Slave不去启动slave threads。后续可以通过start slave启动这些线程</td>
</tr>
<tr>
<td>slave_checkpoint_group </td>
<td>默认512。 对于多线程Slave，在检查点（checkpoint ）操作被调用前，最大能处理的事务数量，超过此数量后检查点操作被调用，以更新show slave status中显示的Slave状态信息。对于单线程的Slave该参数没有意义</td>
</tr>
<tr>
<td>slave_checkpoint_period </td>
<td>默认300。 类似上面，但是限制的是时间（毫秒）而不是事务数量</td>
</tr>
<tr>
<td>slave_compressed_protocol </td>
<td>默认FALSE。 是否使用压缩的Slave/Master协议，前提是主从都支持压缩</td>
</tr>
<tr>
<td>slave_load_tmpdir</td>
<td>默认/tmp。 load data infile时Slave使用的临时目录</td>
</tr>
<tr>
<td>slave_max_allowed_packet</td>
<td>默认1073741824。 Slave的SQL和I/O线程支持的最大包大小</td>
</tr>
<tr>
<td>slave_net_timeout </td>
<td>默认3600。 终止对mast/slave连接的读取操作前的超时</td>
</tr>
<tr>
<td>slave_parallel_workers </td>
<td>默认0。 并行执行复制事件（事务）的线程数</td>
</tr>
<tr>
<td>slave_pending_jobs_size_max </td>
<td>默认16777216。 对于多线程Slave，该参数设置Slave工作队列持有尚未入库的事件能占用内存的最大空间</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">slave_skip_errors</span></strong></td>
<td>默认OFF。 通常，当发生错误时，Slave上的复制操作将终止，设置该参数为true则忽略错误，从而允许后续的手工处理</td>
</tr>
<tr>
<td>slave_transaction_retries </td>
<td>默认10。 如果因为InnoDB死锁导致Slave事务失败，最大的重试次数</td>
</tr>
<tr>
<td>slow_launch_time </td>
<td>默认2。 如果新建线程超过此时间，MySQL增加计数器Slow_launch_threads的值</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>slow_query_log </strong></span></td>
<td>默认FALSE。 是否启用缓慢查询日志记录</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">slow_query_log_file</span></strong></td>
<td>默认$INS_DIR/data/hostname_slow.log。缓慢查询日志的路径</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">socket</span></strong></td>
<td>默认 /tmp/mysql.sock。 对于Unix系统，该参数是本地客户端连接使用的套接字文件的名称；对于Windows系统，该参数是用于本地客户端连接的命名管道的名称，默认为MySQL</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">sort_buffer_size </span></strong></td>
<td>
<p>默认262144。 每个会话都会被分配sort_buffer_size大小的排序缓冲，如果show global status输出每秒Sort_merge_passes增加很大，提示需要增加此参数的值，以便提高无法受益于索引、优化器的order by或者group by查询性能</p>
<p>该参数是为每个线程分配的，设置过大的全局值不但消耗内存（即使不用排序，该缓冲也分配给线程）而且会导致大部分查询变慢，因此最好是在session级别设置</p>
</td>
</tr>
<tr>
<td>super_large_pages </td>
<td>默认FALSE。 是否使用超大（内存）页的支持</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">sync_binlog </span></strong></td>
<td>默认0。 仅用于master，每次事务提交时同步二进制日志，防止崩溃导致的数据丢失</td>
</tr>
<tr>
<td>sync_frm </td>
<td>默认TRUE。 如果设置为1，所有非临时表被创建后，其frm文件被同步到磁盘</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">table_definition_cache </span></strong></td>
<td>默认值依据400 + (table_open_cache / 2)计算。 表定义缓存（.frm）存放表定义的最大数量。如果使用了大量表，增加此缓存可以提高open table的速度</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">table_open_cache </span></strong></td>
<td>默认431。 所有线程能够同时打开的表数量，增加该参数则mysqld需要更多的文件描述符</td>
</tr>
<tr>
<td>table_open_cache_instances </td>
<td>默认1。 上述缓存的缓存池数量</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">thread_cache_size</span></strong></td>
<td>
<p>默认8 + (max_connections / 100)。 服务器创建多少线程以备重用，当一个客户端断开时，如果线程池数量大于thread_cache_size，则该客户端的线程被销毁而不是返回池中。</p>
<p>新连接请求到达时，会首先检查线程池，如果没有空闲线程才会新建</p>
<p>在有良好的线程库的情况下，修改此参数不会有很大的性能提升，但是如果工作负载包含大量短时连接请求（100+/s），则有必要增大此参数</p>
</td>
</tr>
<tr>
<td>thread_concurrency</td>
<td>默认 10。 仅用于Solaris 8-，已废弃</td>
</tr>
<tr>
<td>thread_handling </td>
<td>默认one_thread_per_connection。 线程处理模型，no-threads用于Linux下的debug。如果使用了线程池插件，则可以设置为dynamically-loaded</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">thread_stack</span></strong> </td>
<td>默认262144。 线程的栈的大小，32bit系统默认192KB，64bit系统默认256KB，这些默认值一般足够使用，如果该参数设置的过小，则服务器处理复杂查询的能力受限</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">tmp_table_size </span></strong></td>
<td>
<p>默认16777216。 MySQL内部创建的内存临时表的最大尺寸，该尺寸同时受到max_heap_table_size的限制。如果某个内存临时表超过该参数的限制，MySQL自动将其转换为MyISAM磁盘表</p>
<p>如果需要大量复杂的group by查询并且用于很多内存，则增加max_heap_table_size和tmp_table_size的值</p>
<p>状态变量Created_tmp_disk_tables、Created_tmp_tables有利于判断是否需要增加此值</p>
</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">tmpdir </span></strong></td>
<td>
<p>默认/tmp。 用于创建临时文件、临时表的目录多个文件用:（Linux）或者;（Windows）分隔</p>
<p>使用多个目录可以用来磁盘的负载均衡；使用SSD分区有利于提高性能</p>
</td>
</tr>
<tr>
<td>transaction_alloc_block_size </td>
<td>默认8192。 每个事务独占的内存池的增量分配大小</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">transaction_isolation </span></strong></td>
<td>默认REPEATABLE_READ。事务的隔离级别，支持的值包括 READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE</td>
</tr>
<tr>
<td><strong><span style="background-color: #c0c0c0;">transaction_prealloc_size </span></strong></td>
<td>
<p>默认4096。 每个事务独占的内存池的初始分配大小，如果事务内存池不足，则后续每次分配transaction_alloc_block_size字节，事务结束后，该内存池截断为transaction_prealloc_size</p>
<p>设置该参数足够大，以包含事务的全部语句，可以避免过多的malloc()操作</p>
</td>
</tr>
<tr>
<td>transaction_read_only </td>
<td>默认FALSE。 设置默认的事务访问级别，默认禁用了只读事务</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">配置文件示例（16G专用服务器）</span></div>
<pre class="crayon-plain-tag">[client]
default_character_set=utf8
socket=/var/lib/mysql/mysql.sock
port=3306

[mysql]
default_character_set=utf8

[mysqld]
character_set_server                = utf8
basedir                             = /var/lib/mysql/
datadir                             = /var/lib/mysql/data/                
socket                              = /var/lib/mysql/mysql.sock     
pid_file                            = /var/lib/mysql/mysql.pid        
user                                = mysql                       
port                                = 3306
default_storage_engine              = InnoDB      
transaction_isolation=READ-COMMITTED      
innodb_log_buffer_size              = 8M
innodb_buffer_pool_size             = 4096M                  
innodb_log_file_size                = 512M
innodb_log_files_in_group           = 2
innodb_flush_log_at_trx_commit      = 2
innodb_flush_method                 = O_DIRECT
innodb_file_per_table               = 1                               
innodb_thread_concurrency           = 0
innodb_data_file_path               = ibdata1:1024M:autoextend 
innodb_max_dirty_pages_pct          = 90
innodb_lock_wait_timeout            = 30
innodb_sync_spin_loops              = 100
innodb_file_io_threads              = 4
innodb_read_io_threads              = 4
innodb_write_io_threads             = 4

key_buffer_size                     = 2048M
myisam_block_size                   = 4K
skip_external_locking
myisam-recover

sort_buffer_size                    = 2M
join_buffer_size                    = 2M
read_buffer_size                    = 1M 
read_rnd_buffer_size                = 16M 
bulk_insert_buffer_size             = 64M 
tmp_table_size                      = 256M
max_heap_table_size                 = 256M
thread_stack                        = 192K 
query_cache_type                    = 0
query_cache_size                    = 64M
query_cache_limit                   = 4M 
query_cache_min_res_unit            = 2K
max_connections                     = 3000
max_connect_errors                  = 32
max_allowed_packet                  = 32M
thread_cache_size                   = 256
table_cache_size                    = 9000
open_files_limit                    = 65535    
back_log                            = 600   

log_error                           = /var/lib/mysql/mysql-error.log
long_query_time                     = 2
slow_query_log_file                 = /var/lib/mysql/mysql-slow.log

log_bin                             = /var/lib/mysql/mysql-bin 
server_id                           = 1
sync_binlog                         = 1
innodb_support_xa                   = 1
log_slave_updates                   = 1
relay_log                           = /var/lib/mysql/mysql-relay-bin
skip_slave_start                    = 1
sync_master_info                    = 1
sync_relay_log                      = 1
sync_relay_log_info                 = 1
binlog_cache_size                   = 4M
max_binlog_cache_size               = 8M
max_binlog_size                     = 512M

read_only                           = 1 
expire_logs_days                    = 7
lower_case_table_names              = 1
skip_name_resolve                   = 1

[mysqldump]
quick</pre>
<div class="blog_h2"><span class="graybg">状态变量（Status Variables）</span></div>
<div class="blog_h3"><span class="graybg">show status命令简介</span></div>
<p>使用show status命令、mysqladmin extended-status可以查看服务器/连接的状态信息，<span style="background-color: #c0c0c0;">这些状态是只读的</span>：</p>
<pre class="crayon-plain-tag">mysql&gt; show status like 'Innodb_buffer_pool_pages_d%';
+---------------------------------------+----------+
| Variable_name                         | Value    |
+---------------------------------------+----------+
| Innodb_buffer_pool_pages_data         | 993      |
+---------------------------------------+----------+
#使用information_schema亦可：
mysql&gt; SELECT * FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME  LIKE 'INNODB_BUFFER_POOL%'; --显示全局状态
mysql&gt; SELECT * FROM information_schema.SESSION_STATUS WHERE VARIABLE_NAME LIKE 'INNODB_BUFFER_POOL%'; --显示会话状态</pre>
<p>MySQL5.0以后，show status的功能发生很大变化，MySQL把一部分变量全局的维护，另外一部分<span style="background-color: #c0c0c0;">per-connection</span>的维护，<span style="background-color: #c0c0c0;">show status则是显示两者的混合</span>。</p>
<p><span style="background-color: #c0c0c0;">通过比较一段时间前后的值，可以总体上评估MySQL的工作负载</span>。使用innotop的命令摘要模式（Command Summary mode），可以很容易看到这种对比，亦可使用命令：</p>
<pre class="crayon-plain-tag">#每10秒更新一次包含Select的变量
mysqladmin -uroot -ppassword extended -r -i10 | grep Select</pre>
<p>或者这样的SQL：</p>
<pre class="crayon-plain-tag">SELECT STRAIGHT_JOIN
	LOWER(gs0.VARIABLE_NAME) AS variable_name,
	gs0.VARIABLE_VALUE AS value_0,
	gs1.VARIABLE_VALUE AS value_1,
	( s1.VARIABLE_VALUE - gs0.VARIABLE_VALUE ) AS diff,
	( gs1.VARIABLE_VALUE - gs0.VARIABLE_VALUE ) / 10 AS per_sec,
	( gs1.VARIABLE_VALUE - gs0.VARIABLE_VALUE ) * 60 / 10 AS per_min
FROM
	( SELECT VARIABLE_NAME, VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS UNION ALL
		 SELECT '', SLEEP(10) FROM DUAL
	) AS gs0
JOIN information_schema.GLOBAL_STATUS gs1 USING (VARIABLE_NAME)
WHERE gs1.VARIABLE_VALUE &lt;&gt; gs0.VARIABLE_VALUE;</pre>
<div class="blog_h3"><span class="graybg">状态变量分类</span></div>
<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>线程和连接统计</td>
<td>这些变量跟踪连接尝试、中断的连接、网络流量、线程统计信息</td>
</tr>
<tr>
<td>二进制日志状态</td>
<td>Binlog_cache_use：已经存在于二进制缓存中的事务数<br />Binlog_cache_disk_use：有多少事务太大，导致其语句被存放在临时文件<br />Binlog_stmt_cache_use：对于非事务性语句，已经存在于缓存的数量<br />Binlog_stmt_cache_disk_use：对于非事务性语句，语句太大导致其被存放在临时文件<br />二进制缓存命中率对配置二进制缓存大小没有意义</td>
</tr>
<tr>
<td>命令计数器</td>
<td>
<p>Com_*开头的变量统计各种SQL、底层API命令被执行的次数</p>
</td>
</tr>
<tr>
<td>临时文件和表</td>
<td>Created_tmp*显示系统内部创建的临时表和文件的信息</td>
</tr>
<tr>
<td>Handler Operations</td>
<td>Handler API是MySQL与存储引擎的接口。Handler_*统计handler Op的数量</td>
</tr>
<tr>
<td>MyISAM键缓冲</td>
<td>Key_*显示MyISAM键缓冲的度量和计数信息</td>
</tr>
<tr>
<td>文件描述符</td>
<td>如果主要是用MyISAM，可以通过Open_*查看打开.frm、.MYI、.MYD文件的频繁度</td>
</tr>
<tr>
<td>查询缓存</td>
<td>Qcache_*显示查询缓存相关的统计信息</td>
</tr>
<tr>
<td>SELECT查询类型</td>
<td>
<p>Select_*显示各类查询操作的数量，可能提示查询性能问题</p>
</td>
</tr>
<tr>
<td>排序</td>
<td>
<p>Sort_*显示文件排序的状态信息</p>
</td>
</tr>
<tr>
<td>表锁定</td>
<td>显示服务器级别（不是引擎级别）的锁定信息，Table_locks_immediate、Table_locks_waited分别显示多少锁是立即分配，多少是必须等待的</td>
</tr>
<tr>
<td>InnoDB相关</td>
<td>Innodb_*开头的信息显示了一些包含在show engine innodb status中的内容。注意：检查这些值会导致对InnoDB缓冲池的全局锁定，因此会影响性能，对某些参数的准确性产生影响</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">状态变量详解</span></div>
<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>Aborted_clients</td>
<td>表示客户端连接被中断，原因可能是程序忘记关闭连接，或者客户端意外终止</td>
</tr>
<tr>
<td>Aborted_connects</td>
<td>连接MySQL的尝试失败（错误的密码、数据库名、telnet了3306端口）的数量，如果值太高可能导致某个主机被阻拦。更细致的相关性信息可以从 Connection_errors_*查看</td>
</tr>
<tr>
<td>Binlog_cache_disk_use</td>
<td>使用了临时二进制日志缓存，并且超过binlog_cache_size限制，导致使用临时文件的事务的数量</td>
</tr>
<tr>
<td>Binlog_cache_use</td>
<td>使用了二进制日志缓存的事务数量</td>
</tr>
<tr>
<td>Binlog_stmt_cache_disk_use</td>
<td>使用了二进制日志语句缓存，并且超过binlog_stmt_cache_size限制，导致使用临时文件的非事务性语句的数量</td>
</tr>
<tr>
<td>Binlog_stmt_cache_size</td>
<td>使用了二进制日志语句缓存的非事务性语句的数量</td>
</tr>
<tr>
<td>Bytes_received</td>
<td>从所有客户端的接收到的总字节数</td>
</tr>
<tr>
<td>Bytes_sent</td>
<td>发送给所有客户端的总字节数</td>
</tr>
<tr>
<td>Com_delete</td>
<td>执行的delete语句的数量</td>
</tr>
<tr>
<td>Com_delete_multi</td>
<td>与上面类似，但是针对多表语法</td>
</tr>
<tr>
<td>Com_flush</td>
<td>执行的flush操作的数量，不管是flush tables、flush logs或者其它</td>
</tr>
<tr>
<td>Com_update</td>
<td>执行的update语句的数量</td>
</tr>
<tr>
<td>Com_update_multi</td>
<td>与上面类似，但是针对多表语法</td>
</tr>
<tr>
<td>Com_select</td>
<td>执行的select语句的数量，如果命中查询缓存，计数器Qcache_hits被增加，该计数器不变</td>
</tr>
<tr>
<td>Com_stmt_*</td>
<td>
<p>所有这些变量在预编译语句被处理时增加，不管预编译语句是否成功</p>
</td>
</tr>
<tr>
<td>Compression</td>
<td>
<p>指示客户端连接是否使用压缩</p>
</td>
</tr>
<tr>
<td>Connection_errors_*</td>
<td>
<p>客户端的各类型的连接失败的计数器：</p>
</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Connections</strong></span></td>
<td>连接尝试的数量，不管是否成功</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Created_tmp_disk_tables</strong></span></td>
<td>执行语句时，服务器内部创建的磁盘临时表的数量。当服务器创建一个内存临时表，并且超过最大限制（tmp_table_size和max_heap_table_size中的较小值）时，会自动被转换为磁盘临时表。如果该计数器过大，可能需要增加前面提到的两个系统变量</td>
</tr>
<tr>
<td>Created_tmp_files</td>
<td>mysqld创建的临时文件的总数</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Created_tmp_tables</strong></span></td>
<td>服务器内部创建的临时表的总数，包括内存表和磁盘表，该状态与Created_tmp_disk_tables的比值可以帮助判断是否需要增加tmp_table_size或max_heap_table_size</td>
</tr>
<tr>
<td>Flush_commands</td>
<td>服务器刷空（flush）表的次数，不管因为用户执行flush tables语句，还是由于内部的原因</td>
</tr>
<tr>
<td>Handler_commit</td>
<td>内部commit语句的执行数量</td>
</tr>
<tr>
<td>Handler_delete</td>
<td>数据行被从表中删除的次数</td>
</tr>
<tr>
<td>Handler_external_lock</td>
<td>每当调用external_lock()函数，该计数器增加</td>
</tr>
<tr>
<td>Handler_mrr_init</td>
<td>服务器使用存储引擎自有的多范围（Multi-Range）读取机制来进行表访问的次数</td>
</tr>
<tr>
<td>Handler_prepare</td>
<td>两阶段提交操作的prepare阶段的次数</td>
</tr>
<tr>
<td>Handler_read_first</td>
<td>索引中第一个条目被读取的次数，如果该计数器很高，提示服务器执行了太多的全范围索引（Full index scan）扫描。例如select name from person，如果name是索引列，该语句会导致本计数器增加</td>
</tr>
<tr>
<td>Handler_read_key</td>
<td>基于键读取行的请求的次数，如果该计数器很高，提示表的索引很好的适应了查询需求</td>
</tr>
<tr>
<td>Handler_read_last</td>
<td>类似于Handler_read_first，但是由于order by desc导致索引的逆序扫描</td>
</tr>
<tr>
<td>Handler_read_next</td>
<td>按照所键顺序读取下一行数据的请求次数，当使用范围约束查询一个索引列或者执行索引扫描时，该计数器会增加</td>
</tr>
<tr>
<td>Handler_read_prev</td>
<td>类似于上面，但是由于order by desc导致索引的逆序扫描</td>
</tr>
<tr>
<td>Handler_read_rnd</td>
<td>基于固定位置读取行的请求次数，如果执行很多需要对结果集排序的查询时，该计数器会增高。该计数器升高可能提示很多的全表扫描，或者没有适当使用键的join操作</td>
</tr>
<tr>
<td>Handler_read_rnd_next</td>
<td>请求读取数据文件下一行的次数，如果做了很多表扫描，该计数器会增加。该计数器升高可能提示表没有被合适的索引或者查询语句没有用到索引</td>
</tr>
<tr>
<td>Handler_rollback</td>
<td>请求存储引擎执行回滚的次数</td>
</tr>
<tr>
<td>Handler_savepoint</td>
<td>请求存储引擎设置保存点的次数</td>
</tr>
<tr>
<td>Handler_savepoint_rollback</td>
<td>请求存储引擎回滚到保存点的次数</td>
</tr>
<tr>
<td>Handler_update</td>
<td>请求更新表中一行的次数</td>
</tr>
<tr>
<td>Handler_write</td>
<td>请求插入表中一行的次数</td>
</tr>
<tr>
<td>Innodb_available_undo_logs</td>
<td>总计可用的InnoDB撤销日志的数量。该状态变量是对系统变量innodb_undo_logs的补充，后者报告活动的撤销日志数量</td>
</tr>
<tr>
<td>Innodb_buffer_pool_dump_status</td>
<td>InnoDB缓冲池Dump操作的进度，Dump可能由innodb_buffer_pool_dump_at_shutdown或者innodb_buffer_pool_dump_now触发</td>
</tr>
<tr>
<td>Innodb_buffer_pool_load_status</td>
<td>InnoDB缓冲池Warmup操作的进度，预热可能由innodb_buffer_pool_load_at_startup、innodb_buffer_pool_load_now触发，并且可以被innodb_buffer_pool_load_abort中止。预热过程MySQL会加载一系列和早先时间点相关的数据页到缓冲池</td>
</tr>
<tr>
<td>Innodb_buffer_pool_bytes_data</td>
<td>InnoDB缓冲池中包含数据字节数，包含脏数据</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Innodb_buffer_pool_pages_data</strong></span></td>
<td>InnoDB缓冲池中包含数据的页的数量，包含脏页</td>
</tr>
<tr>
<td>Innodb_buffer_pool_bytes_dirty</td>
<td>InnoDB缓冲池中包含脏数据的字节数</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Innodb_buffer_pool_pages_dirty</strong></span></td>
<td>InnoDB缓冲池中脏页的数量</td>
</tr>
<tr>
<td>Innodb_buffer_pool_pages_flushed</td>
<td>InnoDB缓冲池中刷出的页数量</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Innodb_buffer_pool_pages_free</strong></span></td>
<td>InnoDB缓冲池中空闲页的数量</td>
</tr>
<tr>
<td>Innodb_buffer_pool_pages_latched</td>
<td>InnoDB缓冲池中当前被锁住的页数量，这些页可能正在被读写，或者因为其他原因不能被移除、刷出</td>
</tr>
<tr>
<td>Innodb_buffer_pool_pages_misc</td>
<td>等于：Innodb_buffer_pool_pages_total - Innodb_buffer_pool_pages_free - Innodb_buffer_pool_pages_data。这些额外的页可能被分配用来执行行锁、自适应哈希索引 （ adaptive hash index ）</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Innodb_buffer_pool_pages_total</strong></span></td>
<td>InnoDB缓冲池的总页数</td>
</tr>
<tr>
<td>Innodb_buffer_pool_read_ahead</td>
<td>后台预读（read-ahead）线程读入InnoDB缓冲池的页数</td>
</tr>
<tr>
<td>Innodb_buffer_pool_read_ahead_evicted</td>
<td>符合上一条说明的页，在随后没有被任何查询使用时就被清除出缓冲池的页数</td>
</tr>
<tr>
<td>Innodb_buffer_pool_read_requests</td>
<td>InnoDB缓冲池逻辑读请求次数</td>
</tr>
<tr>
<td>Innodb_buffer_pool_reads</td>
<td>没有命中缓冲的逻辑读请求次数，导致直接磁盘访问</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Innodb_buffer_pool_wait_free</strong></span></td>
<td>等待空闲页出现的次数。当InnoDB需要读取或者创建一个缓冲池页，而没有空白页可用时，InnoDB会刷出某些脏页并等待此操作完成。如果innodb_buffer_pool_size设置适当，该计数器应该很小</td>
</tr>
<tr>
<td>Innodb_buffer_pool_write_requests</td>
<td>对InnoDB缓冲池执行的写操作次数</td>
</tr>
<tr>
<td>Innodb_data_fsyncs</td>
<td>到目前为止fsync()操作的次数，该操作执行频率受到innodb_flush_method影响</td>
</tr>
<tr>
<td>Innodb_data_pending_fsyncs</td>
<td>当前未决fsync()操作的个数</td>
</tr>
<tr>
<td>Innodb_data_pending_reads</td>
<td>当前未决的读请求次数</td>
</tr>
<tr>
<td>Innodb_data_pending_writes</td>
<td>当前未决的写请求次数</td>
</tr>
<tr>
<td>Innodb_data_read</td>
<td>自服务器启动以来读取的数据量</td>
</tr>
<tr>
<td>Innodb_data_reads</td>
<td>总计读取数据的量</td>
</tr>
<tr>
<td>Innodb_data_writes</td>
<td>总计写入数据的量</td>
</tr>
<tr>
<td>Innodb_data_written</td>
<td>到目前为止写入的字节数</td>
</tr>
<tr>
<td>Innodb_dblwr_pages_written</td>
<td>写入双重缓冲的页数量</td>
</tr>
<tr>
<td>Innodb_dblwr_writes</td>
<td>双重缓冲写入的操作次数</td>
</tr>
<tr>
<td><strong>Innodb_log_waits</strong></td>
<td>因重做日志缓冲（log buffer）太小导致必须等待刷出操作完成的次数</td>
</tr>
<tr>
<td>Innodb_log_write_requests</td>
<td>InnoDO重做日志写请求次数</td>
</tr>
<tr>
<td>Innodb_log_writes</td>
<td>InnoDO重做日志物理写次数</td>
</tr>
<tr>
<td>Innodb_num_open_files</td>
<td>InnoDB当前打开的文件句柄数量</td>
</tr>
<tr>
<td>Innodb_os_log_fsyncs</td>
<td>针对InnoDB重做日志的fsync()写操作次数</td>
</tr>
<tr>
<td>Innodb_os_log_pending_fsyncs</td>
<td>针对InnoDB重做日志的未决fsync()操作数量</td>
</tr>
<tr>
<td>Innodb_os_log_pending_writes</td>
<td>针对InnoDB重做日志的未决写操作数量</td>
</tr>
<tr>
<td>Innodb_os_log_written</td>
<td>InnoDB重做日志的写入的字节数</td>
</tr>
<tr>
<td>Innodb_page_size</td>
<td>InnoDB页大小，默认16KB</td>
</tr>
<tr>
<td>Innodb_pages_created</td>
<td>因对InnoDB表执行操作而创建的页数量</td>
</tr>
<tr>
<td>Innodb_pages_read</td>
<td>因对InnoDB表执行操作而读取的页数量</td>
</tr>
<tr>
<td>Innodb_pages_written</td>
<td>因对InnoDB表执行操作而写入的页数量</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Innodb_row_lock_current_waits</strong></span></td>
<td>当前正在等待行锁的操作的数量</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Innodb_row_lock_time</strong></span></td>
<td>系统启动依赖，获取InnoDB行锁总计消耗的毫秒数</td>
</tr>
<tr>
<td>Innodb_row_lock_time_avg</td>
<td>获取InnoDB行锁平均消耗的毫秒数</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Innodb_row_lock_time_max</strong></span></td>
<td>获取InnoDB行锁最大消耗的毫秒数</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Innodb_row_lock_waits</strong></span></td>
<td>因对InnoDB表执行操作而等待行锁的总次数</td>
</tr>
<tr>
<td>Innodb_rows_deleted</td>
<td>从InnoDB表中删除的行数量</td>
</tr>
<tr>
<td>Innodb_rows_inserted</td>
<td>插入到InnoDB表中的行数量</td>
</tr>
<tr>
<td>Innodb_rows_read</td>
<td>从InnoDB表中读取的行数量</td>
</tr>
<tr>
<td>Innodb_rows_updated</td>
<td>更新InnoDB表中的行数量</td>
</tr>
<tr>
<td>Innodb_truncated_status_writes</td>
<td>show engine innodb status命令的输出被截断的次数</td>
</tr>
<tr>
<td>Key_blocks_not_flushed</td>
<td>MyISAM键缓存（Key cache）中已经被修改，但是尚未刷出的键块（Key blocks）数量</td>
</tr>
<tr>
<td>Key_blocks_unused </td>
<td>MyISAM键缓存中未使用的键块数量</td>
</tr>
<tr>
<td>Key_blocks_used</td>
<td>MyISAM键缓存中已使用过的键块数量，这变量显示高水位而不是当前值</td>
</tr>
<tr>
<td>Key_read_requests</td>
<td>从MyISAM键缓存中读取一个键块的请求数量</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Key_reads</strong></span></td>
<td>从磁盘上读取键块到MyISAM键缓存的操作的次数，如果该值太大，提示key_buffer_size太小。Key_reads/Key_read_requests为缓存丢失率</td>
</tr>
<tr>
<td>Key_write_requests</td>
<td>请求写入一个键块到MyISAM键缓冲的次数</td>
</tr>
<tr>
<td>Key_writes</td>
<td>从MyISAM键缓存把键块写入到磁盘的次数</td>
</tr>
<tr>
<td>Last_query_cost</td>
<td>
<p>会话级。查询优化器计算出的上一个编译的查询的总计成本，默认值0表示没有任何查询被编译。该状态可以用来比较同一查询在不同的执行计划下的成本。</p>
<p>注意该状态只能用于“扁平”的查询，如果存在子查询或者union，该状态返回0</p>
</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Max_used_connections</strong></span></td>
<td>从服务器启动开始，连接数使用的峰值</td>
</tr>
<tr>
<td>Open_files</td>
<td>处于打开状态的常规文件的数量，不包含套接字或者管道</td>
</tr>
<tr>
<td>Open_table_definitions</td>
<td>处于打开状态的缓存的.frm文件数量</td>
</tr>
<tr>
<td>Open_tables</td>
<td>处于打开状态的表的数量</td>
</tr>
<tr>
<td>Opened_files</td>
<td>使用my_open()打开的文件总数</td>
</tr>
<tr>
<td>Opened_table_definitions</td>
<td>打开的缓存的.frm文件的总数</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Opened_tables</strong></span></td>
<td>打开的表的总数。如果该状态太大，提示table_open_cache可能过小</td>
</tr>
<tr>
<td>Performance_schema_xxx</td>
<td>performance_schema相关的状态变量</td>
</tr>
<tr>
<td>Prepared_stmt_count</td>
<td>当前预编译语句的数量。最大数量由max_prepared_stmt_count限制</td>
</tr>
<tr>
<td>Qcache_free_blocks</td>
<td>查询缓存中的空闲内存块数量</td>
</tr>
<tr>
<td>Qcache_free_memory</td>
<td>查询缓存中的空闲内存数量</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Qcache_hits</strong></span></td>
<td>查询缓存命中的总计次数</td>
</tr>
<tr>
<td>Qcache_inserts</td>
<td>总计添加到查询缓存的查询数目</td>
</tr>
<tr>
<td>Qcache_lowmem_prunes</td>
<td>由于内存不足，从查询缓存中删除的查询数目</td>
</tr>
<tr>
<td>Qcache_not_cached</td>
<td>没有被缓存（可能由于不可缓存，或者受query_cache_type制约）的查询总计数量</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Qcache_queries_in_cache</strong></span></td>
<td>查询缓存中注册的查询数目</td>
</tr>
<tr>
<td>Qcache_total_blocks</td>
<td>查询缓存总计块数</td>
</tr>
<tr>
<td>Queries</td>
<td>服务器总计执行的查询数量，包括客户端发起的查询、存储过程执行的查询</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Questions</strong></span></td>
<td>同上，但是不包含存储过程执行的查询</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Select_full_join</strong></span></td>
<td>由于不能使用索引（例如没有使用任何过滤条件），导致join操作执行表扫描的次数。如果该值非0，应当检查表索引的设计，如果快速增加，提示有严重性能问题</td>
</tr>
<tr>
<td>Select_full_range_join</td>
<td>使用第N个表中的某个值在第N+1个表（参考表）上进行索引范围扫描的次数</td>
</tr>
<tr>
<td>Select_range</td>
<td>在第一张表上使用范围扫描的join查询数量，即使很大一般也不是重大问题</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Select_range_check</strong></span></td>
<td>在第N+1个表中对第N个表中的每个值进行索引重评估（reevaluate）的join的次数。这说明在N+1表没有合适的索引，查询计划成本很高。如果非0，应当检查表索引的设计，如果快速增加，提示有严重性能问题</td>
</tr>
<tr>
<td>Select_scan</td>
<td>在第一个表上进行全表扫描的join次数，如果第一个表的数据全部需要参与查询，则不是特别致命的情况</td>
</tr>
<tr>
<td>Slow_launch_threads</td>
<td>超过slow_launch_time才创建的线程数量</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Slow_queries</strong></span></td>
<td>执行时间超过long_query_time的查询数量</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Sort_merge_passes</strong></span></td>
<td>MySQL使用该排序缓冲来存放若干(a chunk of)行并进行排序，完成排序后，把这些行合并到结果集中，并增加此计数器的值。如果该计数器过大，考虑增加sort_buffer_size</td>
</tr>
<tr>
<td>Sort_range</td>
<td>使用索引范围扫描完成的排序的次数</td>
</tr>
<tr>
<td>Sort_rows</td>
<td>排序总计牵涉的行数</td>
</tr>
<tr>
<td>Sort_scan</td>
<td>使用表扫描完成的排序的次数</td>
</tr>
<tr>
<td>Table_locks_immediate</td>
<td>请求表锁定并立即获取的次数</td>
</tr>
<tr>
<td><strong>Table_locks_waited</strong></td>
<td>请求表锁定但被迫等待的次数，该数字高提示有性能问题，应当首先优化查询、必要时考虑分区表或者复制</td>
</tr>
<tr>
<td>Table_open_cache_hits</td>
<td>命中已打开表缓存的次数</td>
</tr>
<tr>
<td>Table_open_cache_misses</td>
<td>丢失已打开表缓存的次数</td>
</tr>
<tr>
<td>Table_open_cache_overflows</td>
<td>打开的表缓存溢出的次数</td>
</tr>
<tr>
<td>Threads_cached</td>
<td>线程缓存中的线程数量</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Threads_connected</strong></span></td>
<td>当前打开的连接数</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Threads_created</strong></span></td>
<td>总计创建的线程数，如果该值过高，考虑增加thread_cache_size</td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;"><strong>Threads_running</strong></span></td>
<td>正在运行的（而不是休眠）线程，可能有查询正在执行</td>
</tr>
<tr>
<td>Uptime</td>
<td>服务器启动依赖流逝的秒数</td>
</tr>
<tr>
<td>Uptime_since_flush_status</td>
<td>执行flush status后流逝的秒数</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">show processlist</span></div>
<p>进程列表（process list）是连接到MySQL的Connections（Threads）的列表。show processlist可以显示连接状态的列表。innotop可以显示自动更新的进程列表。show full processlist不会截断文字的显示。</p>
<div class="blog_h2"><span class="graybg">show engine innodb mutex</span></div>
<p>显示InnoDB详细的互斥信息，对分析并发、可扩展性问题很有帮助。该命令的输出形式依MySQL版本的不同而变化，MySQL5.5的输出如下：</p>
<pre class="crayon-plain-tag">mysql&gt; SHOW ENGINE INNODB MUTEX;
+--------+------------------------------+-------------+
| Type   | Name                         | Status      |
+--------+------------------------------+-------------+
| InnoDB | &amp;table-&gt;autoinc_mutex        | os_waits=1  |
| InnoDB | &amp;dict_sys-&gt;mutex             | os_waits=1  |
| InnoDB | &amp;log_sys-&gt;mutex              | os_waits=12 |
| InnoDB | &amp;fil_system-&gt;mutex           | os_waits=11 |
| InnoDB | &amp;kernel_mutex                | os_waits=1  |
| InnoDB | &amp;dict_table_stats_latches[i] | os_waits=2  |
| InnoDB | &amp;dict_operation_lock         | os_waits=13 |
| InnoDB | &amp;log_sys-&gt;checkpoint_lock    | os_waits=66 |
| InnoDB | combined &amp;block-&gt;lock        | os_waits=2  |
+--------+------------------------------+-------------+</pre>
<p>根据输出中等待的数量可以确定InnoDB的哪个部分存在性能瓶颈，任何互斥都代表着潜在的资源争用。</p>
<div class="blog_h2"><span class="graybg">数据复制状态（Replication Status）</span></div>
<p>MySQL提供若干命令来监控数据复制的状态：</p>
<ol>
<li>show master status显示master的复制状态和配置：<br />
<pre class="crayon-plain-tag">mysql&gt; SHOW MASTER STATUS
*************************** 1. row ***************************
            File: mysql-bin.000079
        Position: 13847
    Binlog_Do_DB:
Binlog_Ignore_DB:</pre>
</li>
<li>show binary logs显示二进制日志的列表</li>
<li>show binlog events显示日志中的事件</li>
<li>show relaylog events显示转播日志中的事件</li>
<li>show slave status显示replica的状态</li>
</ol>
<div class="blog_h2"><span class="graybg">information_schema</span></div>
<p>从MySQL5.0+引入，包含一系列类似数据字典的功能。<a href="http://forge.mysql.com" target="_blank">MySQL Forge </a>、<a href="http://code.openark.org/forge/common_schema" target="_blank">common_schema</a>提供很多针对这些字典的视图。</p>
<p>该schema的最大缺点是某些时候比相应的show命令缓慢。</p>
<p><span style="text-decoration: underline;"><strong>InnoDB表</strong></span></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">表 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>INNODB_CMP <br />INNODB_CMP_RESET</td>
<td>显示InnoDB压缩数据信息<br />与上个命令类似，但是具有副作用：重置其包含的数据</td>
</tr>
<tr>
<td>INNODB_CMPMEM <br />INNODB_CMPMEM_RESET</td>
<td>显示InnoDB缓冲池被压缩数据使用的情况，第二个也是重置表</td>
</tr>
<tr>
<td>INNODB_TRX<br />INNODB_LOCKS<br />INNODB_LOCK_WAITS</td>
<td>显示事务、事务持有/等待的锁。对于诊断锁等待、长事务很重要。</td>
</tr>
<tr>
<td>INNODB_METRICS</td>
<td>MySQL 5.6+，显示很多关于InnoDB的性能信息，可以用于代替performance_schema</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">performance_schema</span></div>
<p>从MySQL5.5+引入，默认情况下被禁用，启用后可能导致8% - 11%的资源占用。</p>
<div class="blog_h1"><span class="graybg">常用数据类型</span></div>
<div>
<table style="color: #000000;" border="1" width="100%" cellspacing="0" cellpadding="2">
<tbody>
<tr>
<td style="width: 120px;" valign="top">TINYINT</td>
<td valign="top">整数，8位空间<br />-2^7 (-128) 到 2^7 – 1 (127)</td>
</tr>
<tr>
<td valign="top">SMALLINT</td>
<td valign="top">整数，16位空间<br />-2^15 (-32,768) 到 2^15 – 1 (32,767) </td>
</tr>
<tr>
<td valign="top">MEDIUMINT</td>
<td valign="top">整数，24位空间<br />-2^23 (-8,388,608) 到 2^23 – 1 (8,388,607)</td>
</tr>
<tr>
<td valign="top">INT</td>
<td valign="top">整数，32位空间<br />-2^31 (-2,147,483,648) 到 2^31 – 1 (2,147,483,647)</td>
</tr>
<tr>
<td valign="top">BIGINT</td>
<td valign="top">整数，64位空间，存放不能用SMALLINT 或 INT描述的超大整数（19位数字）<br />-2^63 (-9223372036854775808) 到 2^63-1 (9223372036854775807) </td>
</tr>
<tr>
<td valign="top">FLOAT</td>
<td valign="top">单精度浮点型数据</td>
</tr>
<tr>
<td valign="top">DOUBLE</td>
<td valign="top">双精度浮点型数据</td>
</tr>
<tr>
<td valign="top">DECIMAL</td>
<td valign="top">用户自定义精度的浮点型数据 ，以特别高的精度存储小数数据。</td>
</tr>
<tr>
<td valign="top">CHAR (M)</td>
<td valign="top">固定长度的字符串 。默认255长度，最大65535</td>
</tr>
<tr>
<td valign="top">VARCHAR(M)</td>
<td valign="top">具有最大限制的可变长度的字符串 。默认255长度，最大65535</td>
</tr>
<tr>
<td valign="top">TEXT</td>
<td valign="top">字符串大对象。TinyText 最大 255，Text/SmallText 最大 65K，MediumText 中等16M，LongText 最大 4G。具体内存放多少个字符，与字符集相关</td>
</tr>
<tr>
<td valign="top">BLOB</td>
<td valign="top">二进制大对象。TinyBlob 最大 255，Blob/SmallBlog 最大 65K，MediumBlob 中等16M，LongBlob 最大 4G</td>
</tr>
<tr>
<td valign="top">DATE</td>
<td valign="top">yyyy-mm-dd格式的日期</td>
</tr>
<tr>
<td valign="top">TIME</td>
<td valign="top">hh:mm:ss格式的时间</td>
</tr>
<tr>
<td valign="top">DATETIME</td>
<td valign="top">yyyy-mm-ddhh:mm:ss格式结合日期和时间</td>
</tr>
<tr>
<td valign="top">TIMESTAMP</td>
<td valign="top">以yyyy-mm-ddhh:mm:ss格式结合日期和时间</td>
</tr>
<tr>
<td valign="top">YEAR</td>
<td valign="top">yyyy格式的年份</td>
</tr>
<tr>
<td valign="top">ENUM</td>
<td valign="top">一组数据，用户可从中选择其中一个</td>
</tr>
<tr>
<td valign="top">SET</td>
<td valign="top">一组数据，用户可从中选择其中0，1或更多</td>
</tr>
</tbody>
</table>
</div>
<div class="blog_h1"><span class="graybg">常用数据字典</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 数据库</td>
<td style="width: 150px; text-align: center;">数据字典表</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>information_schema</td>
<td>innodb_trx</td>
<td>
<p>活动的事务，字段说明：</p>
<p>trx_id 事务ID<br />trx_state 事务状态：<br />trx_started 事务开始时间；<br />trx_requested_lock_id innodb_locks.lock_id<br />trx_wait_started 事务开始等待的时间<br />trx_weight <br />trx_mysql_thread_id 事务线程ID<br />trx_query 具体SQL语句<br />trx_operation_state 事务当前操作状态<br />trx_tables_in_use 事务中有多少个表被使用<br />trx_tables_locked 事务拥有多少个锁<br />trx_lock_structs <br />trx_lock_memory_bytes 事务锁住的内存大小（B）<br />trx_rows_locked 事务锁住的行数<br />trx_rows_modified 事务更改的行数<br />trx_concurrency_tickets 事务并发票数<br />trx_isolation_level 事务隔离级别<br />trx_unique_checks 是否唯一性检查<br />trx_foreign_key_checks 是否外键检查<br />trx_last_foreign_key_error 最后的外键错误<br />trx_adaptive_hash_latched <br />trx_adaptive_hash_timeout</p>
</td>
</tr>
<tr>
<td>information_schema</td>
<td>innodb_locks</td>
<td>
<p>当前出现的锁，字段说明：</p>
<p>lock_id 锁ID<br />lock_trx_id 拥有锁的事务ID<br />lock_mode 锁模式<br />lock_type 锁类型<br />lock_table 被锁的表<br />lock_index 被锁的索引<br />lock_space 被锁的表空间号<br />lock_page 被锁的页号<br />lock_rec 被锁的记录号<br />lock_data 被锁的数据</p>
</td>
</tr>
<tr>
<td>information_schema</td>
<td>innodb_lock_waits</td>
<td>
<p>锁等待的对应关系，字段说明：</p>
<p>requesting_trx_id 请求锁的事务ID<br />requested_lock_id 请求锁的锁ID<br />blocking_trx_id 当前拥有锁的事务ID<br />blocking_lock_id 当前拥有锁的锁ID</p>
</td>
</tr>
<tr>
<td>information_schema</td>
<td>plugins</td>
<td>
<p>显示插件版本信息</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">解读MySQL执行计划</span></div>
<p>只需要在select语句前附加explain，即可获取其执行计划。explain命令包含两个变体：</p>
<ol>
<li>explain extended：提示服务器把执行计划“反编译”为SQL语句，通过后续的show warnings命令，即可看到该语句。</li>
<li>explain partitions：显示查询需要访问的所有表分区</li>
</ol>
<p>explain可能会导致语句的执行，例如对于from中包含子查询的SQL，则会执行子查询并把结果放入临时表。</p>
<p>使用pt-visual-explain可以使用树形结构来显示执行计划</p>
<p>执行计划返回的是近似的信息，其包含以下限制：</p>
<ol>
<li>explain不提供任何可能影响查询的触发器、存储函数、UDF信息</li>
<li>对存储过程无效</li>
<li>不提供MySQL执行时即时优化的信息</li>
<li>执行计划显示的统计信息只是近似的，有时会很不准确</li>
<li>某些情形下没有做合理的区分，例如filesort可以表示内存排序、临时文件排序；Using temporary可能指内存或者磁盘临时表</li>
</ol>
<p>MySQL5.6针对执行计划做了以下改进：</p>
<ol>
<li>支持update, insert之类语句的执行计划</li>
<li>尽量推迟临时表的物化，可以在不执行子查询的情况下完成执行计划的解释</li>
<li>添加optimizer trace功能，允许用户查看优化器的决定，以及作出决定的原因</li>
</ol>
<div class="blog_h2"><span class="graybg">执行计划结果列</span></div>
<table border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td> 列名称</td>
<td> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>
<p>显示当前行所属的select，如果没有子查询、union，则只会显示一行，否则<span style="background-color: #c0c0c0;">inner查询会按照其在原始SQL中的出现顺序，每个一行的显示在执行计划的结果</span>中。MySQL把select分为简单、复杂类型，后者包括简单子查询、导出表（derived tables，from中的子查询）、unions：</p>
<pre class="crayon-plain-tag">//简单子查询的例子
mysql&gt; EXPLAIN SELECT (SELECT 1 FROM sakila.actor LIMIT 1) FROM sakila.film;
+----+-------------+-------+...
| id | select_type | table |...
+----+-------------+-------+...
| 1  | PRIMARY     | film  |...
| 2  | SUBQUERY    | actor |...
+----+-------------+-------+...
//导出表的例子
mysql&gt; EXPLAIN SELECT film_id FROM (SELECT film_id FROM sakila.film) AS der;
+----+-------------+------------+...
| id | select_type | table |...
+----+-------------+------------+...
| 1  | PRIMARY     |  |...
| 2  | DERIVED     | film       |...
+----+-------------+------------+...
//UNION的例子
mysql&gt; EXPLAIN SELECT 1 UNION ALL SELECT 1;
+------+--------------+------------+...
| id   | select_type  | table      |...
+------+--------------+------------+...
| 1    | PRIMARY      | NULL       |...
| 2    | UNION        | NULL       |...
//UNION的结果总是存放到临时表中，并重新读取
| NULL | UNION RESULT | &lt;union1,2&gt; |... 
+------+--------------+------------+...</pre>
</td>
</tr>
<tr>
<td>select_type</td>
<td>
<p>显示当前行是简单还是某种复杂查询
<p>SIMPLE：查询不包含子查询或者unions<br />PRIMARY：最外层的select<br />UNION：联合查询的第二个及以后的查询标记为union<br />UNION RESULT：MySQL内部用于从临时表中获取联合查询结果的语句<br />SUBQUERY：子查询中的第一个select<br />DERIVED：表示存在于from子句的子查询，MySQL会递归的执行，并放入临时表<br />MATERIALIZED：物化子查询<br />UNCACHEABLE SUBQUERY：不可缓存的子查询，对于外部查询的每一行，该子查询必须被重新估算<br />DEPENDENT：表示子查询、union依赖于外部查询提供的数据。往往提示使用了相关子查询（correlated subquery）</p>
</td>
</tr>
<tr>
<td>table</td>
<td>
<p>显示当前行访问的表，可能显示表名或者别名，从上而下的阅读此列，即为join操作实际发生的顺序：</p>
<pre class="crayon-plain-tag">mysql&gt; EXPLAIN SELECT film.film_id
-&gt; FROM sakila.film
-&gt; INNER JOIN sakila.film_actor USING(film_id)
-&gt; INNER JOIN sakila.actor USING(actor_id);
+----+-------------+------------+...
| id | select_type | table      |...
+----+-------------+------------+... //这三行都是属于第一个查询
| 1  | SIMPLE      | actor      |... //MySQL修改了原始查询中给定的JOIN顺序
| 1  | SIMPLE      | film_actor |...
| 1  | SIMPLE      | film       |...
+----+-------------+------------+...</pre>
<p>注意MySQL查询计划是一棵最左最深数（left-deep tree），如果顺时针把该树旋转90度，可以很容易的查看JOIN节点与执行计划行的对应关系：<img class="aligncenter  wp-image-3415" src="https://blog.gmem.cc/wp-content/uploads/2011/04/explain-1.jpg" alt="explain-1" width="584" height="144" /></p>
<p>如果from子句中包含子查询，则该列显示，N表示子查询的ID<br />如果当前查询是union，则union result table列包含参与union的查询ID的列表</p>
<p>下面是一个复杂的例子：</p>
<pre class="crayon-plain-tag">1  EXPLAIN
2  select
3    actor_id,
4    (select 1 from film_actor where actor_id = der_1.actor_id limit 1)
5  from (
6    select actor_id
7    from actor limit 5
8  ) AS der_1
9 
10 UNION ALL
11
12 select film_id,
13   (select @var1 from rental LIMIT 1)
14 from(
15   select film_id, (select 1 from store limit 1) from film limit 5
16 ) AS der_2;

+------+----------------------+------------+...
| id   | select_type          | table |...
+------+----------------------+------------+...
//对der_1的前向引用，对应SQL第2行
| 1    | PRIMARY              |  |...
//id为3表示它是第3个SELECT的部分，DERIVED表示它是FROM子查询，对应SQL第6行
| 3    | DERIVED              | actor      |...
//对应SQL第4行，其具有一个较小的id，表示它是后来执行的（executed afterward）
//DEPENDENT表示它的结果依赖于外部查询的结果（亦称相关子查询correlated subquery）
| 2    | DEPENDENT SUBQUERY   | film_actor |...
//表示其是UNION语句中的第二个或者更靠后的查询，从6中获取数据
| 4    | UNION                |  |...
//对应SQL第15行
| 6    | DERIVED              | film       |...
//的SELECT子句的子查询，注意其ID为7，这很重要，它不是相关子查询
| 7    | SUBQUERY             | store      |...
//注意ID变小了，每当出现一个DERIVED类型的查询时，意味着开启了一个嵌套的scope
//其后ID会变大，如果又变得比DERIVED查询小了，说明嵌套scope已经结束
//下一行是使用进行查询的SELECT的一部分
| 5    | UNCACHEABLE SUBQUERY | rental     |...
//从临时表中读取数据，对应的导出表的ID为1、4
| NULL | UNION RESULT         | &lt;union1,4&gt; |...
+------+----------------------+------------+...</pre>
</td>
</tr>
<tr>
<td>type</td>
<td>
<p>访问类型（access type，MySQL官方称为join type）——MySQL决定通过何种方式找到表中的行，从<span style="background-color: #c0c0c0;">效率由差到好</span>，有：<br /><strong><span style="background-color: #c0c0c0;">ALL：</span></strong>全表扫描，需要扫描整个表，另外情况有：使用limit的查询；Extra:Using distinct/not exists<br /><strong><span style="background-color: #c0c0c0;">index：</span></strong>与全表扫描类似只是MySQL根据索引顺序，而非行顺序扫描，其主要优点是避免排序，缺点则是意味着随机访问各行。如果出现Extra:Using index说明MySQL使用了覆盖索引并仅扫描索引数据，这种情况下则不会有前述随机访问的消耗<br /><strong><span style="background-color: #c0c0c0;">range：</span></strong>表示限制范围的索引扫描，从索引的某处开始，返回一系列匹配值范围的行<br /><strong><span style="background-color: #c0c0c0;">ref：</span></strong>索引查找，根据单个索引值返回若干行。只会出现在非唯一性索引或者唯一性索引的某个前缀上。称为ref的原因是通常需要和某个引用值（常量或者从多表连接查询的前某个表获取的值）进行比较<br /><span style="background-color: #c0c0c0;"><strong>ref_or_null：</strong></span>ref的变种，表示MySQL需要在常规索引查找后，找到索引null条目<br /><span style="background-color: #c0c0c0;"><strong>eq_ref：</strong></span>这是一类索引查找，且MySQL知道其只会返回一个值，通常使用唯一键或主键与某个值对比时会出现<br /><span style="background-color: #c0c0c0;"><strong>const, system：</strong></span>如果可以优化掉SQL的某一部分，并用常量代替<br /><span style="background-color: #c0c0c0;"><strong>NULL：</strong></span>表示MySQL可以在优化阶段解决查询，甚至在执行阶段不需要索引或者表访问，例如获取索引的最小值
</td>
</tr>
<tr>
<td>possible_keys</td>
<td>显示MySQL<span style="background-color: #c0c0c0;">可能</span>用在此查询上的索引，根据where子句访问的列、比较操作符确定。该列在优化的早期阶段完成，因此其列出的某些索引在后续优化阶段可能没用</td>
</tr>
<tr>
<td>key</td>
<td>显示MySQL<span style="background-color: #c0c0c0;">决定</span>用来优化查询的索引，如果不在 possible_keys中存在，说明MySQL因为其它原因选择了该索引，例如，在没有WHERE子句的情况下，依然可能选择一个覆盖索引</td>
</tr>
<tr>
<td>key_len</td>
<td>显示MySQL使用的索引长度，注意MySQL5.5-只能使用索引最左边的部分。比如某个表由两个smallint列构成的联合主键，那么其主键索引就是2*2bytes=4字节长，可以依据此值来推断查询只使用了索引左边的一部分还是全部</td>
</tr>
<tr>
<td>ref</td>
<td>用来显示使用前面表（多表连接时）的哪个列或者常量来从key列中查找值</td>
</tr>
<tr>
<td>rows</td>
<td>
<p>为了获得期望的结果，MySQL估计需要读取的数据行数，在嵌套循环（nested-loop）执行计划中，该值是针对单次循环的（平均值）</p>
<p>依据索引的选择度、表统计信息的准确性，该值可能相当的不准确。通过把join相关执行计划行的rows相乘，可以大概估算总计需要访问行数</p>
</td>
</tr>
<tr>
<td>filtered</td>
<td>MySQL 5.1+，使用explain extend时出现。显示表需要参与查询的行数的悲观估计</td>
</tr>
<tr>
<td>Extra</td>
<td>
<p>显示不在其它列中的额外信息</p>
<p>Using index：提示MySQL使用覆盖索引来避免进行表访问<br />Using where：表示一个WHERE子句被使用，以限制哪些行可以与下一张表进行匹配，或限制哪些行用于返回客户端。当type的值是ALL或者index时，如果Extra中没有Using where，可能提示查询存在问题，除非你真的想获取/检查表的所有行<br />Using temporary：表示MySQL将使用临时表进行结果集排序<br />Using filesort：表示需要进行额外的排序操作，MySQL支持两种排序算法，均支持内存、磁盘排序<br />Range checked for each record (index map: N)：表示没有合适的索引，在JOIN中的每行都要进行索引重估算</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">与Java、Oracle类型的对应关系</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> MySQL类型</td>
<td style="text-align: center;">Oracle类型</td>
<td style="text-align: center;"> Java类型</td>
</tr>
</thead>
<tbody>
<tr>
<td>BIGIHT</td>
<td>NUMBER(19,0)</td>
<td>java.lang.Long</td>
</tr>
<tr>
<td>BIT</td>
<td>RAW</td>
<td>byte[]</td>
</tr>
<tr>
<td>BLOB</td>
<td>BLOB RAW</td>
<td>byte[]</td>
</tr>
<tr>
<td>CHAR</td>
<td>CHAR</td>
<td>java.lang.String</td>
</tr>
<tr>
<td>DATE</td>
<td>DATE</td>
<td>java.sql.Date</td>
</tr>
<tr>
<td>DATETIME</td>
<td>DATE</td>
<td>java.sql.Timestamp</td>
</tr>
<tr>
<td>DECIMAL</td>
<td>
<div>FLOAT(24)</div>
</td>
<td>java.math.BigDecimal</td>
</tr>
<tr>
<td>DOUBLE</td>
<td>FLOAT(24)</td>
<td>java.lang.Double</td>
</tr>
<tr>
<td>DOUBLE PRECISION</td>
<td>FLOAT(24)</td>
<td>java.lang.Double</td>
</tr>
<tr>
<td>ENUM</td>
<td>VARCHAR2</td>
<td>java.lang.String</td>
</tr>
<tr>
<td>FLOAT</td>
<td>FLOAT</td>
<td>java.lang.Float</td>
</tr>
<tr>
<td>INT</td>
<td>NUMBER(10,0)</td>
<td>java.lang.Integer</td>
</tr>
<tr>
<td>INTEGER</td>
<td>NUMBER(10,0)</td>
<td>java.lang.Integer</td>
</tr>
<tr>
<td>LONGBLOB</td>
<td>BLOB RAW</td>
<td>byte[]</td>
</tr>
<tr>
<td>LONGTEXT</td>
<td>CLOB RAW</td>
<td>java.lang.String</td>
</tr>
<tr>
<td>MEDIUMBLOB</td>
<td>BLOB RAW</td>
<td>byte[]</td>
</tr>
<tr>
<td>MEDIUMINT</td>
<td>NUMBER(7,0)</td>
<td>java.lang.Integer</td>
</tr>
<tr>
<td>MEDIUMTEXT</td>
<td>CLOB RAW</td>
<td>java.lang.String</td>
</tr>
<tr>
<td>NUMERIC</td>
<td>NUMBER</td>
<td> </td>
</tr>
<tr>
<td>REAL</td>
<td>FLOAT(24)</td>
<td> </td>
</tr>
<tr>
<td>SET</td>
<td>VARCHAR2</td>
<td>java.lang.String</td>
</tr>
<tr>
<td>SMALLINT</td>
<td>NUMBER(5,0)</td>
<td>java.lang.Integer</td>
</tr>
<tr>
<td>TEXT</td>
<td>VARCHAR2 CLOB</td>
<td>java.lang.String</td>
</tr>
<tr>
<td>TIME</td>
<td>DATE</td>
<td>java.sql.Time</td>
</tr>
<tr>
<td>TIMESTAMP</td>
<td>DATE</td>
<td>java.sql.Timestamp</td>
</tr>
<tr>
<td>TINYBLOB</td>
<td>RAW</td>
<td>byte[]</td>
</tr>
<tr>
<td>TINYINT</td>
<td>NUMBER(3,0)</td>
<td>java.lang.Byte</td>
</tr>
<tr>
<td>TINYTEXT</td>
<td>VARCHAR2</td>
<td>java.lang.String</td>
</tr>
<tr>
<td>VARCHAR</td>
<td>VARCHAR2 CLOB</td>
<td>java.lang.String</td>
</tr>
<tr>
<td>YEAR</td>
<td>NUMBER</td>
<td>java.sql.Date</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">show engine innodb status详解</span></div>
<p>与其它的show命令不同，该命令的输出是一个字符串，分为若干个段落，分别关注InnoDB的不同方面。</p>
<p>其输出包含一些<span style="background-color: #c0c0c0;">自上一次命令输出以来的平均统计信息</span>，例如fsync()每秒被调用的次数，如果需要分析这些统计信息，务必<span style="background-color: #c0c0c0;">等待30秒或更长</span>以保证统计的采样更加精确，并且通过多次采样来分析InnoDB的运行走势。诸如innotop之类的工具可以提供比该命令更方便的功能。</p>
<pre class="crayon-plain-tag">mysql&gt; show engine innodb status;
'第一部分是信息头'
1 =====================================
2 070913 10:31:48 INNODB MONITOR OUTPUT
3 =====================================
4 Per second averages calculated from the last 49 seconds '上一次统计以来的流逝的时间，最好大于30秒'

'第二部分是信号量，对于高并发的工作负载的分析具有重要意义。InnoDB使用互斥量和信号量来保护代码关键区的独占访问'
'以及在存在活动reader的时候限制writer的操作，等等。'
'包含两类数据：1、事件计数器；2、一个当前等待的列表。如果出现性能瓶颈，这些数据有利于分析瓶颈的所在'
'下面是信号量的简单输出示例：'
1 ----------
2 SEMAPHORES
3 ----------
'下行显示操作系统等待数组的信息，这是一个插槽（slots）的队列，InnoDB为信号量保留数组中的插槽，操作系统'
'使用信号量来提示（signal）线程可以继续并进行其正在等待（某种资源）去做的事情了，该行显示了InnoDB需要'
'使用系统等待的次数。其中reservation count说明了InnoDB分配插槽的频度；signal count则说明了线程被通过'
'OS等待数组被signal的频度。相比起自旋等待（spin waits），操作系统等待的成本更高'
4 OS WAIT ARRAY INFO: reservation count 13569, signal count 11421
'5-12行显示了当前正在等待互斥量（mutex）的线程的信息，每个等待以Thread  has waited at ...开头'
'本例中，包含2个等待的线程，理想情况下，最好为0个，除非系统并发非常高，从而必须使用操作系统等待'
'这些信息中最有价值的是线程等待的文件名，可以去分析资源争用出现什么地方，例如buf0buf.ic说明存在缓冲池争用'
5 --Thread 1152170336 has waited at ./../include/buf0buf.ic line 630 for 0.00 seconds '等待时间'
the semaphore:
6 Mutex at 0x2a957858b8 created file buf0buf.c line 517, lock var 0 
7 waiters flag 0 '多少个等待者正在等待该互斥量'
8 wait is ending '表示互斥量当前已经可用了，但是操作系统尚未调度线程运行'
9 --Thread 1147709792 has waited at ./../include/buf0buf.ic line 630 for 0.00 seconds
the semaphore:
10 Mutex at 0x2a957858b8 created file buf0buf.c line 517, lock var 0
11 waiters flag 0
12 wait is ending
'InnoDB使用多阶段等待策略，首先，尝试对锁的自旋等待，如果失败，则进入比较昂贵的操作系统等待。尽管自旋等待需要'
'占用CPU周期进行不停的检查，但是通常并不消耗太多资源，因为在CPU等待I/O的时候通常存在空闲CPU周期。除了自旋等待'
'以外，另外一种方式是线程上下文切换，导致当前线程休眠，这期间其它线程可以运行，然后，休眠线程可以通过OS等待队列'
'里面的信号量来激活（signal）。信号量激活是很快的，但是上下文切换则比较昂贵，特别是每秒上千次切换将消耗大量资源'
'调整innodb_sync_spin_loops来在自旋和操作系统等待之间获得平衡，如果自旋循环（rounds）达到每秒成百上千，则可'
'以考虑自旋等待的配置是否有问题。可以结合SHOW ENGINE INNODB MUTEX、performance_schema来解决问题'
'显示几个与互斥有关的计数器。注意操作系统等待的次数'
13 Mutex spin waits 5672442, rounds 3899888, OS waits 4719
'显示与读/写共享锁、独占锁的计数器。注意操作系统等待的次数'
14 RW-shared spins 5920, OS waits 2918; RW-excl spins 3463, OS waits 3163

'第三部分是最近发生的外键错误记录，如果没有外键错误发生，该部分不会显示，如果出现新错误，旧的可能被覆盖'
'外键错误发生时机：1、在进行增删改操作时违反外键约束时；2、尝试删除存在外键约束的表时'
'增删改操作时违反外键约束的例子，依次执行下面三个SQL语句'
'INSERT INTO parent(parent_id) VALUES(1);'
'INSERT INTO child(parent_id) VALUES(1);'
'DELETE FROM parent;'
'会出现错误：'
'ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint'
'fails (`test/child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES'
'`parent` (`parent_id`)) 执行SEIS命令，输出如下：'
1 ------------------------
2 LATEST FOREIGN KEY ERROR
3 ------------------------
4 070913 10:57:34 Transaction:   '显示外键错误发生的时间'
'5-9行显示违反外键约束的事务的详细信息'
5 TRANSACTION 0 3793469, ACTIVE 0 sec, process no 5488, OS thread id 52064 updating or deleting...
6 mysql tables in use 1, locked 1
7 4 lock struct(s), heap size 1216, undo log entries 1
8 MySQL thread id 9, query id 305 localhost baron updating
9 DELETE FROM parent
'10-19行显示修改时出现错误的数据的精确信息'
10 Foreign key constraint fails for table `test/child`:
11 
12 CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`parent_id`)
13 Trying to delete or update in parent table, in index `PRIMARY` tuple:
14 DATA TUPLE: 3 fields;
15 0: len 4; hex 80000001; asc ;; 1: len 6; hex 00000039e23d; asc 9 =;; 2: len
7; hex 000000002d0e24; asc - $;;
16
17 But in child table `test/child`, in index `parent_id`, there is a record:
18 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
19 0: len 4; hex 80000001; asc ;; 1: len 6; hex 000000000500; asc ;;
'尝试删除存在外键约束的表的例子，执行下面的语句：'
'ALTER TABLE parent MODIFY parent_id INT UNSIGNED NOT NULL;会出现错误：'
'ERROR 1025 (HY000): Error on rename of ./test/#sql-1570_9 to ./test/parent'
'上面的1025报错没有给出足够的信息， SEIS命令则阐明了详细的错误原因：'
1 ------------------------
2 LATEST FOREIGN KEY ERROR
3 ------------------------
4 070913 11:06:03 Error in foreign key constraint of table test/child:
5 there is no index in referenced table which would contain
6 the columns as the first columns, or the data types in the
'外键类型必须完全匹配，包括类似UNSIGNED的修饰符'
7 referenced table do not match to the ones in table. Constraint: 
8 ,
9 CONSTRAINT child_ibfk_1 FOREIGN KEY (parent_id) REFERENCES parent (parent_id)
10 The index in the foreign key in table is parent_id
11 See http://dev.mysql.com/doc/refman/5.0/en/innodb-foreign-key-constraints.html
12 for correct foreign key definition.

'第四部分是死锁相关的信息，如果不存在死锁，该部分不会显示，新的死锁可能覆盖旧的信息'
'死锁是等待图（waits-for graph）中出现的闭环，该环可能具有任意的大小，每当事务需要等待一个锁时，InnoDB就会立即'
'进行死锁检测，因此在InnoDB中死锁是即刻发现的，不需要超时机制。死锁可能非常复杂，但是该部分内容只会显示最后两个牵'
'涉到死锁中的事务、每个事务最后执行的语句、以及在等待图中制造了闭环的锁。除了我们平常熟悉的死锁，InnoDB把检查闭环'
'的开销过大的情形也认为是发生死锁，这些情形包括：等待图中超过100万个锁；检查时递归超过200个事务'
'注意除了事务、等待的锁之外，该部分的输出还包括相关的记录本身，这可能非常大，导致内容阶段，可以使用Percona Server'
'下面是一个简单的死锁的例子：'
1 ------------------------
2 LATEST DETECTED DEADLOCK
3 ------------------------
4 070913 11:14:21  '显示死锁发生的时间'
'5-10行显示牵涉到死锁的第一个事务的基本信息'
5 *** (1) TRANSACTION:
6 TRANSACTION 0 3793488, ACTIVE 2 sec, process no 5488, OS thread id 1141287232
starting index read
7 mysql tables in use 1, locked 1
8 LOCK WAIT 4 lock struct(s), heap size 1216
9 MySQL thread id 11, query id 350 localhost baron Updating
10 UPDATE test.tiny_dl SET a = 0 WHERE a &lt;&gt; 0
'11=15行显示了死锁发生时，事务一正在等待的锁'
11 *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
'这一行的内容最重要，说明事务一在等待test.tiny_dl表的GEN_CLUST_INDEX索引的独占锁'
12 RECORD LOCKS space id 0 page no 3662 n bits 72 index `GEN_CLUST_INDEX` of table
`test/tiny_dl` trx id 0 3793488 lock_mode X waiting
13 Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
14 0: len 6; hex 000000000501 ...[ omitted ] ... '这边的信息用于调试InnoDB本身，被略去'
15
'16-21行显示牵涉到死锁的第二个事务的基本信息'
16 *** (2) TRANSACTION:
17 TRANSACTION 0 3793489, ACTIVE 2 sec, process no 5488, OS thread id 1141422400
starting index read, thread declared inside InnoDB 500
18 mysql tables in use 1, locked 1
19 4 lock struct(s), heap size 1216
20 MySQL thread id 12, query id 351 localhost baron Updating
21 UPDATE test.tiny_dl SET a = 1 WHERE a &lt;&gt; 1
'22-26行显示第二个事务持有的锁'
22 *** (2) HOLDS THE LOCK(S):
23 RECORD LOCKS space id 0 page no 3662 n bits 72 index `GEN_CLUST_INDEX` of table
`test/tiny_dl` trx id 0 3793489 lock mode S
24 Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
25 0: ... [ omitted ] ... '这边省略了若干牵涉到的记录'
26
'27-31行显示了死锁发生时，事务二正在等待的锁'
27 *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
28 RECORD LOCKS space id 0 page no 3662 n bits 72 index `GEN_CLUST_INDEX` of table
`test/tiny_dl` trx id 0 3793489 lock_mode X waiting
29 Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
30 0: len 6; hex 000000000501 ...[ omitted ] ...
31
'通常上面的信息足够判断查询使用了什么索引，并可以确定是否可以避免死锁'
'如果能使两个事务按照同样的顺序扫描同一个索引，可能减少死锁发生的次数，因为在同样顺序的情况下，不可能出现闭环'
'例如，两个事务需要更新同一张表，那么在内存中按照主键排序，并且按照该顺序依次更新，则两个事务不会发生死锁'
32 *** WE ROLL BACK TRANSACTION (2) '事务二被回滚了，InnoDB会尝试回滚其认为代价最低的事务'

'第五部分是事务相关一些摘要性的信息'
1 ------------
2 TRANSACTIONS
3 ------------
4 Trx id counter 0 80157601 '当前的事务标识符，对于每一个事务，值会递增'
'InnoDB清理MVCC旧版本的进度——已经到达的事务标识符。通过下面的值与当前事务标识符的差值，可以知道有多少旧版本'
'MMVC尚未被清理：80157601 - 80154573 = 3028。没有严格的规则来判断此差值的安全最大值：如果当前不存在任何'
'数据更新，即使差值很大也不代表存在未清理的数据，因为所有事务都使用同样的版本；反之，如果很多行被更新，或者内'
'内中有很多行的一个或多个版本，则应该尽快的提交事务，因为开启的事务即使什么都不做，也会禁止旧版本数据的清理'
5 Purge done for trxs n:o= 0 80157601, sees</pre>
<div class="blog_h1"><span class="graybg">锁的调试</span></div>
<div class="blog_h2"><span class="graybg">发生在Server级别的锁等待</span></div>
<p>如果在Server级别发生锁争用，则show processlist中会有所证据。</p>
<p>Server级别锁包括以下几类：</p>
<p><span style="text-decoration: underline;"><strong>表锁（Table Locks）</strong></span></p>
<p>表锁可以是明确的或者隐含的，通过lock table语句可以创建明确锁：</p>
<pre class="crayon-plain-tag">-- 在会话1上对表进行共享锁定
LOCK TABLES sakila.film READ;
-- 在会话2上对表进行独占锁定，语句会挂起而无法完成：
LOCK TABLES sakila.film WRITE;
-- 显示进程列表，可以看到
SHOW PROCESSLIST
mysql&gt; SHOW PROCESSLIST\G
*************************** 1. row ***************************
Info: SHOW PROCESSLIST
*************************** 2. row ***************************
     Id: 11
   User: baron
   Host: localhost
     db: NULL
Command: Query
   Time: 4
  State: Locked --注意该线程的状态是被锁定，出现此状态表示在Server层被锁
   Info: LOCK TABLES sakila.film WRITE

-- 锁也可能是隐含获得的，例如在MyISAM上执行以下语句
SELECT SLEEP(30) FROM sakila.film LIMIT 1;
-- 在30秒内，开启另外一个会话，运行LOCK TABLES sakila.film WRITE;会被锁定</pre>
<p>注意InnoDB，对于<span style="background-color: #c0c0c0;">明确锁，它会做你期望做的，但是对于隐含锁，InnoDB会在Server表锁和InnoDB表锁类型进行必要的转换</span>。</p>
<p><span style="background-color: #c0c0c0;">mysqladmin debug</span>命令可以用于发现持有MyISAM表锁的线程。</p>
<p><strong><span style="text-decoration: underline;">全局读锁（The Global Read Lock）</span></strong></p>
<pre class="crayon-plain-tag">-- 使用下面的语句可以获取全局读锁
FLUSH TABLES WITH READ LOCK;
-- 另外一个会话尝试锁定时：
LOCK TABLES sakila.film WRITE;
-- 会挂起，通过SHOW PROCESSLIST可以看到：
State: Waiting for release of readlock  --这意味着全局锁</pre>
<p><span style="text-decoration: underline;"><strong>名称锁（Name Locks）</strong></span></p>
<p>这是一种表锁，当服务器进行表重命名或者DROP的时候会创建，该锁会与其他类型的表锁冲突并导致等待。</p>
<p><span style="text-decoration: underline;"><strong>用户锁（User Locks）</strong></span></p>
<p>本质上是一种命名的互斥量：</p>
<pre class="crayon-plain-tag">-- 会话1中执行：
SELECT GET_LOCK('my lock', 100);
-- 尝试在会话2上述语句，会导致挂起，SHOW PROCESSLIST显示：
State: User lock</pre>
<div class="blog_h2"><span class="graybg">发生在InnoDB级别的锁等待</span></div>
<p>任何支持row-level锁的存储引擎，在其内部实现锁定机制。</p>
<p>InnoDB通过show engine innodb status暴露了一些关于锁的信息，如果某个事务在等待一个锁，在transactions段可以看到相应的输出：</p>
<pre class="crayon-plain-tag">mysql&gt; SET AUTOCOMMIT=0;
mysql&gt; BEGIN;
mysql&gt; SELECT film_id FROM sakila.film LIMIT 1 FOR UPDATE;

show engine innodb status
//显示类似如下的类型
1 LOCK WAIT 2 lock struct(s), heap size 1216
2 MySQL thread id 8, query id 89 localhost baron Sending data
3 SELECT film_id FROM sakila.film LIMIT 1 FOR UPDATE
4 ------- TRX HAS BEEN WAITING 9 SEC FOR THIS LOCK TO BE GRANTED: //已经等待了9秒
//线程正在等待索引idx_fk_language_id的194页的独占锁（lock_mode X）
5 RECORD LOCKS space id 0 page no 194 n bits 1072 index `idx` of table `sakila/film` trx id 0 61714 lock_mode X waiting

//最终，所等待会超时，显示以下内容：
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

//但是，无法看到是谁锁定了表，可以创建下面的表来激活InnoDB 锁监视器
CREATE TABLE innodb_lock_monitor(a int) ENGINE=INNODB;
//上述语句执行后，InnoDB定期（通常每分钟数次）会打印增强版本的（亦可手工执行）
//show engine innodb status信息到标准输出（通常是MySQL错误日志文件）
//如果要停止监控，DROP innodb_lock_monitor

//打开错误日志，可以看到类似下面的详细信息：
1 ---TRANSACTION 0 61717, ACTIVE 3 sec, process no 5102, OS thread id 1141152080
2 3 lock struct(s), heap size 1216
 //显示进程ID，与process list的ID列一致
3 MySQL thread id 11, query id 108 localhost baron
4 show innodb status
//该事务在film表上持有一个隐含的I独占锁X
5 TABLE LOCK table `sakila/film` trx id 0 61717 lock mode IX 
//显示索引上的锁的详细信息
6 RECORD LOCKS space id 0 page no 194 n bits 1072 index `idx` of table `sakila/film` trx id 0 61717 lock_mode X
7 Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
8 ... omitted ... //包含被锁定记录的DUMP，难以看懂，可以使用innotop、Percona Server或者MariaDB
9
10 RECORD LOCKS space id 0 page no 231 n bits 168 index `PRIMARY` of table `sakila/film`
trx id 0 61717 lock_mode X locks rec but not gap
11 Record lock, heap no 2 PHYSICAL RECORD: n_fields 15; compact format; info bits 0
12 ... omitted ...</pre>
<p>可以使用information_schema中的表来获取锁的信息：</p>
<pre class="crayon-plain-tag">-- 显示谁持有锁（blocking），谁在等待(waiting)，等待了多久
SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread,
    TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP) AS wait_time,
    r.trx_query AS waiting_query,
    l.lock_table AS waiting_table_lock,
    b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread,
    SUBSTRING(p.host, 1, INSTR(p.host, ':') - 1) AS blocking_host,
    SUBSTRING(p.host, INSTR(p.host, ':') +1) AS blocking_port,
    IF(p.command = "Sleep", p.time, 0) AS idle_in_trx,
    b.trx_query AS blocking_query
FROM information_schema.INNODB_LOCK_WAITS AS w
INNER JOIN information_schema.INNODB_TRX AS b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.INNODB_TRX AS r ON r.trx_id = w.requesting_trx_id
INNER JOIN information_schema.INNODB_LOCKS AS l ON w.requested_lock_id = l.lock_id
LEFT JOIN information_schema.PROCESSLIST AS p ON p.id = b.trx_mysql_thread_id
ORDER BY wait_time DESC

*************************** 1. row ***************************
    waiting_trx_id: 5D03
    waiting_thread: 3
         wait_time: 6  --已经等待了6秒
     waiting_query: select * from store limit 1 for update
waiting_table_lock: `sakila`.`store`
   blocking_trx_id: 5D02
   blocking_thread: 2
     blocking_host: localhost
     blocking_port: 40298
       idle_in_trx: 8  --持有锁，已经空闲了8秒
    blocking_query: NULL


-- 显示某个锁的持有者，多少线程在等待，等待了多久
SELECT CONCAT('thread ', b.trx_mysql_thread_id, ' from ', p.host) AS who_blocks,
    IF(p.command = "Sleep", p.time, 0) AS idle_in_trx,
    MAX(TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW())) AS max_wait_time,
    COUNT(*) AS num_waiters
FROM information_schema.INNODB_LOCK_WAITS AS w
INNER JOIN information_schema.INNODB_TRX AS b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.INNODB_TRX AS r ON r.trx_id = w.requesting_trx_id
LEFT JOIN information_schema.PROCESSLIST AS p ON p.id = b.trx_mysql_thread_id
GROUP BY who_blocks ORDER BY num_waiters DESC

*************************** 1. row ***************************
   who_blocks: thread 2 from localhost:40298
  idle_in_trx: 1016  -- 线程2已经空闲了很久
max_wait_time: 37    -- 最长的线程已经等待了37秒
  num_waiters: 8     -- 8个线程在等待2完成工作并提交</pre>
<div class="blog_h1"><span class="graybg">复制模式</span></div>
<p>执行同步之前，首先要保证两个服务器的数据集一致。</p>
<div class="blog_h2"><span class="graybg">主主复制</span></div>
<p>主主复制和主从复制类似，只是互相指认对方为Master。需要注意可能存在的修改丢失问题 —— 如果SQL同步完成之间，同一条数据在目标库上修改，则随后发生的同步会覆盖此修改。</p>
<p>本节介绍一个主主复制的实例，服务器HK 47.90.76.189和BJ 39.107.94.255进行主主同步。</p>
<div class="blog_h3"><span class="graybg">创建同步用户</span></div>
<p>在两台机器上分别执行：</p>
<pre class="crayon-plain-tag">mysql -uroot &lt;&lt;SQL
create user 'sync'@'localhost' identified by 'sync';
grant replication slave on *.* to 'sync'@'localhost';
flush privileges;
SQL</pre>
<div class="blog_h3"><span class="graybg">创建安全通道</span></div>
<p>两台服务器位于公网上，因此必须使用安全信道。我们可以使用SSH隧道。</p>
<p>首先安装SSH隧道监控工具：</p>
<pre class="crayon-plain-tag">wget http://www.harding.motd.ca/autossh/autossh-1.4e.tgz
tar xzf autossh-1.4e.tgz &amp;&amp; rm  autossh-1.4e.tgz
cd autossh-1.4e/
./configure --prefix=/usr
make &amp;&amp; make install
cd ..
rm -rf autossh-1.4e</pre>
<p>然后开启自我维护的SSH隧道：</p>
<pre class="crayon-plain-tag"># 在BJ执行：
# localhost:3307:localhost:3306，将本机网络接口localhost上的3307端口绑定到远程主机localhost上的3306端口
autossh -M 22222 -fnN -L localhost:3307:localhost:3306 -i ~/Documents/puTTY/gmem.key root@hk.gmem.cc
# 在HK执行：
autossh -M 22222 -fnN -L localhost:3307:localhost:3306 -i ~/Documents/puTTY/gmem.key root@bj.gmem.cc </pre>
<div class="blog_h3"><span class="graybg">修改配置文件</span></div>
<p>主要相关的配置项如下表：</p>
<pre class="crayon-plain-tag">[mysqld]
# 必须，服务器标识符全局唯一
server-id                      = 1
# 必须，启用二进制日志
log-bin                        = /var/lib/mysql/mysql-bin
# 可选，混合二进制日志格式
binlog_format                  = mixed
# 可选，二进制日志保存时间
expire-logs-days               = 14
# 可选，每次事务提交后，都同步二进制日志缓存到磁盘
sync-binlog                    = 1

# Master相关配置
# 记录二进制日志的数据库列表，逗号分隔
binlog-do-db                   = gmem
# 必须，不记录二进制日志的数据库
binlog-ignore-db               = mysql,information_schema,performance_schema
# 主主同步时，每个数据库都可能插入数据，因此需要防止自增长键的冲突
# 自增长键每次增长的步长
auto-increment-increment       = 2
# 自增长键的偏移量
auto-increment-offset          = 1

# SLAVE相关配置
# 重放二进制日志的数据库列表
replicate-do-db                = gmem
# 必须，忽略二进制日志的数据库
replicate-ignore-db            = mysql,information_schema,performance_schema
# 可选，开启中继日志。复制线程先把Master的日志拷贝到中继日志中，再异步入库
relay_log                      = /var/lib/mysql/relay-bin
# 必须，中继日志执行之后将变化写入自己的二进制文件
log-slave-updates              = ON
# 可选，跳过所有的失败的SQL
slave-skip-errors              = all</pre>
<div class="blog_h3"><span class="graybg">获取二进制日志信息 </span></div>
<p>让两台服务器分别进入只读模式，然后获取二进制日志偏移量： </p>
<pre class="crayon-plain-tag"># HK上的执行结果
mysql&gt; FLUSH TABLES WITH READ LOCK;
Query OK, 0 rows affected (0.00 sec)

mysql&gt; show master status\G
*************************** 1. row ***************************
            File: mysql-bin.000002
        Position: 1373523
    Binlog_Do_DB: gmem
Binlog_Ignore_DB: mysql,information_schema,performance_schema
1 row in set (0.00 sec)

mysql&gt; UNLOCK TABLES;

# BJ上的执行结果：
mysql&gt; FLUSH TABLES WITH READ LOCK;
Query OK, 0 rows affected (0.01 sec)

mysql&gt; show master status\G
*************************** 1. row ***************************
            File: mysql-bin.000002
        Position: 434
    Binlog_Do_DB: gmem
Binlog_Ignore_DB: mysql,information_schema,performance_schema
1 row in set (0.00 sec)</pre>
<div class="blog_h3"><span class="graybg">指定MASTER位置</span></div>
<pre class="crayon-plain-tag"># HK上的命令
mysql&gt; CHANGE MASTER TO MASTER_HOST='localhost',MASTER_PORT=3307,MASTER_USER='sync',MASTER_PASSWORD='sync',MASTER_LOG_FILE='mysql-bin.000002',MASTER_LOG_POS=434;
# BJ上的命令
mysql&gt; CHANGE MASTER TO MASTER_HOST='localhost',MASTER_PORT=3307,MASTER_USER='sync',MASTER_PASSWORD='sync',MASTER_LOG_FILE='mysql-bin.000002',MASTER_LOG_POS=1373523;</pre>
<div class="blog_h3"><span class="graybg">启动Slave线程 </span></div>
<p>在两台服务器上分别执行：</p>
<pre class="crayon-plain-tag">mysql&gt; start slave; 
# 查看状态：
mysql&gt; show slave status\G
...
Slave_IO_Running: Yes 
Slave_SQL_Running: Yes</pre>
<p>除非手工停止，MySQL重启后Slave都会自动启动。</p>
<div class="blog_h3"><span class="graybg">停止Slave线程</span></div>
<p>如果不希望继续进行数据同步，可以在服务器上执行：</p>
<pre class="crayon-plain-tag">mysql&gt; stop slave;</pre>
<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">id</span></div>
<p>该列并非某种序列号，两个相邻的具有相同id的行，意味着基于标准的一遍扫描多次联接（single-sweep multi-join，从第一个表读取一行，然后从第二个表查找其匹配行）方式进行JOIN。</p>
<div class="blog_h3"><span class="graybg">select_type</span></div>
<p>可以用于提示：</p>
<ol>
<li>一个新的子作用域被打开</li>
<li>当前行和前面的行的某种关系</li>
<li>当前行执行何种操作</li>
</ol>
<p>具体如下：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">取值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>SIMPLE</td>
<td>整个查询没有子查询，或者UNION</td>
</tr>
<tr>
<td>PRIMARY</td>
<td>这行是最外层的SELECT</td>
</tr>
<tr>
<td>UNION</td>
<td>UNION语句中第二个，以及后续的SELECT</td>
</tr>
<tr>
<td>DEPENDENT UNION</td>
<td>UNION语句中第二个，以及后续的SELECT，且依赖于外层查询</td>
</tr>
<tr>
<td>UNION RESULT</td>
<td>UNION语句的结果</td>
</tr>
<tr>
<td>SUBQUERY</td>
<td>子查询中的第一个SELECT语句，打开了一个子作用域，出现在WHERE、SELECT中的子查询</td>
</tr>
<tr>
<td>DEPENDENT SUBQUERY</td>
<td>子查询中的第一个SELECT语句，且依赖于外层查询</td>
</tr>
<tr>
<td>DERIVED</td>
<td>出现在WHERE中的子查询</td>
</tr>
<tr>
<td>UNCACHEABLE SUBQUERY</td>
<td>子查询的结果不可以被缓存，必须为外层查询的每一行重新执行子查询</td>
</tr>
<tr>
<td>UNCACHEABLE UNION</td>
<td>子查询是一个UNION，子查询不可以被缓存。这种情况下第二个，以及后续的SELECT显示为此值</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">table</span></div>
<p>通常是表名或者别名。也可以是：</p>
<ol>
<li>derivedN：表示此行对ID为N的子查询所产生的临时表进行访问</li>
<li>unionM,N：表示对ID为M～N的查询的UNION结果进行访问</li>
</ol>
<div class="blog_h3"><span class="graybg">partitions</span></div>
<p>查询匹配哪些分区，仅仅用于分区表。</p>
<div class="blog_h3"><span class="graybg">type</span></div>
<p>说明表如何被JOIN：</p>
<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>system</td>
<td>const的特例，表仅仅包含一行（系统表）</td>
</tr>
<tr>
<td>const</td>
<td>
<p>表中最多包含一行匹配。因而，此唯一匹配行中的任何列的引用，都可以被查询优化器作为常量看待。const表非常快，因为它仅仅被读取一次</p>
<p>如果你在SQL中对主键、唯一性索引和常量值进行等于比较，则const出现</p>
</td>
</tr>
<tr>
<td>eq_ref</td>
<td>
<p>对于前表的所有行（或者前面N表的行的任何组合），<span style="background-color: #c0c0c0;">当前表中的单行</span>被读取，以便与其匹配</p>
<p>除了system/const之外，这是最好的JOIN方式。如果唯一性索引、主键索引被完整使用并且操作符为=，则eq_ref出现</p>
</td>
</tr>
<tr>
<td>ref</td>
<td>
<p>对于前表的所有行（或者前面N表的行的任何组合），当前表索引值匹配的全部行被读取，以便与前者匹配</p>
<p>使用索引前缀、使用非唯一性索引时，ref会出现</p>
</td>
</tr>
<tr>
<td>fulltext</td>
<td>使用全文索引进行JOIN</td>
</tr>
<tr>
<td>ref_or_null</td>
<td>类似于ref，但是MySQL会额外检查NULL值的行</td>
</tr>
<tr>
<td>index_merge</td>
<td>提示使用了索引合并优化。在这种情况下，key列包含了一系列使用到的索引</td>
</tr>
<tr>
<td>unique_subquery</td>
<td>
<p>对于某些形式的子查询，替代eq_ref，例如：</p>
<p style="padding-left: 30px;">value IN (SELECT primary_key FROM single_table WHERE some_expr)</p>
</td>
</tr>
<tr>
<td>index_subquery</td>
<td>
<p>类似于上一条，但是不使用唯一性索引，例如：</p>
<p style="padding-left: 30px;">value IN (SELECT key_column FROM single_table WHERE some_expr)</p>
</td>
</tr>
<tr>
<td>range</td>
<td>
<p>使用索引来匹配行，仅仅索引值在范围内的行被取出。在这种情况下，key列显示用到的索引，key_len显示用到的key长度，ref列显示为NULL</p>
<p>当你将索引列和常量进行=, &lt;&gt;, &gt;, &gt;=, &lt;, &lt;=, IS NULL, &lt;=&gt;, BETWEEN, IN()操作时，会出现range</p>
</td>
</tr>
<tr>
<td>index</td>
<td>
<p>类似于ALL，但是进行索引树扫描，两种情况下发生：</p>
<ol>
<li>如果索引对查询来说是覆盖的，可以获取所有需要的数据，则仅仅扫描索引树。这种情况下Extra提示Using index。index扫描通常大于ALL，因为索引数据通常小于表数据</li>
<li>执行了全表扫描，但是利用索引，按照索引序读取数据行。这种情况下Using index不出现在Extra</li>
</ol>
</td>
</tr>
<tr>
<td>ALL</td>
<td>
<p>对于前表的所有行（或者前面N表的行的任何组合），当前表的所有行都要被取出进行匹配</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">possible_keys</span></div>
<p>MySQL可以选择哪些索引来寻找当前表中的匹配行，如果显示NULL则意味着没有可用索引。</p>
<div class="blog_h3"><span class="graybg">key</span></div>
<p>MySQL实际决定使用的索引。</p>
<div class="blog_h3"><span class="graybg">key_len</span></div>
<p>MySQL使用索引的前多少字节。</p>
<div class="blog_h3"><span class="graybg">ref</span></div>
<p>使用到的索引（key字段）和什么列/常量进行比较，以获取匹配的行。</p>
<div class="blog_h3"><span class="graybg">rows</span></div>
<p>为了执行查询，MySQL需要检查的行数。对于InnoDB来说这一数值是大概的，不一定准确。</p>
<div class="blog_h3"><span class="graybg">filtered</span></div>
<p>大概估算的被条件过滤的行的比例。rows × filtered / 100为实际和前表进行JOIN的行数量。</p>
<p>执行命令EXPLAIN EXTENDED时该列出现。</p>
<div class="blog_h3"><span class="graybg">Extra</span></div>
<p>关于MySQL如何解析查询的额外信息。</p>
<div class="blog_h1"><span class="graybg">Percona工具集</span></div>
<div class="blog_h2"><span class="graybg">pt-visual-explain</span></div>
<p>用于将MySQL的执行计划转换为树状形式。命令格式：</p>
<pre class="crayon-plain-tag"># 从FILES读取执行计划
pt-visual-explain [OPTIONS] [FILES]
# 从MySQL命令输出中读取执行计划
mysql -e SQL | pt-visual-explain</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">树状显示执行计划</span></div>
<pre class="crayon-plain-tag">mysql -h 10.255.223.249 -P 13306 -uuser -ppasswd -D digital -e 
"EXPLAIN SELECT count(1) subscriptionCount 
    FROM media_customer_subscription s 
    LEFT JOIN media_chapter c ON s.media_id = c.media_id 
 WHERE 1 = 1 AND s.media_id = 1960162291 AND s.cust_id = 274516853 AND c.last_modifyed_date &gt;= '2018-03-21 19:35:19'"

| pt-visual-explain

# 输出
JOIN
+- Filter with WHERE
|  +- Bookmark lookup
|     +- Table
|     |  table          c
|     |  possible_keys  index_media_id_index_order
|     +- Index lookup
|        key            c-&gt;index_media_id_index_order
|        possible_keys  index_media_id_index_order
|        key_len        9
|        ref            const
|        rows           2823
+- Filter with WHERE
   +- Index lookup
      key            s-&gt;customer_subscription_cust_media_id
      possible_keys  customer_subscription_media_id,customer_subscription_cust_id_index,customer_subscription_cust_media_id
      key_len        18
      ref            const,const
      rows           1
# 解释如下：
# 1、对s表进行索引扫描，估计访问1行。ref分别为s.media_id、s.cust_id两个常量值
# 2、对c表进行索引扫描，预计访问2823行。ref为c.media_id
# 3、对c表通过索引定位的每一行，进行bookmark查找，即根据非聚簇索引上的条目查找实际数据
# 4、执行JOIN</pre>
<div class="blog_h2"><span class="graybg">频繁CUD表的优化</span></div>
<p>频繁的对表进行插入、修改、删除，可能导致表空间上出现大量零散的空白，即碎片化，碎片会影响表的读取效率。下面的语句可以查看表的碎片化情况：</p>
<pre class="crayon-plain-tag">SELECT
	ENGINE,
	TABLE_NAME,
	Round(DATA_LENGTH / 1024 / 1024) AS data_mb,
	round(INDEX_LENGTH / 1024 / 1024) AS index_mb,
	round(DATA_FREE / 1024 / 1024) AS data_free_mb
FROM
	information_schema. TABLES
WHERE
	DATA_FREE &gt; 0
ORDER BY
  DATA_FREE DESC</pre>
<p>注意一个<span style="background-color: #c0c0c0;">表空间中的所有表，会共享一个data_free</span>，因此如果使用单独数据文件，可能看到很多重复的data_free值。使用下面的语句可以进行表优化：</p>
<pre class="crayon-plain-tag">optimize table wp_posts;
-- 如果提示： Table does not support optimize, doing recreate + analyze instead
-- 说明引擎不支持，可以改用：
alter table wp_posts engine='InnoDB'; 
-- 注意，如果设置：innodb_file_per_table=1，那么alter将为表新建独立的.ibd文件</pre>
<div class="blog_h2"><span class="graybg">1067进程意外终止（process terminated unexpectedly）</span></div>
<p>找到my.ini，定位datadir的位置，其目录下有日志文件：服务器名.error，里面包含详细错误信息。如果配置存在问题，可以通过bin\MySQLIntanceConfig.exe进行重新配置。</p>
<p>注意，MySQL错误日志文件路径可以通过log-error参数定制。</p>
<div class="blog_h2"><span class="graybg">MySQL ERROR 1215 (HY000): Cannot add foreign key constraint</span></div>
<p>添加外键约束时出现此错误，可能原因是：外键字段与目标表的主键字段的类型不兼容，例如varchar与int。</p>
<div class="blog_h2"><span class="graybg">The user specified as a definer ('root'@'%') does not exist</span></div>
<p>属于授权问题，可以授予目标用户相关的权限来解决：</p>
<pre class="crayon-plain-tag">grant all privileges on *.* to root@"%" identified by "root"; </pre>
<div class="blog_h2"><span class="graybg">InnoDB大表批量删除效率问题</span></div>
<p>大表删除速度慢，原因和聚簇索引（cluster index，此索引每张表只能有一个）有关，MySQL会使用主键、唯一键（作为聚簇索引列）来在物理磁盘上对表的数据行进行排序，一旦行被删除，就会进行整个表的重新排序，这个排序正是耗时操作所在。 解决方案：</p>
<ol>
<li>规划主键的设计，如果删除的数据，其主键是集中在一个区间的，最好了</li>
<li>或者，添加一个BIT列，在删除的时候，将其状态改为false，在某个时候进行单次批量删除</li>
</ol>
<div class="blog_h2"><span class="graybg">如何确认MySQL使用哪个my.cnf文件</span></div>
<pre class="crayon-plain-tag">rem 打印MySQL使用的运行参数，可以检测配置是否生效
mysql --print-defaults    
rem 执行命令
mysql --verbose --help | less
rem 一直按回车，直到看见“Default options are read from the following files in the given order”
rem 下一行即为读取的配置文件位置，例如
rem C:\Windows\my.ini C:\Windows\my.cnf C:\my.ini C:\my.cnf D:\Programs\MySQL\my.ini D:\Programs\MySQL\my.cnf</pre>
<p>另外，如果MySQL在Windows下作为服务运行，可能需要注意MySQL服务的配置，其命令行参数可能附带 --defaults-file参数，该参数指明了该服务实例使用的my.cnf文件。</p>
<div class="blog_h2"><span class="graybg">彻底重新安装</span></div>
<pre class="crayon-plain-tag">sudo apt-get purge mysql* -y
sudo apt-get autoremove -y
sudo apt-get autoclean
sudo apt-get install mysql-server -y</pre>
<div class="blog_h2"><span class="graybg">使用MySQL客户端命令</span></div>
<pre class="crayon-plain-tag"># 执行命令行提供的SQL语句并退出
mysql -uroot -proot -e "create database newdb;"</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/mysql-faq">MySQL知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/mysql-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
