<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.5">Jekyll</generator><link href="https://cheng-dp.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://cheng-dp.github.io/" rel="alternate" type="text/html" /><updated>2019-09-19T09:13:15+08:00</updated><id>https://cheng-dp.github.io/feed.xml</id><title type="html">东平的笔记仓库</title><subtitle>我的博客，主要是笔记归档。</subtitle><author><name>Cheng Dongping</name></author><entry><title type="html">详解MySQL中的数据类型</title><link href="https://cheng-dp.github.io/2019/05/21/mysql-data-type/" rel="alternate" type="text/html" title="详解MySQL中的数据类型" /><published>2019-05-21T00:00:00+08:00</published><updated>2019-05-21T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/05/21/mysql-data-type</id><content type="html" xml:base="https://cheng-dp.github.io/2019/05/21/mysql-data-type/">&lt;h2 id=&quot;整型&quot;&gt;整型&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;TINYINT&lt;/li&gt;
  &lt;li&gt;SMALLINT&lt;/li&gt;
  &lt;li&gt;MEDIUMINT&lt;/li&gt;
  &lt;li&gt;INT&lt;/li&gt;
  &lt;li&gt;BIGINT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_int_types.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;MySQL可以为整型指定宽度，&lt;code class=&quot;highlighter-rouge&quot;&gt;int(5) zerofill&lt;/code&gt;表示当数值宽度小于5位时在前面用0填满宽度，此命令对存储层无影响，只是影响客户端的显示方式。&lt;/li&gt;
  &lt;li&gt;UNSIGNED 指定为无符号数，只针对整型。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;浮点型&quot;&gt;浮点型&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_float_double_types.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;FLOAT和DOUBLE都是非精确类型，无法保证运算正确性。&lt;/li&gt;
  &lt;li&gt;FLOAT(M,D)和DOUBLE(M,D)，M：总位数，&lt;code class=&quot;highlighter-rouge&quot;&gt;1 &amp;lt;= M &amp;lt;= 255&lt;/code&gt;。D：小数点后位数，&lt;code class=&quot;highlighter-rouge&quot;&gt;0 &amp;lt;= D &amp;lt;= 30&lt;/code&gt;。如设置(M,D)后插入数据超过限制将报错。&lt;/li&gt;
  &lt;li&gt;FLOAT和DOUBLE不指定精度时，默认按照实际精度显示。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;定点型&quot;&gt;定点型&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;字节&lt;/th&gt;
      &lt;th&gt;范围&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;DECIMAL(M,D)&lt;/td&gt;
      &lt;td&gt;如果M &amp;gt; D，为M + 2&amp;lt;\b&amp;gt; 如果M &amp;lt;= D，为D + 2&lt;/td&gt;
      &lt;td&gt;依赖于M和D的值&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;DECIMAL(M,D)为高精度数据类型，是精确类型，在MySQL内部以字符串形式存放。&lt;/li&gt;
  &lt;li&gt;最大取值范围与DOUBLE相同，给定DECIMAL的有效取值范围由M和D决定。1 &amp;lt; M &amp;lt; 254，0 &amp;lt; D &amp;lt; 60。&lt;/li&gt;
  &lt;li&gt;DECIMAL不指定时，默认M = 10, D = 0。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;日期时间类型&quot;&gt;日期时间类型&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;DATE&lt;/li&gt;
  &lt;li&gt;TIME&lt;/li&gt;
  &lt;li&gt;YEAR&lt;/li&gt;
  &lt;li&gt;DATETIME&lt;/li&gt;
  &lt;li&gt;TIMESTAMP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_datetime_type.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;timestamp&quot;&gt;TIMESTAMP&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;TIMESTAMP和DATETIME显示的结果相同，只是TIMESTAMP保存的是时间戳，为&lt;code class=&quot;highlighter-rouge&quot;&gt;1970-01-01 00:00:00&lt;/code&gt;到当前时间的毫秒数。&lt;/li&gt;
  &lt;li&gt;TIMESTAMP存储范[&lt;code class=&quot;highlighter-rouge&quot;&gt;1970-01-01 00:00:01&lt;/code&gt;,&lt;code class=&quot;highlighter-rouge&quot;&gt;2038-01-19 03:14:07&lt;/code&gt;]，DATETIME存储范围[&lt;code class=&quot;highlighter-rouge&quot;&gt;1000-01-01 00:00:00&lt;/code&gt;,&lt;code class=&quot;highlighter-rouge&quot;&gt;9999-12-31 23:59:59&lt;/code&gt;]。&lt;/li&gt;
  &lt;li&gt;TIMSTAMP显示时会考虑时区。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在MySQL中，由于TIMESTAMP经常用于记录操作时间，因此做了一些特殊处理：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;没有显示设置为NULL的TIMESTAMP将被加上NOT NULL属性。(其他类型没有显示设为NULL默认是NULL)。&lt;/li&gt;
  &lt;li&gt;table中的第一个TIMESTAMP列，如果没有显示设置为NULL且没有DEFAULT和ON UPDATE语句，将被自动添加：DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP。&lt;/li&gt;
  &lt;li&gt;table中非第一个TIMESTAMP列，如果没有显示设置为NULl且没有DEFAULT子句，将自动添加：DEFAULT ‘0000-00-00 00:00:00’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;设置ON UPDATE CURRENT_TIMESTAMP的TIMESTAMP:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;必须同时设置DEFAULT。&lt;/li&gt;
  &lt;li&gt;当更新操作更新了该行数据，自动更新该TIMESTAMP的值。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;上述特殊处理由&lt;code class=&quot;highlighter-rouge&quot;&gt;explicit_defaults_for_timestamp&lt;/code&gt;参数配置，当&lt;code class=&quot;highlighter-rouge&quot;&gt;explicit_defaults_for_timestamp=off&lt;/code&gt;时遵循上述配置，当该值为&lt;code class=&quot;highlighter-rouge&quot;&gt;on&lt;/code&gt;时，timestamp和其他类型一样配置。默认值为&lt;code class=&quot;highlighter-rouge&quot;&gt;on&lt;/code&gt;，即与其他类型一样配置。&lt;/p&gt;

&lt;h3 id=&quot;获取当前时间&quot;&gt;获取当前时间&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;CURRENT_TIMESTAMP&lt;/li&gt;
  &lt;li&gt;CURRENT_TIMESTAMP()&lt;/li&gt;
  &lt;li&gt;NOW()&lt;/li&gt;
  &lt;li&gt;LOCALTIME&lt;/li&gt;
  &lt;li&gt;LOCALTIME()&lt;/li&gt;
  &lt;li&gt;LOCALTIMESTAMP&lt;/li&gt;
  &lt;li&gt;LOCALTIMESTAMP()&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;帮助函数&quot;&gt;帮助函数&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;FROM_UNIXTIME() ： 把UNIX时间戳转换为日期。&lt;/li&gt;
  &lt;li&gt;UNIX_TIMESTAMP()：把日期转换为UNIX时间戳。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;字符串类型&quot;&gt;字符串类型&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;CHAR&lt;/li&gt;
  &lt;li&gt;VARCHAR&lt;/li&gt;
  &lt;li&gt;BIT&lt;/li&gt;
  &lt;li&gt;BINARY&lt;/li&gt;
  &lt;li&gt;VARBINARY&lt;/li&gt;
  &lt;li&gt;BLOB&lt;/li&gt;
  &lt;li&gt;TEXT&lt;/li&gt;
  &lt;li&gt;ENUM&lt;/li&gt;
  &lt;li&gt;SET&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_string_type.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2*24 = 16 777 216 (占3字节)
2*32 = 4 294 967 296 (占4字节)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;char-和-varchar&quot;&gt;CHAR 和 VARCHAR&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;CHAR类型使用固定长度空间进行存储，范围0-255个字符。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;字符长度不是字节长度(字节长度由`字符长度 * 编码字节数`可得)，例如CHAR(30)能放30个字符，存放`abcd`时，在uft-8编码下，实际占用30 * 3 bytes，==尾部会以空格补齐==，但检索时尾部空格会被去除。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;VARCHAR类型保存可变长字符串，长度范围0-65535个字符(受到单行最大64kb的限制)。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;VARCHAR(30)存放`abcd`，实际使用4 + 1 = 5个字节，额外的1个字节用来表示字符串长度(0-255使用1个字节，超过255使用2个字节)。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;char-和-varchar-使用情况&quot;&gt;CHAR 和 VARCHAR 使用情况&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;根据字符长度判断&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;对于短字符长度的字段，CHAR类型不会浪费太多空间，且VARCHAR类型在修改时需要重新分配空间、并且还需要字节维护字节长度，因此，建议直接使用CHAR类型。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;考虑长度是否相近&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果字段的长度总是相同或相近的，则考虑使用CHAR类型。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;从碎片角度考虑&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;CHAR类型存储空间是一次性分配的，字段内容存储在一起，因此不会出现空间碎片。而VARCHAR类型当修改前后数据长度不一致时，由于新的空间分配，会出现碎片问题。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;VARCHAR字符设定不能太慷慨&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;VARCHAR类型虽然在硬盘上的存储空间时根据实际字符长度分配的，但是在内存中依然时按照字符类型定义的长度分配。&lt;/p&gt;

&lt;h4 id=&quot;char_length和length&quot;&gt;char_length()和length()&lt;/h4&gt;

&lt;p&gt;char_length(‘‘)计算字符串字符数。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;select char_length('abcd中文中文');

8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;length(‘‘)计算字符串的字节数，和使用的编码有关。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;select length('abcd中文中文');

16
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;binary-和-varbinary&quot;&gt;BINARY 和 VARBINARY&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;BINARY&lt;/strong&gt;和CHAR类型类似，只是存储的是二进制数据，二进制长度为0-255字节，且不足最大长度的，将在右边填充’0x00’补齐。SQL操作时基于二进制数值进行比较和排序。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VARBINARY&lt;/strong&gt;和VARCHAR类似，只是存储的是二进制数据，二进制长度为0-65535字节，存储的是可变长度数据，不足最大长度不会填充。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;，BINARY的值在查询和取出时，不会删除填充的’0x00’，所有字节在比较时都有意义，因此在使用BINARY存储二进制数据时，要注意填充和删除的特性。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; CREATE TABLE t (c BINARY(3));
Query OK, 0 rows affected (0.01 sec)

mysql&amp;gt; INSERT INTO t SET c = 'a';
Query OK, 1 row affected (0.01 sec)

mysql&amp;gt; SELECT HEX(c), c = 'a', c = 'a\0\0' from t;
+--------+---------+-------------+
| HEX(c) | c = 'a' | c = 'a\0\0' |
+--------+---------+-------------+
| 610000 |       0 |           1 |
+--------+---------+-------------+
1 row in set (0.09 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果检索的值必须和没有填充存储的值一样，那么可能用varbinary或者blob数据类型的一种比较合适。&lt;/p&gt;

&lt;h3 id=&quot;bit&quot;&gt;BIT&lt;/h3&gt;

&lt;p&gt;位字段类型，BIT(M)中表示二进制位数。范围为1 &amp;lt;= M &amp;lt;= 64，默认值为1。如果值的长度小于M位，在值的左边用0填充。&lt;/p&gt;

&lt;h3 id=&quot;blob-和-text&quot;&gt;BLOB 和 TEXT&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;BLOB(Binary Large Object)&lt;/strong&gt;，二进制大对象，是一个可以存储二进制文件的容器。典型的BLOB是一张图片或多媒体文件。在SQL处理时，BLOB数据被视作二进制字符串进行比较和排序。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;大小(字节)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;TINYBLOB&lt;/td&gt;
      &lt;td&gt;最大255B 2*8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;BLOB&lt;/td&gt;
      &lt;td&gt;最大64KB 2*16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MEDIUMBLOB&lt;/td&gt;
      &lt;td&gt;最大16MB 2*24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LONGBLOB&lt;/td&gt;
      &lt;td&gt;最大4GB 2*32&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;当插入的数据超过BLOB的限制大小时，多出的二进制字符会被截断，并返回WARNNING。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TEXT&lt;/strong&gt;保存大文本数据，存储的是字符。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;大小(字符)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;TINYTEXT&lt;/td&gt;
      &lt;td&gt;最大255 2*8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;TEXT&lt;/td&gt;
      &lt;td&gt;最大64KB 2*16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MEDIUMTEXT&lt;/td&gt;
      &lt;td&gt;最大16MB 2*24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LONGTEXT&lt;/td&gt;
      &lt;td&gt;最大4GB 2*32&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;在SQL处理时，TEXT中的字符数据不区分大小写。&lt;/p&gt;

&lt;p&gt;在MySQL中，BLOB/TEXT都不直接存储在数据页中，而是在数据页中存储一个指向BLOB/TEXT数据位置的指针，因此==BLOB/TEXT无法直接成为PRIMARY KEY==。并且==如果要在BLOB/TEXT列上加上索引KEY，需要指明建立索引的前缀数。==&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;engine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnoDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ERROR&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1170&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;42000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BLOB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'doc'&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;used&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;specification&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;without&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;length&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mysql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;engine&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnoDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Query&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;affected&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;set和enum&quot;&gt;SET和ENUM&lt;/h3&gt;

&lt;p&gt;由于MySQL不支持传统的CHECK约束，因此通过ENUM和SET类型可以对插入数据进行简单约束。&lt;/p&gt;

&lt;h4 id=&quot;set&quot;&gt;SET&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;SET&lt;/strong&gt;是一个字符串对象，一个可以有0个或多个值。其值必须来自于列创建时定义的允许范围。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; create table test (name set('jack','green','james','tim'));
Query OK, 0 rows affected (0.02 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; insert into test (name) values ('jack,green,tim');
Query OK, 1 row affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;==SET插入数据时，是以一个字符串的格式同时插入，数据之间以逗号隔开，所以SET定义的数据本身不能含有逗号。==&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SET最多只能存储64个元素&lt;/strong&gt;，且存储方式为：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1～8成员的集合，占1个字节。
9～16成员的集合，占2个字节。
17～24成员的集合，占3个字节。
25～32成员的集合，占4个字节。
33～64成员的集合，占8个字节。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;==SET在数据库中存储的并不是元素本身，而是元素序号==，且MySQL在存储时会按照定义的顺序存储元素。&lt;/p&gt;

&lt;p&gt;使用&lt;code class=&quot;highlighter-rouge&quot;&gt;find_in_set()&lt;/code&gt;方法或LIKE修饰符查找SET。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; select * from test where find_in_set('jack',name);
+----------------+
| name           |
+----------------+
| jack,green,tim |
+----------------+
1 row in set (0.00 sec)

mysql&amp;gt; select * from test where name like '%ja%gr%';
+----------------+
| name           |
+----------------+
| jack,green,tim |
+----------------+
1 row in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;enum&quot;&gt;ENUM&lt;/h4&gt;

&lt;p&gt;ENUM和SET一样也是一个字符串对象，并且在列定义时给出所有允许值列表。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ENUM允许同时定义最多65535个允许值。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MySQL对ENUM的存储同样是存储元素编号。除了插入数据时，除了插入预定义字符串，也可以直接用定义时的枚举顺序。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; create table test (name enum('jack','green','james','tim'));
Query OK, 0 rows affected (0.01 sec)

mysql&amp;gt; insert into test (name) values ('jack');
Query OK, 1 row affected (0.00 sec)

mysql&amp;gt; insert into test (name) values (2);
Query OK, 1 row affected (0.00 sec)

mysql&amp;gt; select * from test;
+-------+
| name  |
+-------+
| jack  |
| green |
+-------+
2 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;由于枚举成员在MySQL存储中映射到枚举顺序，因此可以使用运算符来进行查询：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; select * from test where name &amp;gt; 1;
+-------+
| name  |
+-------+
| green |
+-------+
1 row in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;refs&quot;&gt;REFS&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;http://seanlook.com/2016/04/28/mysql-char-varchar-set/&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/05/21/mysql-data-type/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">整型</summary></entry><entry><title type="html">详解MySQL中的索引机制</title><link href="https://cheng-dp.github.io/2019/05/20/mysql-index/" rel="alternate" type="text/html" title="详解MySQL中的索引机制" /><published>2019-05-20T00:00:00+08:00</published><updated>2019-05-20T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/05/20/mysql-index</id><content type="html" xml:base="https://cheng-dp.github.io/2019/05/20/mysql-index/">&lt;h2 id=&quot;索引分类&quot;&gt;索引分类&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;INDEX 普通索引&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最基本索引，允许出现相同的索引值，可以有NULL值。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;UNIQUE 唯一索引&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不可以出现相同的值，可以有NULL值。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;PRIMARY KEY 主键索引&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不允许出现相同的值，且不能为NULL值，一个表只能有一个primary_key索引。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;fulltext index 全文索引&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上述三种索引都是针对列的值发挥作用，但全文索引，可以针对值中的某个单词，比如一篇文章中的某个词，然而只有myisam以及英文支持，并且效率较低，但是可以用coreseek和xunsearch等第三方应用来完成这个需求。&lt;/p&gt;

&lt;h2 id=&quot;索引crud&quot;&gt;索引CRUD&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;索引创建&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ALTER TABLE&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ALTER TABLE `table_name` ADD INDEX `index_name` (`column_list`) --索引名，可要可不要；如果不要，当前的索引名就是该字段名。 
ALTER TABLE `table_name` ADD UNIQUE (`column_list`) 
ALTER TABLE `table_name` ADD PRIMARY KEY (`column_list`) 
ALTER TABLE `table_name` ADD FULLTEXT KEY (`column_list`)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;CREATE TABLE&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;CREATE TABLE `test1` ( 
  `id` smallint(5) UNSIGNED AUTO_INCREMENT NOT NULL, -- 注意，下面创建了主键索引，这里就不用创建了 
  `username` varchar(64) NOT NULL COMMENT '用户名', 
  `nickname` varchar(50) NOT NULL COMMENT '昵称/姓名', 
  `intro` text, 
  PRIMARY KEY (`id`),  
  UNIQUE KEY `unique1` (`username`), -- 索引名称，可要可不要，不要就是和列名一样 
  KEY `index1` (`nickname`), 
  FULLTEXT KEY `intro` (`intro`) 
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='后台用户表';
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;CREATE INDEX&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--例：只能添加这两种索引 
CREATE INDEX index_name ON table_name (column_list) 
CREATE UNIQUE INDEX index_name ON table_name (column_list)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ol&gt;
  &lt;li&gt;索引删除
```
DROP INDEX &lt;code class=&quot;highlighter-rouge&quot;&gt;index_name&lt;/code&gt; ON &lt;code class=&quot;highlighter-rouge&quot;&gt;talbe_name&lt;/code&gt;&lt;br /&gt;
ALTER TABLE &lt;code class=&quot;highlighter-rouge&quot;&gt;table_name&lt;/code&gt; DROP INDEX &lt;code class=&quot;highlighter-rouge&quot;&gt;index_name&lt;/code&gt; 
– 这两句都是等价的,都是删除掉table_name中的索引index_name;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ALTER TABLE &lt;code class=&quot;highlighter-rouge&quot;&gt;table_name&lt;/code&gt; DROP PRIMARY KEY – 删除主键索引，注意主键索引只能用这种方式删除&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;3. 索引更改

直接删除索引后重建。

4. 查看table中的所有索引
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;show index from tablename;&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;![image](https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_show_index_from_table.png)

## 索引限制
1. 一张表最多建立64个索引。


2. 组合索引最多包括16个列。

3. 单列索引长度

当`INNODB_LARGE_PREFIX=OFF`时，单列索引长度最大为767字节。

当`INNODB_LARGE_PREFIX=ON`时，单列索引长度最大为3072字节(1024*3)。

**为什么是767字节？**

256*3 - 1 = 767字节。3为utf-8字符最大占用空间。

4. 组合索引长度

组合索引长度最大为3072字节。

**为什么是3072字节？**

InnoDB一个Page默认16KB，由于是B+Tree，叶子节点上一个页至少包含两条记录(否则退化为链表)，因此一条记录最大为8KB。

InnoDB的聚簇索引结构，一个二级索引要包含主键索引，因此单个索引最大为4KB。

单个索引空间再减去一些辅助空间，最大为3500字节，取整数为1024×3 = 3072字节。

## 索引实现

### 磁盘预读与页

由于磁盘存储介质的特性，磁盘本身存储比主存慢很多，为了提高效率，磁盘利用数据的局部性原理，每次进行I/O读取时，不仅仅只是读取数据字节，而是将数所在的页都读取进磁盘。及磁盘存储是以页(Page)为单位的。

### 为什么使用B+树

**B树**中一次检索最多需要h-1次I/O（根节点常驻内存），渐进复杂度为O(h)=O(log_dN)。一般实际应用中，出度d是非常大的数字，通常超过100，因此h非常小（通常不超过3），用**B树**作为索引结构效率是非常高的。

而**红黑树**这种结构，h明显要深的多。由于逻辑上很近的节点（父子）物理上可能很远，无法利用局部性，所以红黑树的I/O渐进复杂度也为O(h)，效率明显比**B树**差很多。

从上面分析可以看到，d越大索引的性能越好，而出度的上限取决于节点内key和data的大小：

```math
(d_{max}=floor(pagesize / (keysize + datasize + pointsize)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;floor表示向下取整。&lt;strong&gt;由于B+Tree内节点去掉了data域，因此可以拥有更大的出度，拥有更好的性能。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;myisam索引实现&quot;&gt;MyISAM索引实现&lt;/h3&gt;

&lt;p&gt;MyISAM引擎B+树中，叶子节点的data域存放的是数据记录的地址。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_myISAM_index.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;辅助索引&lt;/strong&gt;和&lt;strong&gt;主索引&lt;/strong&gt;结构上没有任何区别，只是&lt;strong&gt;主索引&lt;/strong&gt;要求Key是唯一的，而&lt;strong&gt;辅助索引&lt;/strong&gt;的Key可以重复。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_myISAM_secondary_index.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引，如果指定的Key存在，则取出其data域的值，然后以data域的值为地址，读取相应数据记录。&lt;/p&gt;

&lt;p&gt;MyISAM索引实现的方式称为&lt;strong&gt;非聚集索引&lt;/strong&gt;，以此与InnoDB的&lt;strong&gt;聚集索引&lt;/strong&gt;区分。&lt;/p&gt;

&lt;h3 id=&quot;innodb索引实现&quot;&gt;InnoDB索引实现&lt;/h3&gt;

&lt;p&gt;MyISAM引擎索引文件和数据文件分离，而在InnoDB中，表数据文件本身就是按B+树组织的一个索引结构，这棵树的叶节点data域保存了完整的数据记录。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_innoDB_index.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;InnoDB中B+树叶节点按照主键聚集，包含了完整的数据记录，这种索引叫做&lt;strong&gt;聚集索引&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InnoDB要求表必须有主键（MyISAM可以没有）&lt;/strong&gt;，如果没有显式指定，则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键，如果不存在这种列，则MySQL自动为InnoDB表生成一个隐含字段作为主键，这个字段长度为6个字节，类型为长整形。&lt;/p&gt;

&lt;p&gt;==InnoDB的辅助索引data域存储相应记录主键的值而不是地址。==&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_innoDB_secondary_index.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;聚集索引这种实现方式使得按主键的搜索十分高效，但是辅助索引搜索需要检索两遍索引：首先检索辅助索引获得主键，然后用主键到主索引中检索获得记录。&lt;/p&gt;

&lt;h2 id=&quot;组合索引前缀索引和最左前缀原理&quot;&gt;组合索引、前缀索引和最左前缀原理&lt;/h2&gt;

&lt;h3 id=&quot;组合索引&quot;&gt;组合索引&lt;/h3&gt;

&lt;p&gt;多个列共同组合成一个索引，MySQL查询时对组合索引执行&lt;strong&gt;最左前缀查询&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ALTER TABLE `myIndex` ADD INDEX `name_city_age` (vc_Name(10),vc_City,i_Age);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;会用到组合索引：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT * FROM myIndex WHREE vc_Name=”erquan” AND vc_City=”郑州”
SELECT * FROM myIndex WHREE vc_Name=”erquan”
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;不会用到组合索引：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT * FROM myIndex WHREE i_Age=20 AND vc_City=”郑州”
SELECT * FROM myIndex WHREE vc_City=”郑州”
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;前缀索引&quot;&gt;前缀索引&lt;/h3&gt;

&lt;p&gt;如果索引列的长度过长，索引时将产生很大的索引文件，同时索引速度降低。&lt;/p&gt;

&lt;p&gt;前缀索引通过限制字符列索引的长度，减少索引文件大小，加快索引查询长度。&lt;/p&gt;

&lt;h3 id=&quot;最左前缀原理&quot;&gt;最左前缀原理&lt;/h3&gt;

&lt;p&gt;MySQL查询按照索引定义从左到右的方式使用索引。&lt;/p&gt;

&lt;p&gt;假设有组合索引：&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;emp_no,title,from_date&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;全列匹配&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当按照索引中所有列进行精确匹配（这里&lt;strong&gt;精确匹配指“=”或“IN”匹配&lt;/strong&gt;）时，索引可以被用到。这里有一点需要注意，理论上索引对顺序是敏感的，但是==MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引。==&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;最左前缀匹配&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当查询条件精确匹配索引的左边连续一个或几个列时，如&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;emp_no&amp;gt;&lt;/code&gt;或&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;emp_no,title&amp;gt;&lt;/code&gt;，索引可以被用到，但是只能用到一部分，即条件所组成的最左前缀。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;查询条件用到了索引中列的精确匹配，但是中间某个条件未提供。&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26';

查询只用到了索引的第一列，而后面的from_date虽然也在索引中，但是由于title不存在而无法和左前缀连接，因此需要对结果进行扫描过滤from_date。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在Title列数据较少时，可以使用填坑法让mySql使用索引查询：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT * FROM employees.titles
WHERE emp_no='10001'
AND title IN ('Senior Engineer', 'Staff', 'Engineer', 'Senior Staff', 'Assistant Engineer', 'Technique Leader', 'Manager')
AND from_date='1986-06-26';
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;LIKE和REGEXP&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LIKE匹配时，只有&lt;code class=&quot;highlighter-rouge&quot;&gt;%&lt;/code&gt;不出现在开头，才会用到索引。&lt;/p&gt;

&lt;p&gt;REGEXP匹配，不会用到索引。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;范围查询&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;范围列可以用到索引（必须是最左前缀），但是范围列后面的列无法用到索引。&lt;/p&gt;

&lt;p&gt;索引最多用于一个范围列，因此如果查询条件中有两个范围列则无法全用到索引。&lt;/p&gt;

&lt;p&gt;有时，==MySQL会将&lt;code class=&quot;highlighter-rouge&quot;&gt;BETWEEN&lt;/code&gt;优化为&lt;code class=&quot;highlighter-rouge&quot;&gt;IN&lt;/code&gt;，从而使用索引匹配。==&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;查询条件含有函数或表达式&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果查询条件含有函数或表达式，MySQL不使用索引。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;字符串与数字比较时不走索引
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-- 字符串与数字比较不使用索引; 
CREATE TABLE `a` (`a` char(10)); 
EXPLAIN SELECT * FROM `a` WHERE `a`=&quot;1&quot; -- 走索引 
EXPLAIN SELECT * FROM `a` WHERE `a`=1 -- 不走索引 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;==如果条件中有or，除非所有or字段都有索引，否则不走索引。==
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;select * from dept where dname='xxx' or loc='xx' or deptno=45 --如果条件中有or，即使其中有条件带索引也不会使用。换言之，就是要求使用的所有字段，都必须建立索引，我们建议大家尽量避免使用or 关键字 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;==如果MySQL估计使用全表扫描比使用索引快，不走索引。==
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-- 如果mysql估计使用全表扫描要比使用索引快，则不使用索引
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;索引选择性和索引前缀优化&quot;&gt;索引选择性和索引前缀优化&lt;/h2&gt;

&lt;h3 id=&quot;索引选择性selectivity&quot;&gt;索引选择性(Selectivity)&lt;/h3&gt;

&lt;p&gt;两种情况不建议为列添加索引：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;表记录较少，通常表记录2000条以上才考虑需要添加索引。&lt;/li&gt;
  &lt;li&gt;索引选择性(Selectivity)较低。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;索引选择性(Selectivity)是指不重复的索引值(Cardinality，基数)与表记录数的比值：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Selectivity(Title) = count(DISTINCT(title))/count(*);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;前缀索引优化&quot;&gt;前缀索引优化&lt;/h3&gt;

&lt;p&gt;基于&lt;strong&gt;索引选择性&lt;/strong&gt;，可以对索引做&lt;strong&gt;前缀索引优化&lt;/strong&gt;，根据&lt;strong&gt;索引选择性&lt;/strong&gt;的值，选择&lt;strong&gt;合适&lt;/strong&gt;的前缀建立索引，而不是对列的全值建立索引，大大减少索引大小，提高索引查询速度。&lt;/p&gt;

&lt;p&gt;例子：对&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;first_name, last_name&amp;gt;&lt;/code&gt;，计算不同的前缀索引选择性大小：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.9313 |
+-------------+

SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.7879 |
+-------------+

SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.9007 |
+-------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;可知，相对于直接使用&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;first_name, last_name&amp;gt;&lt;/code&gt;，对&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;first_name, left(last_name,4)&amp;gt;&lt;/code&gt;建立索引：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;选择性减少不多，几乎相同。&lt;/li&gt;
  &lt;li&gt;索引长度大大减少。
因此，可以对&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;first_name, left(last_name,4)&amp;gt;&lt;/code&gt;建立索引，替代&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;first_name, last_name&amp;gt;&lt;/code&gt;:
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ALTER TABLE employees.employees
ADD INDEX `first_name_last_name4` (first_name, last_name(4));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;索引主键选择&quot;&gt;索引主键选择&lt;/h2&gt;

&lt;p&gt;在使用InnoDB存储引擎时，由于InnoDB使用聚集索引，数据记录本身被存于主索引（一颗B+Tree）的叶子节点上。因此，==使用与业务无关的自增主键(id primary key auto_increment)作为索引要好于使用数据中的唯一字段。==&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果表使用自增主键&lt;/strong&gt;，那么每次插入新的记录，记录就会顺序添加到当前索引节点的后续位置，当一页写满，就会自动开辟一个新的页。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_use_auto_increment_id.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这样就会形成一个紧凑的索引结构，近似顺序填满。由于每次插入时也不需要移动已有数据，因此效率很高，也不会增加很多开销在维护索引上。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果使用非自增主键（如果身份证号或学号等）&lt;/strong&gt;，由于每次插入主键的值近似于随机，因此每次新纪录都要被插到现有索引页得中间某个位置：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_use_distinct_col_primary_key.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;此时MySQL不得不为了将新记录插到合适位置而移动数据，甚至目标页面可能已经被回写到磁盘上而从缓存中清掉，此时又要从磁盘上读回来，这增加了很多开销，同时频繁的移动、分页操作造成了大量的碎片，得到了不够紧凑的索引结构，后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;因此，只要可以，请尽量在InnoDB上采用自增字段做主键。&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;refs&quot;&gt;REFS&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;https://blog.codinglabs.org/articles/theory-of-mysql-index.html&lt;/li&gt;
  &lt;li&gt;http://www.runoob.com/w3cnote/mysql-index.html&lt;/li&gt;
  &lt;li&gt;https://tech.meituan.com/2014/06/30/mysql-index.html&lt;/li&gt;
  &lt;li&gt;https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html&lt;/li&gt;
  &lt;li&gt;https://www.xuebuyuan.com/3241057.html&lt;/li&gt;
  &lt;li&gt;https://segmentfault.com/a/1190000008131735&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/05/20/mysql-index/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">索引分类</summary></entry><entry><title type="html">详解MySQL中得锁机制及实现</title><link href="https://cheng-dp.github.io/2019/05/13/mysql-lock/" rel="alternate" type="text/html" title="详解MySQL中得锁机制及实现" /><published>2019-05-13T00:00:00+08:00</published><updated>2019-05-13T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/05/13/mysql-lock</id><content type="html" xml:base="https://cheng-dp.github.io/2019/05/13/mysql-lock/">&lt;h2 id=&quot;mysql锁分类&quot;&gt;MySQL锁分类&lt;/h2&gt;

&lt;p&gt;MySQL的锁实现在存储引擎层，服务器层完全不了解存储引擎中的锁实现。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;行锁 (row-level lock)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;加在数据行(row)上的锁，行级锁是粒度最低的锁，发生锁冲突的概率也最低、并发度最高。但是加锁慢、开销大，容易发生死锁现象。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InnoDB支持行锁。&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;表锁 (table-level lock)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;加在表(table)上的锁，粒度最高，发生锁冲突的概率大，并发度较低。一次将整个表锁定，加锁块、开销小。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InnoDB和MyISAM支持表锁。&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;页锁 (page-level lock)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快，但冲突多，行级冲突少，但速度慢。所以取了折衷的页级，一次锁定相邻的一组记录。&lt;strong&gt;只有BDB引擎支持页级锁&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InnoDB&lt;/strong&gt;支持表锁和行锁。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MyISAM&lt;/strong&gt;仅支持表锁。&lt;/p&gt;

&lt;h2 id=&quot;innodb锁实现&quot;&gt;InnoDB锁实现&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;InnoDB的行锁(row-level lock)是通过&lt;strong&gt;在索引项上加锁实现的&lt;/strong&gt;，不是在记录上加锁。&lt;/p&gt;

    &lt;p&gt;不论是使用主键索引(primary key)、唯一索引(key)或普通索引(index)，InnoDB都使用行锁。&lt;/p&gt;

    &lt;p&gt;即使不同session事务访问不同行的数据，如果这些数据使用了相同的索引键，依旧会出现冲突。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;只有引擎最终&lt;strong&gt;通过索引&lt;/strong&gt;检索数据，InnoDB才会使用行锁(row-level lock)，否则都使用表锁(table-level lock)。&lt;/p&gt;

    &lt;p&gt;只有MySQL执行时真正使用了索引，才会使用行锁。即使在条件中使用了索引字段，但如果MySQL判断执行时没有使用该索引(如，当MySQL认为全表扫描效率更高时)，使用的依然是表锁。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;innodb锁模式&quot;&gt;InnoDB锁模式&lt;/h3&gt;

&lt;p&gt;表锁和行锁都有的：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;共享锁(S)：允许一个事务读数据，阻止其他事务获得相同数据行的排他锁。&lt;/li&gt;
  &lt;li&gt;排他锁(X)：允许一个事务更新数据，阻止其他事务取得相同数据集的共享锁和排他锁。
为了允许行锁和表锁共存，实现多粒度锁机制，InnoDB还使用意向锁(Intention Locks)，意向锁为表锁：&lt;/li&gt;
  &lt;li&gt;意向共享锁(IS)：事务打算给数据行加共享锁(S)，此前必须先取得该表的意向共享锁(IS)。&lt;/li&gt;
  &lt;li&gt;意向排他锁(IX)：事务打算给数据行加排他锁(X)，此前必须先取得该表的意向排他锁(IX)。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_innodb_lock_compatibility.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;innodb锁算法&quot;&gt;InnoDB锁算法&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;意向锁是InnoDB自动加的，不需用户干预。&lt;/li&gt;
  &lt;li&gt;对于UPDATE、DELETE和INSERT语句，InnoDB会自动加排他锁(X)。&lt;/li&gt;
  &lt;li&gt;对于普通SELECT语句，InnoDB不会加任何锁。(事务中SELECT不会主动加读锁)&lt;/li&gt;
  &lt;li&gt;事务可以通过以下语句显示给记录集加共享锁(S)或排他锁(X)。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;共享锁（Ｓ）：SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁（X）：SELECT * FROM table_name WHERE ... FOR UPDATE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;innodb加锁方式&quot;&gt;InnoDB加锁方式&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;加锁方式&lt;/th&gt;
      &lt;th&gt;锁定内容&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Record Lock&lt;/td&gt;
      &lt;td&gt;记录锁，锁定一个行记录。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Gap Lock&lt;/td&gt;
      &lt;td&gt;间隙锁，锁定一个区间。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Next Lock&lt;/td&gt;
      &lt;td&gt;记录锁+间隙锁，锁定行记录+区间。&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;记录锁和间隙锁都是在索引项上加锁。&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;记录锁&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;记录锁都是基于索引的，只有当MySQL选择使用索引查询时，才会加记录锁，否则加表锁。&lt;/p&gt;

&lt;p&gt;当基于辅助索引查询时，由于InnoDB聚簇索引的特性，也会对主键索引加锁，因此不同事务中对同一行数据不同索引查询依然会互斥。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;间隙锁&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当我们用范围条件而不是相等条件检索数据，并请求共享或排他锁时，InnoDB会给符合条件的已有数据记录的所有索引项加锁。&lt;/p&gt;

&lt;p&gt;对于键值在条件范围内但并不存在的记录，叫做“间隙（GAP)”，InnoDB也会对这个“间隙”加锁，这种锁机制就是所谓的间隙锁（Gap-Key锁）。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;InnoDB使用间隙锁的目的：

1. 防止幻读，以满足相关隔离级别的要求。
2. 满足恢复和复制的需要。

MySQL 通过 BINLOG 录入执行成功的 INSERT、UPDATE、DELETE 等更新数据的 SQL 语句，并由此实现 MySQL 数据库的恢复和主从复制。MySQL 的恢复机制（复制其实就是在 Slave Mysql 不断做基于 BINLOG 的恢复）有以下特点：

    - MySQL 的恢复是 SQL 语句级的，也就是重新执行 BINLOG 中的 SQL 语句。
    - MySQL 的 Binlog 是按照事务提交的先后顺序记录的， 恢复也是按这个顺序进行的。

由此可见，MySQL的恢复机制要求在一个事务未提交前，其他并发事务不能插入满足其锁定条件的任何记录，也就是不允许出现幻读。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;加锁方式举例&quot;&gt;加锁方式举例&lt;/h4&gt;

&lt;p&gt;不同的事务隔离级别、不同的索引类型、是否为等值查询，使用的加锁方式不同。&lt;/p&gt;

&lt;p&gt;当使用InnoDB默认的&lt;strong&gt;Repeatable Read&lt;/strong&gt;隔离级别进行等值查询时：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;等值查询使用的索引类型&lt;/th&gt;
      &lt;th&gt;锁定内容&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;主键(聚簇索引)&lt;/td&gt;
      &lt;td&gt;对聚簇索引加Record Lock&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;唯一索引&lt;/td&gt;
      &lt;td&gt;对辅助索引加Record Lock&amp;lt;/br&amp;gt;对聚簇索引加Record Lock&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;普通索引&lt;/td&gt;
      &lt;td&gt;对辅助索引加Next-key Lock&amp;lt;/br&amp;gt;对聚簇索引加Record Lock&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;不适用索引&lt;/td&gt;
      &lt;td&gt;对聚簇索引全表加Next-key Lock&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ol&gt;
  &lt;li&gt;主键等值查询使用&lt;strong&gt;聚簇索引&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_innodb_lock_primary_key.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;非主键等值查询使用&lt;strong&gt;辅助唯一索引&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_innodb_lock_secondary_only_key.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;非主键等值查询使用&lt;strong&gt;辅助索引&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_innodb_lock_secondary_key.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;表锁&quot;&gt;表锁&lt;/h3&gt;

&lt;p&gt;当InnoDB不使用索引时，将会自动加表锁。&lt;/p&gt;

&lt;h4 id=&quot;lock-tables&quot;&gt;LOCK TABLES&lt;/h4&gt;

&lt;p&gt;MySQL也支持显示加表锁：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SET AUTOCOMMIT=0; 
LOCK TABLES t1 WRITE, t2 READ, ...; 
[do something with tables t1 and t2 here]; 
COMMIT; 
UNLOCK TABLES;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;显示加表锁&lt;code class=&quot;highlighter-rouge&quot;&gt;LOCK TABLES&lt;/code&gt;是在MySQL Server层完成的，仅当&lt;code class=&quot;highlighter-rouge&quot;&gt;autocommit=0&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;innodb_table_lock=1&lt;/code&gt;(默认设置)时，InnoDB层才能知道MySQL加的表锁，ＭySQL Server才能感知InnoDB加的行锁。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;使用&lt;code class=&quot;highlighter-rouge&quot;&gt;LOCK TABLES&lt;/code&gt;前必须设置参数&lt;code class=&quot;highlighter-rouge&quot;&gt;autocommit=0&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;innodb_table_lock=1&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;事务结束前，不要用 UNLOCK TABLES 释放表锁，因为 UNLOCK TABLES会隐含地提交事务。&lt;/li&gt;
  &lt;li&gt;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁，必须用UNLOCK TABLES显示释放表锁。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;表锁使用举例：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 读取两个表的数据并比较数据是否相等。
Lock tables orders read local, order_detail read local; 
Select sum(total) from orders; 
Select sum(subtotal) from order_detail; 
Unlock tables;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;锁分析和优化&quot;&gt;锁分析和优化&lt;/h2&gt;

&lt;h3 id=&quot;行锁状态&quot;&gt;行锁状态&lt;/h3&gt;

&lt;p&gt;查看&lt;code class=&quot;highlighter-rouge&quot;&gt;Innodb_row_lock_%&lt;/code&gt;状态变量：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 0     |
| Innodb_row_lock_time_avg      | 0     |
| Innodb_row_lock_time_max      | 0     |
| Innodb_row_lock_waits         | 0     |
+-------------------------------+-------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;innodb_row_lock_current_waits&lt;/code&gt;: 当前正在等待锁定的数量&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;innodb_row_lock_time&lt;/code&gt;: 从系统启动到现在锁定总时间长度；非常重要的参数，&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;innodb_row_lock_time_avg&lt;/code&gt;: 每次等待所花平均时间；非常重要的参数，&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;innodb_row_lock_time_max&lt;/code&gt;: 从系统启动到现在等待最常的一次所花的时间；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;innodb_row_lock_waits&lt;/code&gt;: 系统启动后到现在总共等待的次数；非常重要的参数。直接决定优化的方向和策略。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;行锁优化&quot;&gt;行锁优化&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;尽可能让所有数据检索都通过索引来完成，避免无索引行或索引失效导致行锁升级为表锁。&lt;/li&gt;
  &lt;li&gt;尽可能避免间隙锁带来的性能下降，减少或使用合理的检索范围。&lt;/li&gt;
  &lt;li&gt;尽可能减少事务的粒度，比如控制事务大小，而从减少锁定资源量和时间长度，从而减少锁的竞争等，提供性能。&lt;/li&gt;
  &lt;li&gt;尽可能低级别事务隔离，隔离级别越高，并发的处理能力越低。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;表锁状态&quot;&gt;表锁状态&lt;/h3&gt;

&lt;p&gt;查看加锁表&lt;code class=&quot;highlighter-rouge&quot;&gt;show open tables where in_use &amp;gt; 0&lt;/code&gt;（1表示加锁，0表示未加锁)&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; show open tables where in_use &amp;gt; 0;
+----------+-------------+--------+-------------+
| Database | Table       | In_use | Name_locked |
+----------+-------------+--------+-------------+
| lock     | myisam_lock |      1 |           0 |
+----------+-------------+--------+-------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;查看加锁状态&lt;code class=&quot;highlighter-rouge&quot;&gt;show status like 'table_locks%'&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; show status like 'table_locks%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Table_locks_immediate      | 104   |
| Table_locks_waited         | 0     |
+----------------------------+-------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;table_locks_immediate&lt;/code&gt;: 表示立即释放表锁数。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;table_locks_waited&lt;/code&gt;: 表示需要等待的表锁数。此值越高则说明存在着越严重的表级锁争用情况。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;refs&quot;&gt;REFS&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;https://zhuanlan.zhihu.com/p/29150809&lt;/li&gt;
  &lt;li&gt;https://segmentfault.com/a/1190000014133576#articleHeader4&lt;/li&gt;
  &lt;li&gt;https://juejin.im/post/5b82e0196fb9a019f47d1823#heading-20&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/05/13/mysql-lock/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">MySQL锁分类</summary></entry><entry><title type="html">MySQL事务实现及Redo Log和Undo Log详解</title><link href="https://cheng-dp.github.io/2019/05/09/mysql-tx-redo-undo/" rel="alternate" type="text/html" title="MySQL事务实现及Redo Log和Undo Log详解" /><published>2019-05-09T00:00:00+08:00</published><updated>2019-05-09T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/05/09/mysql-tx-redo-undo</id><content type="html" xml:base="https://cheng-dp.github.io/2019/05/09/mysql-tx-redo-undo/">&lt;h2 id=&quot;acid实现&quot;&gt;ACID实现&lt;/h2&gt;

&lt;p&gt;事务(Transaction)实现着重于实现事务的ACID属性，即:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;原子性(Atomic)&lt;/li&gt;
  &lt;li&gt;一致性(Consistency)&lt;/li&gt;
  &lt;li&gt;隔离性(Isolation)&lt;/li&gt;
  &lt;li&gt;持久性(Duration)
事务的隔离性由锁机制和MVCC实现，原子性(Atomic)由Undo Log实现，持久性由Redo Log实现，一致性由Undo Log和Redo Log共同实现(即：数据库总是从一个一致状态转移到另一个一致状态)。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;redo-log&quot;&gt;Redo Log&lt;/h2&gt;

&lt;p&gt;重做日志(Redo Log)用来实现事务的&lt;strong&gt;持久性(Duration)&lt;/strong&gt;，记录每次操作上页的物理修改。&lt;/p&gt;

&lt;h3 id=&quot;解决问题&quot;&gt;解决问题&lt;/h3&gt;

&lt;p&gt;InnoDB存储引擎的存储数据存放在磁盘中，同时提供内存缓存(Buffer Pool)包含磁盘中部分数据页的映射，作为数据库访问的缓冲。Buffer Pool中修改的脏页数据会定期刷新到磁盘中。&lt;/p&gt;

&lt;p&gt;==&lt;strong&gt;如果MySQL宕机，而Buffer Pool的数据没有完全刷新到磁盘，就会导致数据丢失，无法保证持久性。&lt;/strong&gt; 因此引入Redo Log解决这个问题。==&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;当数据修改时，首先写入Redo Log，再更新到Buffer Pool，保证数据不会因为宕机而丢失，保证持久性。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;当事务提交时会调用fsync将redo log刷至磁盘持久化。MySQL宕机时，通过读取Redo Log中的数据，对数据库进行恢复。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;==Redo Log也是记录在磁盘中，为什么会比直接将Buffer Pool写入磁盘更快？==&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Buffer Pool刷入脏页至磁盘是随机IO，每次修改的数据位置随机，而Redo Log永远在页中追加，属于顺序IO。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Buffer Pool刷入磁盘是以数据页为单位，每次都需要整页写入。而Redo Log只需要写入真正&lt;strong&gt;物理修改&lt;/strong&gt;的部分，IO数据量大大减少。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;redo-log实现&quot;&gt;Redo Log实现&lt;/h3&gt;

&lt;p&gt;redo log由两部分组成：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;内存中的重做日志缓冲(redo log buffer)&lt;/li&gt;
  &lt;li&gt;重做日志文件(redo log file)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;InnoDB通过&lt;code class=&quot;highlighter-rouge&quot;&gt;Force Log at Commit&lt;/code&gt;机制保证持久性：当事务提交(COMMIT)时，必须先将该事务的所有日志缓冲写入到重做日志文件进行持久化，才能COMMIT成功。&lt;/p&gt;

&lt;p&gt;为了确保每次日志都写入重做日志文件，在每次将重做日志缓冲写入重做日志文件后，InnoDB存储引擎都需要调用一次fsync操作。因此，磁盘的性能决定了事务提交的性能，也就是数据库的性能。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;innodb_flush_log_at_trx_commit&lt;/code&gt;参数控制重做日志刷新到磁盘的策略：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;0，事务提交时不进行写入重做日志操作，仅在master thread每秒进行一次。&lt;/li&gt;
  &lt;li&gt;1，事务提交时必须调用一次&lt;code class=&quot;highlighter-rouge&quot;&gt;fsync&lt;/code&gt;操作。&lt;/li&gt;
  &lt;li&gt;2，仅写入文件系统缓存，不进行&lt;code class=&quot;highlighter-rouge&quot;&gt;fsync&lt;/code&gt;操作。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;log buffer根据如下规则写入到磁盘重做日志文件中：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;事务提交时&lt;/li&gt;
  &lt;li&gt;当log buffer中有一半的内存空间已经被使用&lt;/li&gt;
  &lt;li&gt;log checkpoint时&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;log-block&quot;&gt;log block&lt;/h4&gt;

&lt;p&gt;重做日志以512字节进行存储，重做日志缓存、重做日志文件都是以块(block)的方式进行保存的，称为重做日志块(redo log block)。&lt;/p&gt;

&lt;p&gt;重做日志块由：日志快头(log block header)、日志、日志快尾(log block tailer)三部分组成。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/innodb_redo_log_block.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;log block header&lt;/code&gt;占用12个字节，&lt;code class=&quot;highlighter-rouge&quot;&gt;log block tailer&lt;/code&gt;占用8个字节，因此重做日志在每个重做日志块中占用512 - 12 - 8 = 492个字节。&lt;/p&gt;

&lt;h4 id=&quot;redo-log-file-重做日志文件&quot;&gt;redo log file 重做日志文件&lt;/h4&gt;

&lt;p&gt;重做日志文件存储在log buffer中保存的log block，因此其也是根据块的方式进行物理存储，每个块的大小同样为512字节。&lt;/p&gt;

&lt;p&gt;写入log block时在redo log file最后进行追加，当一个redo log file被写满时，会接着写入下一个redo log file。&lt;/p&gt;

&lt;p&gt;InnoDB存储引擎的存储管理基于页，因此重做日志格式也是基于页的，对于不同的操作类型，InnoDB有不同的重做日志格式，InnoDB 1.2版本时，总共有51种重做日志类型。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/innodb_redo_log_file_format.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;虽然重做日志格式不同，但是有同样的通用头部格式：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;redo_log_type : 重做日志类型&lt;/li&gt;
  &lt;li&gt;space : 表空间ID&lt;/li&gt;
  &lt;li&gt;page_no : 页偏移量&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;log-group&quot;&gt;log group&lt;/h4&gt;

&lt;p&gt;log group是重做日志组，其中有多个重做日志文件，是一个逻辑上的概念。在InnoDB中只有一个log group。&lt;/p&gt;

&lt;h3 id=&quot;通过redo-log恢复&quot;&gt;通过Redo Log恢复&lt;/h3&gt;

&lt;p&gt;InnoDB存储引擎在启动时不管上次数据库是否正常关闭，都会尝试进行恢复操作。&lt;/p&gt;

&lt;p&gt;重做日志记录的是页的物理修改，因此其恢复速度比二进制日志快很多。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;如对table：

CREATE TABLE t ( a INT, b INT, PRIMARY KEY(a), KEY(b) );

执行SQL语句：

INSERT INTO t SELECT 1,2；

其记录的重做日志大致为：

page(2,3), offset 32, value 1,2 # 聚集索引
page(2,4), offset 54, value 2 # 辅助索引
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;log-sequence-number-lsn&quot;&gt;Log Sequence Number LSN&lt;/h4&gt;

&lt;p&gt;LSN日志序列号占用8字节，记录重做日志当前总字节量，是单调递增的。&lt;/p&gt;

&lt;p&gt;LSN不仅记录在重做日志中，还&lt;strong&gt;存在于每个页中&lt;/strong&gt;，在每个页的头部，值&lt;code class=&quot;highlighter-rouge&quot;&gt;FIL_PAGE_LSN&lt;/code&gt;记录该页的LSN。表示&lt;strong&gt;页最后刷新时LSN的大小&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;数据库启动时，页中的LSN用来判断页是否需要进行恢复操作：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;重做日志LSN &amp;gt; 页中LSN，需要进行恢复操作。&lt;/li&gt;
  &lt;li&gt;重做日志LSN &amp;lt; 页中LSN，不许进行恢复操作。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SHOW ENGINE INNODB STATUS可以查看当前数据库LSN情况。&lt;/p&gt;

&lt;h2 id=&quot;undo-log&quot;&gt;Undo Log&lt;/h2&gt;

&lt;p&gt;Undo Log(回滚日志)用来实现事务的原子性(回滚)和隔离性(MVCC)。&lt;/p&gt;

&lt;p&gt;Undo Log和Redo Log正好相反，记录的是数据&lt;strong&gt;被修改前&lt;/strong&gt;的信息，并且只记录&lt;strong&gt;逻辑&lt;/strong&gt;变化，基于Undo Log进行的回滚只是对数据库进行一个相反的操作，而不是直接恢复物理页。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_innodb_undo_log_ex.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;针对每个DELETE操作，生成Insert Log插入Undo Log。&lt;/li&gt;
  &lt;li&gt;针对每个UPDATE操作，生成相反的Update Log插入Undo Log。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;undo-log生成举例&quot;&gt;Undo Log生成举例&lt;/h3&gt;

&lt;p&gt;Undo Log中基于回滚指针(DB_ROLL_PT)维护数据行的所有历史版本。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;初始数据行&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_mvcc_update_row_ex_1.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;事务Transaction1更新数据&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;此时Undo Log记录旧版本的数据值，且由于是第一个版本，&lt;code class=&quot;highlighter-rouge&quot;&gt;DB_TRX_ID&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;DB_ROLL_PT&lt;/code&gt;为NULL。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;用排他锁锁定该行。
记录redo log。
把该行修改前的值Copy到undo log。
修改当前行的值，填写事务编号，使回滚指针指向undo log中的修改前的行。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_mvcc_update_row_ex_2.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;事务Transaction2更新数据&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_mvcc_update_row_ex_3.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;undo-log实现&quot;&gt;Undo Log实现&lt;/h3&gt;

&lt;h4 id=&quot;undo-log格式&quot;&gt;Undo Log格式&lt;/h4&gt;

&lt;p&gt;InnoDB中，undo log分为：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Insert Undo Log&lt;/li&gt;
  &lt;li&gt;Update Undo Log&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;Insert Undo Log&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Insert Undo Log是INSERT操作产生的undo log。&lt;/p&gt;

&lt;p&gt;INSERT操作的记录由于是该数据的第一个记录，对其他事务不可见，该Undo Log可以在事务提交后直接删除。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/innodb_insert_undo_log.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;type_cmpl：undo的类型&lt;/li&gt;
  &lt;li&gt;undo_no：事务的ID&lt;/li&gt;
  &lt;li&gt;table_id：undo log对应的表对象
接着的部分记录了所有主键的列和值。&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;Update Undo Log&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Update Undo Log记录对DELETE和UPDATE操作产生的Undo Log。&lt;/p&gt;

&lt;p&gt;Update Undo Log会提供MVCC机制，因此不能在事务提交时就删除，而是放入undo log链表，等待purge线程进行最后的删除。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/innodb_update_undo_log.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;update_vector表示update操作导致发生改变的列，每个修改的列信息都要记录。对于不同的undo log类型，可能还需要记录对索引列所做的修改。&lt;/p&gt;

&lt;h4 id=&quot;undo-log存储管理&quot;&gt;Undo Log存储管理&lt;/h4&gt;

&lt;p&gt;InnoDB基于Rollback Segment管理Undo Log，每个Rollback Segment记录1024个Undo Segment，Rollback Segment默认存储在共享表空间中。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Rollback Segment管理参数：
- innod\_undo\_directory：设置Rollback Segment文件所在的路径，默认在共享表空间内。
- innodb\_undo\_logs：设置Rollback Segment的个数，默认为128，即innoDB默认支持同事在线的事务限制为128 * 1024。
- innodb\_undo\_tablespaces：构成Rollback Segment的文件数量。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当事务没有提交时，InnoDB必须保留该事务对应的Undo Log。但是当事务提交时，依然不能删除Undo Log，因为要支持MVCC，可能有其他处于Repeatable Read隔离级别下的事务，正在读取对应版本的数据。&lt;/p&gt;

&lt;p&gt;事务提交时，虽然不会立即删除Undo Log，但是会将对应的Undo Log放入一个删除列表中，未来通过purge线程来进行判断并删除。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/05/09/mysql-tx-redo-undo/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">ACID实现</summary></entry><entry><title type="html">详解MVCC</title><link href="https://cheng-dp.github.io/2019/05/07/mysql-mvcc/" rel="alternate" type="text/html" title="详解MVCC" /><published>2019-05-07T00:00:00+08:00</published><updated>2019-05-07T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/05/07/mysql-mvcc</id><content type="html" xml:base="https://cheng-dp.github.io/2019/05/07/mysql-mvcc/">&lt;h2 id=&quot;mvcc概念介绍&quot;&gt;MVCC概念介绍&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;MVCC(Multi-version Concurrent Control)&lt;/code&gt;：多版本并发控制。基于保存多个数据版本实现的并发控制，当需要更新数据时，实现了MVCC的系统不会立即用新数据覆盖原始数据，而是创建该条记录的一个新版本。&lt;/p&gt;

&lt;p&gt;最早的数据库系统，只有读-读之间可以并发，引入多版本控制之后，读-读、读-写、写-读都可以并行，即&lt;strong&gt;读无需加锁、读写不冲突(InnoDB中RR隔离级别下，普通SELECT不加共享锁)&lt;/strong&gt;，极大增加了系统的并发性。&lt;/p&gt;

&lt;p&gt;在InnoDB中，MVCC只在：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;READ COMMITTED&lt;/li&gt;
  &lt;li&gt;REPEATABLE READ
两个隔离级别下工作，因为READ UNCOMMITTED总是读取最新数据行，而SERIALIZABLE会对所有读取的行都加锁。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;mvcc实现原理&quot;&gt;MVCC实现原理&lt;/h2&gt;

&lt;h3 id=&quot;行记录存储格式&quot;&gt;行记录存储格式&lt;/h3&gt;

&lt;p&gt;InnoDB行记录为了支持MVCC，除了基本的数据信息，还有三个额外字段：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;DB_ROW_ID&lt;/code&gt;：InnoDB自动生成的自增&lt;strong&gt;主键id&lt;/strong&gt;。当用户没有显示指定主键、且无非null唯一索引时，InnoDB的聚簇索引会使用&lt;code class=&quot;highlighter-rouge&quot;&gt;DB_ROW_ID&lt;/code&gt;作为主键。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;DB_TRX_ID&lt;/code&gt;：最近更新此行记录的&lt;strong&gt;事务id&lt;/strong&gt;，数据库每开启一个新事务，事务ID自动加一。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;DATA_ROLL_PTR&lt;/code&gt;：指向Undo Log中当前行旧版数据的指针。支持事务回滚。刚Insert的记录没有旧版本，该值为NULL。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;undo-log&quot;&gt;Undo Log&lt;/h3&gt;

&lt;p&gt;Undo Log用来实现事务回滚和MVCC，原理很简单，进行数据修改之前，首先将当前数据保存到Undo Log中。&lt;/p&gt;

&lt;p&gt;Undo Log是逻辑日志，当执行DELETE操作时，Undo Log中记录一条对应的INSERT。当执行UPDATE操作时，Undo Log记录一条相反的UPDATE。&lt;/p&gt;

&lt;p&gt;当执行RollBack时，或MVCC需要读取旧版本数据时，就可以从Undo Log中的逻辑记录进行数据恢复。&lt;/p&gt;

&lt;p&gt;多个Undo Log数据之间通过链表的方式关联，每个Log中存储上一个版本Log的地址。&lt;/p&gt;

&lt;h3 id=&quot;行更新过程举例&quot;&gt;行更新过程举例&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;初始数据行&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_mvcc_update_row_ex_1.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;事务Transaction1更新数据&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;此时Undo Log记录旧版本的数据值，且由于是第一个版本，&lt;code class=&quot;highlighter-rouge&quot;&gt;DB_TRX_ID&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;DB_ROLL_PT&lt;/code&gt;为NULL。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;用排他锁锁定该行。
记录redo log。
把该行修改前的值Copy到undo log。
修改当前行的值，填写事务编号，使回滚指针指向undo log中的修改前的行。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_mvcc_update_row_ex_2.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;事务Transaction2更新数据&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_mvcc_update_row_ex_3.jpeg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;事务链表&quot;&gt;事务链表&lt;/h3&gt;

&lt;p&gt;事务在开始到提交的过程中，会被保存到&lt;code class=&quot;highlighter-rouge&quot;&gt;trx_sys-&amp;gt;trx_list&lt;/code&gt;事务链表，事务一旦被提交，将从事务链表中移除。&lt;/p&gt;

&lt;h3 id=&quot;read-view&quot;&gt;Read View&lt;/h3&gt;

&lt;p&gt;Read View可以认为是一个当前活跃事务的快照，在当前事务&lt;strong&gt;SQL开始&lt;/strong&gt;时，会创建一个Read View，用来判定当前SQL执行时，所有活跃事务哪些改变对当前事务可见、哪些改变对当前事务不可见。&lt;/p&gt;

&lt;h3 id=&quot;版本可见性算法&quot;&gt;版本可见性算法&lt;/h3&gt;

&lt;p&gt;基于行记录、Read View，在SQL开始时能够得到所有活跃事务对当前SQL的可见性。&lt;/p&gt;

&lt;p&gt;记录对当前SQL的可见性算法：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;假设当前行的事务id为 trx_id_current。

假设Read View中最早的事务id为 trx_id_early，Read View中最新的事务id为trx_id_late。
即当前事务中当前SQL开始时，活跃的事务id介于trx_id_early和trx_id_late之间。Read View中不包括当前事务。

1. 如果 trx_id_current &amp;lt; trx_id_early，即当前行的事务已经结束并提交，该行最新记录对当前SQL可见。跳转步骤5。

2. 如果 trx_id_current &amp;gt; trx_id_late，即该行记录最新的更新是在当前事务之后开启的，该行最新记录对当前SQL不可见。跳转步骤4。

3. 如果 trx_id_early &amp;lt; trx_id_current &amp;lt; trx_id_late，即该行记录更新事务在当前SQL执行时处于活跃状态，未提交，该行最新记录对当前SQL不可见。跳转步骤4。

4. 如果不可见，从该行记录的DB_ROLL_PTR指针指向的Undo Log中取出旧数据和旧trx_id，重新进行算法比较，跳转步骤1。

5. 如果可见，则将该值返回。

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;不同隔离级别的区别&quot;&gt;不同隔离级别的区别&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;READ UNCOMMITTED&lt;/p&gt;

    &lt;p&gt;该级别下永远读取最新行，无需MVCC。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;READ COMMITTED&lt;/p&gt;

    &lt;p&gt;该级别下，每次执行SQL，需要能够读取其他事务COMMIT后的最新值。&lt;/p&gt;

    &lt;p&gt;因此，&lt;strong&gt;每个SQL语句执行时都会创建一个新的READ VIEW&lt;/strong&gt;。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;REPEATABLE READ&lt;/p&gt;

    &lt;p&gt;该级别下，事务未提交时只能读取到旧值。&lt;/p&gt;

    &lt;p&gt;因此，&lt;strong&gt;只在第一个SQL执行时创建READ VIEW，此后不再更新&lt;/strong&gt;。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;SERIALIZABLE&lt;/p&gt;

    &lt;p&gt;每次只能有一个事务正在运行，无需MVCC。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;refs&quot;&gt;REFS&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;https://segmentfault.com/a/1190000012650596&lt;/li&gt;
  &lt;li&gt;https://sadwxqezc.github.io/HuangHuanBlog/mysql/2018/05/01/MVCC.html&lt;/li&gt;
  &lt;li&gt;http://mysql.taobao.org/monthly/2018/11/04/&lt;/li&gt;
  &lt;li&gt;https://www.imooc.com/article/17290&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/05/07/mysql-mvcc/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">MVCC概念介绍</summary></entry><entry><title type="html">MySQL中binlog详解及复制机制</title><link href="https://cheng-dp.github.io/2019/05/06/mysql-binlog-replica/" rel="alternate" type="text/html" title="MySQL中binlog详解及复制机制" /><published>2019-05-06T00:00:00+08:00</published><updated>2019-05-06T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/05/06/mysql-binlog-replica</id><content type="html" xml:base="https://cheng-dp.github.io/2019/05/06/mysql-binlog-replica/">&lt;h2 id=&quot;binlog&quot;&gt;binlog&lt;/h2&gt;

&lt;h3 id=&quot;binlog概述&quot;&gt;binlog概述&lt;/h3&gt;

&lt;p&gt;binlog是MySQL Server层维护的&lt;strong&gt;二进制日志&lt;/strong&gt;。binlog记录所有的DDL和DML语句(除了数据查询语句SELECT、SHOW等)，以Event的形式记录，同时记录语句执行时间。&lt;/p&gt;

&lt;p&gt;binlog的作用有：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;复制：Master和Slave之间的主从复制。&lt;/li&gt;
  &lt;li&gt;数据增量备份和恢复：MySQL提供mysqlbinlog工具从binlog中恢复数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;binlog包括两类文件：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;二进制日志索引文件(.index)：记录所有的二进制文件。&lt;/li&gt;
  &lt;li&gt;二进制日志文件(.00000*)：记录所有DDL和DML语句事件。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;binlog格式&quot;&gt;binlog格式&lt;/h3&gt;

&lt;p&gt;binlog有三种格式：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;STATEMENT：基于SQL语句记录&lt;/li&gt;
  &lt;li&gt;ROW：基于行的记录&lt;/li&gt;
  &lt;li&gt;MIXED：混合模式(ROW + STATEMENT)
可以通过&lt;code class=&quot;highlighter-rouge&quot;&gt;my.cnf&lt;/code&gt;配置&lt;code class=&quot;highlighter-rouge&quot;&gt;binlog-format&lt;/code&gt;修改。&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;STATEMENT&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;直接记录造成数据更改的SQL语句。&lt;/p&gt;

&lt;p&gt;优点：&lt;/p&gt;

&lt;p&gt;实现简单，日志紧凑，节省带宽。只需要记录在 master 上所执行的语句的细节，以及执行语句时候的上下文的信息。&lt;/p&gt;

&lt;p&gt;缺点：&lt;/p&gt;

&lt;p&gt;一些SQL语句和当前环境密切相关，无法被正确复制。如CURRENT_USER()、NOW()、UUID()函数等。&lt;/p&gt;

&lt;p&gt;更新必须是串行的，需要更多的锁。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;ROW&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MySQL 5.1开始支持基于行的复制，将&lt;strong&gt;实际更新的行数据&lt;/strong&gt;直接记录在二进制日志中。&lt;/p&gt;

&lt;p&gt;优点：&lt;/p&gt;

&lt;p&gt;能够精确且正确的复制行数据，从库可以直接应用更新数据。&lt;/p&gt;

&lt;p&gt;缺点：&lt;/p&gt;

&lt;p&gt;大数据量更新(如全表更新)大大增加二进制日志大小，无法判断执行了哪些SQL，占用大量带宽。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;MIXED&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;一般的语句修改使用STATEMENT格式保存binlog，对于STATEMENT无法精确记录的如一些函数，则采用ROW格式。&lt;/p&gt;

&lt;h3 id=&quot;binlog和redo-logundo-log区别&quot;&gt;binlog和redo log/undo log区别&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;层次不同&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;redo log/undo log属于innoDB存储引擎，而binlog属于mySQL Server层，和引擎无关。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;记录内容不同&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;redo log/undo log记录的都是页的修改，redo log属于物理日志， undo log属于逻辑日志。&lt;/p&gt;

&lt;p&gt;binlog记录的是事务操作的内容，是二进制日志。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;记录时机不同&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;redo log/undo log在事务执行中随着SQL的执行不断写入，且在事务COMMIT前同步到磁盘文件。&lt;/p&gt;

&lt;p&gt;binlog只在事务COMMIT前写入binlog文件。且根据&lt;code class=&quot;highlighter-rouge&quot;&gt;sync_binlog&lt;/code&gt;参数配置决定刷新到磁盘时间。&lt;/p&gt;

&lt;h3 id=&quot;binlog参数&quot;&gt;binlog参数&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_binlog_variables.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;log_bin&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;log_bin [=file_name]&lt;/code&gt;如果不为OFF则开启binlog功能，可以设置binlog存放路径。&lt;/p&gt;

&lt;p&gt;要开启binlog，必须设置&lt;code class=&quot;highlighter-rouge&quot;&gt;service-id&lt;/code&gt;。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;binlog_format&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;binlog记录格式，ROW/STATEMENT/MIXED。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;binlog_cache_size&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;控制binlog cache大小。&lt;/p&gt;

&lt;p&gt;未提交的binlog记录会首先记录在binlog cache中，待事务COMMIT前一次性写入binlog文件。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;sync_binlog&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;设置binlog fsync刷入文件系统的同步方式。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- 0：默认值，只写入操作系统缓存，不调用fsync，由操作系统决定什么时候同步磁盘文件。
- 1：每次写binlog都调用fsync同步磁盘。
- &amp;gt;1：`sync_binlog=N`，每写N次调用一次fsync。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;max_binlog_size&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果超过了该值，就会产生新的日志文件，后缀名+1，并且记录到.index文件里面。&lt;/p&gt;

&lt;h3 id=&quot;binlog控制&quot;&gt;binlog控制&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;查看所有binlog日志&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; show master logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       970 |
+------------------+-----------+
1 row in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;查看binlog最新状态(最后一个binlog编号)&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; show master status;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 |      970 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;刷新binlog日志，产生一个新的binlog file&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; flush logs;
Query OK, 0 rows affected (0.01 sec)

mysql&amp;gt; show master logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |      1017 |
| mysql-bin.000002 |       154 |
+------------------+-----------+
2 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;重置所有binlog日志&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; reset master;
Query OK, 0 rows affected (0.02 sec)

mysql&amp;gt; show master logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       154 |
+------------------+-----------+
1 row in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;查看binlog内容&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;通过mysqlbinlog工具：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;shell&amp;gt; mysqlbinlog -vv mysql-bin.000001
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;通过mysql数据库读取：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;从binlog中恢复数据&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;shell&amp;gt; mysqlbinlog [选项] mysql-bin.0000xx | mysql -u用户名 -p密码 数据库名
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;复制&quot;&gt;复制&lt;/h1&gt;

&lt;p&gt;MySQL复制指从服务器(Slave)从主服务器(Master)中获取并同步数据。向Master插入数据后，Slave会自动从Master把修改的数据同步过来，保证数据一致性。&lt;/p&gt;

&lt;p&gt;基于复制，MySQL能够实现：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;高可用、故障切换&lt;/li&gt;
  &lt;li&gt;读写分离、负载均衡&lt;/li&gt;
  &lt;li&gt;数据备份&lt;/li&gt;
  &lt;li&gt;业务模块化&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;复制工作原理&quot;&gt;复制工作原理&lt;/h2&gt;

&lt;p&gt;MySQL复制有两种工作模式：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;基于语句的复制&lt;/li&gt;
  &lt;li&gt;基于行的复制&lt;/li&gt;
  &lt;li&gt;混合类型的复制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对应二进制日志(binlog)也有三种格式：STATEMENT，ROW，MIXED。&lt;/p&gt;

&lt;h3 id=&quot;基于语句的复制&quot;&gt;基于语句的复制&lt;/h3&gt;

&lt;p&gt;主库直接记录造成数据更改的SQL语句，当从库读取并重放操作时，只需重新执行该SQL语句。&lt;/p&gt;

&lt;p&gt;优点：&lt;/p&gt;

&lt;p&gt;实现简单，日志紧凑，节省带宽。&lt;/p&gt;

&lt;p&gt;缺点：&lt;/p&gt;

&lt;p&gt;一些SQL语句和当前环境密切相关，无法被正确复制。如CURRENT_USER()函数、NOW()等。&lt;/p&gt;

&lt;p&gt;更新必须是串行的，需要更多的锁。&lt;/p&gt;

&lt;h3 id=&quot;基于行的复制&quot;&gt;基于行的复制&lt;/h3&gt;

&lt;p&gt;MySQL 5.1开始支持基于行的复制，将实际更新数据直接记录在二进制日志中。&lt;/p&gt;

&lt;p&gt;优点：&lt;/p&gt;

&lt;p&gt;正确复制行数据，从库可以直接应用更新数据，无需重放SQL语句。&lt;/p&gt;

&lt;p&gt;缺点：&lt;/p&gt;

&lt;p&gt;大数据量更新(如全表更新)大大增加二进制日志大小，无法判断执行了哪些SQL，占用大量带宽。&lt;/p&gt;

&lt;h2 id=&quot;复制过程&quot;&gt;复制过程&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;主服务器将数据更改记录到二进制日志(binlog)中。&lt;/p&gt;

    &lt;p&gt;每个更新数据的事务完成前，主服务器会将数据更改记录到二进制日志(binlog)中，即使事务执行时交错的，也会串行地写入二进制日志(binlog)中。&lt;/p&gt;

    &lt;p&gt;在写入二进制日志(binlog)后，存储引擎才能提交事务。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;从服务器拷贝主服务器二进制日志(binlog)到自己的中继日志(relay log)。&lt;/p&gt;

    &lt;p&gt;从服务器中专门的IO线程与主服务器连接，主服务器二进制日志(binlog)中读取数据并转储至本地中继日志(relay log)中。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;从服务器重放中继日志(relay log)事件，逐条在本地执行，应用更改。&lt;/p&gt;

    &lt;p&gt;从服务器中专门的SQL线程读取中级日志(relay log)，并将其中事件重放至本地数据。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_copy_process.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;半同步复制&quot;&gt;半同步复制&lt;/h2&gt;

&lt;p&gt;在异步复制的情况下，从库(Slave)是落后于主库(Master)的，无法保证主从的一致性。因此，在主库(Master)与从库(Slave)之间需要建立同步确认以保证一定的一致性。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;半同步复制&lt;/strong&gt;是相对于&lt;strong&gt;同步复制&lt;/strong&gt;而言的。&lt;/li&gt;
  &lt;li&gt;同步复制在每次用户操作时，必须要保证Master和Slave都执行成功才返回给用户。&lt;/li&gt;
  &lt;li&gt;而&lt;strong&gt;半同步复制&lt;/strong&gt;不要求Slave执行成功，而是成功接收Master日志就可以通知Master返回。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;半同步日志有两种实现方式&lt;code class=&quot;highlighter-rouge&quot;&gt;AFTER_COMMIT&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;AFTER_SYNC&lt;/code&gt;两种。&lt;/p&gt;

&lt;h3 id=&quot;after_commit&quot;&gt;AFTER_COMMIT&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_copy_sync_after_commit.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Master将事务的redo log刷入磁盘。&lt;/li&gt;
  &lt;li&gt;Master将事务的binlog刷入磁盘。&lt;/li&gt;
  &lt;li&gt;commit，释放锁，标记事务提交。&lt;/li&gt;
  &lt;li&gt;等待Slave发送的日志同步ACK消息，等到Slave响应后，才返回给用户。&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;Master在commit前崩溃，Slave未同步&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;binlog未传递给Slave，Slave比Master少一个事务，但是不影响，因为用户接收到异常，该事务将重试并回滚。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Master在commit后崩溃，Slave未同步&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;binlog未传递给Slave，Slave比Master少一个事务，但是Master commit成功，造成主库和备库不一致。&lt;/p&gt;

&lt;h3 id=&quot;after_sync&quot;&gt;AFTER_SYNC&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/mysql_copy_sync_after_sync.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;AFTER_SYNC&lt;/code&gt;能够解决&lt;code class=&quot;highlighter-rouge&quot;&gt;AFTER_COMMIT&lt;/code&gt;的问题，即等到slave同步后Master再进行commit操作。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/05/06/mysql-binlog-replica/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">binlog</summary></entry><entry><title type="html">InnoDB三大特性(DoubleWrite, InsertBuffer, Adaptive Hash Index)</title><link href="https://cheng-dp.github.io/2019/05/05/innodb-three-feature/" rel="alternate" type="text/html" title="InnoDB三大特性(DoubleWrite, InsertBuffer, Adaptive Hash Index)" /><published>2019-05-05T00:00:00+08:00</published><updated>2019-05-05T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/05/05/innodb-three-feature</id><content type="html" xml:base="https://cheng-dp.github.io/2019/05/05/innodb-three-feature/">&lt;h2 id=&quot;innodb关键特性&quot;&gt;InnoDB关键特性&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;插入缓冲(Insert Buffer)&lt;/li&gt;
  &lt;li&gt;两次写(Double Write)&lt;/li&gt;
  &lt;li&gt;自适应哈希索引(Adaptive Hash Index)&lt;/li&gt;
  &lt;li&gt;异步IO(Async IO)&lt;/li&gt;
  &lt;li&gt;刷新领接页(Flush Neighbor Page)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;double-write-二次写&quot;&gt;Double Write 二次写&lt;/h2&gt;

&lt;h3 id=&quot;解决的问题&quot;&gt;解决的问题&lt;/h3&gt;

&lt;p&gt;InnoDB的最小数据读写单位是数据页(Page)，一般是16KB。&lt;/p&gt;

&lt;p&gt;而操作系统写磁盘是按照扇区为基本单位，一个扇区通常为512B。&lt;/p&gt;

&lt;p&gt;在极端情况下，如16KB数据写入4KB后断电，此时造成页数据错误，即Partial Page Write问题。&lt;/p&gt;

&lt;p&gt;Redo Log根据Page头信息(如LSN)对页数据进行恢复，然而由于Partial Page Write问题，Page数据已经损坏，无法确定Page头信息，因此无法根据Redo Log恢复。&lt;/p&gt;

&lt;h3 id=&quot;double-write流程&quot;&gt;Double Write流程&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/innodb_double_write.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Double Write有两部分组成：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;内存中的Double Write Buffer，大小为2MB。&lt;/li&gt;
  &lt;li&gt;磁盘上的共享表空间(ibdata)中连续128个页，大小也为2MB。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为了解决Partial Page Write的问题，在将Buffer Pool中的脏页写回磁盘数据未见时：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;脏页不直接写入磁盘数据文件，而是先拷贝(memcopy)至内存中的Double Write Buffer。&lt;/li&gt;
  &lt;li&gt;从Double Write Buffer中分两次写入磁盘共享表空间中，每次写1MB (连续存储，顺序写，性能很高)。&lt;/li&gt;
  &lt;li&gt;2完成后，再将Double Write Buffer中的脏页数据写入实际各个数据文件中。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在3中，当发生Partial Page Write问题时，可以直接从2中共享表空间中恢复数据。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 为什么 Redo Log/Undo Log不需要Double Write技术？

因为Redo Log/Undo Log每次写入磁盘的单位就是512字节，和磁盘IO最小单位相同，因此没有Partial Page Write问题。

2. 为什么不直接从Double Write Buffer写入数据文件？

double write里面的数据是连续的，如果直接写到data page里面，而data page的页又是离散的，写入会很慢。

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;double-write缺点&quot;&gt;Double Write缺点&lt;/h3&gt;

&lt;p&gt;在共享表空间上的Double Write数据也位于物理文件中，写共享表空间会导致系统有更多的fsync操作，降低MySQL的性能。&lt;/p&gt;

&lt;p&gt;但是性能降低并不会非常明显，因为：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;共享表空间上的Double Write空间是一个连续的空间，在写数据时是顺序添加的，不是随机写，性能很高。&lt;/li&gt;
  &lt;li&gt;将数据从Double Write写到数据文件时，InnoDB会对数据进行合并，增加每次写入刷新的页数。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;insert-buffer-插入缓冲&quot;&gt;Insert Buffer 插入缓冲&lt;/h2&gt;

&lt;h3 id=&quot;解决的问题-1&quot;&gt;解决的问题&lt;/h3&gt;

&lt;p&gt;为了减少随机读写带来的性能损耗，通过Insert Buffer将辅助非唯一索引(Non-unique Secondary Index)的数据缓存下来，大幅度提高非唯一辅助索引的插入性能。&lt;/p&gt;

&lt;p&gt;InnoDB中，Insert Buffer只适用于辅助非唯一索引，原因如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;聚集索引(primary key)一般是按照主键递增的顺序插入的，所以通常是顺序的，不需要随机读取，插入速度很快。&lt;/li&gt;
  &lt;li&gt;如果是唯一索引，则在插入时需要首先读取辅助索引页，判断插入索引是否唯一，依然要进行随机读取。
因此，InnoDB中，只有辅助非唯一索引(Non-unique Secondary Index)才会使用Insert Buffer。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;insert-buffer原理&quot;&gt;Insert Buffer原理&lt;/h3&gt;

&lt;p&gt;Insert Buffer插入流程：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;首先判断被修改行所在页是否在内存缓冲池(Buffer Pool)中，如果在缓冲池中则直接插入缓冲池。&lt;/li&gt;
  &lt;li&gt;如果不在缓冲池中，则将数据插入Insert Buffer。&lt;/li&gt;
  &lt;li&gt;后台线程按照合并规则将Insert Buffer中的数据Merge回辅助索引页中，Merge会将Insert Buffer中的数据&lt;strong&gt;先进行合并&lt;/strong&gt;，减少磁盘的离散读取，将多次插入合并为一次操作。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Insert Buffer能够缓存的操作可以是INSERT,UPDATE,DELETE(DML)，最初只能缓存insert操作，所以叫Insert Buffer，现在已经改名为Change Buffer。&lt;/p&gt;

&lt;p&gt;Merge合并规则：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;辅助索引页被读取到缓冲池时。&lt;/li&gt;
  &lt;li&gt;当检测到目标辅助索引页可能空间不够时。&lt;/li&gt;
  &lt;li&gt;Master Thread线程每秒或每10秒进行一次合并。&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Insert Buffer合并举例：
name字段的插入顺序为：
('Maria',10), ('David',7), ('Tim', 11), ('Jim', 7), ('Monty', 10), ('Herry', 7), ('Heikki', 7) 

在insert buffer中，记录根据应插入辅助索引的叶子节点page_no进行排序：
('David',7), ('Jim', 7), ('Herry', 7), ('Heikki', 7) , ('Maria',10), ('Monty', 10), ('Tim', 11)

当要进行合并时，页page_no为7的记录有4条，可以一次性将这4条记录插入到辅助索引中，从而提高数据库的整体性能。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;insert-buffer实现&quot;&gt;Insert Buffer实现&lt;/h3&gt;

&lt;p&gt;Insert Buffer并不是内存缓存，而是物理页，&lt;strong&gt;存在于共享表空间(ibdata)中&lt;/strong&gt;，按照B+树的方式组织数据。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;试图通过独立表空间ibd文件恢复表中数据时，往往会导致check table失败。
这是因为表的辅助索引中的数据可能还在insert buffer中，也就是共享表空间中。
所以通过idb文件进行恢复后，还需要进行repair table 操作来重建表上所有的辅助索引。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;InnoDB对Insert Buffer也做了缓冲，因此缓冲池(Buffer Pool)中也有Insert Buffer，按照回写条件回写至磁盘共享表空间。
因此插入Insert Buffer也可能是插入到缓冲池中。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在MySQL 4.1之前每张表有一颗Insert Buffer B+树。而目前版本全局只有一颗Insert Buffer B+树，存在于共享表空间(ibdata)中，负责所有表的辅助索引缓存。&lt;/p&gt;

&lt;p&gt;Insert Buffer的B+树也是由叶子节点和非叶子节点组成。非叶子节点存放查询的Search Key:
&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/innodb_insert_buffer_search_key.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;space：待插入记录所在的表空间id。&lt;/li&gt;
  &lt;li&gt;marker：用来兼容老版本Insert Buffer。&lt;/li&gt;
  &lt;li&gt;offset：页所在的偏移量。
插入辅助索引数据时，首先将构造一个Search Key，再查询B+树，插入到叶子节点中：
&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/innodb_insert_buffer_data.png&quot; alt=&quot;image&quot; /&gt;
-metadata：记录一些维护Insert Buffer的元数据，如记录插入的顺序，对应辅助索引页的剩余孔空间等。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;由于Insert Buffer的B+树中，数据按照表空间和页所在偏移量排序，所以在Merge回辅助索引时能够很快地将属于相同页的数据合并一次插入。&lt;/p&gt;

&lt;h3 id=&quot;insert-buffer-缺点&quot;&gt;Insert Buffer 缺点&lt;/h3&gt;

&lt;p&gt;插入缓冲主要带来如下两个坏处：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;可能导致数据库宕机后实例恢复时间变长。如果应用程序执行大量的插入和更新操作，且涉及非唯一的聚集索引，一旦出现宕机，这时就有大量内存中的插入缓冲区数据没有合并至索引页中，导致实例恢复时间会很长。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在写密集的情况下，插入缓冲会占用过多的缓冲池内存(Buffer Pool)，默认情况下最大可以占用1/2，这在实际应用中会带来一定的问题。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;自适应哈希索引adaptive-hash-index-ahi&quot;&gt;自适应哈希索引(Adaptive Hash Index, AHI)&lt;/h2&gt;

&lt;p&gt;InnoDB通过自使用哈希索引(Adaptive Hash Index)来加速对索引的访问。在InnoDB中，索引默认是一个B+树，InnoDB会对表上各索引页的查询进行监控，并对频繁访问的索引建立哈希索引。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/innodb_adaptive_hash_index_ex.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;自适应哈希索引通过缓冲池的B+树构造而来，建立速度很快。取部分索引值前缀作为Hash key，value为数据页上的位置，大大节省了B+树中Search Path查找的过程。&lt;/p&gt;

&lt;p&gt;建立自适应哈希索引的条件及问题：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;重复访问某一特定查询模式达到一定数量才会创建，如&lt;code class=&quot;highlighter-rouge&quot;&gt;WHERE a = XXX&lt;/code&gt;,&lt;code class=&quot;highlighter-rouge&quot;&gt;WHERE a = xxx and b = xxx&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;只支持等值查询&lt;code class=&quot;highlighter-rouge&quot;&gt;=&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;IN&lt;/code&gt;，不支持LIKE, REGEXP等。&lt;/li&gt;
  &lt;li&gt;存在内存中，占用缓冲池资源(Buffer Pool)。&lt;/li&gt;
  &lt;li&gt;无法人为干预，只能配置开关：&lt;code class=&quot;highlighter-rouge&quot;&gt;set global innodb_adaptive_hash_index=off/on&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;查看当前状态：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; show engine innodb status;


-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 276671, node heap has 0 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;refs&quot;&gt;REFS&lt;/h2&gt;

&lt;p&gt;DOUBLE WRITE:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;https://www.cnblogs.com/chenpingzhao/p/4876282.html&lt;/li&gt;
  &lt;li&gt;https://www.cnblogs.com/chenpingzhao/p/4883884.html&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;INSERT BUFFER
https://blog.csdn.net/Linux_ever/article/details/61639730
https://www.cnblogs.com/yuyue2014/p/3802779.html
https://www.cnblogs.com/chenpingzhao/p/4883884.html
https://www.zhihu.com/question/278406940&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/05/05/innodb-three-feature/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">InnoDB关键特性</summary></entry><entry><title type="html">MySQL EXPLAIN命令详解</title><link href="https://cheng-dp.github.io/2019/04/28/mysql-explain/" rel="alternate" type="text/html" title="MySQL EXPLAIN命令详解" /><published>2019-04-28T00:00:00+08:00</published><updated>2019-04-28T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/04/28/mysql-explain</id><content type="html" xml:base="https://cheng-dp.github.io/2019/04/28/mysql-explain/">&lt;p&gt;EXPLAIN是MySQL提供的一个命令，可以对SELECT语句进行分析，并输出SELECT执行的详细信息，供开发人员优化。使用方法非常简单，直接在SELECT语句前加上Explain。&lt;/p&gt;

&lt;h2 id=&quot;explain输出格式&quot;&gt;EXPLAIN输出格式&lt;/h2&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql&amp;gt; explain select * from user_info where id = 2\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user_info
   partitions: NULL
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 8
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;id&quot;&gt;id&lt;/h3&gt;

&lt;p&gt;id为SELECT查询标识符，每个SELECT都会自动分配一个唯一的标识符。&lt;/p&gt;

&lt;h3 id=&quot;select_type&quot;&gt;select_type&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;select_type&lt;/code&gt;表示查询类型。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;SIMPLE：查询不包含UNION查询或子查询。&lt;/li&gt;
  &lt;li&gt;PRIMARY：查询是最外层的查询。&lt;/li&gt;
  &lt;li&gt;UNION：查询是UNION的第二或随后的查询。&lt;/li&gt;
  &lt;li&gt;DEPENDENT UNION：UNION中的第二个或后面的查询语句，取决于外面的查询。&lt;/li&gt;
  &lt;li&gt;UNION RESULT：UNION的结果。&lt;/li&gt;
  &lt;li&gt;SUBQUERY：子查询中的第一个select。&lt;/li&gt;
  &lt;li&gt;DEPENDENT SUBQUERY：子查询中得第一个select，取决于外面的查询，即子查询依赖于外层查询的结果。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;table&quot;&gt;table&lt;/h3&gt;

&lt;p&gt;表示查询设计的表或衍生表&lt;/p&gt;

&lt;h3 id=&quot;type&quot;&gt;type&lt;/h3&gt;

&lt;p&gt;查询的类型。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;system：表中只有一条数据，这个类型时特殊的const类型。&lt;/li&gt;
  &lt;li&gt;const：针对主键或唯一索引的等值查询扫描，最多只返回一行数据。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;explain select * from user_id where id=2;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;eq_ref：通常出现在多表join查询，表示对于前表的每一个结果，都只匹配后表的一行结果，查询比较操作通常是’=’，效率很高。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;explain select * from user_info, order_info where user_info.id = order_info.user_id;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;ref：通常出现在夺标join查询，对于非唯一或非主键索引，或者使用了最左前缀规则索引的查询。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;explain select * from user_info, order_info where user_info.id = order_info.user_id and order_info.user_id = 5;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;range：表示使用索引的范围查询，如比较大小、IS NULL, BETWEEN, IN 操作。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;index：全索引扫描(full index scan)，直接在索引树中扫描查询全部数据。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;explain select name from user_info;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;ALL：全表扫描(full table scan)，不使用索引。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;type类型性能比较&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ALL &amp;lt; index &amp;lt; range ~ index_merge &amp;lt; ref &amp;lt; eq_ref &amp;lt; const &amp;lt; system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;possible_keys&quot;&gt;possible_keys&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;possible_keys&lt;/code&gt;表示在查询时，能够使用到的全部索引。然而，即使有些索引在&lt;code class=&quot;highlighter-rouge&quot;&gt;possible_keys&lt;/code&gt;中出现，但是并不表示此索引会真正被MySQL用到，具体使用了的索引记录在key字段中。&lt;/p&gt;

&lt;h3 id=&quot;key&quot;&gt;key&lt;/h3&gt;

&lt;p&gt;MySQL在当前查询时真正使用到的索引。&lt;/p&gt;

&lt;h3 id=&quot;key_len&quot;&gt;key_len&lt;/h3&gt;

&lt;p&gt;表示查询优化器使用了索引的字节数，根据最左前缀原则，这个字段可以评估组合索引是否完全被使用，或只有最左部分字段被使用到。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;key_len&lt;/code&gt;的计算规则如下：
&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/mysql_explain_key_len.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;rows&quot;&gt;rows&lt;/h3&gt;

&lt;p&gt;MySQL查询优化器根据统计信息，估算SQL要查找到结果集需要扫描读取的数据行数。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;rows&lt;/code&gt;值非常直观的显示了SQL的效率好坏，原则上&lt;code class=&quot;highlighter-rouge&quot;&gt;rows&lt;/code&gt;越少越好。&lt;/p&gt;

&lt;h3 id=&quot;extra&quot;&gt;extra&lt;/h3&gt;

&lt;p&gt;extra显示一些额外的信息。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Using filesort&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;表示NySQL需要额外的排序操作，不能通过索引顺序达到排序效果，通常建议去掉。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Using index&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;覆盖索引扫描，表示查询在索引树中就可查找所需数据，不需要扫描表数据文件。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Using temporary&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;查询有使用临时表，一般出现于排序、分组和多表join的情况，查询效率不高，建议优化。&lt;/p&gt;

&lt;h2 id=&quot;refs&quot;&gt;REFS&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;https://segmentfault.com/a/1190000008131735&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/04/28/mysql-explain/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">EXPLAIN是MySQL提供的一个命令，可以对SELECT语句进行分析，并输出SELECT执行的详细信息，供开发人员优化。使用方法非常简单，直接在SELECT语句前加上Explain。</summary></entry><entry><title type="html">Kafka之保证可靠的数据传递</title><link href="https://cheng-dp.github.io/2019/04/25/kafka-maintain-credible-data-transport/" rel="alternate" type="text/html" title="Kafka之保证可靠的数据传递" /><published>2019-04-25T00:00:00+08:00</published><updated>2019-04-25T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/04/25/kafka-maintain-credible-data-transport</id><content type="html" xml:base="https://cheng-dp.github.io/2019/04/25/kafka-maintain-credible-data-transport/">&lt;h2 id=&quot;可靠性保证&quot;&gt;可靠性保证&lt;/h2&gt;

&lt;p&gt;Kafka的数据可靠性保证：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;保证分区消息的顺序&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果使用同一个生产者往同一个分区写入消息，消息B在消息A之后写入，Kafka保证B的偏移量比A大，消费者会先读取A再读取B。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;==只有当消息被写入分区的&lt;strong&gt;所有同步副本&lt;/strong&gt;时(不一定要写入磁盘)，它才被认为是&lt;strong&gt;已提交&lt;/strong&gt;的。==&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;生产者可以选择接受不同类型的确认，比如在消息被完成提交时确认，或者被写入首领时确认，或者被发送时就确认。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;只要还有一个副本是活跃的，已经提交的消息就不会丢失。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;消费者只能读取已经提交的消息。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kafka管理员需要权衡消息存储的可靠性和一致性的重要程度，
以及可用性、高吞吐量、低延迟的硬件成本的重要程度之间的权衡。&lt;/p&gt;

&lt;h2 id=&quot;复制&quot;&gt;复制&lt;/h2&gt;

&lt;p&gt;==Kafka的复制机制和分区的多副本架构是Kafka可靠性保证的核心。==&lt;/p&gt;

&lt;p&gt;Kafka的主题本分为多个分区，分区是基本的数据块。分区存储在单个磁盘上，Kafka保证分区里的消息是有序的。&lt;/p&gt;

&lt;p&gt;每个分区可以有多个副本，其中一个副本是首领副本。所有消息都直接发送给首领副本，或者直接从首领副本读取消息。&lt;/p&gt;

&lt;p&gt;其他副本只需要与首领副本同步，并及时复制最新的消息，当首领副本不可用时，其中一个&lt;strong&gt;同步副本&lt;/strong&gt;将称为新的首领副本。&lt;/p&gt;

&lt;p&gt;跟随者副成称为同步副本的条件：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;与Zookeeper之间有一个活跃的会话。(在过去6s向ZooKeeper发送心跳)&lt;/li&gt;
  &lt;li&gt;在过去10s内从首领那里获取过消息。&lt;/li&gt;
  &lt;li&gt;在过去10s内从首领那里获取过&lt;strong&gt;最新&lt;/strong&gt;的消息。
跟随者由于网络原因成为不同步副本，一旦重新获取最新消息后，可以重新变成同步副本。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;由于Kafka的消息需要所有同步副本确认才能称为&lt;strong&gt;已提交&lt;/strong&gt;，因此，一个滞后的同步副本会降低Kafka的&lt;strong&gt;吞吐率&lt;/strong&gt;。而如果一个副本不再是同步的，就不再会影响Kafka的性能，但是会增大数据风险。&lt;/p&gt;

&lt;h2 id=&quot;broker配置&quot;&gt;broker配置&lt;/h2&gt;

&lt;h3 id=&quot;复制系数&quot;&gt;复制系数&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;replication.factor&lt;/code&gt; : 主题级别的配置参数&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;default.replication.factor&lt;/code&gt; : broker级别的配置参数，配置自动创建的主题&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果复制系数是N，则每个分区总共会被N个不同的broker复制，总共有N个数据副本。&lt;/p&gt;

&lt;p&gt;建议在要求可用性的场景里把复制系统至少设为3.&lt;/p&gt;

&lt;p&gt;副本的分布也很重要：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;broker.rack&lt;/code&gt; : 为每个broker配置所在的机架
Kafka会保证分区的副本被分布在多个机架上。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;不完全的首领选举&quot;&gt;不完全的首领选举&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;unclean.leader.election&lt;/code&gt;=true/false 默认为true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当首领不可用时，其他副本都是不同步的，是否允许不同步副本成为新的首领。&lt;/p&gt;

&lt;p&gt;如果不同步的副本不能成为新首领，在旧首领恢复前，Kafka不可用，降低了可用性。&lt;/p&gt;

&lt;p&gt;如果不同步的副本可以成为新首领，因为不同步副本不包括所有旧首领的消息，可能有数据丢失的风险。&lt;/p&gt;

&lt;h3 id=&quot;最少同步副本&quot;&gt;最少同步副本&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;min.insync.replicas&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;消息只有被写入到所有同步副本后才被认为是已提交的，该参数设定此时“所有同步副本”的最少数目。&lt;/p&gt;

&lt;p&gt;对于一个包含3个副本的主题，如果&lt;code class=&quot;highlighter-rouge&quot;&gt;min.insync.replicas&lt;/code&gt;=2，那么至少要存在两个同步副本才向分区写入数据。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;有3个副本，min.insync.replicase=2，如果两个副本不可用，及“所有同步副本” &amp;lt; min.insync.replicase。

broker会停止接受生产者的请求，返回NotEnoughReplicasException.
消费者仍然可以继续读取已有的数据，broker变为只读。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;生产者的可靠性&quot;&gt;生产者的可靠性&lt;/h2&gt;

&lt;p&gt;即使broker配置的尽可能可靠，如果生产者本身是不可靠的，数据丢失仍然会发生。&lt;/p&gt;

&lt;h3 id=&quot;acks设置&quot;&gt;acks设置&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;acks=0&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;此时生产者不管发送是否成功，很大可能会丢失消息。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;acks=1&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;首领收到消息并写入分区文件后即返回确认。&lt;/p&gt;

&lt;p&gt;如果首领在跟随者副本还没有收到更新时崩溃，消息会丢失。&lt;/p&gt;

&lt;p&gt;如果发送消息时，broker正在进行首领选举，生产者会收到LeaderNotAvailableException异常，生产者需要恰当的处理该异常，重发消息。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;acks=all&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;首需要等待所有同步副本都收到消息后才返回确认。&lt;/p&gt;

&lt;p&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;min.insync.replicas&lt;/code&gt;参数结合，决定在返回确认前至少有多少个副本能够收到消息。&lt;/p&gt;

&lt;p&gt;最可靠，但是吞吐率最低。&lt;/p&gt;

&lt;h3 id=&quot;生产者重试&quot;&gt;生产者重试&lt;/h3&gt;

&lt;p&gt;当错误发生时，对于可以自动处理的错误(如，LeaderNotAvailableException)，可以进行多次重试，直至消息发送成功。&lt;/p&gt;

&lt;p&gt;但是，重试可能造成同个消息多次写入的问题，==broker会收到两个相同的消息，Kafka没法保证每个消息只被处理一次。==&lt;/p&gt;

&lt;p&gt;对于幂等消息(如：这个账号里有100美元)，重复消息不会对结果造成影响。但是对于非幂等消息(如：往账号里增加100美元)，会造成结果错误。&lt;/p&gt;

&lt;p&gt;==对于重复消息，可以在消息里加入唯一标识符，并在消费者中进行清理。==&lt;/p&gt;

&lt;h2 id=&quot;消费者的可靠性&quot;&gt;消费者的可靠性&lt;/h2&gt;

&lt;p&gt;只有已经被写入所有==同步副本==的数据，才会被消费者读取，因此消费者得到的消息已经具备了一致性。&lt;/p&gt;

&lt;p&gt;消费者可靠性主要是跟踪哪些消息是已经读取过的，哪些是还没读取过的，保证读取消息时不会丢失。&lt;/p&gt;

&lt;p&gt;如果消费者提交了偏移量，却未能处理完轮询得到的消息，就可能造成消息丢失。&lt;/p&gt;

&lt;h3 id=&quot;消费者的可靠性配置&quot;&gt;消费者的可靠性配置&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;group.id&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果两个消费者具有相同的group.id，并且订阅了同一个主题，每个消费者会分到主题分区的一个子集，也就是只能读取到所有消息的一个子集。&lt;/p&gt;

&lt;p&gt;如果希望消费者可以看到主题的所有消息，需要为它设置唯一的group.id。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;auto.offset.reset = earliest / latest&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;配置在没有偏移量可以提交时，或请求的偏移量在broker上不存在时，消费者的读取位置。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;earliest : 从分区开始位置读取。造成重复读取，不会丢失。&lt;/li&gt;
  &lt;li&gt;latest : 从分区末尾开始读取。可能丢失，不会重复。&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;enable.auto.commit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;自动提交偏移量。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;auto.commit.interval.ms&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;偏移量提交时间间隔，默认为5s。&lt;/p&gt;

&lt;h3 id=&quot;消费者的可靠性-1&quot;&gt;消费者的可靠性&lt;/h3&gt;

&lt;h5 id=&quot;总是在处理完事件后再提交偏移量&quot;&gt;总是在处理完事件后再提交偏移量&lt;/h5&gt;

&lt;p&gt;提交的偏移量应该是处理完成的消息偏移量，而不是读取到的偏移量。&lt;/p&gt;

&lt;h5 id=&quot;偏移量提交频率是性能和重复消息数量之间的权衡&quot;&gt;偏移量提交频率是性能和重复消息数量之间的权衡&lt;/h5&gt;

&lt;p&gt;可以在一个循环里多次提交偏移量，也可以在多个循环只提交一次偏移量。&lt;/p&gt;

&lt;h5 id=&quot;注意再均衡&quot;&gt;注意再均衡&lt;/h5&gt;

&lt;p&gt;==注意要在再均衡发生前提交偏移量。==&lt;/p&gt;

&lt;h5 id=&quot;消费者重试&quot;&gt;消费者重试&lt;/h5&gt;

&lt;p&gt;在不影响轮询读取的情况下，对&lt;strong&gt;处理&lt;/strong&gt;失败的消息进行重试。&lt;/p&gt;

&lt;p&gt;如，记录#30处理失败，#31处理成功，此时需要在不丢弃#30和不影响轮询的状态下对#30进行重试。&lt;/p&gt;

&lt;p&gt;方法一：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;提交最后一个处理成功的偏移量，把处理失败的消息保存到缓冲区。&lt;/li&gt;
  &lt;li&gt;调用&lt;code class=&quot;highlighter-rouge&quot;&gt;KafkaConsumer#pause(Collection&amp;lt;TopicPartition&amp;gt; partitions)&lt;/code&gt;使得轮询不再返回新数据。&lt;/li&gt;
  &lt;li&gt;尝试重新处理缓冲区中的消息，直至成功或到达重试上限。&lt;/li&gt;
  &lt;li&gt;调用&lt;code class=&quot;highlighter-rouge&quot;&gt;KafkaConsumer#resume(Collection&amp;lt;TopicPartition&amp;gt; partitions)&lt;/code&gt;使得轮询返回新数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;KafkaConsumer:

public void pause(Collection&amp;lt;TopicPartition&amp;gt; partitions)

暂停从给定分区中获取数据，新的poll调用不会给消费者返回任何数据。

public void resume(Collection&amp;lt;TopicPartition&amp;gt; partitions)

从暂停中恢复。

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;==使用&lt;code class=&quot;highlighter-rouge&quot;&gt;pause&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;resume&lt;/code&gt;方法是因为，不能跳出poll循环，也不能长时间阻塞轮询，会造成长时间没有发出心跳，Kafka broker会认为消费者宕机，造成再均衡。==&lt;/p&gt;

&lt;p&gt;方法二：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;把错误写入一个独立的主题。&lt;/li&gt;
  &lt;li&gt;建立一个独立的消费者群组专门负责从错误主题上读取需要重试的数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;长时间处理&quot;&gt;长时间处理&lt;/h5&gt;

&lt;p&gt;暂停轮询的时间不能超过几秒，否则客户端和broker的心跳将断开。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;使用线程池处理需要长时间处理的数据。&lt;/li&gt;
  &lt;li&gt;调用pause()，保持轮询，等待工作线程完成处理。&lt;/li&gt;
  &lt;li&gt;调用resume()，继续获取数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;仅一次处理&quot;&gt;仅一次处理&lt;/h5&gt;

&lt;p&gt;消费者如果要支持仅一次处理语义(及每个消息只被写到外部系统一次，不处理重复消息)。&lt;/p&gt;

&lt;p&gt;最简单的办法是把结果写到一个支持唯一键的系统里，如键值存储引擎、关系型数据库、ElasticSearch或其他数据引擎，可以在消息里直接包含一个唯一的键，也可以使用主题 + 分区 + 偏移量的组合创建唯一键。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/04/25/kafka-maintain-credible-data-transport/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">可靠性保证</summary></entry><entry><title type="html">Kafka成员管理及消息管理机制</title><link href="https://cheng-dp.github.io/2019/04/22/kafka-broker-manager-and-data-manager/" rel="alternate" type="text/html" title="Kafka成员管理及消息管理机制" /><published>2019-04-22T00:00:00+08:00</published><updated>2019-04-22T00:00:00+08:00</updated><id>https://cheng-dp.github.io/2019/04/22/kafka-broker-manager-and-data-manager</id><content type="html" xml:base="https://cheng-dp.github.io/2019/04/22/kafka-broker-manager-and-data-manager/">&lt;h2 id=&quot;集群成员关系&quot;&gt;集群成员关系&lt;/h2&gt;

&lt;p&gt;Kafka使用ZooKeeper维护集群成员的信息，每个broker有唯一的ID，并在启动时&lt;strong&gt;创建临时节点&lt;/strong&gt;把自己的ID注册到ZooKeeper /brokers/ids路径。&lt;/p&gt;

&lt;p&gt;Kafka组件订阅ZooKeeper的/brokers/ids路径，可以获得broker创建或宕机的通知。&lt;/p&gt;

&lt;p&gt;关闭broker时，它的ID从ZooKeeper上删除，但是继续存在于其他数据结构中(如：主题的副本列表)，完全关闭一个broker后，如果使用相同的ID启动另一个全新的broker，它会立即加入集群，并拥有与旧broker相同的分区和主题。&lt;/p&gt;

&lt;h2 id=&quot;控制器controller&quot;&gt;控制器(Controller)&lt;/h2&gt;

&lt;p&gt;控制器是一个broker，除了一般broker的功能外，还负责==分区首领的选举==。&lt;/p&gt;

&lt;p&gt;集群里的broker通过在Zookeeper创建临时节点/controller竞争成为控制器。其他broker创建失败后会向/controller节点注册watch对象。&lt;/p&gt;

&lt;p&gt;当前控制器宕机后，其他broker会收到watch消息，并尝试创建/controller竞争称为新的控制器。&lt;/p&gt;

&lt;p&gt;每个新选出的控制器通过Zookeeper的条件递增操作获得一个新的controller epoch, 其他broker在知道当前controller epoch后，会忽略之前控制器发出的包含较旧epoch的消息。&lt;/p&gt;

&lt;h2 id=&quot;复制&quot;&gt;复制&lt;/h2&gt;

&lt;p&gt;Kafka是&lt;strong&gt;一个分布式的、可分区的、可复制的提交日志服务&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;Kafka使用主题来组织数据，每个主题分为若干个分区，每个分区有多个副本。&lt;/p&gt;

&lt;p&gt;==副本保存在broker上，每个broker可以保存属于不同主题和分区的多个副本。==&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;首领副本&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;每个分区只有一个首领副本，所有生产者请求和消费者请求都经过该副本。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;跟随者副本&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;除首领副本外都是跟随者副本。跟随者副本不处理来自客户端的请求，唯一的任务就是从首领那里复制消息，保持与首领一致的状态。如果首领副本所在的broker崩溃，其中的一个跟随者将成为新首领副本。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;跟随者副本—同步的副本&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;跟随者向首领发送和消费者一样的，获取数据的请求，请求包含有序的偏移量。只有收到前一个偏移量请求的回复后，才会继续请求下一个偏移量的请求。&lt;/p&gt;

&lt;p&gt;==通过查看每个跟随者请求的偏移量，首领就会知道每个跟随者复制的进度。==如果跟随者在10s内没有请求任何消息、或者虽然在请求消息，但在10s内没有请求最新的数据，就被认为是不同步的。&lt;/p&gt;

&lt;p&gt;持续请求得到最新消息的副本被称为&lt;strong&gt;同步的副本&lt;/strong&gt;，==只有同步的副本才能被选为新首领。==&lt;/p&gt;

&lt;h3 id=&quot;分区首领副本的选举&quot;&gt;分区首领副本的选举&lt;/h3&gt;

&lt;p&gt;Kafka在ZooKeeper上为每个Topic维护一个所有==同步副本的集合==，称为ISR(In-Sync Replica)。&lt;/p&gt;

&lt;p&gt;当Leader分区不可用时，控制器(Controller)broker直接从ISR列表中取出第一个broker作为新的首领，如果不行则依次类推。&lt;/p&gt;

&lt;h2 id=&quot;处理请求&quot;&gt;处理请求&lt;/h2&gt;

&lt;p&gt;Kafka提供了一个二进制协议(基于TCP)，指定了请求消息的格式以及broker如何对请求做出响应。客户端发起连接并发送请求，broker按请求到达的顺序处理请求并做出响应。&lt;/p&gt;

&lt;p&gt;标准消息头：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Request type : API key&lt;/li&gt;
  &lt;li&gt;Request version : broker可以处理不同版本的客户端请求，根据客户端版本做出不同响应。&lt;/li&gt;
  &lt;li&gt;Correlation ID : 标识请求消息。&lt;/li&gt;
  &lt;li&gt;Client ID : 表示发送请求的客户端。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;broker请求处理流程：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;brokerAcceptor线程监听端口，创建连接并交给Processor线程。&lt;/li&gt;
  &lt;li&gt;Processor线程将客户端请求放入请求队列、从响应队列获取响应消息发给客户端。
&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/kafka_broker_process_line.png&quot; alt=&quot;image&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;常见的请求类型：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;生产请求&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;生产者向broker发送要写入的消息。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;获取请求&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;消费者&lt;strong&gt;和跟随者&lt;/strong&gt;从broker读取消息。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;元数据请求&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;==生产请求和获取请求都必须发送给分区的首领副本==，如果broker收到一个针对特定分区的请求，而该分区的首领在另一个broker上，那么发送请求的客户端会收到一个 ==“非分区首领”== 错误。因此，客户端需要利用&lt;strong&gt;元数据请求&lt;/strong&gt;知道生产和获取请求的目标broker。&lt;/p&gt;

&lt;p&gt;客户端向服务器请求感兴趣的主题列表信息，服务端的响应消息里指明了主题包含的分区、每个分区有哪些副本、哪个副本是首领副本，副本所在的broker。&lt;/p&gt;

&lt;p&gt;元数据请求可以发送给任意一个broker，因为所有broker都缓存了所有主题的元数据。&lt;/p&gt;

&lt;p&gt;客户端会定期发送(metadta.max.age.ms)元数据请求刷新主题分区信息，并将这些元数据缓存在本地。
&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/kafka_client_metadata_request.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;生产请求&quot;&gt;生产请求&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;请求验证
broker收到生产请求，对请求做验证：
    &lt;ul&gt;
      &lt;li&gt;发送数据的用户是否有主题写入权限？&lt;/li&gt;
      &lt;li&gt;请求包含的acks值是否有效？(0, 1, all) ?&lt;/li&gt;
      &lt;li&gt;如果acks = all, 是否有足够多的同步副本保证消息已经被安全写入
如果此时同步副本数目小于配置，broker可以拒绝处理新消息。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;消息写入&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;验证后，消息将被写入本地磁盘(文件系统缓存)，并不保证何时刷新到磁盘上，Kafka不会一直等待数据被写到磁盘上，它依赖复制功能来保证消息的持久性。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;检查acks参数并返回&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;==如果acks=0或1, broker立即返回响应，如果acks=all，请求将被加入缓冲区，直到首领发现所有跟随者副本都复制了消息，才向客户端返回响应。==&lt;/p&gt;

&lt;h3 id=&quot;获取请求&quot;&gt;获取请求&lt;/h3&gt;

&lt;p&gt;客户端向broker请求主题分区里特定偏移量的消息：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;把
主题Test，分区0，偏移量从53开始，的消息
以及
主题Test，分区3，偏移量从64开始，的消息
发给我
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ol&gt;
  &lt;li&gt;客户端可以指定broker最多从一个分区里返回的数据上限。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果没有这个限制，broker返回大量数据有可能耗尽客户端的内存。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;客户端也可以指定broker返回数据的下限。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;即broker将等到有足够的数据量时，才返回给客户端。同时，客户端可以定义一个超时时间，当等到超时时间到达时，即使没有足够的数据量，broker也将返回。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;broker检查请求是否有效。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如，指定的偏移量在分区上是否存在，如果检查失败返回错误。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;broker向客户端发送数据。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;==Kafka使用&lt;strong&gt;零复制&lt;/strong&gt;技术向客户端发送消息==，直接把消息从文件(文件系统缓存)中发送到网络通道，不经过中间缓冲区。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;大部分客户端只能读取已经被写入所有&lt;strong&gt;同步副本&lt;/strong&gt;的数据。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;还没有足够多副本复制的消息被认为是不安全的，如果首领发生崩溃，这些消息可能丢失。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/kafka_consumer_read_leader.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果broker间的消息复制变慢，那么消息到达消费者的时间也会变长。&lt;/p&gt;

&lt;h2 id=&quot;消息存储&quot;&gt;消息存储&lt;/h2&gt;

&lt;p&gt;Kafka的基本存储单位是分区(Partition)。&lt;/p&gt;

&lt;h3 id=&quot;分区分配&quot;&gt;分区分配&lt;/h3&gt;

&lt;p&gt;==在创建主题时，Kafka首先会决定如何在broker间分配分区。==&lt;/p&gt;

&lt;p&gt;分配目标：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;在broker间平均地分布分区副本。&lt;/li&gt;
  &lt;li&gt;确保分区的不同副本分布在不同的broker上。&lt;/li&gt;
  &lt;li&gt;如果为broker指定了机架信息(或机房信息)，尽可能把每个分区的副本非配到不同机架的broker上。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;分配过程：&lt;/p&gt;

&lt;p&gt;假设有6个broker，创建包含10个分区的主题，复制系数为3，也就是有30个分区副本。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;从随机的broker开始，使用轮询的方式分配&lt;strong&gt;首领分区&lt;/strong&gt;。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;随机选中broker 4，则首领0分配在broker 4, 首领1分配在broker 5， 首领2分配在broker0 (broker为0-5)...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;从分区首领开始，依次分配跟随者副本。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;如首领0分配在broker4，则跟随者0在broker 5，跟随者1在broker0...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果配置了机架信息，就不是轮询broker ID，而是轮询机架ID。&lt;/p&gt;

&lt;p&gt;为分区副本分配broker目录：&lt;/p&gt;

&lt;p&gt;计算每个目录里的分区数量，新的分区总是被添加到分区数量最小的那个目录里。&lt;/p&gt;

&lt;h3 id=&quot;消息文件&quot;&gt;消息文件&lt;/h3&gt;

&lt;h5 id=&quot;文件管理&quot;&gt;文件管理&lt;/h5&gt;

&lt;p&gt;Kafka管理员能为每个主题配置数据保留期限，规定数据被删除之前可以保留多长时间，或者保留的最大数据量大小。&lt;/p&gt;

&lt;p&gt;==分区(Partition)被分成若干个片段(Segment)，默认为1G，达到片段上线，就关闭当前文件并打开一个新文件。==&lt;/p&gt;

&lt;p&gt;当前正在写入的片段文件叫做&lt;strong&gt;活跃片段&lt;/strong&gt;，活跃片段永远不会被删除。&lt;/p&gt;

&lt;h5 id=&quot;文件格式&quot;&gt;文件格式&lt;/h5&gt;

&lt;p&gt;==Kafka保存在文件的消息格式与生产者发送以及发送给消费者的格式一致。==&lt;/p&gt;

&lt;p&gt;==因为使用了相同的消息格式进行磁盘存储和网络传输，Kafka可以使用&lt;strong&gt;零复制&lt;/strong&gt;技术，同时避免在broker上对生产者压缩过的消息进行解压和再压缩。==&lt;/p&gt;

&lt;p&gt;如果生产者发送的是压缩过的消息，那么同一个批次的消息会被压缩在一起，被当做“包装消息”发送，broker将直接记录压缩消息，然后再整个批次发送给消费者。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/kafka_message_store_format.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;索引&quot;&gt;索引&lt;/h5&gt;

&lt;p&gt;Kafka broker需要迅速定位消费者要读取的偏移量位置，因此Kafka为&lt;strong&gt;每个分区&lt;/strong&gt;维护了一个索引。把偏移量映射到片段文件和偏移量在文件里的位置。&lt;/p&gt;

&lt;p&gt;索引也被分成片段，在删除消息时，也可以删除相应的索引。索引由Kafka读取消时自动生成，因此如果损坏或删除，Kafka都会自动重新生成。&lt;/p&gt;

&lt;h3 id=&quot;消息清理&quot;&gt;消息清理&lt;/h3&gt;

&lt;p&gt;清理策略：
&lt;code class=&quot;highlighter-rouge&quot;&gt;log.cleanup.policy&lt;/code&gt;=&lt;strong&gt;delete&lt;/strong&gt; / &lt;strong&gt;compact&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;delete 策略&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;根据设置的时间保留数据，把超时的旧数据删除。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;compact 策略&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为每个键保留最新的值，删除旧值。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;无论是delete策略还是compact策略都不会清理当前活跃片段。&lt;/strong&gt;&lt;/p&gt;

&lt;h5 id=&quot;清理工作原理&quot;&gt;清理工作原理&lt;/h5&gt;

&lt;p&gt;每个broker启动一个清理管理器线程和多个(log.cleaner.threads)清理线程, 清理线程每隔一定时间(log.retention.check.interval.ms)检查是否有日志需要清理， 清理线程每次选择dirtyRatio较高的分区进行清理。&lt;/p&gt;

&lt;h5 id=&quot;delete策略&quot;&gt;delete策略&lt;/h5&gt;

&lt;ol&gt;
  &lt;li&gt;基于日志文件总大小(空间维度)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;参数：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;log.retention.bytes&lt;/code&gt; : broker级别(默认-1，未开启)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;retention.bytes&lt;/code&gt; : topic级别(默认-1，未开启)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;清理线程比较 [当前日志总大小] - [阈值] &amp;gt;= [日志段大小]，及当前所有日志段总大小是否比阈值大至少一个日志段大小，如果是，则从最老的日志段开始删除。&lt;/p&gt;

&lt;p&gt;删除的最小单位是日志段。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;基于日志分段最新修改时间(时间维度)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;检查当前日志分段文件最新修改时间，删除和当前时间差值超过设定的时间阈值的日志段。&lt;/p&gt;

&lt;p&gt;参数：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;log.retention.hours=168&lt;/li&gt;
  &lt;li&gt;log.retention.minutes=null&lt;/li&gt;
  &lt;li&gt;log.retention.ms=null&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;基于分区日志起始偏移量&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果日志段的下一个偏移量(end + 1)小于设置的起始偏移量，则删除。&lt;/p&gt;

&lt;h5 id=&quot;compact策略&quot;&gt;compact策略&lt;/h5&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;log.cleaner.enable&lt;/code&gt;=true
&lt;code class=&quot;highlighter-rouge&quot;&gt;log.cleanup.policy&lt;/code&gt;=compact&lt;/p&gt;

&lt;p&gt;每个日志片段(segment)分为两个部分：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;干净(clean)的部分：之前已经被清理过。&lt;/li&gt;
  &lt;li&gt;污浊(dirty)的部分：在上一次清理后写入的消息。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/kafka_message_clean_segments.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;==清理线程从dirtyRatio较高的分区进行清理，维护一个map，对每一个key，只保留最新值，删除就版本的数据。==&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/cheng-dp/ImageHostInGithub/master/kafka_log_clean_compact.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;==删除完成后，偏移量可能是不连续的。==&lt;/p&gt;

&lt;p&gt;compact策略只适合对每个key的旧值不关心的特殊场景，如key是用户ID，value是用户的资料，整个消息集里只需要保存用户的最新资料。&lt;/p&gt;

&lt;p&gt;compact策略下的删除：&lt;/p&gt;

&lt;p&gt;==如果需要删除key最新的值，可以向broker发送值为null的消息(墓碑消息)，broker首先会进行常规清理，删除null之前的消息，之后，null值消息会被保存一段时间后删除。==&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;本文地址：https://cheng-dp.github.io/2019/04/22/kafka-broker-manager-and-data-manager/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Cheng Dongping</name></author><summary type="html">集群成员关系</summary></entry></feed>