1. Mysql概述
1.1. mysql概念
MySQL 是最流行的开源 SQL 数据库管理系统,由 Oracle Corporation 开发、分发和支持。
- MySQL是一个数据库管理系统。
- MySQL 数据库是关系型的。
- MySQL 软件是开源的。
- MySQL 数据库服务器非常快速、可靠、可扩展且易于使用。
- MySQL 服务器在客户端/服务器或嵌入式系统中工作。
1.2. mysql主要特点
内部结构和便携性
- 用 C 和 C++ 编写。
- 使用各种不同的编译器进行了测试。
- 适用于许多不同的平台。
- 采用多层服务器设计,独立模块。
- 设计为使用内核线程实现完全多线程,以便在多个 CPU 可用时轻松使用。
- 提供事务性和非事务性存储引擎。
- 使用
MyISAM带有索引压缩的非常快的 B 树磁盘表 ( )。 - 旨在使添加其他存储引擎相对容易。如果您想为内部数据库提供 SQL 接口,这将非常有用。
- 使用非常快速的基于线程的内存分配系统。
- 使用优化的嵌套循环连接执行非常快速的连接。
- 实现内存中的哈希表,用作临时表。
- 使用高度优化的类库实现 SQL 函数,该类库应该尽可能快。通常在查询初始化之后根本没有内存分配。
- 将服务器作为在客户端/服务器网络环境中使用的单独程序提供,并作为可以嵌入(链接)到独立应用程序中的库。此类应用程序可以单独使用,也可以在没有网络可用的环境中使用。
数据类型
- 许多数据类型:有符号/无符号整数1,2,3,4,和8个字节长,
FLOAT,DOUBLE,CHAR,VARCHAR,BINARY,VARBINARY,TEXT,BLOB,DATE,TIME,DATETIME,TIMESTAMP,YEAR,SET,ENUM,和开放GIS空间类型。请参阅第 11 章,数据类型。 - 固定长度和可变长度字符串类型。
语句和函数
查询
SELECT列表和WHERE子句中的 完整运算符和函数支持 。例如:1
2
3mysql> SELECT CONCAT(first_name, ' ', last_name)
-> FROM citizen
-> WHERE income/dependents > 10000 AND age > 30;完全支持 SQL
GROUP BY和ORDER BY子句。支持基函数(COUNT(),AVG(),STD(),SUM(),MAX(),MIN(),和GROUP_CONCAT())。支持
LEFT OUTER JOIN并RIGHT OUTER JOIN使用标准 SQL 和 ODBC 语法。支持标准 SQL 要求的表和列的别名。
支持
DELETE,INSERT,REPLACE,和UPDATE以返回更改(受影响)的行数,或返回通过连接到服务器时设置标志,而不是匹配的行的数量。支持
SHOW检索有关数据库、存储引擎、表和索引的信息的特定于 MySQL 的语句。支持INFORMATION_SCHEMA数据库,按照标准SQL实现。一个
EXPLAIN语句来显示优化器如何解决一个查询。函数名独立于表名或列名。例如,
ABS是有效的列名。唯一的限制是对于函数调用,函数名和它后面的“(”之间不允许有空格 。请参见 第 9.3 节“关键字和保留字”。您可以在同一语句中引用来自不同数据库的表。
安全
- 一种非常灵活和安全的特权和密码系统,可实现基于主机的验证。
- 当您连接到服务器时,通过加密所有密码流量来确保密码安全。
可扩展性和限制
- 支持大型数据库。我们将 MySQL Server 与包含 5000 万条记录的数据库一起使用。我们还知道使用 MySQL Server 的用户有 200,000 个表和大约 5,000,000,000 行。
- 每个表最多支持 64 个索引。每个索引可能由 1 到 16 列或部分列组成。
InnoDB表的最大索引宽度为767 字节或 3072 字节。请参阅第 14.22 节,“InnoDB 限制”。MyISAM表的最大索引宽度为 1000 字节。请参阅 第 15.2 节,“MyISAM 存储引擎”。索引可使用的柱的前缀CHAR,VARCHAR,BLOB,或TEXT列类型。
1.3. 索引
索引是从数据库中的一个或多个列构建的键,可加快从表或视图中获取行的速度。 该键可帮助 Oracle、SQL Server、MySQL 等数据库快速找到与键值关联的行。
提高操作性能的最佳方法 SELECT是在查询中测试的一个或多个列上创建索引。索引条目就像指向表行的指针一样,允许查询快速确定哪些行与WHERE子句中的条件匹配,并检索这些行的其他列值。所有 MySQL 数据类型都可以建立索引。
尽管为查询中使用的每个可能的列创建索引可能很诱人,但不必要的索引浪费了 MySQL 确定使用哪些索引的空间和时间。索引还会增加插入、更新和删除的成本,因为每个索引都必须更新。您必须找到正确的平衡点,才能使用最佳索引集实现快速查询。
了解索引之前可以先了解一下B树和B+树等,可以参考这几篇文章
漫画:什么是B-树https://blog.csdn.net/moakun/article/details/79927830
漫画:什么是B+树https://zhuanlan.zhihu.com/p/54102723
漫画:什么是红黑树https://zhuanlan.zhihu.com/p/143396578
什么是树堆:https://zhuanlan.zhihu.com/p/375122996
索引是什么
那索引到底是什么呢?你是不是还停留在大学学『数据库原理』时老师讲的“索引就像字典的目录”这样的概念?老师讲的没错,但没有深入去讲。
其实索引就是一种用于快速查找数据的数据结构,是帮助MySQL高效获取数据的排好序的数据结构。
索引的好处
举例说明索引的好处以及是怎么加快查询的。
假设我们有一个表t,它有俩字段,Col1和Col2,如下:

不加索引
不加索引的情况下,SQL: select * from t where t.col2=89 ,需要从表的第一行一行行遍历比对col2的值是否等于89,这样需要比对6次才能查到。这只是只有几行记录的表,那如果是百万级千万级的表呢?是不是就比较的次数就更多了,那还不得慢死。
加索引
如果col2这列加了索引,mysql内部会维护一个数据结构。假设mysql用的数据结构是红黑树(右子树的元素大于根节点,左子树的元素小于根节点)的数据结构建立索引,那就像上图右边那样。这样的话,刚才的那条SQL是不是只需要2次磁盘IO就查到了,是不是快很多了。
这就是索引的好处。索引使用比较巧妙的数据结构,利用数据结构的特性来大大减少查找遍历次数。
索引底层数据结构的探索
既然索引底层原理是利用一些巧妙的数据结构维护我们的数据,使得查询效率很高,那索引底层使用的什么数据结构呢?又是怎样来维护我们的数据呢?下面就带着大家一起探索一下索引的底层数据结构。
索引可选的数据结构 :
- 二叉树
- 红黑树
- hash
- B-Tree
但mysql索引的底层用的并不是二叉树和红黑树。因为二叉树和红黑树在某些场景下都会暴露出一些弊端或者说缺点。
二叉树
我们看一下二叉树如果作为索引的底层数据结构在什么样的场景下有怎么样的缺点和不足。
假设把刚才的SQL改一下,用col1作为条件来查找,SQL: select * from t where t.col1 = 6 。
假如把col1作为索引,col1这列的数据特点是从上到下依次递增,类似于自增主键,那在每插入一行在维护二叉树这样一个数据结构的时候,我们看一下二叉树维护成什么样子了。
打开这个网址(国外的),可以演示数据结构维护的过程。依次插入1、2、3、4、5…
通过这个网站的演示插入这些数据,我们可以看到这样的一个二叉树是不是一直在单边增长,没有左子树。再仔细看一下和我们学过的链表是不是很像,也就是说二叉树在某些场景下退化成了链表。


链表的查找是不是需要从头部遍历啊,这时候和没加索引从表的第一行遍历是不是没什么太大区别?这就是mysql索引底层没有使用二叉树这种数据结构的原因之一。
当二叉树像上图一样退化成链表后,我们去查col1=6的记录是不是从二叉树的根节点依次遍历,遍历6次才能查到,和不加索引从表里一行行的遍历没太大差别。这是二叉树所谓索引底层数据结构的弊端之一。
红黑树
那有没有更好的数据结构用来存储索引,帮助我们更快的查找呢?比方说红黑树或hash表。
我们先看下红黑树。红黑树是什么?
是一种平衡二叉树,JDK1.8的hashmap就用到了红黑树。
那我们把刚才的一样的数据用红黑树来看一下是什么样的效果,同样打开刚才的网址,我们选择红黑树。

依次插入1、2、3、4、5、6、7看一下效果,可以看到,当有单边增长的趋势时红黑树会进行一个平衡(旋转)。这时,我们查询col1=6的数据时,查了3次,比二叉树又有了改进。


先告诉你mysql索引用的数据结构也不是红黑树,而是B+Tree(B-Tree的变种)。那为什么MySQL也没用红黑树做索引的数据结构呢?说白了红黑树还是有缺陷的。
红黑树做索引底层数据结构的缺陷
我们可以想一下,对于一些大公司特别是互联网公司,表数据动辄数百万数千万,那这样的表我们可以想象一下,现在我们只有7条记录,树的高度就达到了4层,那数百万数千万甚至上亿记录的表创建的索引它的树高得有多高?
假如说我查找的数据在底层的叶子节点上,一般来说都是从根节点开始查找,假如树的高度是50,那我要进行50次查找,50次磁盘IO那得多慢啊这开销已经很大了。这就是红黑树作为索引数据结构的弊端:树的高度过高导致查询效率变慢。
那能不能做一点改造呢?我们看,红黑树的树越高遍历次数会越多,会因为树的高度影响查询效率。所以我们要解决的问题就是减少树的高度,尽量控制它的高度在一个阈值范围内。假设说不大于5,即使数据达到1千万2千万最多也就5次磁盘IO就找到了,5次磁盘IO也是可以接受的毕竟表数据这么大嘛。
怎么改造能达到这个效果呢????想一下,既然树的高度不让增加,又想存很多数据。也就是说限制了纵向发展,那就横向发展呗。(身高已经增长不了了,长胖还是可以的)
对于上图的红黑树来说每个节点的子节点最多就2个,那基于横向增长的思想就让他变成3叉、4叉、5叉…..让子节点增加,让每一个高度可以存储更多的索引元素,每个节点又分叉,分出来的叉又有很多个节点。那么存储同等数量级别的数据,横向存储的越多,树高就越小了。这样的一个改造结果就是B-Tree。
Hash
待会儿有别的问题会引入hash。
B-Tree
- 叶节点具有相同的深度,叶节点的指针为空
- 所有索引元素不重复
- 节点中的数据索引从左到右递增排序

就这样的一个结构。也就是说在一个节点上可以存储更多的元素,k-v,key就是索引字段,data就是索引字段所在的那一行的数据或是那一行数据坐在的的磁盘文件地址、指针,再去查找元素的时候一次性不是Load一个小元素,而是把一个大的节点的数据一次性全部load到内存,然后再在内存里再去比对,在内存里操作是比较快的。
如果我们要查找49这个元素,实际上是从根节点开始查找的,它一次性将根节点这个大节点一次性load到内存里,然后用要查找的元素在这里去比对,49大于15小于56,在15和56之间有一个节点存储的是下一个节点的磁盘地址指向下一个节点(这个节点的索引都是大于15小于56的),然后再将这个节点一次性load到内存去找这个元素,然后比对就找到了。
注意,一次load节点是一次磁盘IO,是非常慢的,但是我们把它load到内存中之后在你内存里随机的找某一个元素是非常快的,跟一次磁盘IO这个时间消耗去比对的话几乎可以忽略不计。
那按这种说法树的高度越小越好,那按这种思路可不可以把一个表的数据都放到一个大的节点上?然后把这个节点一次性load到内存里,我再在内存里一个个去比对不行吗?不是说内存里去比较查找元素是非常的快嘛,跟一次磁盘IO去比对快的多。不可以这样吗?
答案是否定的。
凡事都有个度。你想想,假如我们有几千万数据,在磁盘上面全部放到一个节点上去是不可能的,你的数据表是一行行插入的,存在磁盘上面几百兆甚至几个G,一次性load到内存中合适吗?内存本来就有限,一次性load这么大的数据,而且如果你学过计算机组成原理你也知道,磁盘IO跟内存打交道的单位是4K,一次可能读取4K的数据,可能有时候有一些局部读取的原理可能会取几十K(4的整数倍),取个16K,24K也是可以的 。但是一次交互取这么大是搞不定的,这是计算机组成原理定的,一次磁盘IO取那么多数据,对内存也是非常的浪费,而且这一次磁盘IO也是非常慢的。所以这个节点的大小设置要合适,不能太大也不能太小,mysql对这个节点大小设置的是16K,用下面这个SQL就是可以查到 show clobal status like 'Innodb_page_size' 。

为啥设置16K?为什么不是更大的如16M呢,16K已经足够用了。
MySQL索引选择的不是原生的B-Tree,而是对他进行了改造,得到的是一种叫做B+Tree的数据结构
B+Tree(B-Tree变种)
- 非叶子节点不存储data,只存储索引(冗余),可以放更多的索引
- 叶子节点包含所有索引字段
- 叶子节点用指针连接,提高区间访问的性能
和B-Tree有啥区别?非叶子节点没有数据,数据都挪到叶子节点,叶子节点之间还有指针,非叶子节点之间跟原来一样没有指针。
为啥data元素挪到叶子节点?非叶子节点只存储索引元素,叶子节点存储了一份完整表的所有行的索引字段,data元素是每个索引元素对应要查找的行记录的位置或行数据,这样非叶子节点的每个节点就可以存储更多的索引元素(等会会有一个大致的估算)。实际上非叶子节点存储的是一些冗余索引,看一下上图,15/20/49,选择的是整张表的哪些数据作为索引?选择的是处于中间位置的,因为它要用到B+Tree一些比大小去查找,B+Tree本质可以叫做多叉平衡树,单看B+Tree的某一小块他还是一个二叉树。

还有一个特点,某一个节点的元素处于一个递增的顺序,会提取叶子节点的一些处于中间位置的数据作为冗余索引,查找的时候从根节点开始查找,先把根节点加载到内存里去,然后在内存里去比对。

比如要查找索引为30的数据,先在根节点跟15去比较,大于15,然后小于56,然后从他俩中间的指针查找下一个节点把它load到内存,再在内存里去比对,大于15,大于20,然后小于49,就根据20和49之间的指针找到下一个节点,然后loa到内存,去比对,不等于20下一个30,相等,OK了。
为什么把中间的元素提取出来做冗余元素,为的是查找效率更高。
回到刚刚的问题,为啥要搞这些冗余索引,而且把这些冗余索引的data元素搞到叶子节点?也就是说B+Tree相当于与B-Tree来说我的非叶子节点是不存储data元素的,叶子几点才存储data元素?
你想一下,一个节点不能太大也不能太小,就是16K,把data元素挪走以后,是不是这个节点就能存更多的冗余索引了,意味着分叉就更多了,意味着叶子节点就能存储更多的数据了。
假设索引字段类型是Bigint,8bit,每两个元素之间存的是下一个节点的地址,mysql分配的是6bit,也就是说一个索引后面配对一个节点地址,成对出现,可以算一下16K的节点可以存多少对也就是多少个索引,8b+6b=14b,16K /14b=1170个索引,叶子节点有索引有data元素,假设占1K,那一个节点就放16K/1K=16个元素,假设树高是3,所有节点都放满,能放多少数据?可以算一下,1170*1170*16=21902400,2千多万,mysql设置16K的大小,数据就可以存2千多万就已经足够了吧,既能保证一次磁盘IO不要Load太多的数据 又能保证一次load的性能,即便表的数据在几千万的数量也能保证树的高度在一个可控的范围。
可以看一下几千万的数据表是不是加了索引几十毫秒几百毫秒就出结果了,所以就解释了几千万的表精确的使用索引后他的性能依旧比较高。
树的高度只有3的情况下就能存储2千多万的数据,即便某一个索引在叶子节点,那也就2、3次磁盘IO就能查找到,当然很快了。而且mysql底层的索引他的根节点,是常驻内存的,直接就放到内存的,查找叶子节点,一个2千万的数据放到B+Tree上面,要查找叶子节点,就只需要2次磁盘IO就搞定了,在内存里比对的时间基本可以忽略。
1. 聚集(簇)索引与非聚集(簇)索引
聚集索引定义了数据在表中存储的顺序,该顺序只能以一种方式排序。 因此,每个表只能有一个聚集索引。 在 RDBMS 中,主键通常允许您基于该特定列创建聚集索引。
聚集索引对于那些经常要搜索范围值的列特别有效。使用聚集索引找到包含第一个值的行后,便可以确保包含后续索引值的行在物理相邻。例如,如果应用程序执行 的一个查询经常检索某一日期范围内的记录,则使用聚集索引可以迅速找到包含开始日期的行,然后检索表中所有相邻的行,直到到达结束日期。这样有助于提高此 类查询的性能。同样,如果对从表中检索的数据进行排序时经常要用到某一列,则可以将该表在该列上聚集(物理排序),避免每次查询该列时都进行排序,从而节省成本。
聚集索引是 SQL Server 和 MySQL 中的默认表结构。 尽管即使表没有主键,MySQL 也会添加隐藏的簇索引,但如果表具有主键列,SQL Server 始终会构建一个簇索引。 否则,SQL Server 将存储为堆表。
聚集索引可以加速通过聚集索引键过滤记录的查询,就像通常的 CRUD 语句一样。 由于记录位于叶节点中,因此在按主键值定位记录时无需额外查找额外的列值。
非聚集索引将数据存储在一个位置,将索引存储在另一个位置。 该索引包含指向该数据位置的指针。 单个表可以有多个非聚集索引,因为非聚集索引中的一个索引存储在不同的地方。
例如,一本书可以有多个索引,一个在开头以显示一本书的内容为单位,而第二个索引按字母顺序显示术语的索引。
在表的非排序字段中定义了非聚簇索引。 这种类型的索引方法可帮助您提高使用未指定为主键的键的查询的性能。 非聚集索引允许您为表添加唯一键。
当索引值唯一时,使用聚集索引查找特定的行也很有效率。例如,使用唯一雇员 ID 列 emp_id 查找特定雇员的最快速的方法,是在 emp_id 列上创建聚集索引或 PRIMARY KEY 约束。
1 | 主要区别 |
索引和约束
在表列上定义 PRIMARY KEY 和 UNIQUE 约束时会自动创建索引。 例如,当您创建具有 UNIQUE 约束的表时,数据库引擎会自动创建一个非聚集索引。 如果配置 PRIMARY KEY,数据库引擎会自动创建聚集索引,除非聚集索引已存在。 当您尝试对现有表强制执行 PRIMARY KEY 约束并且该表上已存在聚集索引时,SQL Server 将使用非聚集索引强制执行主键。
查询优化器如何使用索引
精心设计的索引可以减少磁盘 I/O 操作并消耗更少的系统资源,从而提高查询性能。 索引对包含 SELECT、UPDATE、DELETE 或 MERGE 语句的各种查询很有帮助。 考虑查询 SELECT Title, HireDate FROM HumanResources.Employee WHERE EmployeeID = 250在 AdventureWorks2012 数据库中。 执行此查询时,查询优化器会评估用于检索数据的每个可用方法并选择最有效的方法。 该方法可以是表扫描,或者可以扫描一个或多个索引(如果存在)。
执行表扫描时,查询优化器读取表中的所有行,并提取符合查询条件的行。 表扫描会生成许多磁盘 I/O 操作,并且可能会占用大量资源。 但是,表扫描可能是最有效的方法,例如,如果查询的结果集是表中行的高百分比。
当查询优化器使用索引时,它会搜索索引键列,找到查询所需行的存储位置,并从该位置提取匹配的行。 通常,搜索索引比搜索表快得多,因为与表不同,索引通常每行只包含很少的列,并且行按顺序排列。
查询优化器通常在执行查询时选择最有效的方法。 但是,如果没有可用的索引,查询优化器必须使用表扫描。 您的任务是设计和创建最适合您的环境的索引,以便查询优化器可以选择有效的索引以供选择。 SQL Server 提供了 数据库引擎优化顾问 来帮助分析您的数据库环境和选择合适的索引。
表或视图可以包含以下类型的索引:
- 聚集
- 聚集索引根据键值对表或视图中的数据行进行排序和存储。 这些是索引定义中包含的列。 每个表只能有一个聚集索引,因为数据行本身只能以一种顺序存储。
- 表中的数据行按排序顺序存储的唯一时间是表包含聚集索引时。 当表具有聚集索引时,该表称为聚集表。 如果表没有聚集索引,则其数据行存储在称为堆的无序结构中。
- 非聚集
- 非聚集索引具有与数据行分离的结构。 非聚集索引包含非聚集索引键值,每个键值条目都有一个指向包含键值的数据行的指针。
- 从非聚集索引中的索引行到数据行的指针称为行定位符。 行定位器的结构取决于数据页是存储在堆中还是聚簇表中。 对于堆,行定位符是指向行的指针。 对于聚簇表,行定位符是聚簇索引键。
- 您可以将非键列添加到非聚集索引的叶级别以绕过现有索引键限制,并执行完全覆盖的索引查询。 有关更多信息,请参阅 创建包含列的索引 。
**聚集索引与非聚集索引的区别 **
| 参数 | 聚集 | 非聚集 |
|---|---|---|
| 作用 | 您可以按照顺序对记录进行排序并将聚集索引物理存储在内存中。 | 非聚集索引可帮助您为数据行创建逻辑顺序并为物理数据文件使用指针。 |
| 保存方法 | 允许您在索引的叶节点中存储数据页。 | 这种索引方法从不将数据页存储在索引的叶节点中。 |
| 尺寸 | 聚集索引的大小相当大。 | 与聚集索引相比,非聚集索引的大小较小。 |
| 数据访问 | 更快速 | 与聚集索引相比更慢 |
| 额外的磁盘空间 | 不需要 | 需要单独存储索引 |
| 钥匙类型 | 默认情况下,表的主键是聚集索引。 | 它可以与作为复合键的表的唯一约束一起使用。 |
| 主要特征 | 聚集索引可以提高数据检索的性能。 | 它应该在连接中使用的列上创建。 |
聚集索引的优点
聚集索引的优点/好处是:
- 聚集索引是范围或分组的理想选择,具有最大、最小、计数类型查询
- 在这种类型的索引中,搜索可以直接转到数据中的特定点,以便您可以从那里继续按顺序阅读。
- 聚集索引方法使用定位机制在范围的开始处定位索引条目。
- 当请求搜索关键字值范围时,它是范围搜索的有效方法。
- 帮助您最大限度地减少页面传输并最大限度地提高缓存命中率。
**非聚集索引的优点 **
使用非聚集索引的优点是:
- 非聚簇索引可帮助您从数据库表中快速检索数据。
- 帮助您避免与聚集索引相关的开销成本
- 一个表在 RDBMS 中可能有多个非聚集索引。 因此,它可用于创建多个索引。
聚集索引的缺点
这里是使用聚集索引的缺点/缺点:
- 大量按非顺序插入
- 聚集索引创建了许多常量页拆分,其中包括数据页和索引页。
- 用于插入、更新和删除的 SQL 的额外工作。
- 当聚集索引中的字段发生更改时,聚集索引需要更长的时间来更新记录。
- 叶节点主要包含聚集索引中的数据页。
非聚集索引的缺点
这里是使用非聚集索引的缺点/缺点:
- 非聚集索引可帮助您按逻辑顺序存储数据,但不允许对数据行进行物理排序。
- 非聚集索引的查找过程变得昂贵。
- 每次更新聚簇键时,都需要对非聚簇索引进行相应的更新,因为它存储了聚簇键。
转载至:https://www.guru99.com/clustered-vs-non-clustered-index.html
作者:guru99
Innodb 引擎和 Myisam 引擎的实现
Mysql 底层数据引擎以插件形式设计,最常见的是 Innodb 引擎和 Myisam 引擎,用户可以根据个人需求选择不同的引擎作为 Mysql 数据表的底层引擎。我们刚分析了,B+树作为 Mysql 的索引的数据结构非常合适,但是数据和索引到底怎么组织起来也是需要一番设计,设计理念的不同也导致了 Innodb 和 Myisam 的出现,各自呈现独特的性能。
MyISAM 虽然数据查找性能极佳,但是不支持事务处理。Innodb 最大的特色就是支持了 ACID 兼容的事务功能,而且他支持行级锁。Mysql 建立表的时候就可以指定引擎,比如下面的例子,就是分别指定了 Myisam 和 Innodb 作为 user 表和 user2 表的数据引擎。


执行这两个指令后,系统出现了以下的文件,说明这两个引擎数据和索引的组织方式是不一样的。

Innodb 创建表后生成的文件有:
- frm:创建表的语句
- idb:表里面的数据+索引文件
Myisam 创建表后生成的文件有
- frm:创建表的语句
- MYD:表里面的数据文件(myisam data)
- MYI:表里面的索引文件(myisam index)
从生成的文件看来,这两个引擎底层数据和索引的组织方式并不一样,MyISAM 引擎把数据和索引分开了,一人一个文件,这叫做非聚集索引方式;Innodb 引擎把数据和索引放在同一个文件里了,这叫做聚集索引方式。下面将从底层实现角度分析这两个引擎是怎么依靠 B+树这个数据结构来组织引擎实现的。
- MyISAM 引擎的底层实现(非聚集索引方式)
MyISAM 用的是非聚集索引方式,即数据和索引落在不同的两个文件上。MyISAM 在建表时以主键作为 KEY 来建立主索引 B+树,树的叶子节点存的是对应数据的物理地址。我们拿到这个物理地址后,就可以到 MyISAM 数据文件中直接定位到具体的数据记录了。

当我们为某个字段添加索引时,我们同样会生成对应字段的索引树,该字段的索引树的叶子节点同样是记录了对应数据的物理地址,然后也是拿着这个物理地址去数据文件里定位到具体的数据记录。
- Innodb 引擎的底层实现(聚集索引方式)
InnoDB 是聚集索引方式,因此数据和索引都存储在同一个文件里。首先 InnoDB 会根据主键 ID 作为 KEY 建立索引 B+树,如左下图所示,而 B+树的叶子节点存储的是主键 ID 对应的数据,比如在执行 select * from user_info where id=15 这个语句时,InnoDB 就会查询这颗主键 ID 索引 B+树,找到对应的 user_name=’Bob’。
这是建表的时候 InnoDB 就会自动建立好主键 ID 索引树,这也是为什么 Mysql 在建表时要求必须指定主键的原因。当我们为表里某个字段加索引时 InnoDB 会怎么建立索引树呢?比如我们要给 user_name 这个字段加索引,那么 InnoDB 就会建立 user_name 索引 B+树,节点里存的是 user_name 这个 KEY,叶子节点存储的数据的是主键 KEY。注意,叶子存储的是主键 KEY!拿到主键 KEY 后,InnoDB 才会去主键索引树里根据刚在 user_name 索引树找到的主键 KEY 查找到对应的数据。

问题来了,为什么 InnoDB 只在主键索引树的叶子节点存储了具体数据,但是其他索引树却不存具体数据呢,而要多此一举先找到主键,再在主键索引树找到对应的数据呢?
其实很简单,因为 InnoDB 需要节省存储空间。一个表里可能有很多个索引,InnoDB 都会给每个加了索引的字段生成索引树,如果每个字段的索引树都存储了具体数据,那么这个表的索引数据文件就变得非常巨大(数据极度冗余了)。从节约磁盘空间的角度来说,真的没有必要每个字段索引树都存具体数据,通过这种看似“多此一举”的步骤,在牺牲较少查询的性能下节省了巨大的磁盘空间,这是非常有值得的。
在进行 InnoDB 和 MyISAM 特点对比时谈到,MyISAM 查询性能更好,从上面索引文件数据文件的设计来看也可以看出原因:MyISAM 直接找到物理地址后就可以直接定位到数据记录,但是 InnoDB 查询到叶子节点后,还需要再查询一次主键索引树,才可以定位到具体数据。等于 MyISAM 一步就查到了数据,但是 InnoDB 要两步,那当然 MyISAM 查询性能更高。
本文首先探讨了哪种数据结构更适合作为 Mysql 底层索引的实现,然后再介绍了 Mysql 两种经典数据引擎 MyISAM 和 InnoDB 的底层实现。最后再总结一下什么时候需要给你的表里的字段加索引吧:
- 较频繁的作为查询条件的字段应该创建索引;
- 唯一性太差的字段不适合单独创建索引,即使该字段频繁作为查询条件;
- 更新非常频繁的字段不适合创建索引。
转载至:https://zhuanlan.zhihu.com/p/113917726
作者:腾讯技术工程
2. 聚集索引和二级索引
每个InnoDB表都有一个称为聚集索引的特殊索引,用于存储行数据。通常,聚集索引与主键同义。为了从查询、插入和其他数据库操作中获得最佳性能,了解如何InnoDB使用聚集索引来优化常见查找和 DML 操作非常重要。
- 在
PRIMARY KEY表上定义 a时,InnoDB将其用作聚集索引。应该为每个表定义一个主键。如果没有逻辑唯一且非空的列或列集使用主键,请添加自动增量列。自动递增列值是唯一的,并在插入新行时自动添加。 - 如果没有
PRIMARY KEY为表定义 a ,则InnoDB使用第一个UNIQUE索引,并将所有键列定义为NOT NULL聚集索引。 - 如果表没有索引
PRIMARY KEY或没有合适的UNIQUE索引,则InnoDB生成以GEN_CLUST_INDEX包含行 ID 值的合成列命名的隐藏聚集索引 。行按InnoDB分配的行 ID 排序。行 ID 是一个 6 字节的字段,随着插入新行而单调增加。因此,按行 ID 排序的行在物理上是按插入顺序排列的。
聚集索引如何加快查询速度
通过聚集索引访问一行很快,因为索引搜索直接指向包含行数据的页面。如果表很大,与使用与索引记录不同的页面存储行数据的存储组织相比,聚集索引体系结构通常可以节省磁盘 I/O 操作。
二级索引与聚集索引的关系
聚集索引以外的索引称为二级索引。在 中InnoDB,二级索引中的每条记录都包含该行的主键列,以及为二级索引指定的列。 InnoDB使用此主键值搜索聚集索引中的行。
如果主键很长,二级索引会占用更多的空间,所以主键短是有利的。
3. 哈希索引
一种索引,用于使用相等运算符而不是范围运算符(例如大于或 )的查询BETWEEN。它可用于MEMORY表。尽管MEMORY出于历史原因,哈希索引是表的默认值 ,但该存储引擎也支持 B 树索引,这通常是通用查询的更好选择。
MySQL 包括这种索引类型的变体, 自适应哈希索引,InnoDB如果需要,它会根据运行时条件自动为表构建 。
哈希表是做数据快速检索的有效利器。
哈希算法:也叫散列算法,就是把任意值(key)通过哈希函数变换为固定长度的 key 地址,通过这个地址进行具体数据的数据结构。

考虑这个数据库表 user,表中一共有 7 个数据,我们需要检索 id=7 的数据,SQL 语法是:
1 | select \* from user where id=7; |
哈希算法首先计算存储 id=7 的数据的物理地址 addr=hash(7)=4231,而 4231 映射的物理地址是 0x77,0x77 就是 id=7 存储的额数据的物理地址,通过该独立地址可以找到对应 user_name=’g’这个数据。这就是哈希算法快速检索数据的计算过程。
但是哈希算法有个数据碰撞的问题,也就是哈希函数可能对不同的 key 会计算出同一个结果,比如 hash(7)可能跟 hash(199)计算出来的结果一样,也就是不同的 key 映射到同一个结果了,这就是碰撞问题。解决碰撞问题的一个常见处理方式就是链地址法,即用链表把碰撞的数据接连起来。计算哈希值之后,还需要检查该哈希值是否存在碰撞数据链表,有则一直遍历到链表尾,直达找到真正的 key 对应的数据为止。


从算法时间复杂度分析来看,哈希算法时间复杂度为 O(1),检索速度非常快。比如查找 id=7 的数据,哈希索引只需要计算一次就可以获取到对应的数据,检索速度非常快。但是 Mysql 并没有采取哈希作为其底层算法,这是为什么呢?
因为考虑到数据检索有一个常用手段就是范围查找,比如以下这个 SQL 语句:
1 | select \* from user where id \>3; |
针对以上这个语句,我们希望做的是找出 id>3 的数据,这是很典型的范围查找。如果使用哈希算法实现的索引,范围查找怎么做呢?一个简单的思路就是一次把所有数据找出来加载到内存,然后再在内存里筛选筛选目标范围内的数据。但是这个范围查找的方法也太笨重了,没有一点效率而言。
所以,使用哈希算法实现的索引虽然可以做到快速检索数据,但是没办法做数据高效范围查找。
自适应哈希索引AHI
通过在内存中构造哈希索引InnoDB,可以加快使用=和 IN运算符的 查找速度的表 优化。MySQL 监控表的索引搜索,如果查询可以从哈希索引中受益,它会自动为 经常访问的索引页构建一个。从某种意义上说,自适应哈希索引在运行时配置 MySQL 以利用充足的主内存,更接近主内存数据库的架构。此功能由 InnoDBinnodb_adaptive_hash_index 配置选项。因为此功能有利于某些工作负载而不是其他工作负载,并且用于哈希索引的内存保留在缓冲池中,通常您应该启用和禁用此功能进行基准测试。
哈希索引始终基于表上现有的B 树索引构建 。MySQL 可以在为 B 树定义的任何长度的键的前缀上构建哈希索引,具体取决于针对索引的搜索模式。哈希索引可以是部分的;整个 B 树索引不需要缓存在缓冲池中。
InnoDB 存储引擎将监控表上索引的搜索。如果观察到建立哈希索引可以提高速度,就会建立一个自适应哈希索引,它本质上是一个哈希表:从某个检索条件到一个数据页的哈希表。
索引使用次数超过 17 次 AHI 是为索引树构建的(AHI 仅在索引树的层数过多时才起作用)。如果一个索引只使用一两次,为其建立AHI会导致AHIS过多,维护成本大于收益。当一个索引被使用超过 17 次时,它就通过了过滤。
使用超过 100 次的 哈希信息为使用超过 17 次的索引构建哈希信息。哈希信息用于描述检索条件与索引的匹配程度。在建立AHI时,我们可以根据匹配程度,提取数据的匹配部分作为AHI的key。当hash info 被使用超过100 次时,表示hash info 被频繁使用。
- hash info结构:匹配的索引列数,下一列匹配的字节数,是否从左边匹配。
- hash info 命中页面数据大于 1 / 16
这里的自适应Hash索引相当于B+Tree索引的基础上建立的索引,Mysql中只有Memory引擎支持hash索引,而Oracle也是支持Hash索引的,所以InnoDB层自己加了一个Hash索引。只要符合Hash数据结构的特点,比如不用范围查询,而是(where XX = ?)的查询特别多,InnoDB引擎就会自动建立Hash索引。 我们知道B+树索引是一棵多路平衡查找树,当1200路左右时,可以存储接近17亿的数据,而树的层级为4层左右,并且平常搜索树的查询时间复杂度是O(logN)。当查询到节点处时,也可以理解内部大致为二分查询时间复杂度也是O(logN),即B+树索引的读写时间复杂度是O(logN). 而Hash索引读写的时间复杂度近似O(1). InnoDB存储引擎官方的文档显示,启用AHI后,读取和写入速度可以提高2倍,辅助索引的连接操作性能可以提高5倍。
InnoDB的自适应Hash索引是默认开启的,可以通过配置参数设置:innodb_adaptive_hash_index = off进行关闭。自适应Hash索引使用分片进行实现的,分片数可以使用配置参数设置:innodb_adaptive_hash_index_parts = 8,默认是8个分片最大支持配置 512。并且在 Mysql 5.7之前的分片会使用同一把锁,可能存在并发问题,即Hash索引反而照成了性能问题。信息会被写入”btr0sea.c“文件中,即如果出现有线程在等待”RW-latch“,。 但是后面的版本会使用多把锁解决并发问题。
回顾一下 MySQL 建立 AHI 的整个过程:
- 随着数据量增大,索引树变得越来越高,查询数据页成本变大
- MySQL 引入 AHI 作为查询数据页的缓存,想降低查询数据页的成本
- AHI 的"自适应"想解决的问题是 缓存不能太大,也不能太小
- AHI 建立的过程中,通过不断限制条件,只为经常使用的索引和经常使用的数据页建立缓存
理解了 AHI 的建立过程,在运维过程中就更容易理解 AHI 的状态,我们简要盘点一下 AHI 的运维:
- innodb_adaptive_hash_index_parts。凡是缓存都会涉及多个缓存消费者间的锁竞争。MySQL 通过设立多个 AHI 分区,每个分区使用独立的锁,来减少锁竞争。
- SHOW ENGINE INNODB STATUS。其中有 AHI 的每个分区的使用率和 AHI 的命中率。如果你的业务 AHI 使用率过低,理解了 AHI 建立的原理后,就可以分析该业务为何不命中 AHI,来判断业务是否合理,是否需要改变访问模式或者将数据冷热隔离。也可以考虑关闭 AHI,减少 AHI 的维护成本。
- 在低版本 MySQL 上使用 AHI,先查阅 MySQL bug 列表。低版本是存在一些与 AHI 相关的影响业务的缺陷,在新版本上均已修复,新版本 MySQL 可放心使用。
补充
问题现象:
- 出现大量慢查询
- 接口访问速度缓慢
- 机器大量内存被占用
问题排查:
(确定是否是自适应哈希索引带来的问题?)
数据查询语句
查询Innodb存储引擎的使用情况,会显示出比较多统计信息
1 | show engine innodb status; |
关注其中的一项”SEMAPHORES“(信号)

这是一切正常的显示,如果异常的话,会出现类似下面的显示

可以看到有线程在等待”RW“锁,并将信息写入了buf0buf.c文件里。
如果是自适应哈希索引的问题,根据官方描述,
1 | You can monitor adaptive hash index use and contention in theSEMAPHORESsection ofSHOW ENGINE INNODB STATUSoutput. If there are numerous threads waiting on RW-latches created inbtr0sea.c, consider increasing the number of adaptive hash index partitions or disabling the adaptive hash index feature. |
信息会被写入”btr0sea.c“文件中,即如果出现有线程在等待”RW-latch“, 并将信息写入了buf0sea.c的文件里,就代表出现因为自适应哈希索引带来的数据库性能问题。
官方有给出解决方案,增加分区和关闭自适应哈希索引。
1 | consider increasing the number of adaptive hash index partitions or disabling the adaptive hash index feature. |
产生问题的原因:
MySQL5.7之前的版本是每个分区共享一把锁,MySQL5.7版本是每个分区都有自己的锁,可想而知MySQL5.7之前的版本,会出现多个请求要等1把锁,也就是之前你的自适应hash索引查询越多,也可能因为这1把锁的问题,导致大量线程在等待。
如果是MySQL5.7版本也会出现这样的问题,那就是现在分区已经支撑不了现在的请求量了,依旧是官方的解决方案,那就是增加分区(变相的增加锁)或关掉自适应哈希索引。但是MySQL5.7之前的版本,增加分区是不起作用的,因为锁还是只有1个。
转载至:https://zhuanlan.zhihu.com/p/268341368
作者:ooker
MySQL查询缓存与Innodb引擎的自适应哈希索引
MySQL与引擎之间更像是两套体系,相互之间协同提供更好的数据服务,查询缓存是MySQL在8.0版本之前提供的一个特性,当客户端与数据库连接完毕,需要执行查询语句时,查询缓存就会发挥作用,MySQL会将查询语句进行对比,如果之前执行过该语句,执行语句和执行结果会以键值对的形式被直接缓存到内存里,因为使用查询语句作为key,MySQL可以用语句来查询对应的key,在缓存中找到的话,就可以将key对应value的值返回给客户端,少去了后来再通过分析器,优化器,执行器,引擎等各个阶段复杂的处理。
通过上面的执行过程就能看出一些问题,首先缓存语句的“范围”太大了,因为不同的语句可能会涉及很多复杂复合查询,简单粗暴的缓存起来的后果是,每当有对表的更新,这个表上所有的查询缓存都会被清空,如果是频繁更新的数据表,查询缓存的失效也会特别频繁,这时基本没有使用的意义了,查询缓存的意义更多是针对一些固定内容的表,或者是静态表,例如字典表或者配置表,但这样又有了新的问题,既然已经是固定的数据,那我们直接使用其他缓存数据库多好,查询效率快,也不用担心数据不一致的问题,为什么还要在MySQL中开启这个有些鸡肋的功能呢,所以查询缓存的功能被大大局限,原本的初衷是好的,但到了具体场景中,并没有达到预期的效果。
自适应hash索引访问模式可以是 where a=xxx,哈希索引只能用来搜索等值的查询,对于其他范围查询是不可以使用的,官方文档显示,启动自适应哈希索引,读取和写入速度可以提高2倍,辅助索引的连接操作性能可以提高5倍,之所以成为自适应,就是整个过程无需人为干预调整,完全由数据库自身来优化。还以为通过命令 show engine innodb status来查询当前哈希索引的使用情况。
索引优势说完这些可以分析一下为什么自适应哈希索引要比查询缓存好,首先它细粒度化控制数据的缓存,而不是一步到位,只有满足条件才可以加索引缓存,它的使用范围也有限,只能在等值查询条件使用,也能进一步缩小缓存区间,最重要的是,它缓存的并不是数据内容,而是索引页,这样就不需要考虑数据更新的问题,索引页的更新,合并都有插入缓存这些特性来实现,哈希索引只要能保证快速链接到所要访问的索引页即可。
4. 全文索引
全文索引是在基于文本的列(CHAR、 VARCHAR或TEXT列)上创建的, 以加快对这些列中包含的数据的查询和 DML 操作。
全文索引被定义为CREATE TABLE语句的一部分 或使用ALTER TABLE 或添加到现有表中CREATE INDEX。
InnoDB全文索引采用倒排索引设计。倒排索引存储一个单词列表,对于每个单词,还有一个该单词出现的文档列表。为了支持邻近搜索,每个单词的位置信息也被存储为字节偏移量。
InnoDB 全文索引表
当InnoDB被创建全文索引,一组索引表被创建,如显示在下面的例子:
1 | mysql> CREATE TABLE opening_lines ( |
前六个索引表构成倒排索引,称为辅助索引表。当传入的文档被标记化时,单个词(也称为 “标记”)与位置信息和关联的 DOC_ID. 根据单词的第一个字符的字符集排序权重,在六个索引表中对单词进行完全排序和分区。
倒排索引分为六个辅助索引表,以支持并行索引创建。默认情况下,两个线程对单词和相关数据进行标记、排序和插入到索引表中。执行此工作的线程数可使用该innodb_ft_sort_pll_degree 变量进行配置 。在大型表上创建全文索引时,请考虑增加线程数。
InnoDB 全文索引缓存
插入文档时,将对其进行标记化,并将单个单词和相关数据插入到全文索引中。这个过程,即使对于小文档,也可能导致大量小的插入到辅助索引表中,使得对这些表的并发访问成为一个争论点。为了避免这个问题,InnoDB使用全文索引缓存来临时缓存最近插入的行的索引表插入。这个内存中的缓存结构保存插入直到缓存已满,然后将它们批量刷新到磁盘(到辅助索引表)。
缓存和批量刷新行为避免了对辅助索引表的频繁更新,这可能会在繁忙的插入和更新时间内导致并发访问问题。批处理技术还避免了对同一单词的多次插入,并最大限度地减少了重复条目。不是单独刷新每个单词,而是将同一单词的插入合并并作为单个条目刷新到磁盘,从而提高插入效率,同时保持辅助索引表尽可能小。
全文索引缓存存储与辅助索引表相同的信息。但是,全文索引缓存仅缓存最近插入的行的标记化数据。已刷新到磁盘(到辅助索引表)的数据在查询时不会带回到全文索引缓存中。直接查询辅助索引表中的数据,将辅助索引表的结果与全文索引缓存的结果合并后返回。
InnoDB 全文索引删除处理
删除具有全文索引列的记录可能会导致辅助索引表中出现大量小的删除,从而使对这些表的并发访问成为一个争用点。为避免此问题,每当从索引表中删除DOC_ID记录时,已删除文档的记录FTS_*_DELETED都会记录在特殊 表中,并且索引记录仍保留在全文索引中。在返回查询结果之前,FTS_*_DELETED使用表中的信息过滤掉deletedDOC_IDs。这种设计的好处是删除速度快且成本低。缺点是删除记录后索引的大小不会立即减小。要删除已删除记录的全文索引条目,请OPTIMIZE TABLE在索引表上 运行innodb_optimize_fulltext_only=ON 以重建全文索引。有关更多信息,请参阅 优化 InnoDB 全文索引。
InnoDB 全文索引事务处理
InnoDB由于其缓存和批处理行为,全文索引具有特殊的事务处理特性。具体来说,全文索引的更新和插入是在事务提交时处理的,这意味着全文搜索只能看到提交的数据。
补充
概述
通过数值比较、范围过滤等就可以完成绝大多数我们需要的查询,但是,如果希望通过关键字的匹配来进行查询过滤,那么就需要基于相似度的查询,而不是原来的精确数值比较。全文索引就是为这种场景设计的。
你可能会说,用 like + % 就可以实现模糊匹配了,为什么还要全文索引?like + % 在文本比较少时是合适的,但是对于大量的文本数据检索,是不可想象的。全文索引在大量的数据面前,能比 like + % 快 N 倍,速度不是一个数量级,但是全文索引可能存在精度问题。
你可能没有注意过全文索引,不过至少应该对一种全文索引技术比较熟悉:各种的搜索引擎。虽然搜索引擎的索引对象是超大量的数据,并且通常其背后都不是关系型数据库,不过全文索引的基本原理是一样的。
版本支持
开始之前,先说一下全文索引的版本、存储引擎、数据类型的支持情况
- MySQL 5.6 以前的版本,只有 MyISAM 存储引擎支持全文索引;
- MySQL 5.6 及以后的版本,MyISAM 和 InnoDB 存储引擎均支持全文索引;
- 只有字段的数据类型为 char、varchar、text 及其系列才可以建全文索引。
测试或使用全文索引时,要先看一下自己的 MySQL 版本、存储引擎和数据类型是否支持全文索引。
两种全文索引
自然语言的全文索引
默认情况下,或者使用 in natural language mode 修饰符时,match() 函数对文本集合执行自然语言搜索,上面的例子都是自然语言的全文索引。
自然语言搜索引擎将计算每一个文档对象和查询的相关度。这里,相关度是基于匹配的关键词的个数,以及关键词在文档中出现的次数。在整个索引中出现次数越少的词语,匹配时的相关度就越高。相反,非常常见的单词将不会被搜索,如果一个词语的在超过 50% 的记录中都出现了,那么自然语言的搜索将不会搜索这类词语。上面提到的,测试表中必须有 4 条以上的记录,就是这个原因。
这个机制也比较好理解,比如说,一个数据表存储的是一篇篇的文章,文章中的常见词、语气词等等,出现的肯定比较多,搜索这些词语就没什么意义了,需要搜索的是那些文章中有特殊意义的词,这样才能把文章区分开。
布尔全文索引
在布尔搜索中,我们可以在查询中自定义某个被搜索的词语的相关性,当编写一个布尔搜索查询时,可以通过一些前缀修饰符来定制搜索。
MySQL 内置的修饰符,上面查询最小搜索长度时,搜索结果 ft_boolean*_*syntax 变量的值就是内置的修饰符,下面简单解释几个,更多修饰符的作用可以查手册
- + 必须包含该词
- - 必须不包含该词
- > 提高该词的相关性,查询的结果靠前
- < 降低该词的相关性,查询的结果靠后
- (*)星号 通配符,只能接在词后面
对于上面提到的问题,可以使用布尔全文索引查询来解决,使用下面的命令,a、aa、aaa、aaaa 就都被查询出来了。
1 | select * test where match(content) against('a*' in boolean mode); |
总结
MySQL 的全文索引最开始仅支持英语,因为英语的词与词之间有空格,使用空格作为分词的分隔符是很方便的。亚洲文字,比如汉语、日语、汉语等,是没有空格的,这就造成了一定的限制。不过 MySQL 5.7.6 开始,引入了一个 ngram 全文分析器来解决这个问题,并且对 MyISAM 和 InnoDB 引擎都有效。
事实上,MyISAM 存储引擎对全文索引的支持有很多的限制,例如表级别锁对性能的影响、数据文件的崩溃、崩溃后的恢复等,这使得 MyISAM 的全文索引对于很多的应用场景并不适合。所以,多数情况下的建议是使用别的解决方案,例如 Sphinx、Lucene 等等第三方的插件,亦或是使用 InnoDB 存储引擎的全文索引。
几个注意点
- 使用全文索引前,搞清楚版本支持情况;
- 全文索引比 like + % 快 N 倍,但是可能存在精度问题;
- 如果需要全文索引的是大量数据,建议先添加数据,再创建索引;
- 对于中文,可以使用 MySQL 5.7.6 之后的版本,或者第三方插件。
转载至:https://zhuanlan.zhihu.com/p/35675553
作者:潜心做事
5. 空间索引
空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。
对于InnoDB和MyISAM 表,MySQL 可以使用类似于创建常规索引的语法创建空间索引,但使用 SPATIAL关键字。必须声明空间索引中的列NOT NULL。
Mysql空间索引
1 | 本文主要根据mysql 8.0的文档翻译总结,如果使用的是mysql 5.7版本,可能会有些许差异 |
在涉及LBS的服务开发过程中,经常需要存储地理空间的位置并进行一定计算(附近商家等需求),本文主要介绍mysql对于LBS的支持。
1 | LBS: Location Based Services, 围绕地理位置数据而展开的服务 |
Mysql的空间扩展主要提供一下几个方面的功能:
- 表示空间数值的数据类型
- 操作空间数值的函数
- 空间索引,用于提供访问空间列的速度
其中前两点对InnoDB,MyISAM,NDB,ARCHIVE等mysql存储引擎都支持,第三点只有对InnoDB和MyISAM的支持,由于InnoDB的支持行锁以及事务的特性,现在基本上已经是默认存储引擎了,所以本文以下内容都默认使用InnoDB。
创建空间列以及空间索引的语句如下:
1 | CREATE TABLE geom (g GEOMETRY NOT NULL SRID 4326, SPATIAL INDEX(g)); |
Mysql空间数据类型
Mysql的空间数据类型与OpenGIS的数据类型相对应。
单一几何值的空间数据类型:
- GEOMETRY(几何学)
- POINT(指向)
- LINESTRING(线串)
- POLYGON(多边形)
其中GEOMETRY可以存储任意类型的集合类型,POINT LINESTRING POLYGON则限制了集合类型
- 空间集合数据类型:
- MULTIPOINT(多点)
- MULTILINESTRING(线集合)
- MULTIPOLYGON(多边形集)
- GEOMETRYCOLLECTION(几何收集)
空间数据类型的表示形式
Mysql的空间数据有不同表示格式,其中咱能看懂的也就第一种
- Well-known Text Format (WKT)形如 Point(1 1)
- Well-know Binary Format (WKB) 二进制表示,贴下帧结构,自己感受下
| Component | Size | Value |
|---|---|---|
| Byte order | 1 byte | 01 |
| WKB type | 4 bytes | 01000000 |
| X coordinate | 8 bytes | 000000000000F03F |
| Y coordinate | 8 bytes | 000000000000F0BF |
- Mysql内部几何存储结构就是在WKB的前面加上4个字节来表示SRID,就变成了mysql存储的数据结构
什么是SRID
因为上文提到了SRID,这里说下什么是SRID,SR是指Spatial Reference,也就是我们常说的空间参考系,mysql支持卡迪尔坐标系和地理坐标系,其中地理坐标系又有好多种,下面说几种常用的空间参考系
SRID=0表示一个无限的卡迪尔坐标系平面,且坐标轴上无单位SRID=4326表示GPS坐标系SRID=3857是web地图投影,就是你在谷歌地图上看到的坐标系
1 | ALTER TABLE geom ADD position POINT SRID 4326 |
Mysql的所有空间坐标系都存在表mysql.st_spatial_reference_system中,这个表是隐藏的,看不见的,但是你可以通过infomation_shcema.st_spatial_reference_system中查看参考系的信息,这个表就是mysql.st_spatial_reference_system的一个视图的实现。
1 | SRS的解析是在GIS函数调用后才会去懒加载,并把解析的地理位置定义缓存到数据字典中,以后每次需要SRS的信息时不会重复解析 |
转载至:https://www.jianshu.com/p/74a554ff973a
作者:ZackJiang
补充
概述
索引我们都用过,它是一种特殊的存储结构,就像图书馆里书的分类存放策略或是现代化图书馆里的图书查询系统,能帮助我们快速找到自己需要的书。 数据库中,索引的存储一般使用 B树 或 B+树 来实现,通过二分法来查找法来快速定位到数据位置。
普通索引对于一维数据(key->data)是无往不利,可是面对空间数据(lon,lat -> data)就有些无能为力了,如果查询(116.27636, 40.041285)附近的点:
- 我们在 lon 或 lat 列上创建普通索引,假设是 lon 列,那么通过 lon 列查找到同一经度的数据后,还要在此基础上过滤掉纬度差异过大的数据。
- 如果在 lon,lat 上创建多列索引,查询到同一经度、纬度相近的数据固然快,但附近的点并不只是经度相同。
如此下来,就要用到空间索引了。空间索引通过 四叉树、R 树等数据结构,还有 GeoHash 算法将二维数据转化为一维使用普通B树索引 来实现,它们都能实现对空间范围内的快速搜索。
mysql空间索引
Mysql 的重要性和强大不必多言,它的存储引擎 MyISAM 很早就支持空间索引。而 InnoDB 则在5.7.4 labs版本中才添加对空间索引的支持。
它们都是通过 R 树来实现空间索引。
使用
Mysql 中空间索引使用时要注意:
- 对空间索引的字段首先要设置为
field geometry NOT NULL; - 使用建立空间索引
SPATIAL KEYidx_fld(geom)来创建一列空间索引; - SQL语句中字符串与geometry的转换函数
POINTFROMTEXT('POINT(lon lat)'); - 进行范围查询时要先构造空间区域:
GEOMFROMTEXT('Polygon((lon1 lat1,lon2 lat2,lon3 lat3,lon4 lat4 ...))'
以下是一个典型的空间查询语句(查询距目标点3km以内的点):
1 | SELECT id, ST_Distance_Sphere(Point(-73.951368, 40.716743), geom) as dist, tags, ST_AsText(loc) |
由于 Innodb 的功能比 MyISAM 强大太多,且事务、行锁、B+树索引等功能的不可替代性,这里不再讨论 MyISAM。
Mysql 的空间索引查询效率不低。作为传统的关系型数据库,其多条件支持、分词也都被很好地支持。
虽然对 InnoDB 的空间索引有信心,也略期待,可是对一个长时间存在的系统来说,数据库版本的升级真正不是一个简单的事。
测试
除了mysql,还有Redis, Mongo, PostgreSQL, 这几个知名的支持空间索引的数据库。
我以 126万 poi 数据进行了测试,查询范围 3km 内的点(最多取200条)。 系统信息: macos10.12 (x86_64); 内核: 2 GHz Intel Core i5; 内存: 8 GB 1867 MHz LPDDR3;
以下是各数据库的对比情况:
| 数据库 | 耗时 | 区域查询 | 多条件支持 | 分词支持 | 运维复杂度 | 备注 |
|---|---|---|---|---|---|---|
| redis(3.2.8) | 1-10ms | 不支持 | 不支持 | 不支持 | 低 | 简单但功能单一 |
| mongo(3.4.4) | 10-50ms | 支持 | 支持 | 不支持 | 中 | 结果数据量大时性能下降明显 |
| postgreSQL(9.6.2) | 3-8ms | 支持 | 支持 | 支持 | 中 | 数据写入较慢 |
| mysql(5.7.18 Innodb) | 8-15ms | 支持 | 支持 | 支持 | 低 | 版本升级太困难 |
转载至:https://www.cnblogs.com/zhenbianshu/p/6817569.html
作者:枕边书
R树
R树的优点在于支持多维区域查询,与 kd 树相比,它同时可以动态的增加删除数据,使用更为方便。R 树是利用多个维中的区 间来表示数据对象的,它是一种高度平衡的树,其组成结构与 B 树十分类似,而 B 树 只能进行一维数据查询,R 树可以对任意多维数据建立查询结构并提供查询功能,并且 可以动态插入和删除节点。R 树由中间节点和叶节点组成,实际数据对象的最小外接矩 形存储在叶节点中,中间节点通过聚集其低层节点的外接矩形形成,包含所有这些外接 矩形。而在操作上则是基于在每一个内部节点中封闭矩形的面积的启发式优化。而 R* 树则是 R 树的一个改进,在构造算法上,R*树不仅考虑了索引空间的“面积”,而且还考 虑了索引空间的重叠。该方法对结点的插入、分裂算法进行了改进,并采用“强制重新 插入”的方法使树的结构得到优化。
R 树是高度平衡的树,在其叶节点的索引记录中包含指向数据对象的指针。对于 R 树的叶子节点,其包含索引记录的条目,基本形式为(I, 元组标示符),其中元祖标示符 指向对应的数据,而 I 则是一个包含在一个边界盒内的空间对象的 n 维矩形,表示为: 0 1 1 ( , ,… ) n I I I I 其中 n 是指维度的个数, i I 是一个封闭的有界区间[a, b],用来描述在维度 i 上的空 间对象的范围, i I 可以有一个或两个边界都是无穷大的,表面对象是无穷大的。R 树的 非叶子节点则包含了形如(I, child-pointer)的条目,这里 child-pointer 是一个低级节点在 R 树中的地址,而 I 覆盖了所有低级节点条目中的矩形。简单来讲,即每个节点包含了多 个子节点或数据(当节点为叶子时),而节点中又包含了多维矩形 I 表示所有子节点或 数据的最小包围矩形。
mysql空间索引是基于R树实现的,R树详细介绍可以查看这篇PPT:https://dsa.cs.tsinghua.edu.cn/~deng/cg/project/2009f/2009f-2-f.pdf
6. 复合索引
MySQL 可以创建复合索引(即多列上的索引)。一个索引最多可以包含 16 列。
对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。下面用几个例子对比查询条件的不同对性能影响.
create table test( a int, b int, c int, KEY a(a,b,c) );
1 | 优: select * from test where a=10 and b>50 |
1 | 优: select * from test where order by a |
1 | 优: select * from test where a=10 order by a |
1 | 优: select * from test where a>10 order by a |
1 | 优: select * from test where a=10 and b=10 order by a |
1 | 优: select * from test where a=10 and b>10 order by b |
索引原则
索引越少越好 原因:主要在修改数据时,第个索引都要进行更新,降低写速度。
最窄的字段放在键的左边
避免file sort排序,临时表和表扫描.
利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引不同于使用两个单独的索引。复合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。如果您知道姓,电话簿将非常有用;如果您知道姓和名,电话簿则更为有用,但如果您只知道名不姓,电话簿将没有用处。 所以说创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。 如:建立 姓名、年龄、性别的复合索引。

复合索引的建立原则:
如果您很可能仅对一个列多次执行搜索,则该列应该是复合索引中的第一列。如果您很可能对一个两列索引中的两个列执行单独的搜索,则应该创建另一个仅包含第二列的索引。 如上图所示,如果查询中需要对年龄和性别做查询,则应当再新建一个包含年龄和性别的复合索引。 包含多个列的主键始终会自动以复合索引的形式创建索引,其列的顺序是它们在表定义中出现的顺序,而不是在主键定义中指定的顺序。在考虑将来通过主键执行的搜索,确定哪一列应该排在最前面。 请注意,创建复合索引应当包含少数几个列,并且这些列经常在select查询里使用。在复合索引里包含太多的列不仅不会给带来太多好处。而且由于使用相当多的内存来存储复合索引的列的值,其后果是内存溢出和性能降低。
复合索引对排序的优化:
复合索引只对和索引中排序相同或相反的order by 语句优化。 在创建复合索引时,每一列都定义了升序或者是降序。如定义一个复合索引:
Sql代码 收藏代码 CREATE INDEX idx_example ON table1 (col1 ASC, col2 DESC, col3 ASC)
其中 有三列分别是:col1 升序,col2 降序, col3 升序。现在如果我们执行两个查询 1:Select col1, col2, col3 from table1 order by col1 ASC, col2 DESC, col3 ASC 和索引顺序相同 2:Select col1, col2, col3 from table1 order by col1 DESC, col2 ASC, col3 DESC 和索引顺序相反 查询1,2 都可以别复合索引优化。 如果查询为: Select col1, col2, col3 from table1 order by col1 ASC, col2 ASC, col3 ASC 排序结果和索引完全不同时,此时的查询不会被复合索引优化。
查询优化器在在where查询中的作用:
如果一个多列索引存在于 列 Col1 和 Col2 上,则以下语句:Select * from table where col1=val1 AND col2=val2 查询优化器会试图通过决定哪个索引将找到更少的行。之后用得到的索引去取值。 1. 如果存在一个多列索引,任何最左面的索引前缀能被优化器使用。所以联合索引的顺序不同,影响索引的选择,尽量将值少的放在前面。 如:一个多列索引为 (col1 ,col2, col3) 那么在索引在列 (col1) 、(col1 col2) 、(col1 col2 col3) 的搜索会有作用。
Sql代码 收藏代码
SELECT * FROM tb WHERE col1 = val1
SELECT * FROM tb WHERE col1 = val1 and col2 = val2
SELECT * FROM tb WHERE col1 = val1 and col2 = val2 AND col3 = val3
2. 如果列不构成索引的最左面前缀,则建立的索引将不起作用。 如:
Sql代码 收藏代码
SELECT * FROM tb WHERE col3 = val3
SELECT * FROM tb WHERE col2 = val2
SELECT * FROM tb WHERE col2 = val2 and col3=val3
3. 如果一个 Like 语句的查询条件不以通配符起始则使用索引。 如:%车 或 %车% 不使用索引。 车% 使用索引。 索引的缺点:
- 占用磁盘空间。
- 增加了插入和删除的操作时间。一个表拥有的索引越多,插入和删除的速度越慢。如 要求快速录入的系统不宜建过多索引。
下面是一些常见的索引限制问题
1、使用不等于操作符(<>, !=) 下面这种情况,即使在列dept_id有一个索引,查询语句仍然执行一次全表扫描 select * from dept where staff_num <> 1000; 但是开发中的确需要这样的查询,难道没有解决问题的办法了吗? 有! 通过把用 or 语法替代不等号进行查询,就可以使用索引,以避免全表扫描:上面的语句改成下面这样的,就可以使用索引了。
Sql代码 收藏代码
select * from dept shere staff_num < 1000 or dept_id > 1000;
2、使用 is null 或 is not null 使用 is null 或is nuo null也会限制索引的使用,因为数据库并没有定义null值。如果被索引的列中有很多null,就不会使用这个索引(除非索引是一个位图索引,关于位图索引,会在以后的blog文章里做详细解释)。在sql语句中使用null会造成很多麻烦。 解决这个问题的办法就是:建表时把需要索引的列定义为非空(not null)
3、使用函数 如果没有使用基于函数的索引,那么where子句中对存在索引的列使用函数时,会使优化器忽略掉这些索引。下面的查询就不会使用索引:
Sql代码 收藏代码
select * from staff where trunc(birthdate) = ‘01-MAY-82’;
但是把函数应用在条件上,索引是可以生效的,把上面的语句改成下面的语句,就可以通过索引进行查找。
Sql代码 收藏代码
select * from staff where birthdate < (to_date(‘01-MAY-82’) + 0.9999);
4、比较不匹配的数据类型 比较不匹配的数据类型也是难于发现的性能问题之一。 下面的例子中,dept_id是一个varchar2型的字段,在这个字段上有索引,但是下面的语句会执行全表扫描。
Sql代码 收藏代码
select * from dept where dept_id = 900198;
这是因为oracle会自动把where子句转换成to_number(dept_id)=900198,就是3所说的情况,这样就限制了索引的使用。 把SQL语句改为如下形式就可以使用索引
Sql代码 收藏代码
select * from dept where dept_id = ‘900198’;
转载至:https://cloud.tencent.com/developer/article/1687334
作者:居士
复合索引在B+树上的存储结构及数据查找方式
引言
上一篇文章《MySQL索引那些事》主要讲了MySQL索引的底层原理,且对比了B+Tree作为索引底层数据结构相对于其他数据结构(二叉树、红黑树、B树)的优势,最后还通过图示的方式描述了索引的存储结构。但都是基于单值索引,由于文章篇幅原因也只是在文末略提了一下联合索引,并没有大篇幅的展开讨论,所以这篇文章就单独去讲一下联合索引在B+树上的存储结构。
本文主要讲解的内容有:
- 联合索引在B+树上的存储结构
- 联合索引的查找方式
- 为什么会有最左前缀匹配原则
在分享这篇文章之前,我在网上查了关于MySQL联合索引在B+树上的存储结构这个问题,翻阅了很多博客和技术文章,其中有几篇讲述的与事实相悖。庆幸的是看到搜索引擎列出的有一条是来自思否社区的问答,有答主回答了这个问题,贴出一篇文章和一张图以及一句简单的描述。PS:贴出的文章链接已经打不开了。
所以在这样的条件下这篇文章就诞生了。
联合索引的存储结构
下面就引用思否社区的这个问答来展开我们今天要讨论的联合索引的存储结构的问题。
来自思否的提问,联合索引的存储结构 (https://segmentfault.com/q/1010000017579884) 有码友回答如下:

联合索引 bcd , 在索引树中的样子如图 , 在比较的过程中 ,先判断 b 再判断 c 然后是 d ,
由于回答只有一张图一句话,可能会让你有点看不懂,所以我们就借助前人的肩膀用这个例子来更加细致的讲探寻一下联合索引在B+树上的存储结构吧。
首先,表T1有字段a,b,c,d,e,其中a是主键,除e为varchar其余为int类型,并创建了一个联合索引idx_t1_bcd(b,c,d),然后b、c、d三列作为联合索引,在B+树上的结构正如上图所示。联合索引的所有索引列都出现在索引数上,并依次比较三列的大小。上图树高只有两层不容易理解,下面是假设的表数据以及我对其联合索引在B+树上的结构图的改进。PS:基于InnoDB存储引擎。

bcd联合索引在B+树上的结构图

T1表
通过这俩图我们心里对联合索引在B+树上的存储结构就有了个大概的认识。下面用我的语言为大家解释一下吧。
我们先看T1表,他的主键暂且我们将它设为整型自增的(PS:至于为什么是整型自增上篇文章有详细介绍这里不再多说),InnoDB会使用主键索引在B+树维护索引和数据文件,然后我们创建了一个联合索引(b,c,d)也会生成一个索引树,同样是B+树的结构,只不过它的data部分存储的是联合索引所在行的主键值(上图叶子节点紫色背景部分),至于为什么辅助索引data部分存储主键值上篇文章也有介绍,感兴趣或还不知道的可以去看一下。
好了大致情况都介绍完了。下面我们结合这俩图来解释一下。
对于联合索引来说只不过比单值索引多了几列,而这些索引列全都出现在索引树上。对于联合索引,存储引擎会首先根据第一个索引列排序,如上图我们可以单看第一个索引列,如,1 1 5 12 13…他是单调递增的;如果第一列相等则再根据第二列排序,依次类推就构成了上图的索引树,上图中的1 1 4 ,1 1 5以及13 12 4,13 16 1,13 16 5就可以说明这种情况。
联合索引的查找方式
当我们的SQL语言可以应用到索引的时候,比如 select * from T1 where b = 12 and c = 14 and d = 3; 也就是T1表中a列为4的这条记录。存储引擎首先从根节点(一般常驻内存)开始查找,第一个索引的第一个索引列为1,12大于1,第二个索引的第一个索引列为56,12小于56,于是从这俩索引的中间读到下一个节点的磁盘文件地址,从磁盘上Load这个节点,通常伴随一次磁盘IO,然后在内存里去查找。当Load叶子节点的第二个节点时又是一次磁盘IO,比较第一个元素,b=12,c=14,d=3完全符合,于是找到该索引下的data元素即ID值,再从主键索引树上找到最终数据。

最左前缀匹配原则
之所以会有最左前缀匹配原则和联合索引的索引构建方式及存储结构是有关系的。
首先我们创建的idx_t1_bcd(b,c,d)索引,相当于创建了(b)、(b、c)(b、c、d)三个索引,看完下面你就知道为什么相当于创建了三个索引。
我们看,联合索引是首先使用多列索引的第一列构建的索引树,用上面idx_t1_bcd(b,c,d)的例子就是优先使用b列构建,当b列值相等时再以c列排序,若c列的值也相等则以d列排序。我们可以取出索引树的叶子节点看一下。

转载至:https://blog.csdn.net/ibigboy/article/details/104571930?depth_1-
作者:问北
7. 主键索引
表的主键表示您在最重要的查询中使用的一列或一组列。它有一个关联的索引,用于快速查询性能。查询性能受益于NOT NULL优化,因为它不能包含任何NULL值。使用InnoDB存储引擎,表数据在物理上进行组织,以根据主键列或列进行超快速查找和排序。
如果您的表很大而且很重要,但没有明显的列或一组列用作主键,您可以创建一个单独的列,并使用自动递增值作为主键。当您使用外键连接表时,这些唯一 ID 可以用作指向其他表中相应行的指针。
唯一索引是在表上一个或者多个字段组合建立的索引,这个(或这几个)字段的值组合起来在表中不可以重复。一张表可以建立任意多个唯一索引,但一般只建立一个。
主键是一种特殊的唯一索引,区别在于,唯一索引列允许null值,而主键列不允许为null值。一张表最多建立一个主键,也可以不建立主键。
外键索引
如果一个表有很多列,并且您查询了许多不同的列组合,那么将不经常使用的数据拆分成单独的表,每个表都有几列,并通过复制数字 ID 将它们关联回主表可能会很有效主表中的列。这样,每个小表都可以有一个主键,用于快速查找其数据,并且您可以使用连接操作仅查询所需的列集。根据数据的分布方式,查询可能执行更少的 I/O 并占用更少的缓存内存,因为相关列在磁盘上打包在一起。(为了最大化性能,查询尝试从磁盘读取尽可能少的数据块;
8. 其他补充
索引不可见
MySQL 支持不可见索引;也就是说,优化器未使用的索引。该功能适用于主键以外的索引(显式或隐式)。
不可见索引使得测试删除索引对查询性能的影响成为可能,而无需进行破坏性更改,如果需要该索引,则必须撤消该更改。对于大型表,删除和重新添加索引的成本可能很高,而使其不可见和可见是快速的就地操作。
如果优化器实际上需要或使用了一个不可见的索引,有几种方法可以注意到它的缺失对表查询的影响:
- 包含引用不可见索引的索引提示的查询会发生错误。
- Performance Schema 数据显示受影响查询的工作负载增加。
- 查询有不同的
EXPLAIN执行计划。 - 查询出现在之前没有出现在慢查询日志中。
索引降序
MySQL 支持降序索引:DESC在索引定义中不再被忽略,而是导致按降序存储键值。以前,可以以相反的顺序扫描索引,但会降低性能。可以按前向顺序扫描降序索引,这样效率更高。当最有效的扫描顺序混合了某些列的升序和其他列的降序时,降序索引还使优化器可以使用多列索引。
覆盖索引
MySQL只需要通过索引就能取到想要的数据,不需要在回表查询数据了,也就说在这个查询中,索引age已经覆盖了我们的查询需求,这种情况称之为覆盖索引,其实我们在上一篇讲联合索引时已经用到了覆盖索引的技术了。
举个例子:select id from t where age=23,我们只需要拿到id就行了,不需要知道其他的字段值。通过explian查看执行计划时,可以在Extra列看到using index,表示用的覆盖索引。
由于覆盖索引不需要回表,减少了树的搜索次数,能显著的提升查询性能。
1 | mysql> explain select id from t where age=23; |
索引下推(index condition pushdown)
Index Condition Pushdown是MySQL5.6引入的根据索引从表中检索行的一种查询优化方式。
在没有使用ICP技术时,存储引擎会遍历索引然后回表找到对应的行,并将它们返回给MySQL服务器,服务器根据where条件进行过滤。启用ICP后,可以在索引遍历过程中,由存储引擎对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少了回表的次数。ICP可以减少回表的次数,同时也能减少server层与引擎层交互的次数。
接着我们还是用一个列子来说明下索引下推吧,还是用上篇文章的表,建表语句如下
1 | CREATE TABLE `t` ( |
SQL语句是:
1 | select * from t where age = '30' and name like '%ck'; |
首先select *无法使用到覆盖索引的策略,上篇文章我们也说过范围查找时%放在前面时无法利用索引查找,这条SQL只能用到联合索引的age列。
当不使用ICP策略时,引擎会通过二级索引根据age=30的条件找到对应的rowid,再根据rowid回表找到对应的数据行,然后拿到server层,最后在server层根据where条件进行过滤。
开启ICP策略后,引擎在遍历索引的过程中就会对name字段进行判断,直接过滤掉不满足 name like ‘%ck’ 条件的记录,然后再去获取行记录。减少了回表的次数,也减少了server层的二次判断,大大提高了查询的效率。
当使用ICP时,通过explain查看执行计划时Extra列会显示Using index condition,如下图:

ICP优化策略虽然好,但是受限于以下条件(根据官方文档整理):
- ICP用于range、ref、eq_ref和ref_or_null等访问方式且需要访问数据行时。
- ICP可以用于Innodb和MyISAM引擎表,包括两种引擎的分区表。
- 对于Innodb引擎表,ICP只适用于二级索引。ICP的主要目的就是减少回表的次数,从而减少I/O操作。但是对于Innodb引擎的聚簇索引,完整的数据行记录已经被读入到innodb bufferpool中,在这种情况下使用ICP并不能减少I/O操作。
- 在条件引用子查询时无法使用ICP。
- 当使用存储函数时无法使用ICP,存储引擎无法调用存储函数。
对于以下两种情况可以考虑使用ICP优化策略。
对于where constant + like 查询时可以尝试创建联合索引。
1 | select * from t where age = '30' and name like '%ck'; |
- 对于where constant + order by index column时可以尝试创建联合索引。
1 | select * from t where age = '30' order by name; |
MySQL是默认启用索引下推策略,可以通过optimizer_switch变量控制是否开启
1 | SET global optimizer_switch = 'index_condition_pushdown=off';#关闭ICP策略 |
Multi-Range Read Optimization(MRR)
MRR技术也是MySQL5.6版本开始引入的,当一个表很大并且没有缓存在bufferpool中时,由于二级索引和主键的排列顺序一般情况下是不一样的,在二级索引上使用范围扫描回表读取行数据时会导致产生大量的随机I/O,通过MRR优化,MySQL会通过索引扫描收集相关行数据的主键,将主键值的集合存储到read_rnd_buffer中,然后在buffer中对主键进行排序,最后利用排好序的主键再回表查询。同时,如果缓冲池不够大的话,频繁的离散读还会导致缓存中的页频繁的被替换出缓冲池,然后又不断的被读入缓冲池,若按照主键顺序进行访问的话,可以减少数据页的读取,降低数据页被频繁替换出入缓冲池的情况。
MRR优化的目的就是为了减少磁盘的随机访问,并将随机I/O转化顺序I/O,降低查询过程中的I/O开销,同时减少缓冲池中数据页被替换的频次。
举个栗子(例子),还是用上一节的t表,SQL语句如下:
1 | select * from t where age >30 and age <80; |
从下图可以看出这条SQL采用MRR优化策略。

可以通过以下命令来开启或者关闭MRR策略。
1 | #表示依据基于成本的算法选择是否启用MRR优化,如果发现优化后的成本过高就不使用MRR优化 |
小结与建议
在上篇文章中我们介绍了B+Tree以及B+Tree索引的种类,今天这篇文章又介绍了索引内部的一些优化策略,比如使用覆盖索引、索引下推、MRR等,最后我们对索引的知识做一个总结。
首先说下索引的优势:
- 减少磁盘扫描,提高检索效率,避免了全表扫描。
- 提高排序和分组的效率。
- 将随机IO转化为顺序IO。
- 提高部分聚合函数的效率,比如min(),max()等。
如何创建高效的索引呢,下面给出几点建议仅供参考:
- 在经常用于排序和分组查询的字段上建立索引,可以避免了内存排序和随机I/O。
- 在选择性较高的字段上建立索引,查看选择性公式select count(distinct a)/count(*) from t1,越接近1越好,一般超过33%就算是比较高效的索引了。
- 如果没有强烈的业务需求,建议建立自增主键,这样的主键占用空间小,顺序写入,减少页分裂。
- 利用较短的键值作为索引性能比较好,可能的话尽量使用整数类型。
- 对于where条件中涉及多个字段时可以考虑建立联合索引,建议将选择性高的列放到 索引最左列,SQL查询时满足最左原则。
- 对于select后面经常用到的字段可以考虑创建索引,查询时使用覆盖索引查询,避免回表。
- 索引字段尽量设置为NOT NULL,NULL值会更加运算的复杂度。
- 如果有 order by 的场景,尽量利用索引的有序性,避免出现using filesort 的情况,影响查询性能,请参看上一章的联合索引部分。
- SQL投产前查看执行计划,SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴开发手册要求)
- SQL语句中尽量避免使用左模糊或者全模糊查询,无法利用B+Tree 最左前缀匹配特性。
- 考虑针对较长字符串型列使前缀索引,区分度可以使用 count(distinct left(列名, 索引长度))/count(*)来确定,请参看上一章的前缀索引部分。
- 业务上具有唯一特性的字段,即使是组合字段,也建议建成唯一索引,数据库层面避免了脏数据的产生,对insert的影响可以忽略(阿里巴巴开发手册要求)。
- 在表查询中,建议明确字段,不要使用 * 作为查询的字段列表。
- 索引不宜过多,一般建议不超过6个,由于索引的创建和维护是有代价的,所以请不要创建不必要的索引。
- 定期清理冗余索引、未使用过得索引以及查看全表扫描的SQL等,具体监控手段请参见之前的文章<<MySql监控分析视图-sys schema>>
常见的索引失效的场景:
- 通过索引扫描的行数超过全表的20%-30%时,引擎会认为走全表扫描更有效。
- 使用联合索引时没有遵循最左原则。
- where后面出现 or条件 ,且没有建立单列索引会导致失效。
- 对索引使用了函数计算。
- 统计信息不真实(严重不真实),导致执行计划错误。
- 访问小表时,更倾向于全表扫描。
- Where条件中对索引列使用左模糊或者全模糊查询。
1.4 锁机制
示例可以参考这篇博客:https://blog.csdn.net/samjustin1/article/details/52210125
一、加锁的目的是什么?
在我们了解数据库锁之前,首先我们必须要明白加锁的目的是为了解决什么问题,如果你还不清楚的话,那么从现在起你应该知道,数据库的锁是为了解决事务的隔离性问题,为了让事务之间相互不影响,每个事务进行操作的时候都会对数据加上一把特有的锁,防止其他事务同时操作数据。如果你想一个人静一静,不被别人打扰,那么请在你的房门上加上一把锁。
二、锁实是基于什么实现的?
为了后面大家后面对锁理解的更透彻,所以务必要对此进行说明,锁是基于什么实现的,你现实生活中家里的锁是基于门来实现的,那么数据库的锁又是基于什么实现的呢? 那么我在这里可以告诉你,数据库里面的锁是基于索引实现的,在Innodb中我们的锁都是作用在索引上面的,当我们的SQL命中索引时,那么锁住的就是命中条件内的索引节点(行锁),如果没有命中索引的话,那我们锁的就是整个索引树(表锁),如下图一下锁住的是整棵树还是某几个节点,完全取决于你的条件是否有命中到对应的索引节点。
三、锁的分类。
数据库里有的锁有很多种,为了方面理解,所以我根据其相关性”人为”的对锁进行了一个分类,分别如下
基于锁的属性分类:共享锁、排他锁。
基于锁的粒度分类:表锁、行锁、记录锁、间隙锁、临键锁。
基于锁的状态分类:意向共享锁、意向排它锁。
1. 共享锁和排他锁
mysql锁机制分为表级锁和行级锁,本文就和大家分享一下我对mysql中行级锁中的共享锁与排他锁进行分享交流。
共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。

排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据,对于排他锁大家的理解可能就有些差别,我当初就犯了一个错误,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。
说了这么多,咱们来看下以下简单的例子:

简单查询,加了排他锁,没有commit,锁就不会释放,可以查询数据
打开新的窗口,同样使用共享锁查询,发现一直在加载,使用排它锁查询,依然在加载
图1. 加排它锁

图2.加共享锁

图3.普通查询可以查询,因为普通查询没有加任何锁

同样的道理,可以来看先加了共享锁之后,再携带共享锁,可以查询,排它锁则不行
图1.加了排它锁,一直在加载,无法查询

图2.加了共享锁,可以成功查询

共享锁:可以加共享锁,不能加排它锁
排它锁:不能加共享锁,不能加排它锁
转载至:https://www.cnblogs.com/boblogsbo/p/5602122.html
事例2
共享锁我们使用test表来做测试,执行下面的两个事务:
事务1:
1 | /* 开启事务1 */ |
事务2:
1 | /* 开启事务2 */ |
我们依次执行事务1->事务2,我们先来看看事务1:

可以看到,事务1已经成功的拿到了 id = 1 这行数据的共享锁,但是当想修改 id = 1 的数据的时候,发觉发生了死锁(此时事务2也拿到了共享锁):
[Err] 1213 - Deadlock found when trying to get lock; try restarting transaction
大概意思是:试图获取锁时发现死锁;尝试重新启动事务
1 | 我的执行结果是:Lock wait timeout exceeded; try restarting transaction |
接下来我们来看看事务2的执行结果:

可以看到,事务2的 SELECT 语句能正常拿到 id = 1 这行数据的共享锁,并不会给阻塞,而事务2的 UPDATE 语句,则会给阻塞。
那拿到 id = 1 数据的共享锁的事务,在什么情况下能修改 id = 1 的数据呢?
我们再来进行一个案例测试,此时只有事务1:
1 | /* 开启事务1 */ |
我们执行事务1,来看看结果:

我们可以看到,事务1的 SELECT 语句,成功的拿到了 id = 1 数据的共享锁,但后面的 UPDATE 语句,也是能修改成功的。(此时只有一个事务拿到了 id = 1 数据的共享锁)
结论
- 多个事务的查询语句可以共用一把共享锁;
- 如果只有一个事务拿到了共享锁,则该事务可以对数据进行 UPDATE DETELE 等操作;
- 如果多个事务拿到共享锁,更改行为没有发生死锁,只是要等待其他事务释放锁。
排他锁我们继续使用test表来做测试,执行下面的两个事务:
事务1:
1 | /* 开启事务1 */ |
事务2:
1 | /* 开启事务2 */ |
继续先执行事务1,再执行事务2,我们来看看事务1的结果:

我们可以看到,事务1拿到了 id = 1 数据的排它锁之后,可以正常的对 id = 1 数据进行 UPDATE DETALE 等操作,我们再来看看事务2的结果:


我们可以看到,事务2中的普通 SELECT 查询不会被阻塞,而加锁的 SELECT 查询,则会给阻塞掉。我们来修改下事务2的语句:
1 | /* 开启事务2 */ |
我们再依次执行事务1->事务2,我们来看看事务2的结果:

我们可以看到,事务2的 UPDATE 操作依然给阻塞了。
结论
- 只有一个事务能获取该数据的排它锁;
- 一旦有一个事务获取了该数据的排它锁之后,其余事务对于该数据的操作将会被阻塞,直至锁释放。
1 | 例:(死锁的发生) |
共享/排它锁的使用场景
共享锁
- 确保某个事务查到最新的数据;
- 这个事务不需要对数据进行修改、删除等操作;
- 也不允许其它事务对数据进行修改、删除等操作;
- 其它事务也能确保查到最新的数据。
排它锁
- 确保某个事务查到最新的数据;
- 并且只有该事务能对数据进行修改、删除等操作。
共享/排它锁对于性能的影响
- 因为排它锁只允许一个事务获取,所以如果是业务繁忙的情况下,一旦有某个业务不能及时的释放锁,则会导致其它事务的锁等待、锁等待超时、死锁等问题;
- 虽然共享锁可以给多个事务共享,但一旦有多个事务同时拥有共享锁,则所有事务都不能对数据进行 UPDATE DETELE 等操作,也会导致其它事务的锁等待、锁等待超时、死锁等问题;
- 都会影响数据库的并发能力。
转载至:https://zhuanlan.zhihu.com/p/48127815
2. 记录锁(Record Locks)
记录锁是 封锁记录,记录锁也叫行锁,例如:
1 | SELECT * FROM `test` WHERE `id`=1 FOR UPDATE; |
它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行。
3. 间隙锁(Gap Locks)(重点)
间隙锁是封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围。
记录锁、间隙锁、临键锁都是排它锁,而记录锁的使用方法跟之前的一篇文章 共享/排它锁 里的排它锁介绍一致,这里就不详细多讲。
产生间隙锁的条件(RR事务隔离级别下;):
- 使用普通索引锁定;
- 使用多列唯一索引;
- 使用唯一索引锁定多行记录。
以上情况,都会产生间隙锁,下面是小编看了官方文档理解的:
对于使用唯一索引来搜索并给某一行记录加锁的语句,不会产生间隙锁。(这不包括搜索条件仅包括多列唯一索引的一些列的情况;在这种情况下,会产生间隙锁。)例如,如果id列具有唯一索引,则下面的语句仅对具有id值100的行使用记录锁,并不会产生间隙锁:
1 | SELECT * FROM child WHERE id = 100 FOR UPDATE; |
这条语句,就只会产生记录锁,不会产生间隙锁。
打开间隙锁设置
首先查看 innodb_locks_unsafe_for_binlog 是否禁用:
1 | show variables like 'innodb_locks_unsafe_for_binlog'; |
查看结果:

innodb_locks_unsafe_for_binlog:默认值为OFF,即启用间隙锁。因为此参数是只读模式,如果想要禁用间隙锁,需要修改 my.cnf(windows是my.ini) 重新启动才行。
1 | # 在 my.cnf 里面的[mysqld]添加 |
唯一索引的间隙锁
测试环境:
环境:MySQL,InnoDB,默认的隔离级别(RR)
数据表:
1 | CREATE TABLE `test` ( |
数据:
1 | INSERT INTO `test` VALUES ('1', '小罗'); |
在进行测试之前,我们先来看看test表中存在的隐藏间隙:
- (-infinity, 1]
- (1, 5]
- (5, 7]
- (7, 11]
- (11, +infinity]
只使用记录锁,不会产生间隙锁
我们现在进行以下几个事务的测试:
1 | /* 开启事务1 */ |
上诉的案例,由于主键是唯一索引,而且是只使用一个索引查询,并且只锁定一条记录,所以以上的例子,只会对 id = 5 的数据加上记录锁,而不会产生间隙锁。
产生间隙锁
我们继续在 id 唯一索引列上做以下的测试:
1 | /* 开启事务1 */ |
从上面我们可以看到,(5, 7]、(7, 11] 这两个区间,都不可插入数据,其它区间,都可以正常插入数据。所以我们可以得出结论:当我们给 (5, 7] 这个区间加锁的时候,会锁住 (5, 7]、(7, 11] 这两个区间。
我们再来测试如果我们锁住不存在的数据时,会怎样:
1 | /* 开启事务1 */ |
我们可以看出,指定查询某一条记录时,如果这条记录不存在,会产生间隙锁。
结论
- 对于指定查询某一条记录的加锁语句,如果该记录不存在,会产生记录锁和间隙锁,如果记录存在,则只会产生记录锁,如:WHERE
id= 5 FOR UPDATE; - 对于查找某一范围内的查询语句,会产生间隙锁,如:WHERE
idBETWEEN 5 AND 7 FOR UPDATE;
普通索引的间隙锁
数据准备
创建 test1 表:
1 | # 注意:number 不是唯一值 |
在这张表上,我们有 id number 这两个字段,id 是我们的主键,我们在 number 上,建立了一个普通索引,为了方便我们后面的测试。现在我们要先加一些数据:
1 | INSERT INTO `test1` VALUES (1, 1); |
在进行测试之前,我们先来看看test1表中 number 索引存在的隐藏间隙:
- (-infinity, 1]
- (1, 3]
- (3, 8]
- (8, 12]
- (12, +infinity]
案例说明
我们执行以下的事务(事务1最后提交),分别执行下面的语句:
1 | /* 开启事务1 */ |
我们会发现有些语句可以正常执行,有些语句被阻塞了。我们再来看看我们表中的数据:

执行之后的数据
这里可以看到,number (1 - 8) 的间隙中,插入语句都被阻塞了,而不在这个范围内的语句,正常执行,这就是因为有间隙锁的原因。我们再进行以下的测试,方便我们更好的理解间隙锁的区域(我们要将数据还原成原来的那样):
1 | /* 开启事务1 */ |
我们来看看结果:

这里有一个奇怪的现象:
- 事务3添加 id = 6,number = 8 的数据,给阻塞了;
- 事务4添加 id = 8,number = 8 的数据,正常执行了。
- 事务7将 id = 11,number = 12 的数据修改为 id = 11, number = 5的操作,给阻塞了;
这是为什么呢?我们来看看下边的图,大家就明白了。

从图中可以看出,当 number 相同时,会根据主键 id 来排序,所以:
- 事务3添加的 id = 6,number = 8,这条数据是在 (3, 8) 的区间里边,所以会被阻塞;
- 事务4添加的 id = 8,number = 8,这条数据则是在(8, 12)区间里边,所以不会被阻塞;
- 事务7的修改语句相当于在 (3, 8) 的区间里边插入一条数据,所以也被阻塞了。
结论
- 在普通索引列上,不管是何种查询,只要加锁,都会产生间隙锁,这跟唯一索引不一样;
- 在普通索引跟唯一索引中,数据间隙的分析,数据行是优先根据普通索引排序,再根据唯一索引排序。
4. 临键锁(Next-key Locks)
临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。
注:临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。
本文要点
- 记录锁、间隙锁、临键锁,都属于排它锁;
- 记录锁就是锁住一行记录;
- 间隙锁只有在事务隔离级别 RR 中才会产生;
- 唯一索引只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁,指定给某条存在的记录加锁的时候,只会加记录锁,不会产生间隙锁;
- 普通索引不管是锁住单条,还是多条记录,都会产生间隙锁;
- 间隙锁会封锁该条记录相邻两个键之间的空白区域,防止其它事务在这个区域内插入、修改、删除数据,这是为了防止出现 幻读 现象;
- 普通索引的间隙,优先以普通索引排序,然后再根据主键索引排序(多普通索引情况还未研究);
- 事务级别是RC(读已提交)级别的话,间隙锁将会失效
转载至:https://zhuanlan.zhihu.com/p/48269420
作者:咖啡屋小罗
Next-Key锁
下一个键锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。
1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。
InnoDB以这样的方式执行行级锁定,即当它搜索或扫描表索引时,它会在遇到的索引记录上设置共享锁或排它锁。因此,行级锁实际上是索引记录锁。索引记录上的下一个键锁定也会影响该索引记录之前的“ 间隙 ”。也就是说,下一个键锁定是索引记录锁定加上索引记录之前的间隙的间隙锁定。如果一个会话R在索引中具有记录中的共享或排它锁定 ,则另一个会话不能R在索引顺序中立即在间隙中插入新的索引记录 。
假设索引包含值10,11,13和20.此索引的可能下一个键锁定涵盖以下区间,其中圆括号表示排除区间端点,方括号表示包含端点:
1 | (negative infinity, 10] |
对于最后一个时间间隔,下一个键锁定将索引中的最大值之上的间隙锁定,并且“ 上游 ” 伪记录的值高于实际在索引中的任何值。上确界不是一个真正的索引记录,所以实际上,这个下一个键锁只锁定了最大索引值之后的间隔。
gap lock不需要用于那些使用唯一索引锁住行来查找唯一行的语句。(这不包括一个情况,那就是当查询条件包含了复合唯一索引的一部分时,gap lock确实会存在。)。
gap lock的意义只在于阻止区间被插入,因此是可以共存的。一个事务获取的gap lock不会阻止另一个事务获取同一个gap的gap lock。共享和排他的gap lock是没有区别的。他们相互不冲突,且功能相同。
gap lock可以被显示禁止。当在READ COMMITTED等级下时,gap lock被禁止用于索引查找,而只用于外检约束检查和重复键检查。
个人补充: next-key lock的效果相当于一个记录锁加一个间隙锁。当next-key lock加在某索引上,则该记录和它前面的区间都被锁定。 假设有记录1, 3, 5, 7,现在记录5上加next-key lock,则会锁定区间(3, 5],任何试图插入到这个区间的记录都会阻塞。
5. 自增锁
最近小编在写程序的时候,遇到这样一个问题:
- 先执行查询1,查出了 id 为 1,2,3 的3条数据;
- 事务1新增了一条语句(数据的id = 4),此时未提交;
- 事务2新增了一条语句(数据的id = 5),并已经提交;
- 执行了查询2,此时查出了 id 为 1,2,3,5 的4条数据;
- 事务1提交;
- 执行了查询3,此时查出了 id 为 1,2,3,4,5 的5条数据;
从这里就有一个问题,为什么第二个查询,查询不到 id = 4 的数据呢?在解决这个问题之前,我们首先要了解,为什么主键要使用自增?
为什么主键通常要使用自增?
要先解决这个问题之前,我们需要先来看看MySQL的 Inoodb 引擎是基于B+树索引结构的,我们来看看下图:

我们可以看到B+树索引的关键字都是有序的,我们来看看插入连续的值跟插入不连续的值,B+树会有什么变化:
插入连续的值:

插入不连续的值:

所以我们在主键上设置自增属性,可以保证每次插入都是插入到最后面,可以有效的减少索引页的分裂和数据的移动。
现在我们回到第一个问题:为什么第二个查询,查询不到 id = 4 的数据呢?这是因为自增锁的原因导致的。何为自增锁呢?
自增锁
自增锁是MySQL一种特殊的锁,如果表中存在自增字段,MySQL便会自动维护一个自增锁。
案例说明
我们来重现第一个问题,首先我们得先做好准备:
数据库软件:MySQL
数据库引擎:InooDB
事务隔离级别:RR
数据表:
1 | CREATE TABLE `test` ( |
准备三条数据:
1 | INSERT INTO `test` VALUES ('1', '小罗'); |
此时我们已经搭建好了我们的测试环境,现在我们准备三个事务进行测试:
查询语句:
1 | /* 查询1 */ |
事务1:
1 | /* 开启事务1 */ |
事务2:
1 | /* 开启事务2 */ |
我们的执行顺序是:查询1 -> 事务2(未提交) -> 事务3(提交) -> 查询2 -> 事务2(提交) -> 查询3,来看看三次查询的结果:

查询的数据是刚开始的数据,接下来我们看看查询2的结果:

在这里,我们可以看到查询2查出了事务2添加的小张数据,id = 5,此时查询不到 id = 4 的数据,我们再来看看查询3的结果:

此时我们可以看到,查询3查出了事务1和事务2添加的数据,这里就出现了一个问题,查询2好像出现了幻读一样,查询不到 id = 4 的数据。这是为什么呢?
这是因为自增锁引起的,我们来看看自增值(AUTO_INCREMENT)的变化:
- 执行之前,AUTO_INCREMENT = 4;
- 事务1执行,插入一条数据 id = AUTO_INCREMENT;
- AUTO_INCREMENT = 4 + 1 = 5;
- 事务1未提交,查询不到 id = 4 的数据;
- 事务2执行,插入一条数据 id = AUTO_INCREMENT;
- AUTO_INCREMENT = 5 + 1 = 6;
- 事务2提交,可以查询 id = 5 的数据;
- 事务1提交,可以查询 id = 4 的数据;
注意
- 如果将三条查询语句放到同一事务中,在RR事务隔离级别下,是不会出现这种情况的,因为RR事务隔离级别,会解决幻读现象。
- 自增锁不仅是只对 INSERT INTO 语句才会出现,还对其它的插入语句生效。
- 关于自增锁,还有更深层次的东西,小编还没有理解透彻,后期会写一篇学习心得,如果同学们有兴趣的,可以查看下面的资料去了解。
转载至:https://zhuanlan.zhihu.com/p/48207652
6. 意向锁(Intent Locks)
意向锁就是说在屋(比如代表一个表)门口设置一个标识,说明屋子里有人(比如代表某些记录)被锁住了。另一个人想知道屋子 里是否有人被锁,不用进屋子里一个一个的去查,直接看门口标识就行了。
当一个表中的某一行被加上排他锁后,该表就不能再被加表锁。数据库程序如何知道该表不能被加表锁?一种方式是逐条的判断该 表的每一条记录是否已经有排他锁,另一种方式是直接在表这一层级检查表本身是否有意向锁,不需要逐条判断。显然后者效率高。
1 | 例12: |
插入意向锁
插入意向锁是在插入一条记录行前,由 INSERT 操作产生的一种间隙锁。该锁用以表示插入意向,当多个事务在同一区间(gap)插入位置不同的多条数据时,事务之间不需要互相等待。假设存在两条值分别为 4 和 7 的记录,两个不同的事务分别试图插入值为 5 和 6 的两条记录,每个事务在获取插入行上独占的(排他)锁前,都会获取(4,7)之间的间隙锁,但是因为数据行之间并不冲突,所以两个事务之间并不会产生冲突(阻塞等待)。
总结来说,插入意向锁的特性可以分成两部分:
- 插入意向锁是一种特殊的间隙锁 ——间隙锁可以锁定开区间内的部分记录。
- 插入意向锁之间互不排斥,所以即使多个事务在同一区间插入多条记录,只要记录本身(主键、唯一索引)不冲突,那么事务之间就不会出现冲突等待。
需要强调的是,虽然插入意向锁中含有意向锁三个字,但是它并不属于意向锁而属于间隙锁,因为意向锁是表锁而插入意向锁是行锁。
7. 计划锁(Schema Locks)
1 | 例14: |
8. 更新锁(Update lock)
1 | 为解决死锁,引入更新锁。 |
9. 何时加锁
如何加锁,何时加锁,加什么锁,你可以通过hint手工强行指定,但大多是数据库系统自动决定的。这就是为什么我们可以不懂锁也可以高高兴兴的写SQL。
1 | 例15: |
10. 锁的粒度
锁的粒度就是指锁的生效范围,就是说是行锁,还是页锁,还是整表锁. 锁的粒度同样既可以由数据库自动管理,也可以通过手工指定hint来管理。
11. 锁与事物隔离级别的优先级
1 | 手工指定的锁优先, |
12. 锁的超时等待
例26
1 | SET LOCK_TIMEOUT 4000 用来设置锁等待时间,单位是毫秒,4000意味着等待 |
T2执行时,会等待T1释放排他锁,等了4秒钟,如果T1还没有释放排他锁,T2就会抛出异常: Lock request time out period exceeded.
13. 各种锁的兼容关系表
1 | | Requested mode | IS | S | U | IX | SIX | X | |
转载至:https://blog.csdn.net/samjustin1/article/details/52210125
1.5 mysql整体架构




转载至:https://bbs.huaweicloud.com/blogs/detail/173476
转载至:https://zhuanlan.zhihu.com/p/359847222
2. Mysql系统表
2.1 存储对象
2.1.1 概述
本章讨论存储的数据库对象,这些对象是根据存储在服务器上供以后执行的 SQL 代码定义的。
存储的对象包括以下对象类型:
- 存储过程:
CREATE PROCEDURE使用该CALL语句创建和调用的对象 。过程没有返回值,但可以修改其参数以供调用者稍后检查。它还可以生成返回给客户端程序的结果集。 - 存储函数:
CREATE FUNCTION使用内置函数创建和使用的对象 。您在表达式中调用它,它在表达式求值期间返回一个值。 - 触发器:用
CREATE TRIGGER它创建的与表相关联的对象。当表发生特定事件(例如插入或更新)时,会激活触发器。 - 事件:
CREATE EVENT根据计划由服务器创建和调用的对象。 - 视图:使用该对象创建的对象在
CREATE VIEW被引用时会产生一个结果集。视图充当虚拟表。
本文档中使用的术语反映了存储的对象层次结构:
- 存储例程包括存储过程和函数。
- 存储的程序包括存储的例程、触发器和事件。
- 存储的对象包括存储的程序和视图。
本章介绍如何使用存储对象。以下部分提供有关与这些对象相关的语句的 SQL 语法以及对象处理的附加信息:
- 为每个对象类型,还有
CREATE,ALTER和DROP语句控制哪些对象存在以及它们是如何定义的。 - 该
CALL语句用于调用存储过程。 - 存储的程序定义包括一个可以使用复合语句、循环、条件和声明变量的主体。
- 检测到存储程序引用的对象的元数据更改,并在下次执行程序时自动重新解析受影响的语句。
2.1.2 存储程序
每个存储的程序都包含一个由 SQL 语句组成的主体。该语句可能是由多个以分号 ( ;) 字符分隔的语句组成的复合语句。
2.1.3 存储例程
MySQL 支持存储例程(过程和函数)。存储例程是一组可以存储在服务器中的 SQL 语句。完成此操作后,客户端无需继续重新发布单个语句,而是可以引用存储的例程。
存储的例程在某些情况下特别有用:
- 当多个客户端应用程序使用不同语言编写或工作在不同平台上,但需要执行相同的数据库操作时。
- 当安全至上时。例如,银行将存储过程和函数用于所有常见操作。这提供了一致且安全的环境,并且例程可以确保正确记录每个操作。在这样的设置中,应用程序和用户将无法直接访问数据库表,而只能执行特定的存储例程。
存储的例程可以提供改进的性能,因为服务器和客户端之间需要发送的信息更少。权衡是这确实增加了数据库服务器的负载,因为更多的工作在服务器端完成,而在客户端(应用程序)端完成的工作更少。如果许多客户端计算机(例如 Web 服务器)仅由一个或几个数据库服务器提供服务,请考虑这一点。
存储例程还使您能够在数据库服务器中拥有函数库。这是现代应用程序语言共享的功能,可以在内部启用此类设计(例如,通过使用类)。即使在数据库使用范围之外,使用这些客户端应用程序语言功能对程序员也是有益的。
2.1.4 触发器
触发器是与表关联的命名数据库对象,并在表发生特定事件时激活。触发器的一些用途是对要插入到表中的值执行检查或对更新中涉及的值执行计算。
触发器定义为在语句插入、更新或删除关联表中的行时激活。这些行操作是触发事件。例如,可以通过 INSERT或LOAD DATA语句插入行,并且为每个插入的行激活一个插入触发器。触发器可以设置为在触发事件之前或之后激活。例如,您可以在插入表的每一行之前或在更新的每一行之后激活触发器。
1 | 重要的 |
2.1.5 事件调度器
在MySQL的事件调度 管理事件,也就是说,按照时间表运行的任务调度和执行。以下讨论涵盖事件调度程序,并分为以下部分:
- 提供 MySQL 事件的介绍和概念概述。
- 讨论了用于创建、更改和删除 MySQL 事件的 SQL 语句。
- 显示了如何获取有关事件的信息以及 MySQL 服务器如何存储这些信息。
- 讨论了处理事件所需的特权以及事件在执行时对特权的影响。
存储例程需要系统数据库中的events数据字典表mysql。该表是在 MySQL 8.0 安装过程中创建的。如果您要从早期版本升级到 MySQL 8.0,请务必执行升级过程以确保您的系统数据库是最新的。
MySQL 事件是根据计划运行的任务。因此,我们有时将它们称为 预定事件。创建事件时,您正在创建一个命名数据库对象,其中包含一个或多个 SQL 语句,这些 SQL 语句将按一个或多个规则间隔执行,在特定日期和时间开始和结束。从概念上讲,这类似于 Unix crontab (也称为“ cron 作业”)或 Windows 任务计划程序的想法。
这种类型的计划任务有时也被称为 “时间触发器”,这意味着这些是随着时间的推移而触发的对象。虽然这基本上是正确的,但我们更喜欢使用术语 事件以避免与讨论的类型的触发器混淆。更具体地说,事件不应与“临时触发器”混淆. 触发器是一个数据库对象,其语句是响应于给定表上发生的特定类型的事件而执行的,而(计划的)事件是一个对象,其语句是响应于指定时间间隔的经过而执行的。
MySQL 事件具有以下主要特性和属性:
在 MySQL 中,事件由其名称和分配给它的模式唯一标识。
事件根据计划执行特定操作。此操作由 SQL 语句组成,
BEGIN ... END如果需要,它可以是块中的复合语句 。事件的时间可以是一次性的 或重复的. 一次性事件仅执行一次。重复事件以固定间隔重复其操作,并且可以为重复事件安排特定的开始日期和时间、结束日期和时间,或者两者都没有。(默认情况下,重复事件的计划在创建后立即开始,并无限期地持续下去,直到它被禁用或删除。)如果重复事件未在其调度间隔内终止,则结果可能是该事件的多个实例同时执行。如果这是不可取的,您应该建立一种机制来防止同时发生实例。例如,您可以使用
GET_LOCK()函数、行或表锁定。用户可以使用用于这些目的的 SQL 语句来创建、修改和删除计划事件。语法无效的事件创建和修改语句失败并显示相应的错误消息。用户可以在事件的操作中包含语句,这些语句需要用户实际上并不拥有的权限。事件创建或修改语句成功,但事件的操作失败。
可以使用 SQL 语句设置或修改事件的许多属性。这些属性包括事件的名称、时间、持久性(即,是否在其计划到期后保留)、状态(启用或禁用)、要执行的操作以及分配给它的模式。
事件的默认定义者是创建事件的用户,除非事件已被更改,在这种情况下,定义者是发布
ALTER EVENT影响该事件的最后一条语句的用户 。任何EVENT对定义事件的数据库具有特权的用户都可以修改 事件。事件的操作语句可能包括存储例程中允许的大多数 SQL 语句。
事件由一个特殊的事件调度线程执行;当我们提到事件调度器时,我们实际上是指这个线程。运行时,具有PROCESS特权的用户可以在 的输出中看到事件调度程序线程及其当前状态SHOW PROCESSLIST,
2.1.6 视图
MySQL 支持视图,包括可更新的视图。视图是存储查询,调用时会生成结果集。视图充当虚拟表。
视图定义中可引用的最大表数为 61。
视图处理未优化:
- 无法在视图上创建索引。
- 索引可用于使用合并算法处理的视图。但是,使用临时表算法处理的视图无法利用其基础表上的索引(尽管可以在生成临时表期间使用索引)。
2.1.7 存储对象访问控制
存储的程序(过程、函数、触发器和事件)和视图在使用前定义,并在被引用时在确定其权限的安全上下文中执行。适用于执行存储对象的权限由其DEFINER属性和 SQL SECURITY特征控制。
2.1.8 存储程序二进制日志
二进制日志包含有关修改数据库内容的 SQL 语句的信息。此信息以描述修改的“事件”的形式存储。(二进制日志事件不同于预定的事件存储对象。)二进制日志有两个重要目的:
- 对于复制,二进制日志在源复制服务器上用作要发送到副本服务器的语句的记录。源将其二进制日志中包含的事件发送到其副本,副本执行这些事件以进行与源相同的数据更改。
- 某些数据恢复操作需要使用二进制日志。备份文件恢复后,会重新执行备份后记录的二进制日志中的事件。这些事件使数据库从备份点开始更新。
但是,如果日志记录发生在语句级别,则存在与存储程序(存储过程和函数、触发器和事件)相关的某些二进制日志记录问题:
- 在某些情况下,一条语句可能会影响源和副本上的不同行集。
- 在副本上执行的复制语句由副本的应用程序线程处理。除非您实现复制权限检查,这在 MySQL 8.0.18 中可用,应用程序线程具有完全权限。在这种情况下,过程可能会在源服务器和副本服务器上遵循不同的执行路径,因此用户可以编写包含仅在副本上执行的危险语句的例程。
- 如果修改数据的存储程序是不确定的,则它是不可重复的。这可能导致源和副本上的数据不同,或导致恢复的数据与原始数据不同。
通常,当二进制日志记录发生在 SQL 语句级别(基于语句的二进制日志记录)时,会导致此处描述的问题。如果您使用基于行的二进制日志,日志包含由于执行 SQL 语句而对单个行所做的更改。当例程或触发器执行时,会记录行更改,而不是进行更改的语句。对于存储过程,这意味着CALL不会记录该 语句。对于存储的函数,会记录函数内所做的行更改,而不是函数调用。对于触发器,会记录触发器所做的行更改。在副本方面,只能看到行更改,而不是存储的程序调用。
混合格式二进制日志 ( binlog_format=MIXED) 使用基于语句的二进制日志,除非只有基于行的二进制日志才能保证产生正确的结果。对于混合格式,当存储函数、存储过程、触发器、事件或准备好的语句包含任何对基于语句的二进制日志记录不安全的内容时,整个语句将被标记为不安全并以行格式记录。用于创建和删除过程、函数、触发器和事件的语句始终是安全的,并以语句格式记录。
2.2. performance_schema
2.2.1. 概述
MySQL Performance Schema 是用于在低级别监视 MySQL 服务器执行的功能。Performance Schema 具有以下特征:
Performance Schema 提供了一种在运行时检查服务器内部执行的方法。它是使用
PERFORMANCE_SCHEMA存储引擎和performance_schema数据库实现的。Performance Schema 主要关注性能数据。这不同于INFORMATION_SCHEMA用于检查元数据的 。Performance Schema 监视服务器事件。一个 “事件”是什么,该服务器确实需要时间和被装备,使时序信息可以收集。通常,事件可以是函数调用、等待操作系统、SQL 语句执行阶段(例如解析或排序),或者整个语句或语句组。事件收集提供对服务器和多个存储引擎的同步调用(例如互斥锁)文件和表 I/O、表锁等信息的访问。
Performance Schema 事件不同于写入服务器二进制日志的事件(描述数据修改)和事件调度程序事件(这是一种存储程序)。
Performance Schema 事件特定于 MySQL 服务器的给定实例。Performance Schema 表被认为是服务器本地的,对它们的更改不会复制或写入二进制日志。
提供当前事件以及事件历史和摘要。这使您能够确定执行了多少次检测活动以及它们花费了多少时间。事件信息可用于显示特定线程的活动,或与特定对象(例如互斥锁或文件)相关联的活动。
该
PERFORMANCE_SCHEMA存储引擎使用收集事件数据“检测点”在服务器的源代码。收集到的事件存储在
performance_schema数据库的表中 。可以SELECT像其他表一样使用语句来查询这些表。Performance Schema 配置可以
performance_schema通过 SQL 语句更新数据库中的表来动态修改。配置更改会立即影响数据收集。Performance Schema 中的表是不使用持久磁盘存储的内存表。内容在服务器启动时重新填充,并在服务器关闭时丢弃。
监控在 MySQL 支持的所有平台上可用。
数据收集是通过修改服务器源代码添加检测来实现的。与其他功能(如复制或事件调度程序)不同,没有与 Performance Schema 关联的单独线程。
Performance Schema 旨在提供对有关服务器执行的有用信息的访问,同时对服务器性能的影响最小。实现遵循以下设计目标:
激活性能表不会导致服务器行为发生变化。例如,它不会导致线程调度发生变化,也不会导致查询执行计划(如 所示
EXPLAIN)发生变化。服务器监控以很少的开销持续且不显眼地发生。激活性能表不会使服务器无法使用。
解析器不变。没有新的关键字或语句。
即使性能模式在内部失败,服务器代码的执行也会正常进行。
当在最初的事件收集期间或稍后的事件检索期间执行处理之间进行选择时,优先考虑使收集更快。这是因为收集是持续进行的,而检索是按需进行的,并且可能永远不会发生。
大多数 Performance Schema 表都有索引,这使优化器可以访问除全表扫描之外的执行计划。有关更多信息,请参阅 第 8.2.4 节,“优化性能模式查询”。
添加新的检测点很容易。
仪表是版本化的。如果检测实现发生变化,先前检测的代码将继续工作。这有利于第三方插件的开发人员,因为无需升级每个插件即可与最新的 Performance Schema 更改保持同步。
2.2.2. 快速入门
默认情况下启用性能表。要显式启用或禁用它,请使用performance_schema设置为适当值的变量启动服务器 。例如,在服务器my.cnf文件中使用这些行:
1 | [mysqld] |
当服务器启动时,它会看到 performance_schema并尝试初始化性能表。要验证成功初始化,请使用以下语句:
1 | mysql> SHOW VARIABLES LIKE 'performance_schema'; |
值ON表示 Performance Schema 已成功初始化并可以使用。值 OFF表示发生了一些错误。检查服务器错误日志以获取有关出错的信息。
如果服务器在 Performance Schema 初始化期间无法分配任何内部缓冲区,Performance Schema 将禁用自身并设置 performance_schema为 OFF,并且服务器在没有检测的情况下运行。
Performance Schema 是作为存储引擎实现的,因此您可以在INFORMATION_SCHEMA.ENGINES表或SHOW ENGINES语句的输出中看到它 :
1 | mysql> SELECT * FROM INFORMATION_SCHEMA.ENGINES |
在PERFORMANCE_SCHEMA存储引擎上的表进行操作performance_schema 数据库。您可以创建performance_schema默认数据库,以便对其表的引用不需要使用数据库名称进行限定:
1 | mysql> USE performance_schema; |
Performance Schema 表存储在 performance_schema数据库中。与任何其他数据库一样,可以通过从INFORMATION_SCHEMA数据库中选择或使用 SHOW语句来获取有关此数据库及其表的结构的信息 。例如,使用以下任一语句查看存在哪些 Performance Schema 表:
1 | mysql> SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES |
随着额外检测的实施,Performance Schema 表的数量会随着时间的推移而增加。
performance_schema数据库 的名称是小写的,其中的表的名称也是小写的。查询应以小写形式指定名称。
要查看单个表的结构,请使用 SHOW CREATE TABLE:
1 | mysql> SHOW CREATE TABLE performance_schema.setup_consumers\G |
表结构也可以通过从诸如 的表中选择 INFORMATION_SCHEMA.COLUMNS或使用诸如 的语句来获得SHOW COLUMNS。
performance_schema数据库中的 表可以根据其中的信息类型进行分组:当前事件、事件历史和摘要、对象实例和设置(配置)信息。以下示例说明了这些表的一些用途。有关每个组中表的详细信息,请参阅 第 27.12 节,“性能模式表描述”。
最初,并非所有仪器和使用者都启用,因此性能模式不会收集所有事件。要打开所有这些并启用事件计时,请执行两条语句(行数可能因 MySQL 版本而异):
1 | mysql> UPDATE performance_schema.setup_instruments |
要查看服务器此刻正在做什么,请检查该 events_waits_current表。它包含每个线程的一行,显示每个线程的最新监控事件:
1 | mysql> SELECT * |
此事件表明线程 0 等待 86,526 微微秒以获取THR_LOCK::mutex对mysys子系统中的互斥 锁的锁定 。前几列提供以下信息:
- ID 列指示事件来自哪个线程以及事件编号。
EVENT_NAME指示已检测的内容并SOURCE指示哪个源文件包含已检测的代码。- 计时器列显示事件何时开始和停止以及花费了多长时间。如果事件仍在进行中,则
TIMER_END和TIMER_WAIT值为NULL。计时器值是近似值并以微微秒表示。有关计时器和事件时间收集的信息,请参阅 第 27.4.1 节,“性能表事件计时”。
历史表包含与当前事件表相同类型的行,但有更多行并显示服务器“最近”而不是 “当前”所做的事情。”在 events_waits_history和 events_waits_history_long表包含每个线程的最近10个事件和最近10,000个事件,分别。例如,要查看线程 13 生成的最近事件的信息,请执行以下操作:
1 | mysql> SELECT EVENT_ID, EVENT_NAME, TIMER_WAIT |
当新事件添加到历史表时,如果表已满,旧事件将被丢弃。
Performance Schema 使用收集到的事件来更新performance_schema数据库中的表,这些表充当 事件信息的“消费者”。该 setup_consumers表列出了可用的消费者以及已启用的消费者:
1 | mysql> SELECT * FROM performance_schema.setup_consumers; |
更多查询方式可以查考官网:https://dev.mysql.com/doc/refman/8.0/en/performance-schema-quick-start.html
2.2.3. 事件计时
事件是通过添加到服务器源代码中的工具收集的。仪器时间事件,这是性能模式如何提供事件需要多长时间的想法。也可以将仪器配置为不收集计时信息。
性能模式计时器
Performance Schema 计时器的精度和开销大小各不相同。要查看可用的计时器及其特性,请查看下 performance_timers表:
1 | mysql> SELECT * FROM performance_schema.performance_timers; |
如果与给定计时器名称关联的值为 NULL,则您的平台不支持该计时器。
这些列具有以下含义:
- 该
TIMER_NAME列显示可用计时器的名称。CYCLE是指基于CPU(处理器)周期计数器的定时器。 TIMER_FREQUENCY表示每秒的计时器单位数。对于循环定时器,频率一般与 CPU 速度有关。显示的值是在具有 2.4GHz 处理器的系统上获得的。其他计时器基于固定的秒数。TIMER_RESOLUTION指示定时器值一次增加的定时器单元数。如果计时器的分辨率为 10,则其值每次增加 10。TIMER_OVERHEAD是使用给定计时器获得一个定时的最小开销周期数。每个事件的开销是显示值的两倍,因为计时器在事件的开始和结束时被调用。
Performance Schema 按如下方式分配计时器:
- 等待计时器使用
CYCLE. - 空闲、阶段、语句和事务计时器
NANOSECOND在NANOSECOND计时器可用的平台上 使用,MICROSECOND否则。
在服务器启动时,Performance Schema 验证在构建时所做的关于计时器分配的假设是否正确,并在计时器不可用时显示警告。
对等待事件计时,最重要的标准是减少开销,但可能会牺牲定时器的准确性,因此CYCLE最好使用定时器。
执行语句(或阶段)所需的时间通常比执行单个等待所需的时间大几个数量级。对于时间语句,最重要的标准是有一个准确的度量,它不受处理器频率变化的影响,因此最好使用不基于周期的计时器。语句的默认计时器是NANOSECOND。额外的 “开销”与 CYCLEtimer 并不重要,因为与用于执行语句本身的 CPU 时间相比,两次调用计时器(一次在语句开始时,一次在语句结束时)造成的开销要少几个数量级。使用CYCLE计时器在这里没有任何好处,只有缺点。
cycle计数器提供的精度取决于处理器速度。如果处理器以 1 GHz(10 亿个周期/秒)或更高的频率运行,则周期计数器可提供亚纳秒级精度。使用周期计数器比获取一天中的实际时间便宜得多。例如,标准gettimeofday()函数可能需要数百个周期,这对于每秒可能发生数千或数百万次的数据收集来说是不可接受的开销。
循环计数器也有缺点:
- 最终用户希望看到以挂钟为单位的计时,例如几分之一秒。从周期转换为几分之一秒可能很昂贵。因此,转换是一种快速且相当粗略的乘法运算。
- 处理器周期率可能会发生变化,例如当笔记本电脑进入省电模式或 CPU 速度减慢以减少热量产生时。如果处理器的周期率波动,从周期到实时单位的转换就会出错。
- 循环计数器可能不可靠或不可用,具体取决于处理器或操作系统。例如,在 Pentium 上,该指令是
RDTSC(一种汇编语言而不是 C 指令),理论上操作系统可以阻止用户模式程序使用它。 - 一些与乱序执行或多处理器同步相关的处理器细节可能会导致计数器看起来快或慢,最多可达 1000 个周期。
MySQL 与 x386(Windows、macOS、Linux、Solaris 和其他 Unix 版本)、PowerPC 和 IA-64 上的循环计数器一起使用。
2.2.4. 事件过滤
事件以生产者/消费者的方式处理:
- 检测代码是事件的来源并产生要收集的事件。该
setup_instruments表列出了可以收集事件的仪器,它们是否已启用,以及(对于已启用的仪器)是否收集计时信息:
1 | mysql> SELECT NAME, ENABLED, TIMED |
Performance Schema 表是事件和消费事件的目的地。下 setup_consumers表列出了可以向其发送事件信息的消费者类型以及它们是否已启用:
1 | mysql> SELECT * FROM performance_schema.setup_consumers; |
过滤可以在性能监控的不同阶段完成:
预过滤。 这是通过修改 Performance Schema 配置来完成的,以便仅从生产者收集某些类型的事件,并且收集的事件仅更新某些消费者。为此,请启用或禁用仪器或消费者。预过滤由 Performance Schema 完成,并具有适用于所有用户的全局效果。
使用预过滤的原因:
- 以减少开销。即使启用了所有工具,Performance Schema 开销也应该是最小的,但也许您想进一步减少它。或者您不关心时序事件并希望禁用时序代码以消除时序开销。
- 避免用您不感兴趣的事件填充当前事件或历史表。预过滤在这些表中为启用的工具类型的行的实例留下了更多的“空间”。如果您仅启用带有预过滤的文件工具,则不会为非文件工具收集任何行。通过后过滤,收集非文件事件,为文件事件留下更少的行。
- 避免维护某些类型的事件表。如果禁用消费者,则服务器不会花时间维护该消费者的目的地。例如,如果您不关心事件历史记录,则可以禁用历史记录表使用者以提高性能。
后过滤。 这涉及
WHERE在查询中使用从 Performance Schema 表中选择信息的子句,以指定您想要查看哪些可用事件。后过滤是在每个用户的基础上执行的,因为单个用户选择感兴趣的可用事件。使用后过滤的原因:
- 避免为个人用户做出有关哪些事件信息感兴趣的决定。
- 当预先不知道使用预过滤施加的限制时,使用性能模式来调查性能问题。
当您更改监控配置时,Performance Schema 不会刷新历史表。已经收集的事件保留在当前事件和历史表中,直到被更新的事件取代。如果禁用仪器,则可能需要等待一段时间,然后它们的事件会被较新的感兴趣的事件取代。或者,用于 TRUNCATE TABLE清空历史表。
进行检测更改后,您可能希望截断汇总表。通常,效果是将汇总列重置为 0 或NULL,而不是删除行。这使您能够清除收集的值并重新启动聚合。例如,在您进行运行时配置更改之后,这可能很有用。此截断行为的例外情况在各个汇总表部分中注明。
2.2.5. 表描述
大多数 Performance Schema 表都有索引,这使优化器可以访问除全表扫描之外的执行计划。这些索引还提高了相关对象的性能,例如sys使用这些表的表视图。有关更多信息,请参阅 第 8.2.4 节,“优化性能模式查询”。
仪器名称空间具有树状结构。乐器名称的元素从左到右提供了从更一般到更具体的进展。名称具有的元素数量取决于乐器的类型。
performance_schema数据库中的 表可以分组如下:
设置表。这些表用于配置和显示监控特性。
当前事件表。该
events_waits_current表包含每个线程的最新事件。其他类似的表包含事件层次结构不同级别的当前事件:events_stages_current阶段事件、events_statements_current语句事件和events_transactions_current事务事件。历史表。这些表与当前事件表具有相同的结构,但包含更多行。例如,对于等待事件,
events_waits_history表包含每个线程最近的 10 个事件。events_waits_history_long包含最近的 10,000 个事件。其他类似的表存在于阶段、语句和事务历史中。要更改历史表的大小,请在服务器启动时设置适当的系统变量。例如,要设置等待事件历史记录表的大小,请设置
performance_schema_events_waits_history_size和performance_schema_events_waits_history_long_size。汇总表。这些表包含在事件组上聚合的信息,包括那些已从历史表中丢弃的信息。
实例表。这些表记录了检测的对象类型。被检测的对象在被服务器使用时会产生一个事件。这些表提供事件名称和解释性说明或状态信息。
各种表。这些不属于任何其他表组。
表 27.1 性能模式表
2.2.6. 设置表
设置表提供有关当前检测的信息并允许更改监控配置。因此,如果您有UPDATE 权限,可以更改这些表中的某些列。
使用表而不是单个变量来获取设置信息在修改 Performance Schema 配置时提供了高度的灵活性。例如,您可以使用具有标准 SQL 语法的单个语句同时进行多个配置更改。
这些设置表可用:
setup_actors: 如何初始化对新前台线程的监控setup_consumers:可以发送和存储事件信息的目的地setup_instruments:可以为其收集事件的检测对象的类setup_objects: 应该监控哪些对象setup_threads: 检测线程名称和属性
该setup_actors表包含确定是否为新的前台服务器线程(与客户端连接关联的线程)启用监视和历史事件日志记录的信息。默认情况下,此表的最大大小为 100 行。要更改表大小,请performance_schema_setup_actors_size 在服务器启动时修改 系统变量。
setup_consumers表列出了可以存储和启用事件信息的消费者类型。
setup_instruments表列出了可以为其收集事件的检测对象的类。
setup_threads表列出了检测的线程类。它公开线程类名称和属性。
setup_objects记录了检测的对象类型。它们提供事件名称和解释性说明或状态信息。
cond_instances表列出了服务器执行时性能模式看到的所有条件。条件是代码中使用的一种同步机制,用于发出特定事件已发生的信号,以便等待此条件的线程可以恢复工作,当一个线程正在等待某事发生时,条件名称表明该线程正在等待什么,但没有直接的方法来判断哪个或哪些其他线程导致该条件发生。
file_instances表列出了在执行文件 I/O 检测时性能模式看到的所有文件。如果磁盘上的文件从未被打开过,它不会显示在 file_instances. 当文件从磁盘中删除时,它也会从file_instances表中删除 。
mutex_instances表列出了服务器执行时性能表看到的所有互斥锁。互斥体是代码中使用的一种同步机制,用于强制在给定时间只有一个线程可以访问某些公共资源。据说该资源受到互斥锁的“保护”。
rwlock_instances表列出了服务器执行时性能模式看到的所有rwlock(读写锁)实例。Anrwlock是代码中使用的一种同步机制,用于强制线程在给定时间可以按照某些规则访问某些公共资源。该资源被说成是 “保护”由rwlock. 访问是共享的(多个线程可以同时拥有读锁)、排他的(在给定时间只有一个线程可以拥有写锁)或共享排他的(一个线程可以拥有写锁同时允许不一致被其他线程读取)。共享独占访问也称为 sxlock优化并发性并提高读写工作负载的可扩展性。
socket_instances表提供到 MySQL 服务器的活动连接的实时快照。该表包含每个 TCP/IP 或 Unix 套接字文件连接的一行。此表中可用的信息提供了与服务器的活动连接的实时快照。
2.2.7. 表分类
1. 等待事件表
Performance Schema 工具等待,这是需要时间的事件。在事件层次结构中,等待事件嵌套在阶段事件中,阶段事件嵌套在语句事件中,语句事件嵌套在事务事件中。
这些表存储等待事件:
events_waits_current:每个线程的当前等待事件。events_waits_history:每个线程结束的最近等待事件。events_waits_history_long:最近全局结束的等待事件(跨所有线程)。
events_waits_current表包含当前等待事件。该表为每个线程存储一行,显示线程最近监视的等待事件的当前状态,因此没有用于配置表大小的系统变量。
在包含等待事件行的表中, events_waits_current是最基本的。其他包含等待事件行的表是从当前事件逻辑派生的。例如, 表events_waits_history和 events_waits_history_long表是最近结束的等待事件的集合,分别达到每个线程的最大行数和所有线程的全局行数。
events_waits_history表包含*N*个线程结束的最新等待事件。等待事件在结束之前不会添加到表中。当表包含给定线程的最大行数时,当为该线程添加新行时,最旧的线程行将被丢弃。当一个线程结束时,它的所有行都被丢弃。
events_waits_history_long 表包含*N*所有线程中全局结束的最新等待事件。等待事件在结束之前不会添加到表中。当表变满时,添加新行时将丢弃最旧的行,而不管哪个线程生成了任一行。
2. 事件表
Performance Schema 检测阶段,它们是语句执行过程中的步骤,例如解析语句、打开表或执行 filesort操作。阶段对应SHOW PROCESSLIST于INFORMATION_SCHEMA.PROCESSLIST 表中显示的或可见 的线程状态。当状态值改变时阶段开始和结束。
在事件层次结构中,等待事件嵌套在阶段事件中,阶段事件嵌套在语句事件中,语句事件嵌套在事务事件中。
这些表存储阶段事件:
events_stages_current:每个线程的当前阶段事件。events_stages_history:每个线程结束的最新阶段事件。events_stages_history_long:已全局结束的最近阶段事件(跨所有线程)。
Performance Schema 阶段事件表包含两列,它们合起来为每一行提供一个阶段进度指示器:
WORK_COMPLETED:阶段完成的工作单元数WORK_ESTIMATED:阶段预期的工作单元数
events_stages_current表包含当前阶段事件。该表为每个线程存储一行,显示线程最近监控的阶段事件的当前状态,因此没有用于配置表大小的系统变量。
events_stages_history表包含*N*个线程结束的最新阶段事件。舞台事件在结束之前不会添加到表中。当表包含给定线程的最大行数时,当为该线程添加新行时,最旧的线程行将被丢弃。当一个线程结束时,它的所有行都被丢弃。
events_stages_history_long 表包含*N*所有线程中全局结束的最新阶段事件。舞台事件在结束之前不会添加到表中。当表变满时,添加新行时将丢弃最旧的行,而不管哪个线程生成了任一行。
3. 语句事件表
Performance Schema 检测语句执行。语句事件发生在事件层次结构的高层。在事件层次结构中,等待事件嵌套在阶段事件中,阶段事件嵌套在语句事件中,语句事件嵌套在事务事件中。
这些表存储语句事件:
events_statements_current: 每个线程的当前语句事件。events_statements_history:每个线程结束的最新语句事件。events_statements_history_long:最近全局结束的语句事件(跨所有线程)。prepared_statements_instances: 准备好的语句实例和统计信息
events_statements_current 表包含当前语句事件。该表为每个线程存储一行,显示该线程最近监控的语句事件的当前状态,因此没有用于配置表大小的系统变量。
events_statements_history 表包含*N*个线程结束的最新语句事件。语句事件在结束之前不会添加到表中。当表包含给定线程的最大行数时,当为该线程添加新行时,最旧的线程行将被丢弃。当一个线程结束时,它的所有行都被丢弃。
events_statements_history_long 表包含*N*所有线程中全局结束的最新语句事件。语句事件在结束之前不会添加到表中。当表变满时,添加新行时将丢弃最旧的行,而不管哪个线程生成了任一行。
4. 报表
表中提供了有关准备好的语句的信息 prepared_statements_instances 。此表可以检查服务器中使用的准备好的语句并提供有关它们的汇总统计信息。要控制此表的大小,请performance_schema_max_prepared_statements_instances 在服务器启动时设置 系统变量。
5. 事务表
Performance Schema 检测事务。在事件层次结构中,等待事件嵌套在阶段事件中,阶段事件嵌套在语句事件中,语句事件嵌套在事务事件中。
这些表存储交易事件:
events_transactions_current:每个线程的当前事务事件。events_transactions_history:每个线程结束的最近事务事件。events_transactions_history_long:最近在全局(跨所有线程)结束的事务事件。
events_transactions_current 表包含当前交易事件。
6. 连接表
当客户端连接到 MySQL 服务器时,它使用特定的用户名并从特定的主机连接。Performance Schema 提供有关这些连接的统计信息,使用这些表按帐户(用户和主机组合)以及按用户名和主机名分别跟踪它们:
连接表 中“帐户”的含义类似于它在mysql系统数据库中的 MySQL 授权表中的含义 ,从这个意义上说,该术语指的是用户和主机值的组合。它们的不同之处在于,对于授权表,帐户的主机部分可以是模式,而对于性能模式表,主机值始终是特定的非模式主机名。
该accounts表包含已连接到 MySQL 服务器的每个帐户的行。对于每个帐户,该表计算当前的连接数和总连接数。表大小在服务器启动时自动调整。要显式设置表大小,请performance_schema_accounts_size 在服务器启动时设置 系统变量。要禁用帐户统计,请将此变量设置为 0。
该hosts表包含客户端连接到 MySQL 服务器的每个主机的行。对于每个主机名,该表计算当前连接数和总连接数。表大小在服务器启动时自动调整。要显式设置表大小,请performance_schema_hosts_size 在服务器启动时设置 系统变量。要禁用主机统计信息,请将此变量设置为 0。
该users表包含连接到 MySQL 服务器的每个用户的行。对于每个用户名,该表计算当前连接数和总连接数。表大小在服务器启动时自动调整。要显式设置表大小,请performance_schema_users_size 在服务器启动时设置 系统变量。要禁用用户统计,请将此变量设置为 0。
7. 连接属性表
连接属性是应用程序可以在连接时传递给服务器的键值对。对于基于libmysqlclient客户端库实现的 C API 的应用程序 , mysql_options()和 mysql_options4()函数定义了连接属性集。其他 MySQL 连接器可能提供自己的属性定义方法。
这些 Performance Schema 表公开了属性信息:
session_account_connect_attrs:当前会话的连接属性,以及与会话帐户关联的其他会话session_connect_attrs:所有会话的连接属性
session_connect_attrs表包含所有会话的连接属性。要仅查看当前会话以及与会话帐户关联的其他会话的连接属性,请使用该 session_account_connect_attrs 表。
8. 用户定义的变量表
Performance Schema 提供了一个user_variables_by_thread公开用户定义变量的 表。这些是在特定会话中定义的变量,包括@ 名称前的字符;请参阅 第 9.4 节,“用户定义的变量”。
9. 复制表
Performance Schema 提供了公开复制信息的表。这类似于SHOW REPLICA | SLAVE STATUS语句中可用的信息,但表格形式的表示更易于访问并且具有可用性优势:
SHOW REPLICA | SLAVE STATUS输出对于目视检查很有用,但对于程序使用则没有那么多。相比之下,使用 Performance Schema 表,可以使用一般SELECT查询(包括复杂WHERE条件、联接等)搜索有关副本状态的信息 。- 查询结果可以保存在表中以供进一步分析,或分配给变量,从而在存储过程中使用。
- 复制表提供了更好的诊断信息。对于多线程副本操作,
SHOW REPLICA | SLAVE STATUS使用Last_SQL_Errno和Last_SQL_Error字段报告所有协调器和工作线程错误 ,因此只有最近的这些错误是可见的,并且信息可能会丢失。复制表在每个线程的基础上存储错误而不会丢失信息。 - 最后看到的事务在每个工作人员的复制表中可见。这是无法从 获得的信息
SHOW REPLICA | SLAVE STATUS。 - 熟悉 Performance Schema 接口的开发人员可以通过向表中添加行来扩展复制表以提供附加信息。
复制表说明
Performance Schema 提供以下与复制相关的表:
- 包含有关副本与源的连接信息的表:
replication_connection_configuration: 连接源的配置参数replication_connection_status:与源的连接的当前状态replication_asynchronous_connection_failover:异步连接故障转移机制的源列表
- 包含有关事务应用程序的一般(非线程特定)信息的表:
replication_applier_configuration:副本上事务应用程序的配置参数。replication_applier_status:副本上事务应用程序的当前状态。
- 包含有关负责应用从源接收的事务的特定线程的信息的表:
replication_applier_status_by_coordinator:协调器线程的状态(除非副本是多线程的,否则为空)。replication_applier_status_by_worker:如果副本是多线程的,则应用程序线程或工作线程的状态。
- 包含有关基于通道的复制过滤器信息的表:
replication_applier_filters:提供有关在特定复制通道上配置的复制过滤器的信息。replication_applier_global_filters:提供有关适用于所有复制通道的全局复制过滤器的信息。
- 包含有关组复制成员信息的表:
replication_group_members:为群组成员提供网络和状态信息。replication_group_member_stats:提供有关组成员及其参与的交易的统计信息。
10. NDB 集群表
表 27.3 性能模式导航台表
| 表名 | 描述 | 介绍 |
|---|---|---|
ndb_sync_excluded_objects |
无法同步的 NDB 对象 | 8.0.21 |
ndb_sync_pending_objects |
等待同步的 NDB 对象 | 8.0.21 |
从 NDB 8.0.21 开始,在这两个 Performance Schema 表中充当 NDB Cluster 中的 SQL 节点的 MySQL 服务器公开了有关自动同步当前状态的更多详细信息:
ndb_sync_pending_objects:显示有关NDB在NDB字典和 MySQL 数据字典之间检测到不匹配的数据库对象的信息。尝试同步此类对象时,NDB从等待同步的队列和该表中删除该对象,并尝试协调不匹配。如果对象的同步由于临时错误而失败,则在下次NDB执行不匹配检测时将其拾取并添加回队列(以及该表);如果尝试因永久性错误而失败,则将对象添加到ndb_sync_excluded_objects表中。ndb_sync_excluded_objects:显示NDB由于不匹配导致的永久错误导致自动同步失败的数据库对象的信息,如果没有人工干预就无法协调;这些对象被列入阻止名单,并且在完成此操作之前不会再次考虑进行不匹配检测。
在ndb_sync_pending_objects和 ndb_sync_excluded_objects表存在,只有在MySQL有支持启用 NDBCLUSTER存储引擎。
11. 锁表
Performance Schema 通过这些表公开锁信息:
data_locks: 数据锁持有和请求data_lock_waits: 数据锁拥有者和被这些拥有者阻止的数据锁定请求者之间的关系metadata_locks:持有和请求的元数据锁table_handles: 持有和请求的表锁
data_lock_waits表实现了多对多关系,显示data_locks表中的哪些数据锁请求被表中的哪些数据锁阻塞 data_locks。只有当它们阻止某些锁定请求时,data_locks才会出现 持有的锁定 data_lock_waits。
MySQL 使用元数据锁定来管理对数据库对象的并发访问并确保数据一致性;请参阅 第 8.11.4 节,“元数据锁定”。元数据锁定不仅适用于表,还适用于模式、存储程序(过程、函数、触发器、计划事件)、表空间、使用GET_LOCK()函数获取的用户锁 (请参阅 第 12.15 节“锁定函数”)以及使用第 5.6.9.1 节“锁定服务”中描述 的锁定服务。
Performance Schema 通过metadata_locks表公开元数据锁定信息:
- 已授予的锁(显示哪些会话拥有哪些当前元数据锁)。
- 已请求但尚未授予的锁定(显示哪些会话正在等待哪些元数据锁定)。
- 已被死锁检测器终止的锁定请求。
- 已超时并正在等待请求会话的锁定请求被丢弃的锁定请求。
12. 系统变量表
MySQL 服务器维护了许多系统变量,表明它是如何配置的(参见 第 5.1.8 节,“服务器系统变量”)。这些性能表表中提供了系统变量信息:
global_variables: 全局系统变量。只需要全局值的应用程序应该使用此表。session_variables: 当前会话的系统变量。想要为其自己的会话使用所有系统变量值的应用程序应该使用此表。它包括其会话的会话变量,以及没有会话对应的全局变量的值。variables_by_thread:每个活动会话的会话系统变量。想要了解特定会话的会话变量值的应用程序应使用此表。它仅包含会话变量,由线程 ID 标识。persisted_variables:为mysqld-auto.cnf存储持久性全局系统变量设置的文件提供 SQL 接口。请参阅 第 27.12.14.1 节,“性能模式 persisted_variables 表”。variables_info:显示每个系统变量的最近设置来源及其值范围。请参阅 第 27.12.14.2 节,“性能模式 variables_info 表”。
该persisted_variables表为mysqld-auto.cnf存储持久性全局系统变量设置的文件提供了一个 SQL 接口 ,允许在运行时使用SELECT语句检查文件内容 。变量使用SET PERSISTorPERSIST_ONLY 语句持久化 ;请参阅第 13.7.6.1 节,“变量赋值的 SET 语法”。该表包含文件中每个持久系统变量的行。未持久化的变量不会出现在表中。
13. 状态变量表
MySQL 服务器维护许多状态变量,提供有关其操作的信息(请参阅 第 5.1.10 节,“服务器状态变量”)。这些 Performance Schema 表中提供了状态变量信息:
global_status: 全局状态变量。只需要全局值的应用程序应该使用此表。session_status:当前会话的状态变量。想要为自己的会话使用所有状态变量值的应用程序应该使用此表。它包括其会话的会话变量,以及没有会话对应的全局变量的值。status_by_thread:每个活动会话的会话状态变量。想要了解特定会话的会话变量值的应用程序应使用此表。它仅包含会话变量,由线程 ID 标识。
还有汇总表提供按帐户、主机名和用户名聚合的状态变量信息。
14. 线程池表
以下部分描述了与线程池插件关联的 Performance Schema 表(请参阅 第 5.6.3 节,“MySQL 企业线程池”)。它们提供有关线程池操作的信息:
tp_thread_group_state:有关线程池线程组状态的信息。tp_thread_group_stats: 线程组统计。tp_thread_state:有关线程池线程状态的信息。
这些表中的行代表时间快照。在 的情况下 tp_thread_state,线程组的所有行都包含时间快照。因此,MySQL 服务器在生成快照时持有线程组的互斥锁。但是它不会同时在所有线程组上持有互斥锁,以防止一条语句 tp_thread_state阻止整个 MySQL 服务器。
Performance Schema 线程池表由线程池插件实现,并在加载和卸载该插件时加载和卸载(请参阅 第 5.6.3.2 节,“线程池安装”)。这些表不需要特殊的配置步骤。但是,这些表取决于启用的线程池插件。如果线程池插件已加载但被禁用,则不会创建表。
15. 防火墙表
以下部分描述了与 MySQL Enterprise Firewall 关联的 Performance Schema 表(请参阅第 6.4.7 节,“MySQL 企业防火墙”)。它们提供有关防火墙操作的信息:
firewall_groups:有关防火墙组配置文件的信息。firewall_group_allowlist:已注册防火墙组配置文件的允许列表规则。firewall_membership:注册防火墙组配置文件的成员(帐户)。
firewall_groups表提供了 MySQL Enterprise Firewall 内存数据缓存的视图。它列出了注册防火墙组配置文件的名称和操作模式。它与mysql.firewall_groups提供防火墙数据持久存储的系统表结合使用 ;
16. 密钥环表
以下部分描述了与 MySQL 密钥环关联的性能模式表(请参阅 第 6.4.4 节,“MySQL 密钥环”)。它们提供有关密钥环操作的信息:
keyring_component_status:有关正在使用的密钥环组件的信息。keyring_keys:MySQL 密钥环中密钥的元数据。
该keyring_component_status 表(从 MySQL 8.0.24 开始可用)提供有关正在使用的密钥环组件的属性的状态信息(如果已安装)。如果未安装密钥环组件,则该表为空(例如,如果未使用密钥环,或配置为使用密钥环插件而不是密钥环组件管理密钥库)。
没有固定的属性集。每个密钥环组件都可以自由定义自己的集合。
17. 克隆表
以下部分描述了与克隆插件关联的性能模式表(请参阅 第 5.6.7 节,“克隆插件”)。这些表提供了有关克隆操作的信息。
clone_status:有关当前或上次执行的克隆操作的状态信息。clone_progress:有关当前或上次执行的克隆操作的进度信息。
Performance Schema 克隆表由克隆插件实现,并在加载和卸载该插件时加载和卸载(请参阅第 5.6.7.1 节,“安装克隆插件”)。这些表不需要特殊的配置步骤。但是,这些表取决于启用的克隆插件。如果克隆插件已加载但被禁用,则不会创建表。
Performance Schema 克隆插件表仅用于接收方 MySQL 服务器实例。数据在服务器关闭和重新启动时保持不变。
18. 汇总表
汇总表提供了一段时间内终止事件的汇总信息。该组中的表以不同的方式汇总事件数据。
每个汇总表都有用于确定如何对要聚合的数据进行分组的分组列,以及包含聚合值的汇总列。以类似方式汇总事件的表通常具有相似的汇总列集,仅在用于确定事件聚合方式的分组列上有所不同。
汇总表可以用 截断 TRUNCATE TABLE。通常,效果是将汇总列重置为 0 或 NULL,而不是删除行。这使您能够清除收集的值并重新启动聚合。例如,在您进行运行时配置更改之后,这可能很有用。此截断行为的例外情况在各个汇总表部分中注明。
19. 阶段汇总表
Performance Schema 维护用于收集当前和最近阶段事件的表,并在汇总表中聚合该信息。 第 27.12.5 节,“性能表阶段事件表”描述了阶段摘要所基于的事件。有关舞台事件内容、当前和历史舞台事件表以及如何控制舞台事件收集(默认情况下禁用)的信息,请参阅该讨论。
20. 语句汇总表
Performance Schema 维护用于收集当前和最近语句事件的表,并在汇总表中聚合该信息。 第 27.12.6 节,“性能表语句事件表” 描述了语句摘要所基于的事件。有关语句事件的内容、当前和历史语句事件表以及如何控制语句事件收集(默认情况下部分禁用)的信息,请参阅该讨论。
21. 语句直方图汇总表
Performance Schema 维护语句事件摘要表,其中包含有关最小、最大和平均语句延迟的信息(请参阅 第 27.12.20.3 节,“语句摘要表”)。这些表允许对系统性能进行高级别的评估。为了允许在更细粒度的级别进行评估,Performance Schema 还收集语句延迟的直方图数据。这些直方图提供了对延迟分布的额外洞察。
222. 交易汇总表
Performance Schema 维护用于收集当前和最近事务事件的表,并在汇总表中聚合该信息。 第 27.12.7 节,“性能表事务表” 描述了事务摘要所基于的事件。有关事务事件的内容、当前和历史事务事件表以及如何控制事务事件收集(默认情况下禁用)的信息,请参阅该讨论。
23. 对象等待汇总表
Performance Schema 维护objects_summary_global_by_type 用于聚合对象等待事件的表。
24. 文件 I/O 汇总表
Performance Schema 维护文件 I/O 汇总表,这些表汇总了有关 I/O 操作的信息。
25. I/O 和锁等待汇总表
以下部分描述了表 I/O 和锁等待摘要表:
table_io_waits_summary_by_index_usage: 每个索引的表 I/O 等待table_io_waits_summary_by_table: 每个表的表 I/O 等待table_lock_waits_summary_by_table: 每个表的表锁等待
26. 套接字汇总表
这些套接字摘要表汇总了套接字操作的计时器和字节计数信息:
socket_summary_by_event_name:wait/io/socket/*针对每个套接字工具,工具为所有套接字 I/O 操作生成的聚合计时器和字节计数统计信息 。socket_summary_by_instance:wait/io/socket/*针对每个套接字实例,仪器为所有套接字 I/O 操作生成的聚合计时器和字节计数统计信息 。当连接终止时,socket_summary_by_instance与其对应的行 被删除。
idle当套接字等待来自客户端的下一个请求时, 套接字摘要表不会聚合由事件生成的 等待。对于idle 事件聚合,使用等待事件汇总表;
27. 内存汇总表
Performance Schema 检测内存使用情况并汇总内存使用情况统计信息,详细信息如下:
- 使用的内存类型(各种缓存、内部缓冲区等)
- 线程、账户、用户、主机间接执行内存操作
Performance Schema 检测内存使用的以下方面
- 使用的内存大小
- 操作次数
- 低水位和高水位
内存大小有助于了解或调整服务器的内存消耗。
操作计数有助于了解或调整服务器对内存分配器施加的总体压力,这会对性能产生影响。将单个字节分配一百万次与一次分配一百万个字节不同;跟踪大小和计数可以揭示差异。
低水位线和高水位线对于检测工作负载峰值、整体工作负载稳定性和可能的内存泄漏至关重要。
内存摘要表不包含时序信息,因为内存事件不计时。
28. 错误汇总表
性能模式使状态变量信息在第 27.12.15 节,“性能模式状态变量表”中描述的表中可用 。它还使汇总表中的聚合状态变量信息可用,如下所述。每个状态变量汇总表都有一个或多个分组列来指示该表如何聚合状态值:
status_by_account具有USER、HOST和VARIABLE_NAME列以按帐户汇总状态变量。status_by_host有HOST和VARIABLE_NAME列按客户端连接的主机汇总状态变量。status_by_user有USER和VARIABLE_NAME列按客户端用户名汇总状态变量。
每个状态变量汇总表都有包含聚合值的汇总列:
VARIABLE_VALUE活动和终止会话的聚合状态变量值。
29. 杂项表
以下部分描述了不属于前面部分中讨论的表类别的表:
error_log:写入错误日志的最新事件。host_cache:来自内部主机缓存的信息。log_status:有关用于备份目的的服务器日志的信息。performance_timers:哪些事件计时器可用。threads:有关服务器线程的信息。tls_channel_status:连接接口的 TLS 上下文属性。user_defined_functions:由组件、插件或CREATE FUNCTION语句注册的可加载函数 。
2.2.8. 命令行和变量
可以参考官网:https://dev.mysql.com/doc/refman/8.0/en/performance-schema-option-variable-reference.html
2.2.9. 内存分配模型
Performance Schema 使用此内存分配模型:
- 可以在服务器启动时分配内存
- 可能会在服务器运行期间分配额外的内存
- 在服务器运行期间永远不要释放内存(尽管它可能会被回收)
- 释放关闭时使用的所有内存
结果是放宽了内存限制,以便可以以较少的配置使用 Performance Schema,并减少内存占用,以便消耗随服务器负载而扩展。使用的内存取决于实际看到的负载,而不是估计或明确配置的负载。
2.2.10. 使用性能模式诊断问题
Performance Schema 是一种帮助 DBA 进行性能调优的工具,它通过进行实际测量而不是“胡乱猜测”。”本节演示了为此目的使用 Performance Schema 的一些方法。这里的讨论依赖于事件过滤的使用,这在 第 27.4.2 节,“性能模式事件过滤”中进行了描述。
以下示例提供了一种可用于分析可重复问题的方法,例如调查性能瓶颈。首先,您应该有一个可重复的用例,其中性能被认为“太慢”并且需要优化,并且您应该启用所有检测(根本没有预过滤)。
运行用例。
使用 Performance Schema 表,分析性能问题的根本原因。这种分析在很大程度上依赖于后过滤。
对于排除的问题区域,禁用相应的仪器。例如,如果分析表明问题与特定存储引擎中的文件 I/O 无关,请禁用该引擎的文件 I/O 工具。然后截断历史和汇总表以删除以前收集的事件。
重复步骤 1 的过程。
在每次迭代中,Performance Schema 输出,尤其是
events_waits_history_long表,包含越来越少的由无关紧要的工具引起的“噪音”,并且鉴于该表具有固定大小,包含越来越多与手头问题分析相关的数据。随着每次迭代,随着“信噪比”的提高,调查应该越来越接近问题的根本原因 ,从而使分析更容易。
一旦确定了性能瓶颈的根本原因,就采取适当的纠正措施,例如:
- 调整服务器参数(缓存大小、内存等)。
- 通过以不同方式编写查询来调整查询,
- 调整数据库模式(表、索引等)。
- 调整代码(这仅适用于存储引擎或服务器开发人员)。
从第 1 步重新开始,以查看更改对性能的影响。
在mutex_instances.LOCKED_BY_THREAD_ID与 rwlock_instances.WRITE_LOCKED_BY_THREAD_ID 列调查性能瓶颈或死锁非常重要的。Performance Schema 检测使这成为可能,如下所示:
假设线程 1 卡在等待互斥锁中。
您可以确定线程正在等待什么:
1
2SELECT * FROM performance_schema.events_waits_current
WHERE THREAD_ID = thread_1;假设查询结果表明该线程正在等待 mutex A,在
events_waits_current.OBJECT_INSTANCE_BEGIN.您可以确定哪个线程持有互斥锁 A:
1
2SELECT * FROM performance_schema.mutex_instances
WHERE OBJECT_INSTANCE_BEGIN = mutex_A;假设查询结果标识它是线程 2 持有互斥锁 A,如
mutex_instances.LOCKED_BY_THREAD_ID.你可以看到线程 2 在做什么:
1
2SELECT * FROM performance_schema.events_waits_current
WHERE THREAD_ID = thread_2;
2.2.11. 使用性能模式的查询分析
以下示例演示了如何使用 Performance Schema 语句事件和阶段事件来检索与由SHOW PROFILES和SHOW PROFILE语句提供的分析信息相当的数据。
该setup_actors表可用于限制主机、用户或帐户对历史事件的收集,以减少运行时开销和历史表中收集的数据量。该示例的第一步显示了如何将历史事件的收集限制为特定用户。
Performance Schema 以纳秒(万亿分之一秒)为单位显示事件计时器信息,以将计时数据规范化为标准单位。在以下示例中, TIMER_WAIT值除以 1000000000000 以秒为单位显示数据。值也被截断为 6 位小数,以与SHOW PROFILES和 SHOW PROFILE语句相同的格式显示数据。
- 将历史事件的收集限制为运行查询的用户。默认情况下,
setup_actors配置为允许对所有前台线程进行监控和历史事件收集:
1 | mysql> SELECT * FROM performance_schema.setup_actors; |
更新setup_actors表中的默认行, 对所有前台线程禁用历史事件收集和监控,并插入一个新行,为运行查询的用户启用监控和历史事件收集:
1 | mysql> UPDATE performance_schema.setup_actors |
setup_actors表中的 数据现在应类似于以下内容:
1 | mysql> SELECT * FROM performance_schema.setup_actors; |
- 通过更新
setup_instruments表确保启用语句和阶段检测 。默认情况下,某些仪器可能已启用。
1 | mysql> UPDATE performance_schema.setup_instruments |
- 确保
events_statements_*和events_stages_*消费者已启用。默认情况下,某些使用者可能已经启用。
1 | mysql> UPDATE performance_schema.setup_consumers |
- 在您正在监控的用户帐户下,运行您要分析的语句。例如:
1 | mysql> SELECT * FROM employees.employees WHERE emp_no = 10001; |
EVENT_ID通过查询events_statements_history_long表来 识别语句的 。此步骤类似于运行SHOW PROFILES以识别Query_ID. 以下查询产生类似于以下内容的输出SHOW PROFILES:
1 | mysql> SELECT EVENT_ID, TRUNCATE(TIMER_WAIT/1000000000000,6) as Duration, SQL_TEXT |
- 查询
events_stages_history_long表以检索语句的阶段事件。阶段使用事件嵌套链接到语句。每个阶段事件记录都有一个NESTING_EVENT_ID包含EVENT_ID父语句的列。
1 | mysql> SELECT event_name AS Stage, TRUNCATE(TIMER_WAIT/1000000000000,6) AS Duration |
2.2.11. 获取父事件信息
该data_locks表显示了持有和请求的数据锁。该表的行有一 THREAD_ID列指示拥有锁的会话的线程 ID,以及一 EVENT_ID列指示导致锁的性能模式事件。( THREAD_ID, EVENT_ID) 值的元组隐式标识其他 Performance Schema 表中的父事件:
- 表中 的父等待事件
events_waits_*xxx* - 表中 的父阶段事件
events_stages_*xxx* - 表中 的父语句事件
events_statements_*xxx* - 在父事务事件
events_transactions_current表
要获取有关父事件的详细信息,请将 THREAD_ID和EVENT_ID 列与相应父事件表中名称相似的列连接起来。该关系基于嵌套集数据模型,因此连接具有多个子句。给定分别由parent和 表示的父表和子表child,连接如下所示:
1 | WHERE |
加入的条件是:
- 父事件和子事件在同一个线程中。
- 子事件在父事件之后开始,所以它的
EVENT_ID值大于父事件的 值。 - 父事件已完成或仍在运行。
2.2.12. 对性能模式的限制
Performance Schema 避免使用互斥锁来收集或生成数据,因此无法保证一致性,结果有时可能不正确。performance_schema表中的事件值 是不确定且不可重复的。
如果将事件信息保存在另一个表中,则不应假设原始事件稍后仍可用。例如,如果您从一个performance_schema表中选择事件 到一个临时表中,并打算稍后将该表与原始表连接起来,则可能没有匹配项。
2.2.13 补充
在mysql5.5版本之后新增了performance_schema的数据库用于监视数据库性能,该数据库中表的引擎都是performance_schema。PS数据库默认是关闭的,其中的表都是内存表,不存储在磁盘中,在服务器重启后数据消失。在数据文件performance_schema目录下只有表结构文件不存在数据文件,对这些表的改变不会记录到binlog中。数据收集是通过修改服务器源代码来实现的,不存在与PS相关联的单独线程。PS数据库消耗很少的性能,官方文档介绍即使将PS中所有监控项开启也不会对mysql server性能造成太大影响。
2.3 information_schema
2.3.1 概述
INFORMATION_SCHEMA提供对数据库 元数据、有关 MySQL 服务器的信息(例如数据库或表的名称、列的数据类型或访问权限)的访问。有时用于此信息的其他术语是 数据字典和 系统目录。
INFORMATION_SCHEMA是每个 MySQL 实例中的一个数据库,该位置存储有关 MySQL 服务器维护的所有其他数据库的信息。该 INFORMATION_SCHEMA数据库包含几个只读表。它们实际上是视图,而不是基表,因此没有与它们关联的文件,并且您不能对它们设置触发器。此外,没有具有该名称的数据库目录。
虽然你可以选择INFORMATION_SCHEMA与一个默认的数据库USE 语句,你只能读取表的内容,不执行 INSERT, UPDATE或 DELETE对他们的操作。
1 | mysql> SELECT table_name, table_type, engine |
说明:该语句请求数据库中所有表的列表db5,仅显示三条信息:表名、表类型和存储引擎。
权限
对于大多数INFORMATION_SCHEMA表,每个 MySQL 用户都有权访问它们,但只能看到表中与用户具有适当访问权限的对象对应的行。在某些情况下(例如,表中的ROUTINE_DEFINITION列 INFORMATION_SCHEMA ROUTINES),权限不足的用户会看到NULL. 有些表有不同的权限要求;对于这些,适用的表格说明中提到了要求。例如,InnoDB表(名称以 开头的表INNODB_)需要PROCESS特权。
相同的权限适用于从 语句中选择信息 INFORMATION_SCHEMA和通过SHOW语句查看相同的信息。无论哪种情况,您都必须对某个对象具有某种权限才能查看有关它的信息。
性能注意事项
INFORMATION_SCHEMA从多个数据库中搜索信息的查询可能需要很长时间并影响性能。要检查查询的效率,您可以使用EXPLAIN. 有关使用EXPLAIN输出调整INFORMATION_SCHEMA查询的信息,请参阅 第 8.2.3 节,“优化 INFORMATION_SCHEMA 查询”。
2.3.2 通用表
以下部分描述了可能被称为 “通用”INFORMATION_SCHEMA 表集的内容。这些是与特定存储引擎、组件或插件无关的表。
2.3.3 InnoDB 表
INFORMATION_SCHEMA InnoDB 表可用于监控正在进行的InnoDB 活动,在低效率变成问题之前检测它们,或对性能和容量问题进行故障排除。随着您的数据库变得越来越大、越来越忙,遇到硬件容量的限制,您可以监控和调整这些方面以保持数据库平稳运行。
| 表名 | 描述 | 介绍 |
INNODB_BUFFER_PAGE |
InnoDB 缓冲池中的页面 | |
INNODB_BUFFER_PAGE_LRU |
InnoDB 缓冲池中页面的 LRU 排序 | |
INNODB_BUFFER_POOL_STATS |
InnoDB 缓冲池统计信息 | |
INNODB_CACHED_INDEXES |
InnoDB 缓冲池中每个索引缓存的索引页数 | |
INNODB_CMP |
与压缩 InnoDB 表相关的操作状态 | |
INNODB_CMP_PER_INDEX |
与压缩 InnoDB 表和索引相关的操作状态 | |
INNODB_CMP_PER_INDEX_RESET |
与压缩 InnoDB 表和索引相关的操作状态 | |
INNODB_CMP_RESET |
与压缩 InnoDB 表相关的操作状态 | |
INNODB_CMPMEM |
InnoDB 缓冲池中压缩页面的状态 | |
INNODB_CMPMEM_RESET |
InnoDB 缓冲池中压缩页面的状态 | |
INNODB_COLUMNS |
每个 InnoDB 表中的列 | |
INNODB_DATAFILES |
InnoDB file-per-table 和通用表空间的数据文件路径信息 | |
INNODB_FIELDS |
InnoDB 索引的关键列 | |
INNODB_FOREIGN |
InnoDB 外键元数据 | |
INNODB_FOREIGN_COLS |
InnoDB 外键列状态信息 | |
INNODB_FT_BEING_DELETED |
INNODB_FT_DELETED 表的快照 | |
INNODB_FT_CONFIG |
InnoDB 表 FULLTEXT 索引和相关处理的元数据 | |
INNODB_FT_DEFAULT_STOPWORD |
InnoDB FULLTEXT 索引的默认停用词列表 | |
INNODB_FT_DELETED |
从 InnoDB 表 FULLTEXT 索引中删除的行 | |
INNODB_FT_INDEX_CACHE |
InnoDB FULLTEXT 索引中新插入行的令牌信息 | |
INNODB_FT_INDEX_TABLE |
针对 InnoDB 表 FULLTEXT 索引处理文本搜索的倒排索引信息 | |
INNODB_INDEXES |
InnoDB 索引元数据 | |
INNODB_METRICS |
InnoDB 性能信息 | |
INNODB_SESSION_TEMP_TABLESPACES |
会话临时表空间元数据 | 8.0.13 |
INNODB_TABLES |
InnoDB 表元数据 | |
INNODB_TABLESPACES |
InnoDB file-per-table、通用和撤消表空间元数据 | |
INNODB_TABLESPACES_BRIEF |
每个表的简要文件、通用、撤消和系统表空间元数据 | |
INNODB_TABLESTATS |
InnoDB 表低级状态信息 | |
INNODB_TEMP_TABLE_INFO |
有关活动用户创建的 InnoDB 临时表的信息 | |
INNODB_TRX |
活动 InnoDB 事务信息 | |
INNODB_VIRTUAL |
InnoDB 虚拟生成列元数据 |
2.3.4 线程池表
INFORMATION_SCHEMA 线程池表
| 表名 | 描述 |
|---|---|
TP_THREAD_GROUP_STATE |
线程池线程组状态 |
TP_THREAD_GROUP_STATS |
线程池线程组统计 |
TP_THREAD_STATE |
线程池线程信息 |
这些表中的行代表时间快照。在TP_THREAD_STATE的情况下 ,线程组的所有行都包含时间快照。因此,MySQL 服务器在生成快照时持有线程组的互斥锁。但是它不会同时在所有线程组上持有互斥锁,以防止一条语句TP_THREAD_STATE阻止整个 MySQL 服务器。
该INFORMATION_SCHEMA线程池表由各个插件和决策执行是否加载一个可以由独立于其他的(见 第5.6.3.2,“线程池安装”)。但是,所有表的内容取决于启用的线程池插件。如果启用了表插件但未启用线程池插件,则表变为可见且可以访问但为空。
2.3.5 连接控制表
INFORMATION_SCHEMA 连接控制表
| 表名 | 描述 |
|---|---|
CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS |
每个帐户的当前连续失败连接尝试次数 |
2.3.6 防火墙表参考
INFORMATION_SCHEMA 防火墙表
| 表名 | 描述 | 已弃用 |
|---|---|---|
MYSQL_FIREWALL_USERS |
帐户配置文件的防火墙内存数据 | 8.0.26 |
MYSQL_FIREWALL_WHITELIST |
帐户配置文件许可名单的防火墙内存数据 | 8.0.26 |
2.3.7 SHOW语句的扩展
SHOW语句的 一些扩展伴随着的实现 INFORMATION_SCHEMA:
INFORMATION_SCHEMA是一个信息数据库,因此它的名称包含在 SHOW DATABASES. 同样, SHOW TABLES可以使用 with INFORMATION_SCHEMA来获取其表的列表:
1 | mysql> SHOW TABLES FROM INFORMATION_SCHEMA; |
SHOW COLUMNS并且 DESCRIBE可以显示有关各个INFORMATION_SCHEMA表中列的信息 。
SHOW接受LIKE限制显示行的子句的语句 也允许WHERE指定所选行必须满足的更一般条件的子句:
1 | SHOW CHARACTER SET |
此语句显示多字节字符集:
1 | mysql> SHOW CHARACTER SET WHERE Maxlen > 1; |
2.4 sys_schema
2.4.1 概述
MySQL 8.0 包含 sys模式,这是一组帮助 DBA 和开发人员解释性能模式收集的数据的对象。sys模式对象可用于典型的调优和诊断用例。此表中的对象包括:
- 将 Performance Schema 数据汇总为更易于理解的形式的视图。
- 执行性能模式配置和生成诊断报告等操作的存储过程。
- 查询性能表配置并提供格式化服务的存储函数。
在使用sys表之前,必须满足本节中描述的先决条件。
2.4.2. 先决条件
由于sys表提供了访问性能表的替代方法,因此必须启用性能表才能使 sys表工作。请参阅 第 27.3 节,“性能表启动配置”。
要完全访问sys表,用户必须具有以下权限:
SELECT在所有sys表和视图上EXECUTE在所有sys存储过程和函数上INSERT而UPDATE对于sys_config表中,如果改变是它使- 某些
sys模式存储过程和函数的附加权限 ,如其描述中所述(例如,ps_setup_save()过程)
还需要对sys表对象下的对象具有特权:
SELECT在sys模式对象访问的任何性能模式表上,以及UPDATE使用sys模式对象 更新的任何表PROCESS为INFORMATION_SCHEMAINNODB_BUFFER_PAGE桌子
某些 Performance Schema 工具和使用者必须启用并(对于工具)计时以充分利用 sys模式功能:
- 所有
wait仪器 - 所有
stage仪器 - 所有
statement仪器 *xxx*_current和*xxx*_history_long所有事件的消费者
您可以使用sys表本身来启用所有其他工具和使用者:
1 | CALL sys.ps_setup_enable_instrument('wait'); |
2.4.3. 使用方式
1 | mysql> USE sys; |
该sys表包含许多以各种方式汇总性能表表的视图。大多数这些视图都是成对出现的,这样一对中的一个成员与另一个成员具有相同的名称,并加上一个x$前缀。例如,该host_summary_by_file_io 视图汇总了按主机分组的文件 I/O,并显示了从皮秒转换为更易读的值(带单位)的延迟;
1 | mysql> SELECT * FROM sys.host_summary_by_file_io; |
该x$host_summary_by_file_io视图汇总了相同的数据,但显示未格式化的皮秒延迟:
1 | mysql> SELECT * FROM sys.x$host_summary_by_file_io; |
没有x$前缀的视图旨在提供对用户更友好且更易于人类阅读的输出。带有x$以原始形式显示相同值的前缀的视图更适合与其他对数据执行自己处理的工具一起使用。
要检查sys表对象定义,请使用适当的 SHOW语句或 INFORMATION_SCHEMA查询。例如,要检查session视图和 format_bytes()函数的定义 ,请使用以下语句:
1 | mysql> SHOW CREATE VIEW sys.session; |
2.4.4. 进度报告
以下sys表视图为长时间运行的事务提供进度报告:
1 | processlist |
假设启用了所需的工具和使用者progress,这些视图的列显示了支持进度报告的阶段完成的工作百分比。
阶段进度报告需要 events_stages_current启用消费者,以及需要进度信息的工具。这些阶段的工具目前支持进度报告:
1 | stage/sql/Copying to tmp table |
对于不支持估计和已完成工作报告的阶段,或者如果所需的工具或使用者未启用,则该progress列是 NULL.
2.4.5. 表和触发器
| 表或触发器名称 | 描述 |
|---|---|
sys_config |
sys 表配置选项表 |
sys_config_insert_set_user |
sys_config 插入触发器 |
sys_config_update_set_user |
sys_config 更新触发器 |
2.4.6 视图
该sys表包含许多以各种方式汇总性能表的视图。大多数这些视图都是成对出现的,这样一对中的一个成员与另一个成员具有相同的名称,并加上一个x$ 前缀。
2.4.7 存储过程
2.4.7 存储函数
| 函数名称 | 描述 | 已弃用 |
|---|---|---|
extract_schema_from_file_name() |
提取文件名的模式名部分 | |
extract_table_from_file_name() |
提取文件名的表名部分 | |
format_bytes() |
将字节数转换为带单位的值 | 8.0.16 |
format_path() |
用符号系统变量名替换路径名中的目录 | |
format_statement() |
将长语句截断为固定长度 | |
format_time() |
将皮秒时间转换为单位值 | 8.0.16 |
list_add() |
将项目添加到列表 | |
list_drop() |
从列表中删除项目 | |
ps_is_account_enabled() |
是否启用了帐户的 Performance Schema 检测 | |
ps_is_consumer_enabled() |
是否启用 Performance Schema 使用者 | |
ps_is_instrument_default_enabled() |
Performance Schema 工具是否默认启用 | |
ps_is_instrument_default_timed() |
Performance Schema 工具是否默认计时 | |
ps_is_thread_instrumented() |
是否启用了连接 ID 的性能模式检测 | |
ps_thread_account() |
与 Performance Schema 线程 ID 关联的帐户 | |
ps_thread_id() |
与连接 ID 关联的性能表线程 ID | 8.0.16 |
ps_thread_stack() |
连接 ID 的事件信息 | |
ps_thread_trx_info() |
线程 ID 的事务信息 | |
quote_identifier() |
引号字符串作为标识符 | |
sys_get_config() |
sys 表配置选项值 | |
version_major() |
MySQL服务器主要版本号 | |
version_minor() |
MySQL 服务器次要版本号 | |
version_patch() |
MySQL服务器补丁发布版本号 |
3. Mysql程序
3.1. 概述
MySQL 安装中有许多不同的程序。
MySQL 服务器mysqld是执行 MySQL 安装中大部分工作的主程序。服务器附带了几个相关脚本,可帮助您启动和停止服务器:
-
SQL 守护进程(即 MySQL 服务器)。 使用客户端程序, mysqld 必须正在运行,因为客户端通过连接到服务器来访问数据库。
-
服务器启动脚本。mysqld_safe 尝试启动mysqld。
-
服务器启动脚本。此脚本用于使用 System V 样式运行目录的系统,该目录包含为特定运行级别启动系统服务的脚本。它调用 mysqld_safe来启动 MySQL 服务器。
-
可以启动或停止系统上安装的多个服务器的服务器启动脚本。
几个程序在 MySQL 安装或升级期间执行设置操作:
-
该程序在 MySQL 构建/安装过程中使用。它从错误源文件编译错误消息文件。
-
该程序使您能够提高 MySQL 安装的安全性。
-
此程序会创建支持安全连接所需的 SSL 证书和密钥文件以及 RSA 密钥对文件(如果这些文件丢失)。由mysql_ssl_rsa_setup创建的文件 可用于使用 SSL 或 RSA 的安全连接。
-
该程序
mysql使用主机系统zoneinfo 数据库(描述时区的文件集)的内容在数据库中加载时区表 。 -
在 MySQL 8.0.16 之前,此程序在 MySQL 升级操作后使用。它使用较新版本的 MySQL 中所做的任何更改更新授权表,并检查表是否不兼容并在必要时修复它们。
连接到 MySQL 服务器的 MySQL 客户端程序:
-
用于交互式输入 SQL 语句或以批处理模式从文件执行它们的命令行工具。
-
执行管理操作的客户端,例如创建或删除数据库、重新加载授权表、将表刷新到磁盘以及重新打开日志文件。 mysqladmin还可用于从服务器检索版本、进程和状态信息
-
用于检查、修复、分析和优化表的表维护客户端。
-
将 MySQL 数据库作为 SQL、文本或 XML 转储到文件中的客户端。
-
使用
LOAD DATA. -
将 MySQL 数据库作为 SQL 转储到文件中的客户端。
mysqlsh
MySQL Shell 是 MySQL 服务器的高级客户端和代码编辑器。
-
显示有关数据库、表、列和索引的信息的客户端。
-
旨在模拟 MySQL 服务器的客户端负载并报告每个阶段的时间的客户端。它就像多个客户端正在访问服务器一样工作。
MySQL 管理和实用程序:
-
离线
InnoDB离线文件校验和实用程序。 -
显示有关
MyISAM表中全文索引信息的实用程序 。 -
用于描述、检查、优化和修复
MyISAM表的实用程序 。 -
处理
MyISAM日志文件内容的实用程序 。 -
压缩
MyISAM表以生成更小的只读表的实用程序 -
一个实用程序,它使您能够将身份验证凭据存储在名为 的安全加密登录路径文件中
.mylogin.cnf。 -
用于在一个密钥环组件和另一个之间迁移密钥的实用程序。
-
从二进制日志中读取语句的实用程序。二进制日志文件中包含的已执行语句日志可用于帮助从崩溃中恢复。
-
用于读取和汇总慢查询日志内容的实用程序。
MySQL 程序开发实用程序:
-
编译 MySQL 程序时生成所需选项值的 shell 脚本。
-
显示选项文件的选项组中存在哪些选项的实用程序。
其他实用程序:
Oracle Corporation 还提供 MySQL Workbench GUI 工具,该工具用于管理 MySQL 服务器和数据库,创建、执行和评估查询,以及从其他关系数据库管理系统迁移模式和数据以用于 MySQL。
使用 MySQL 客户端/服务器库与服务器通信的 MySQL 客户端程序使用以下环境变量。
| 环境变量 | 意义 |
|---|---|
MYSQL_UNIX_PORT |
默认的 Unix 套接字文件;用于连接到 localhost |
MYSQL_TCP_PORT |
默认端口号;用于 TCP/IP 连接 |
MYSQL_DEBUG |
调试时的调试跟踪选项 |
TMPDIR |
创建临时表和文件的目录 |
3.2. 调用程序语句
要从命令行(即从您的 shell 或命令提示符)调用 MySQL 程序,请输入程序名称,后跟任何选项或其他参数,以指示程序执行您希望它执行的操作。以下命令显示了一些示例程序调用。
1 | mysql --user=root test |
一些选项对于许多程序是通用的。其中最常用的是指定连接参数的 --host(or -h)、 --user(or -u) 和--password(or -p) 选项。它们指示运行 MySQL 服务器的主机,以及您的 MySQL 帐户的用户名和密码。所有 MySQL 客户端程序都理解这些选项;它们使您能够指定要连接到的服务器以及要在该服务器上使用的帐户。其他连接选项是 --port(或-P)指定 TCP/IP 端口号和 --socket(或-S) 指定 Unix 上的 Unix 套接字文件
选项是按顺序处理的,所以如果一个选项被多次指定,最后一次出现的优先。以下命令使mysql连接到运行的服务器localhost:
1 | mysql -h example.com -h localhost |
在 Unix 和类 Unix 系统上读取的选项文件
| 文件名 | 目的 |
|---|---|
/etc/my.cnf |
全局选项 |
/etc/mysql/my.cnf |
全局选项 |
*SYSCONFDIR*/my.cnf |
全局选项 |
$MYSQL_HOME/my.cnf |
服务器特定选项(仅限服务器) |
defaults-extra-file |
指定的文件 --defaults-extra-file,如果有 |
~/.my.cnf |
用户特定的选项 |
~/.mylogin.cnf |
用户特定的登录路径选项(仅限客户端) |
*DATADIR*/mysqld-auto.cnf |
系统变量用SET PERSISTor 持久化 SE PERSIST_ONLY(仅限服务器) |
这是一个典型的全局选项文件:
1 | [client] |
这是一个典型的用户选项文件:
1 | [client] |
–defaults-file=file_name
只读给定的选项文件。如果文件不存在或无法访问,则会发生错误。 *file_name*如果作为相对路径名而不是完整路径名给出,则相对于当前目录进行解释。
许多 MySQL 程序具有可以在运行时使用该SET 语句设置的内部变量 。
大多数这些程序变量也可以在服务器启动时使用与指定程序选项相同的语法进行设置。例如,mysql有一个 max_allowed_packet变量来控制其通信缓冲区的最大大小。要将mysql的max_allowed_packet变量 设置 为 16MB 的值,请使用以下任一命令:
1 | mysql --max_allowed_packet=16777216 |
在选项文件中,变量设置不带前导破折号:
1 | [mysql] |
如果您愿意,可以将选项名称中的下划线指定为破折号。以下选项组是等效的。两者都将服务器的密钥缓冲区的大小设置为 512MB:
1 | [mysqld] |
在程序调用时设置变量时可以使用用于指定值乘数的后缀,但不能SET在运行时设置值 。另一方面,使用 SET,您可以使用表达式分配变量的值,当您在服务器启动时设置变量时,这是不正确的。例如,以下第一行在程序调用时是合法的,但第二行不是:
1 | shell> mysql --max_allowed_packet=16M |
3.3. 连接传输协议
对于使用 MySQL 客户端库的程序(例如 mysql和mysqldump),MySQL 支持基于多种传输协议的服务器连接:TCP/IP、Unix 套接字文件、命名管道和共享内存。本节介绍如何选择这些协议,以及它们的相似和不同之处。
传输协议选择
对于给定的连接,如果没有明确指定传输协议,则隐式确定。例如,localhost在 Unix 和类 Unix 系统上产生套接字文件连接的连接,127.0.0.1否则产生 TCP/IP 连接。
--protocol 价值 |
使用的传输协议 | 适用平台 |
|---|---|---|
TCP |
TCP/IP | 全部 |
SOCKET |
Unix套接字文件 | Unix 和类 Unix 系统 |
PIPE |
命名管道 | 视窗 |
MEMORY |
共享内存 | 视窗 |
本地和远程连接的传输支持
TCP/IP 传输支持连接到本地或远程 MySQL 服务器。
套接字文件、命名管道和共享内存传输仅支持连接到本地 MySQL 服务器。(命名管道传输确实允许远程连接,但在 MySQL 中没有实现此功能。)
localhost的解释
如果未明确指定传输协议, localhost则解释如下:
- 在 Unix 和类 Unix 系统上,连接到
localhost套接字文件连接。 - 否则,连接到 会
localhost导致 TCP/IP 连接到127.0.0.1。
如果明确指定了传输协议, localhost则根据该协议进行解释。例如,使用 --protocol=TCP, localhost连接到127.0.0.1所有平台上都会产生 TCP/IP 连接 。
加密和安全特性
TCP/IP 和套接字文件传输受 TLS/SSL 加密的约束,使用加密连接的命令选项中描述的 选项。命名管道和共享内存传输不受 TLS/SSL 加密的约束。
如果连接是通过默认安全的传输协议建立的,则默认情况下是安全的。否则,对于受 TLS/SSL 加密约束的协议,可以使用加密使连接安全:
- 默认情况下,TCP/IP 连接并不安全,但可以加密以使其安全。
- 默认情况下,套接字文件连接是安全的。它们也可以加密,但加密套接字文件连接会使其不再安全并增加 CPU 负载。
- 默认情况下,命名管道连接不安全,并且不进行加密以使其安全。但是,
named_pipe_full_access_group系统变量可用于控制允许哪些 MySQL 用户使用命名管道连接。 - 默认情况下,共享内存连接是安全的。
如果require_secure_transport 启用了系统变量,则服务器只允许使用某种形式的安全传输的连接。根据前面的说明,使用 TLS/SSL、套接字文件或共享内存加密的 TCP/IP 的连接是安全连接。未使用 TLS/SSL 加密的 TCP/IP 连接和命名管道连接不安全。
3.4. 连接压缩控制
与服务器的连接可以对客户端和服务器之间的流量使用压缩,以减少通过连接发送的字节数。默认情况下,连接是未压缩的,但如果服务器和客户端就相互允许的压缩算法达成一致,则可以压缩连接。
压缩连接源自客户端,但会影响客户端和服务器端的 CPU 负载,因为双方都执行压缩和解压缩操作。因为启用压缩会降低性能,所以它的好处主要出现在网络带宽低、网络传输时间在压缩和解压缩操作的成本中占主导地位以及结果集很大时。
3.5. 服务器和程序
1. mysqld-MySQL 服务器
mysqld,也称为 MySQL 服务器,是一个单一的多线程程序,它在 MySQL 安装中完成大部分工作。它不会产生额外的进程。MySQL Server 管理对包含数据库和表的 MySQL 数据目录的访问。数据目录也是其他信息(如日志文件和状态文件)的默认位置。
当 MySQL 服务器启动时,它会侦听来自客户端程序的网络连接并代表这些客户端管理对数据库的访问。
2. mysqld_safe-MySQL 服务器启动脚本
mysqld_safe是在 Unix 上启动mysqld服务器的推荐方法 。 mysqld_safe添加了一些安全功能,例如在发生错误时重新启动服务器并将运行时信息记录到错误日志中。
| 选项名称 | 描述 |
|---|---|
| –basedir | MySQL安装目录的路径 |
| –core-file-size | mysqld 应该能够创建的核心文件的大小 |
| –datadir | 数据目录路径 |
| –defaults-extra-file | 除了通常的选项文件外,还读取命名的选项文件 |
| –defaults-file | 只读命名选项文件 |
| –help | 显示帮助信息并退出 |
| –ledir | 服务器所在目录的路径 |
| –log-error | 将错误日志写入指定文件 |
| –malloc-lib | 用于 mysqld 的替代 malloc 库 |
| –mysqld | 要启动的服务器程序的名称(在 ledir 目录中) |
| –mysqld-safe-log-timestamps | 记录的时间戳格式 |
| –mysqld-version | 服务器程序名称的后缀 |
| –nice | 使用nice程序设置服务器调度优先级 |
| –no-defaults | 不读取选项文件 |
| –open-files-limit | mysqld 应该能够打开的文件数 |
| –pid-file | 服务器进程 ID 文件的路径名 |
| –plugin-dir | 插件安装目录 |
| –port | 侦听 TCP/IP 连接的端口号 |
| –skip-kill-mysqld | 不要试图杀死流浪的 mysqld 进程 |
| –skip-syslog | 不要将错误消息写入系统日志;使用错误日志文件 |
| –socket | 用于侦听 Unix 套接字连接的套接字文件 |
| –syslog | 将错误消息写入系统日志 |
| –syslog-tag | 写入系统日志的消息的标签后缀 |
| –timezone | 将 TZ 时区环境变量设置为命名值 |
| –user | 以具有名称 user_name 或数字用户 ID user_id 的用户身份运行 mysqld |
3. mysql.server-MySQL 服务器启动脚本
Unix 和类 Unix 系统上的 MySQL 发行版包括一个名为mysql.server的脚本,它使用mysqld_safe启动 MySQL 服务器。它可用于 Linux 和 Solaris 等使用 System V 风格的运行目录来启动和停止系统服务的系统。MySQL 的 macOS 启动项也使用它。
要使用mysql.server脚本手动启动或停止服务器,请使用 start或 stop参数从命令行调用它:
1 | mysql.server start |
mysql.server 选项-文件选项
| 选项名称 | 描述 | 类型 |
|---|---|---|
basedir |
MySQL安装目录的路径 | 目录名 |
datadir |
MySQL数据目录的路径 | 目录名 |
pid-file |
服务器应在其中写入其进程 ID 的文件 | 文件名 |
service-startup-timeout |
等待服务器启动需要多长时间 | 整数 |
4. mysqld_multi-管理多个 MySQL 服务器
mysqld_multi旨在管理多个mysqld进程,这些 进程侦听不同 Unix 套接字文件和 TCP/IP 端口上的连接。它可以启动或停止服务器,或报告它们的当前状态。
以下示例显示了如何设置用于mysqld_multi的选项文件。其中顺序mysqld的程序被启动或停止取决于它们出现在选项文件的顺序。组号不需要形成一个完整的序列。 示例中有意省略了第一组和第五组,以说明选项文件中可以有“间隙”。这为您提供了更大的灵活性。 [mysqld*N*]
1 | # This is an example of a my.cnf file for mysqld_multi. |
5. comp_err-编译 MySQL 错误信息文件
comp_err创建mysqlderrmsg.sys使用的 文件, 以确定要针对不同错误代码显示的错误消息。comp_err 通常在 MySQL 构建时自动运行。它从 MySQL 源代码发行版中的文本格式错误信息编译文件: errmsg.sys
6. mysql_secure_installation-提高 MySQL 安装安全性
该程序使您能够通过以下方式提高 MySQL 安装的安全性:
- 您可以为
root帐户设置密码。 - 您可以删除
root可从本地主机外部访问的帐户。 - 您可以删除匿名用户帐户。
- 您可以删除
test数据库(默认情况下,所有用户,甚至匿名用户都可以访问),以及允许任何人访问名称以test_.
| 选项名称 | 描述 | 介绍 |
|---|---|---|
| –defaults-extra-file | 除了通常的选项文件外,还读取命名的选项文件 | |
| –defaults-file | 只读命名选项文件 | |
| –defaults-group-suffix | 选项组后缀值 | |
| –help | 显示帮助信息并退出 | |
| –host | MySQL 服务器所在的主机 | |
| –no-defaults | 不读取选项文件 | |
| –password | 接受但总是被忽略。每当调用 mysql_secure_installation 时,都会提示用户输入密码,无论 | |
| –port | 用于连接的 TCP/IP 端口号 | |
| –print-defaults | 打印默认选项 | |
| –protocol | 要使用的传输协议 | |
| –socket | 要使用的 Unix 套接字文件或 Windows 命名管道 | |
| –ssl-ca | 包含受信任 SSL 证书颁发机构列表的文件 | |
| –ssl-capath | 包含受信任的 SSL 证书颁发机构证书文件的目录 | |
| –ssl-cert | 包含 X.509 证书的文件 | |
| –ssl-cipher | 用于连接加密的允许密码 | |
| –ssl-crl | 包含证书吊销列表的文件 | |
| –ssl-crlpath | 包含证书吊销列表文件的目录 | |
| –ssl-fips-mode | 是否在客户端启用 FIPS 模式 | |
| –ssl-key | 包含 X.509 密钥的文件 | |
| –tls-ciphersuites | 用于加密连接的允许的 TLSv1.3 密码套件 | 8.0.16 |
| –tls-version | 加密连接允许的 TLS 协议 | |
| –use-default | 在没有用户交互的情况下执行 | |
| –user | 连接到服务器时使用的 MySQL 用户名 |
7. mysql_ssl_rsa_setup-创建 SSL/RSA 文件
该程序创建 SSL 证书和密钥文件以及 RSA 密钥对文件,以支持使用 SSL 的安全连接和使用 RSA 通过未加密连接的安全密码交换(如果这些文件丢失)。 如果现有的 SSL 文件已过期,mysql_ssl_rsa_setup也可用于创建新的 SSL 文件。
8. mysql_tzinfo_to_sql-加载时区表
该mysql_tzinfo_to_sql程序加载的时区表mysql的数据库。它用于具有 zoneinfo数据库(描述时区的文件集)的系统。此类系统的示例包括 Linux、FreeBSD、Solaris 和 macOS。这些文件的一个可能位置是/usr/share/zoneinfo 目录(/usr/share/lib/zoneinfo在 Solaris 上)。如果您的系统没有 zoneinfo 数据库,mysql_tzinfo_to_sql可以通过多种方式调用:
1 | mysql_tzinfo_to_sql tz_dir |
9. mysql_upgrade-检查和升级 MySQL 表
每次升级 MySQL 时,您都应该执行 mysql_upgrade,它会查找与升级后的 MySQL 服务器的不兼容性:
- 它升级
mysql表中的系统表, 以便您可以利用可能已添加的新权限或功能。 - 它升级了性能表
INFORMATION_SCHEMA、 和sys表。 - 它检查用户模式。
如果mysql_upgrade发现某个表可能存在不兼容,它会执行表检查,如果发现问题,则尝试修复表。如果表无法修复,请参阅第 2.11.13 节,“重建或修复表或索引”以了解手动表修复策略。
mysql_upgrade直接与 MySQL 服务器通信,向它发送执行升级所需的 SQL 语句。
1 | 警告 |
10. mysql-MySQL 命令行客户端
mysql是一个简单的 SQL shell,具有输入行编辑功能。它支持交互式和非交互式使用。以交互方式使用时,查询结果以 ASCII 表格式显示。当以非交互方式使用时(例如,作为过滤器),结果以制表符分隔的格式显示。可以使用命令选项更改输出格式。
如果由于大型结果集的内存不足而遇到问题,请使用该--quick选项。这会强制mysql 一次从服务器检索一行结果,而不是检索整个结果集并在显示之前将其缓冲在内存中。这是通过使用mysql_use_result()客户端/服务器库中的 C API 函数而不是 mysql_store_result().
| 选项名称 | 描述 | 介绍 | 已弃用 |
|---|---|---|---|
| –auto-rehash | 启用自动重新哈希 | ||
| –auto-vertical-output | 启用自动垂直结果集显示 | ||
| –batch | 不要使用历史文件 | ||
| –binary-as-hex | 以十六进制表示法显示二进制值 | ||
| –binary-mode | 禁用 \r\n - 到 - \n 翻译和将 \0 处理为查询结束 | ||
| –bind-address | 使用指定的网络接口连接到 MySQL 服务器 | ||
| –character-sets-dir | 安装字符集的目录 | ||
| –column-names | 在结果中写入列名 | ||
| –column-type-info | 显示结果集元数据 | ||
| –comments | 是否保留或删除发送到服务器的语句中的注释 | ||
| –compress | 压缩客户端和服务器之间发送的所有信息 | 8.0.18 | |
| –compression-algorithms | 允许连接到服务器的压缩算法 | 8.0.18 | |
| –connect-expired-password | 向服务器表明客户端可以处理过期密码沙箱模式 | ||
| –connect-timeout | 连接超时前的秒数 | ||
| –database | 要使用的数据库 | ||
| –debug | 写调试日志;仅当 MySQL 在构建时支持调试时才支持 | ||
| –debug-check | 程序退出时打印调试信息 | ||
| –debug-info | 程序退出时打印调试信息、内存和CPU统计信息 | ||
| –default-auth | 要使用的身份验证插件 | ||
| –default-character-set | 指定默认字符集 | ||
| –defaults-extra-file | 除了通常的选项文件外,还读取命名的选项文件 | ||
| –defaults-file | 只读命名选项文件 | ||
| –defaults-group-suffix | 选项组后缀值 | ||
| –delimiter | 设置语句分隔符 | ||
| –dns-srv-name | 使用 DNS SRV 查找主机信息 | 8.0.22 | |
| –enable-cleartext-plugin | 启用明文身份验证插件 | ||
| –execute | 执行语句并退出 | ||
| –force | 即使发生 SQL 错误也继续 | ||
| –get-server-public-key | 从服务器请求 RSA 公钥 | ||
| –help | 显示帮助信息并退出 | ||
| –histignore | 指定要忽略日志记录的语句的模式 | ||
| –host | MySQL 服务器所在的主机 | ||
| –html | 生成 HTML 输出 | ||
| –ignore-spaces | 忽略函数名后的空格 | ||
| –init-command | 连接后执行的 SQL 语句 | ||
| –line-numbers | 写下错误的行号 | ||
| –load-data-local-dir | 在 LOAD DATA LOCAL 语句中命名的文件的目录 | 8.0.21 | |
| –local-infile | 启用或禁用 LOAD DATA 的 LOCAL 功能 | ||
| –login-path | 从 .mylogin.cnf 读取登录路径选项 | ||
| –max-allowed-packet | 发送到服务器或从服务器接收的最大数据包长度 | ||
| –max-join-size | 使用 –safe-updates 时连接中行的自动限制 | ||
| –named-commands | 启用命名的 mysql 命令 | ||
| –net-buffer-length | TCP/IP 和套接字通信的缓冲区大小 | ||
| –network-namespace | 指定网络命名空间 | 8.0.22 | |
| –no-auto-rehash | 禁用自动重新哈希 | ||
| –no-beep | 发生错误时不要发出哔哔声 | ||
| –no-defaults | 不读取选项文件 | ||
| –one-database | 忽略除命令行中命名的默认数据库之外的语句 | ||
| –pager | 使用给定的命令进行分页查询输出 | ||
| –password | 连接到服务器时使用的密码 | ||
| –pipe | 使用命名管道连接到服务器(仅限 Windows) | ||
| –plugin-dir | 插件安装目录 | ||
| –port | 用于连接的 TCP/IP 端口号 | ||
| –print-defaults | 打印默认选项 | ||
| –prompt | 将提示设置为指定格式 | ||
| –protocol | 要使用的传输协议 | ||
| –quick | 不缓存每个查询结果 | ||
| –raw | 写入列值而不进行转义转换 | ||
| –reconnect | 如果与服务器的连接丢失,自动尝试重新连接 | ||
| –safe-updates, –i-am-a-dummy | 仅允许指定键值的 UPDATE 和 DELETE 语句 | ||
| –select-limit | 使用 –safe-updates 时 SELECT 语句的自动限制 | ||
| –server-public-key-path | 包含 RSA 公钥的文件的路径名 | ||
| –shared-memory-base-name | 共享内存连接的共享内存名称(仅限 Windows) | ||
| –show-warnings | 如果有任何语句,则在每个语句后显示警告 | ||
| –sigint-ignore | 忽略 SIGINT 信号(通常是输入 Control+C 的结果) | ||
| –silent | 静音模式 | ||
| –skip-auto-rehash | 禁用自动重新哈希 | ||
| –skip-column-names | 不要在结果中写列名 | ||
| –skip-line-numbers | 跳过错误的行号 | ||
| –skip-named-commands | 禁用命名的 mysql 命令 | ||
| –skip-pager | 禁用分页 | ||
| –skip-reconnect | 禁用重新连接 | ||
| –socket | 要使用的 Unix 套接字文件或 Windows 命名管道 | ||
| –ssl-ca | 包含受信任 SSL 证书颁发机构列表的文件 | ||
| –ssl-capath | 包含受信任的 SSL 证书颁发机构证书文件的目录 | ||
| –ssl-cert | 包含 X.509 证书的文件 | ||
| –ssl-cipher | 用于连接加密的允许密码 | ||
| –ssl-crl | 包含证书吊销列表的文件 | ||
| –ssl-crlpath | 包含证书吊销列表文件的目录 | ||
| –ssl-fips-mode | 是否在客户端启用 FIPS 模式 | ||
| –ssl-key | 包含 X.509 密钥的文件 | ||
| –ssl-mode | 与服务器的连接所需的安全状态 | ||
| –syslog | 将交互式语句记录到 syslog | ||
| –table | 以表格格式显示输出 | ||
| –tee | 将输出的副本附加到命名文件 | ||
| –tls-ciphersuites | 用于加密连接的允许的 TLSv1.3 密码套件 | 8.0.16 | |
| –tls-version | 加密连接允许的 TLS 协议 | ||
| –unbuffered | 每次查询后刷新缓冲区 | ||
| –user | 连接到服务器时使用的 MySQL 用户名 | ||
| –verbose | 详细模式 | ||
| –version | 显示版本信息并退出 | ||
| –vertical | 垂直打印查询输出行(每列值一行) | ||
| –wait | 如果无法建立连接,请等待并重试而不是中止 | ||
| –xml | 生成 XML 输出 | ||
| –zstd-compression-level | 连接到使用 zstd 压缩的服务器的压缩级别 | 8.0.18 |
例如,要显式禁用除位于/my/local/data 目录中的文件之外的本地数据加载,请像这样调用mysql:
1 | mysql --local-infile=0 --load-data-local-dir=/my/local/data |
11. 客户端命令
mysql将您发出的每个 SQL 语句发送到要执行的服务器。还有一组mysql自己解释的命令。有关这些命令的列表,请在 提示符处键入help或 : \h``mysql>
1 | mysql> help |
12. 客户端日志记录
在MySQL的客户端可以做这些类型的日志记录,用于交互式执行的语句:
- 在 Unix 上,mysql将语句写入历史文件。默认情况下,此文件
.mysql_history在您的主目录中命名 。要指定不同的文件,请设置MYSQL_HISTFILE环境变量的值 。 - 在所有平台上,如果
--syslog给出了该选项,mysql会将语句写入系统日志记录工具。在 Unix 上,这是syslog; 在 Windows 上,它是 Windows 事件日志。记录消息出现的目的地取决于系统。在 Linux 上,目标通常是/var/log/messages文件。
日志记录是如何发生的
对于每个启用的日志记录目标,语句日志记录如下:
- 仅当以交互方式执行时才记录语句。语句是非交互式的,例如,从文件或管道中读取时。也可以使用
--batchor--execute选项来抑制语句日志记录。 - 如果语句与“ ignore ”列表中的任何模式匹配,则语句将被忽略且不记录。该列表将在后面描述。
- mysql单独记录每个未忽略的非空语句行。
- 如果一个未忽略的语句跨越多行(不包括终止分隔符), mysql连接这些行以形成完整的语句,将换行符映射到空格,并记录结果,加上一个分隔符。
控制历史文件
该.mysql_history文件应受限制访问模式保护,因为敏感信息可能会写入其中,例如包含密码的 SQL 语句文本。请参见第 6.1.2.1 节,“密码安全的最终用户指南”。当使用向上箭头键调用历史记录时,可以从mysql客户端 访问文件中的语句 。请参阅 禁用交互式历史记录。
如果不想维护历史文件,请先将.mysql_history其删除( 如果存在)。然后使用以下任一技术来防止再次创建它:
将
MYSQL_HISTFILE环境变量设置为/dev/null. 要使此设置在您每次登录时生效,请将其放在您的 shell 的启动文件之一中。创建
.mysql_history为符号链接/dev/null;这只需要做一次:1
ln -s /dev/null $HOME/.mysql_history
系统日志记录特性
如果--syslog给出该选项, mysql将交互式语句写入系统日志记录工具。消息记录具有以下特征。
日志记录发生在“信息”级别。这对应于 Unix/Linux 功能和 Windows 事件日志的LOG_INFO优先级 。有关日志记录功能的配置,请参阅系统文档。 syslog``syslog``EVENTLOG_INFORMATION_TYPE
消息大小限制为 1024 字节。
消息由标识符 MysqlClient后跟以下值组成:
SYSTEM_USER操作系统用户名(登录名)或
--用户是否未知。MYSQL_USERMySQL 用户名(使用
--user选项指定 )或--用户是否未知。CONNECTION_ID:客户端连接标识符。这
CONNECTION_ID()与会话中的函数值相同 。DB_SERVER服务器主机或
--主机未知。DB默认数据库或未
--选择任何数据库。QUERY记录语句的文本。
这是在 Linux 上使用 --syslog. 此输出已格式化以提高可读性;每条记录的消息实际上只占一行。
1 | Mar 7 12:39:25 myhost MysqlClient[20824]: |
13. 从文本文件执行 SQL 语句
创建一个*text_file*包含要执行的语句的文本文件 。然后调用 mysql,如下所示:
1 | mysql db_name < text_file |
如果您已经在运行mysql,则可以使用以下source 命令或\.命令执行 SQL 脚本文件:
1 | mysql> source file_name |
有时您可能希望脚本向用户显示进度信息。为此,您可以插入如下语句:
1 | SELECT '<info_to_display>' AS ' '; |
该语句显示输出 <info_to_display>。
14. mysqladmin-MySQL 服务器管理程序
mysqladmin是用于执行管理操作的客户端。您可以使用它来检查服务器的配置和当前状态、创建和删除数据库等。
像这样 调用mysqladmin:
1 | mysqladmin [options] command [command-arg] [command [command-arg]] ... |
1 | 警告 |
15. mysqlcheck-一个表维护程序
所述mysqlcheck的客户端执行表维护:它检查,修理,优化,或分析表。
每个表都被锁定,因此在处理时对其他会话不可用,尽管对于检查操作,该表仅被锁定READ锁定。表维护操作可能很耗时,特别是对于大表。如果使用 --databasesor --all-databases选项处理一个或多个数据库中的所有表,则调用 mysqlcheck可能需要很长时间。(对于 MySQL 升级过程也是如此,如果它确定需要检查表,因为它以相同的方式处理表。)
mysqld服务器运行时必须使用 mysqlcheck,这意味着您不必停止服务器进行表维护。
mysqlcheck以方便用户的方式使用 SQL 语句 CHECK TABLE、 REPAIR TABLE、 ANALYZE TABLE和 OPTIMIZE TABLE。它确定要执行的操作使用哪些语句,然后将这些语句发送到要执行的服务器。
并非所有存储引擎都支持所有四种维护操作。在这种情况下,会显示错误消息。例如,如果test.t是一个 MEMORY表,尝试检查它会产生以下结果:
1 | shell> mysqlcheck test t |
如果mysqlcheck无法修复表,请参阅第 2.11.13 节,“重建或修复表或索引”以了解手动表修复策略。例如,对于InnoDB可以使用 进行检查CHECK TABLE但无法使用 进行修复的表, 就是这种情况 REPAIR TABLE。
1 | 警告 |
16. mysqldump-一个数据库备份程序
所述的mysqldump客户实用程序执行 逻辑备份,产生一组能够被执行以再现原始数据库对象定义和表数据的SQL语句。它转储一个或多个 MySQL 数据库以进行备份或传输到另一台 SQL 服务器。所述的mysqldump 命令也可以生成CSV输出,其他分隔符的文本或XML格式。
1 | 考虑使用MySQL Shell 转储实用程序,它提供多线程并行转储、文件压缩和进度信息显示,以及云功能,例如 Oracle 云基础设施对象存储流和 MySQL 数据库服务兼容性检查和修改。使用MySQL Shell 负载转储实用程序可以轻松地将转储导入 MySQL 服务器实例或 MySQL 数据库服务数据库系统。可以在此处找到 MySQL Shell 的安装说明。 |
17. mysqlimport-一个数据导入程序
该mysqlimport的客户提供了一个命令行界面的LOAD DATASQL语句。mysqlimport 的大多数选项 直接对应于LOAD DATA语法子句 。
18. mysqlpump-一个数据库备份程序
mysqlpump客户实用程序执行 逻辑备份,产生一组能够被执行以再现原始数据库对象定义和表数据的SQL语句。它转储一个或多个 MySQL 数据库以进行备份或传输到另一台 SQL 服务器。
mysqlpump功能包括:
- 并行处理数据库和数据库中的对象,以加快转储过程
- 更好地控制要转储的数据库和数据库对象(表、存储的程序、用户帐户)
- 将用户帐户转储为帐户管理语句 (
CREATE USER,GRANT) 而不是插入mysql系统数据库 - 创建压缩输出的能力
- 进度指标(数值为估计值)
- 对于转储文件重新加载,
InnoDB通过在插入行后添加索引来更快地为表 创建二级索引
mysqlpump至少需要SELECT对转储表、SHOW VIEW转储视图、TRIGGER转储触发器以及未使用LOCK TABLES该 --single-transaction选项的 特权。转储用户定义需要系统数据库的SELECT特权mysql。某些选项可能需要其他权限,如选项说明中所述
19. mysqlshow-显示数据库、表和列信息
该mysqlshow客户可用来快速查看哪些数据库中存在,他们的表或表中的列或索引。
mysqlshow为多个 SQLSHOW语句提供命令行界面。
像这样 调用mysqlshow:
1 | mysqlshow [options] [db_name [tbl_name [col_name]]] |
- 如果没有给出数据库,则显示数据库名称列表。
- 如果没有给出表,则显示数据库中所有匹配的表。
- 如果未指定列,则显示表中所有匹配的列和列类型。
20. mysqlslap-负载模拟客户端
mysqlslap是一个诊断程序,旨在模拟 MySQL 服务器的客户端负载并报告每个阶段的时间。它就像多个客户端正在访问服务器一样工作。
某些选项,例如--create 或--query使您能够指定包含 SQL 语句的字符串或包含语句的文件。如果您指定一个文件,默认情况下它必须每行包含一个语句。(即,隐式语句定界符是换行符。)使用该 --delimiter选项指定不同的定界符,这使您能够指定跨越多行的语句或将多个语句放在一行上。您不能在文件中包含注释; mysqlslap不理解它们。
mysqlslap分三个阶段运行:
- 创建模式、表和可选的任何存储程序或数据以用于测试。此阶段使用单个客户端连接。
- 运行负载测试。此阶段可以使用许多客户端连接。
- 清理(断开连接,如果指定则删除表)。此阶段使用单个客户端连接。
例子:
提供您自己的创建和查询 SQL 语句,其中有 50 个客户端查询和 200 个选择(在一行中输入命令):
1 | mysqlslap --delimiter=";" --create="CREATE TABLE a (b int);INSERT INTO a VALUES (23)" --query="SELECT * FROM a" --concurrency=50 --iterations=200 |
让mysqlslap用两INT列三VARCHAR列的表构建查询SQL语句。使用五个客户端,每个客户端查询 20 次。不要创建表或插入数据(即使用之前测试的表和数据):
1 | mysqlslap --concurrency=5 --iterations=20 |
| 选项名称 | 描述 | 介绍 | 已弃用 |
|---|---|---|---|
| –auto-generate-sql | 未在文件中提供或使用命令选项时自动生成 SQL 语句 | ||
| –auto-generate-sql-add-autoincrement | 将 AUTO_INCREMENT 列添加到自动生成的表 | ||
| –auto-generate-sql-execute-number | 指定要自动生成多少查询 | ||
| –auto-generate-sql-guid-primary | 为自动生成的表添加基于 GUID 的主键 | ||
| –auto-generate-sql-load-type | 指定测试负载类型 | ||
| –auto-generate-sql-secondary-indexes | 指定要添加到自动生成的表中的二级索引的数量 | ||
| –auto-generate-sql-unique-query-number | 为自动测试生成多少个不同的查询 | ||
| –auto-generate-sql-unique-write-number | 为 –auto-generate-sql-write-number 生成多少个不同的查询 | ||
| –auto-generate-sql-write-number | 在每个线程上执行多少行插入 | ||
| –commit | 提交前要执行多少条语句 | ||
| –compress | 压缩客户端和服务器之间发送的所有信息 | 8.0.18 | |
| –compression-algorithms | 允许连接到服务器的压缩算法 | 8.0.18 | |
| –concurrency | 发出 SELECT 语句时要模拟的客户端数 | ||
| –create | 包含用于创建表的语句的文件或字符串 | ||
| –create-schema | 运行测试的数据库名称 | ||
| –csv | 以逗号分隔值格式生成输出 | ||
| –debug | 写调试日志 | ||
| –debug-check | 程序退出时打印调试信息 | ||
| –debug-info | 程序退出时打印调试信息、内存和CPU统计信息 | ||
| –default-auth | 要使用的身份验证插件 | ||
| –defaults-extra-file | 除了通常的选项文件外,还读取命名的选项文件 | ||
| –defaults-file | 只读命名选项文件 | ||
| –defaults-group-suffix | 选项组后缀值 | ||
| –delimiter | 在 SQL 语句中使用的分隔符 | ||
| –detach | 在每个 N 语句之后分离(关闭并重新打开)每个连接 | ||
| –enable-cleartext-plugin | 启用明文身份验证插件 | ||
| –engine | 用于创建表的存储引擎 | ||
| –get-server-public-key | 从服务器请求 RSA 公钥 | ||
| –help | 显示帮助信息并退出 | ||
| –host | MySQL 服务器所在的主机 | ||
| –iterations | 运行测试的次数 | ||
| –login-path | 从 .mylogin.cnf 读取登录路径选项 | ||
| –no-defaults | 不读取选项文件 | ||
| –no-drop | 不要删除在测试运行期间创建的任何表 | ||
| –number-char-cols | 指定 –auto-generate-sql 时使用的 VARCHAR 列数 | ||
| –number-int-cols | 如果指定了 –auto-generate-sql 要使用的 INT 列数 | ||
| –number-of-queries | 将每个客户端限制为大约此数量的查询 | ||
| –only-print | 不要连接到数据库。mysqlslap 只打印它会做的事情 | ||
| –password | 连接到服务器时使用的密码 | ||
| –pipe | 使用命名管道连接到服务器(仅限 Windows) | ||
| –plugin-dir | 插件安装目录 | ||
| –port | 用于连接的 TCP/IP 端口号 | ||
| –post-query | 包含测试完成后要执行的语句的文件或字符串 | ||
| –post-system | 测试完成后使用 system() 执行的字符串 | ||
| –pre-query | 包含在运行测试之前要执行的语句的文件或字符串 | ||
| –pre-system | 在运行测试之前使用 system() 执行的字符串 | ||
| –print-defaults | 打印默认选项 | ||
| –protocol | 要使用的传输协议 | ||
| –query | 包含用于检索数据的 SELECT 语句的文件或字符串 | ||
| –server-public-key-path | 包含 RSA 公钥的文件的路径名 | ||
| –shared-memory-base-name | 共享内存连接的共享内存名称(仅限 Windows) | ||
| –silent | 静音模式 | ||
| –socket | 要使用的 Unix 套接字文件或 Windows 命名管道 | ||
| –sql-mode | 为客户端会话设置 SQL 模式 | ||
| –ssl-ca | 包含受信任 SSL 证书颁发机构列表的文件 | ||
| –ssl-capath | 包含受信任的 SSL 证书颁发机构证书文件的目录 | ||
| –ssl-cert | 包含 X.509 证书的文件 | ||
| –ssl-cipher | 用于连接加密的允许密码 | ||
| –ssl-crl | 包含证书吊销列表的文件 | ||
| –ssl-crlpath | 包含证书吊销列表文件的目录 | ||
| –ssl-fips-mode | 是否在客户端启用 FIPS 模式 | ||
| –ssl-key | 包含 X.509 密钥的文件 | ||
| –ssl-mode | 与服务器的连接所需的安全状态 | ||
| –tls-ciphersuites | 用于加密连接的允许的 TLSv1.3 密码套件 | 8.0.16 | |
| –tls-version | 加密连接允许的 TLS 协议 | ||
| –user | 连接到服务器时使用的 MySQL 用户名 | ||
| –verbose | 详细模式 | ||
| –version | 显示版本信息并退出 | ||
| –zstd-compression-level | 连接到使用 zstd 压缩的服务器的压缩级别 | 8.0.18 |
例子:测试我自己库里的数据库名为user_info的表名为
1 | mysqlslap --delimiter=";" --query="use user_info;SELECT * FROM interface_data;" --concurrency=50 --iterations=200 -u root -pxxx |
21. ibd2sdi-InnoDB 表空间 SDI 提取实用程序
ibd2sdi是一种用于从表空间文件中提取序列化字典信息(SDI) 的实用程序 InnoDB。SDI 数据持久存在于所有InnoDB表空间文件中。
ibd2sdi可以在 file-per-table 表 空间文件 (*.ibdfiles)、 通用表空间文件 (*.ibdfiles)、 系统表空间 文件 (ibdata*files) 和数据字典表空间 (filesmysql.ibd) 上运行。不支持与临时表空间或撤消表空间一起使用。
ibd2sdi可以在运行时或服务器离线时使用。在 与SDI相关的DDL 操作、 ROLLBACK操作、undo日志清除操作中, ibd2sdi可能会出现短暂的时间间隔 无法读取存储在表空间中的SDI数据。
ibd2sdi从指定的表空间执行未提交的 SDI 读取。不访问重做日志和撤消日志。
22. innochecksum-离线 InnoDB 文件校验和实用程序
innochecksum打印InnoDB文件的校验和 。该工具读取 InnoDB表空间文件,计算每个页面的校验和,将计算出的校验和与存储的校验和进行比较,并报告不匹配,这表明页面已损坏。它最初是为了在断电后加快验证表空间文件的完整性而开发的,但也可以在文件复制后使用。由于校验和不匹配会导致 InnoDB故意关闭正在运行的服务器,因此最好使用此工具而不是等待生产中的服务器遇到损坏的页面。
innochecksum不能用于服务器已经打开的表空间文件。对于此类文件,您应该使用CHECK TABLE检查表空间中的表。尝试在服务器已打开的表空间上运行 innochecksum 会导致无法锁定文件错误。
如果发现校验和不匹配,则从备份恢复表空间或启动服务器并尝试使用 mysqldump对表空间中的表进行备份。
23. myisam_ftdump-显示全文索引信息
myisam_ftdump显示有关 表中FULLTEXT MyISAM索引的信息。它直接读取MyISAM索引文件,因此必须在表所在的服务器主机上运行。在使用myisam_ftdump之前,FLUSH TABLES如果服务器正在运行,请务必先发出声明。
myisam_ftdump扫描并转储整个索引,速度不是特别快。另一方面,词的分布很少变化,因此不需要经常运行。
24. myisamchk-MyISAM 表维护实用程序
该myisamchk的程序获取有关数据库表或检查,修理,或将其提供更优的信息。 myisamchk的作品与 MyISAM表(有表 .MYD和.MYI文件存储数据和索引)。
您还可以使用CHECK TABLE andREPAIR TABLE语句来检查和修复MyISAM表。
不支持对分区表 使用myisamchk。
1 | 警告 |
myisamchk支持以下选项用于除表检查和修复之外的操作:
--analyze,-a分析键值的分布。这通过使连接优化器能够更好地选择连接表的顺序以及它应该使用的索引来提高连接性能。要获取有关密钥分发的信息,请使用myisamchk –description –verbose*
tbl_name* 命令或语句。--block-search=*offset*,-b *offset*查找给定偏移量的块所属的记录。
--description,-d打印一些关于表的描述性信息。指定
--verbose选项一次或两次会产生附加信息。--set-auto-increment[=*value*],-A[*value*]强制
AUTO_INCREMENT新记录编号从给定值开始(或更高,如果存在具有AUTO_INCREMENT如此大值的现有记录)。如果*value*未指定,则AUTO_INCREMENT新记录的数字以表中当前的最大值加一开始。--sort-index,-S按高低顺序对索引树块进行排序。这优化了查找并使使用索引的表扫描更快。
--sort-records=*N*,-R *N*根据特定索引对记录进行排序。这使你的数据更局部化并且可以加快基于范围的
SELECT,并ORDER BY使用该索引操作。(第一次使用此选项对表进行排序时,可能会很慢。)要确定表的索引号,请使用SHOW INDEX,它以myisamchk看到的相同顺序显示表的索引 。索引从 1 开始编号。如果键没有打包 (
PACK_KEYS=0),它们的长度相同,因此当 myisamchk 对记录进行排序和移动时,它只会覆盖索引中的记录偏移量。如果key被打包(PACK_KEYS=1), myisamchk必须先解包key块,然后重新创建索引并再次打包key块。(在这种情况下,重新创建索引比更新每个索引的偏移量要快。)
运行myisamchk
运行myisamchk时,内存分配很重要 。myisamchk使用的内存不超过其内存相关变量的设置。如果你打算在非常大的表上使用myisamchk,你应该首先决定你想要它使用多少内存。默认是只使用大约 3MB 来执行修复。通过使用更大的值,您可以让myisamchk运行得更快。例如,如果您有超过 512MB 的可用 RAM,您可以使用以下选项(除了您可能指定的任何其他选项):
1 | myisamchk --myisam_sort_buffer_size=256M \ |
--myisam_sort_buffer_size=16M对于大多数情况, 使用可能就足够了。
请注意myisamchk在TMPDIR. 如果TMPDIR 指向内存文件系统,则很容易发生内存不足错误。如果发生这种情况,请运行myisamchk并 选择指定位于具有更多空间的文件系统上的目录。 --tmpdir=*dir_name*
在进行修复操作时,myisamchk 也需要大量的磁盘空间:
数据文件大小的两倍(原始文件和副本)。如果您使用 进行修复,则不需要此空间
--quick;在这种情况下,仅重新创建索引文件。此空间必须与原始数据文件位于同一文件系统上,因为副本与原始数据文件在同一目录中创建。用于替换旧索引文件的新索引文件的空间。旧索引文件在修复操作开始时被截断,因此您通常会忽略此空间。此空间必须与原始数据文件位于同一文件系统上。
使用
--recoveror 时--sort-recover(但不使用--safe-recover)时,您需要磁盘空间进行排序。该空间分配在临时目录中(由TMPDIR或 指定)。以下公式产生所需的空间量:--tmpdir=*dir_name*1
(largest_key + row_pointer_length) * number_of_rows * 2
如果您在修复过程中遇到磁盘空间问题,您可以尝试 --safe-recover代替 --recover。
25. myisamlog-显示 MyISAM 日志文件内容
myisamlog处理MyISAM日志文件的内容 。要创建这样的文件,请使用一个 选项启动服务器 。 --log-isam=log_file
像这样 调用myisamlog:
1 | myisamlog [options] [file_name [tbl_name] ...] |
默认操作是更新 ( -u)。如果恢复完成 ( -r),则所有写入以及可能的更新和删除均已完成,并且仅计算错误。myisam.log如果没有 *log_file*给出参数,则默认日志文件名是。如果在命令行中命名了表,则仅更新这些表。
26. myisampack-生成压缩的、只读的 MyISAM 表
该myisampack工具压缩 MyISAM表。myisampack 的 工作原理是分别压缩表中的每一列。通常,myisampack 会打包数据文件的 40% 到 70%。
稍后使用该表时,服务器会将解压缩列所需的信息读入内存。这会在访问单个行时带来更好的性能,因为您只需解压缩恰好一行。
MySQLmmap()在可能的情况下使用对压缩表执行内存映射。如果 mmap()不起作用,MySQL 会回退到正常的读/写文件操作。
请注意以下事项:
- 如果在禁用外部锁定的情况下调用mysqld服务器,如果表可能在打包过程中被服务器更新,则调用myisampack不是一个好主意 。在服务器停止的情况下压缩表是最安全的。
- 打包表后,它变为只读。这通常是有意的(例如在访问 CD 上的打包表时)。
- myisampack不支持分区表。
像这样 调用myisampack:
1 | myisampack [options] file_name ... |
每个文件名参数应该是索引 ( .MYI) 文件的名称。如果您不在数据库目录中,则应指定文件的路径名。允许省略.MYI扩展名。
27. mysql_config_editor-MySQL 配置实用程序
该mysql_config_editor实用程序,可以存储在一个名为模糊化的登录路径文件认证证书.mylogin.cnf。文件位置是%APPDATA%\MySQLWindows 上的目录和非 Windows 系统上当前用户的主目录。MySQL 客户端程序稍后可以读取该文件以获取用于连接到 MySQL 服务器的身份验证凭据。
.mylogin.cnf 登录路径文件 的非混淆格式由选项组组成,类似于其他选项文件。在每个选项组 .mylogin.cnf被称为“登录路径, ”这是一组只允许特定的选项:host,user, password,port和 socket。将登录路径选项组视为一组选项,用于指定要连接到的 MySQL 服务器以及要进行身份验证的帐户。这是一个未混淆的示例:
1 | [client] |
当您调用客户端程序连接到服务器时,客户端会把.mylogin.cnf与其他选项文件一起使用。它的优先级高于其他选项文件,但低于客户端命令行上明确指定的选项。
mysql_config_editor对.mylogin.cnf文件进行混淆处理,因此无法将其作为明文读取,并且客户端程序未混淆的 内容仅在内存中使用。通过这种方式,密码可以以非明文格式存储在文件中并在以后使用,而无需在命令行或环境变量中公开。mysql_config_editor提供了一个print用于显示登录路径文件内容的 命令,但即使在这种情况下,密码值也会被屏蔽,以便永远不会以其他用户可以看到的方式出现。
mysql_config_editor 使用的混淆 防止密码.mylogin.cnf以明文形式出现,并通过防止无意中暴露密码来 提供安全措施。例如,如果您my.cnf在屏幕上显示常规的未混淆 选项文件,则任何人都可以看到其中包含的任何密码。使用 .mylogin.cnf,这是不正确的,但使用的混淆不太可能阻止坚定的攻击者,您不应该认为它牢不可破。可以在您的机器上获得系统管理权限以访问您的文件的用户可以.mylogin.cnf 通过一些努力来取消混淆文件。
28. mysql_migrate_keyring-密钥环密钥迁移实用程序
所述mysql_migrate_keyring一个密钥环部件和另一个之间效用迁移密钥。它支持离线和在线迁移。
像这样 调用mysql_migrate_keyring(在一行中输入命令):
1 | mysql_migrate_keyring |
29. mysqlbinlog-处理二进制日志文件的实用程序
服务器的二进制日志由包含“事件”的文件组成,这些文件描述了对数据库内容的修改。服务器以二进制格式写入这些文件。要以文本格式显示其内容,请使用 mysqlbinlog实用程序。您还可以使用 mysqlbinlog显示复制设置中副本服务器写入的中继日志文件的内容,因为中继日志的格式与二进制日志相同。
该--hexdump选项导致 mysqlbinlog生成二进制日志内容的十六进制转储:
1 | mysqlbinlog --hexdump source-bin.000001 |
十六进制输出由以 开头的注释行组成 #,因此对于前面的命令,输出可能如下所示:
1 | /*!40019 SET @@SESSION.max_insert_delayed_threads=0*/; |
使用mysqlbinlog备份二进制日志文件
默认情况下,mysqlbinlog读取二进制日志文件并以文本格式显示其内容。这使您能够更轻松地检查文件中的事件并重新执行它们(例如,通过将输出用作mysql 的输入 )。mysqlbinlog可以直接从本地文件系统读取日志文件,或者,通过该 --read-from-remote-server 选项,它可以连接到服务器并从该服务器请求二进制日志内容。mysqlbinlog将文本输出写入其标准输出,或写入指定为该 选项值的文件( 如果给出了该选项)。 --result-file=*file_name*
mysqlbinlog可以读取二进制日志文件并写入包含相同内容的新文件——即以二进制格式而不是文本格式。此功能使您能够轻松备份原始格式的二进制日志。 mysqlbinlog可以进行静态备份,备份一组日志文件并在到达最后一个文件末尾时停止。它还可以进行连续(“实时”)备份,在到达最后一个日志文件的末尾时保持与服务器的连接,并在新事件生成时继续复制它们。在连续备份操作中, mysqlbinlog一直运行到连接结束(例如服务器退出时)或 mysqlbinlog被强制终止。当连接结束时,mysqlbinlog不会等待并重试连接,这与副本服务器不同。要在服务器重新启动后继续实时备份,您还必须重新启动mysqlbinlog。
1 | 重要的 |
30. mysqldumpslow-总结慢查询日志文件
MySQL 慢查询日志包含有关需要很长时间执行的查询的信息(请参阅 第 5.4.5 节,“慢查询日志”)。 mysqldumpslow解析 MySQL 慢查询日志文件并汇总其内容。
通常,mysqldumpslow将相似的查询分组,但数字和字符串数据值的特定值除外。据“抽象”这些值 N和'S'显示摘要输出时。要修改值抽象行为,请使用 -a和-n选项。
像这样 调用mysqldumpslow:
1 | mysqldumpslow [options] [log_file ...] |
31.mysql_config-编译客户端的显示选项
mysql_config为您提供有关编译 MySQL 客户端并将其连接到 MySQL 的有用信息。它是一个 shell 脚本,所以它只在 Unix 和类 Unix 系统上可用。
笔记
pkg-config可用作mysql_config的替代方法, 用于获取编译 MySQL 应用程序所需的编译器标志或链接库等信息。有关更多信息,请参阅 使用 pkg-config 构建 C API 客户端程序。
32.my_print_defaults-选项文件中的显示选项
my_print_defaults显示选项文件的选项组中存在的选项。输出指示读取指定选项组的程序使用哪些选项。例如, mysqlcheck程序读取 [mysqlcheck]和[client] 选项组。要查看标准选项文件中这些组中存在哪些选项,请像这样调用 my_print_defaults:
1 | shell> my_print_defaults client mysql |
33. lz4_decompress-解压mysqlpump LZ4-压缩输出
所述lz4_decompress实用程序解压缩 mysqlpump这是使用LZ4压缩创建的输出。
34. perror-显示MySQL错误信息
perror显示 MySQL 或操作系统错误代码的错误消息。像这样调用 perror:
1 | perror [options] errorcode ... |
35. zlib_decompress-解压 mysqlpump ZLIB 压缩输出
所述zlib_decompress实用程序解压缩 mysqlpump这是使用ZLIB压缩创建的输出。
4. Mysql服务器
4.1 基本信息
mysqld是 MySQL 服务器。以下讨论涵盖了这些 MySQL 服务器配置主题:
- 服务器支持的启动选项。您可以在命令行上、通过配置文件或同时在两者中指定这些选项。
- 服务器系统变量。这些变量反映了启动选项的当前状态和值,其中一些可以在服务器运行时进行修改。
- 服务器状态变量。这些变量包含有关运行时操作的计数器和统计信息。
- 如何设置服务器SQL模式。此设置修改 SQL 语法和语义的某些方面,例如为了与来自其他数据库系统的代码兼容,或控制特定情况下的错误处理。
- 服务器如何管理客户端连接。
- 配置和使用 IPv6 和网络命名空间支持。
- 配置和使用时区支持。
- 使用资源组。
- 服务器端帮助功能。
- 提供用于启用客户端会话状态更改的功能。
- 服务器关闭过程。根据表的类型(事务性或非事务性)以及是否使用复制,存在性能和可靠性方面的考虑。
MySQL 服务器mysqld有许多命令选项和系统变量,可以在启动时设置以配置其操作。要确定服务器使用的默认命令选项和系统变量值,请执行以下命令:
1 | shell> mysqld --verbose --help |
从 MySQL 8.0.16 开始,MySQL Server 支持一个 --validate-config选项,可以在不以正常操作模式运行服务器的情况下检查启动配置是否存在问题:
如果未发现错误,则服务器以退出代码 0 终止。如果发现错误,服务器将显示诊断消息并以退出代码 1 终止。例如:
1 | mysqld --validate-config --no-such-option |
一旦发现任何错误,服务器就会终止。要进行其他检查,请更正最初的问题并--validate-config 再次运行服务器。
对于前面的示例,在使用 --validate-config结果显示错误消息的情况下,服务器退出代码为 1。也可能显示警告和信息消息,具体取决于 log_error_verbosity值,但不会产生立即验证终止或退出代码 1 . 比如这个命令会产生多个警告,都显示出来。但是没有出现错误,所以退出代码为0:
1 | shell> mysqld --validate-config --log_error_verbosity=2 |
服务器系统变量和状态变量
MySQL 服务器维护了许多配置其操作的系统变量。第 5.1.8 节,“服务器系统变量”,描述了这些变量的含义。每个系统变量都有一个默认值。可以在服务器启动时使用命令行或选项文件中的选项设置系统变量。其中大部分可以在服务器运行时通过SET 语句动态更改 ,这使您无需停止和重新启动服务器即可修改服务器的操作。您还可以在表达式中使用系统变量值。
系统变量存在两个作用域。全局变量影响服务器的整体运行。会话变量会影响其对单个客户端连接的操作。给定的系统变量可以同时具有全局值和会话值。全局和会话系统变量的关系如下:
当服务器启动时,它会将每个全局变量初始化为其默认值。这些默认值可以通过在命令行或选项文件中指定的选项进行更改。(请参阅 第 4.2.2 节,“指定程序选项”。)
服务器还为每个连接的客户端维护一组会话变量。客户端的会话变量在连接时使用相应全局变量的当前值进行初始化。比如客户端的SQL模式是由session
sql_mode值控制的 ,当客户端连接到全局sql_mode值的时候初始化。对于某些系统变量,会话值不是从对应的全局值初始化的;如果是,则在变量描述中指明。
MySQL 服务器维护许多状态变量,提供有关其操作的信息。您可以使用SHOW [GLOBAL | SESSION] STATUS语句查看这些变量及其值(请参阅第 13.7.7.37 节,“显示状态语句”)。可选GLOBAL关键字聚合所有连接SESSION的值,并显示当前连接的值。
服务器 SQL 模式
MySQL 服务器可以在不同的 SQL 模式下运行,并且可以根据sql_mode系统变量的值对不同的客户端应用不同的模式。DBA 可以设置全局 SQL 模式以匹配站点服务器操作要求,每个应用程序可以将其会话 SQL 模式设置为自己的要求。
模式会影响 MySQL 支持的 SQL 语法以及它执行的数据验证检查。这使得在不同环境中使用 MySQL 以及与其他数据库服务器一起使用 MySQL 变得更容易。
有关 MySQL 中服务器 SQL 模式的常见问题的答案,请参阅第 A.3 节,“MySQL 8.0 常见问题:服务器 SQL 模式”。
使用InnoDB表时,还要考虑innodb_strict_mode系统变量。它为InnoDB表启用额外的错误检查 。
连接管理
服务器能够侦听多个网络接口上的客户端连接。连接管理器线程在服务器侦听的网络接口上处理客户端连接请求:
- 在所有平台上,一个管理器线程处理 TCP/IP 连接请求。
- 在 Unix 上,同一个管理器线程也处理 Unix 套接字文件连接请求。
- 在 Windows 上,一个管理器线程处理共享内存连接请求,另一个处理命名管道连接请求。
- 在所有平台上,可以启用额外的网络接口来接受管理 TCP/IP 连接请求。该接口可以使用处理“普通” TCP/IP 请求的管理器线程,也可以使用单独的线程。
服务器不会创建线程来处理它不侦听的接口。例如,不支持命名管道连接的 Windows 服务器不会创建线程来处理它们。
连接管理器线程将每个客户端连接与一个专用于该连接的线程相关联,该线程为该连接处理身份验证和请求处理。管理器线程在必要时创建一个新线程,但通过首先查询线程缓存以查看它是否包含可用于连接的线程来尝试避免这样做。当连接结束时,如果缓存未满,则其线程将返回到线程缓存。
DNS 查找和主机缓存
MySQL 服务器维护一个内存中的主机缓存,其中包含有关客户端的信息:IP 地址、主机名和错误信息。Performance Schema host_cache表公开了主机缓存的内容,以便可以使用SELECT语句对其进行检查 。
主机缓存操作
服务器仅将主机缓存用于非本地主机 TCP 连接。对于使用环回接口地址(例如,127.0.0.1或::1)建立的 TCP 连接,或使用 Unix 套接字文件、命名管道或共享内存建立的连接,它不使用缓存 。
服务器将主机缓存用于多种目的:
- 通过缓存 IP 到主机名查找的结果,服务器避免为每个客户端连接执行域名系统 (DNS) 查找。相反,对于给定的主机,它只需要为来自该主机的第一个连接执行查找。
- 缓存包含有关客户端连接过程中发生的错误的信息。一些错误被认为是“阻塞”。”如果在没有成功连接的情况下从给定主机连续发生太多此类事件,服务器将阻止来自该主机的进一步连接。的
max_connect_errors系统变量决定了允许数量的连续错误的阻塞发生之前。
对于每个适用的新客户端连接,服务器使用客户端 IP 地址来检查客户端主机名是否在主机缓存中。如果是,则服务器根据主机是否被阻塞而拒绝或继续处理连接请求。如果主机不在缓存中,服务器会尝试解析主机名。首先,它将 IP 地址解析为主机名,然后将该主机名解析回 IP 地址。然后它将结果与原始 IP 地址进行比较以确保它们相同。服务器将有关此操作结果的信息存储在主机缓存中。如果缓存已满,则丢弃最近最少使用的条目。
服务器处理主机缓存中的条目,如下所示:
- 当第一个 TCP 客户端连接从给定的 IP 地址到达服务器时,会创建一个新的缓存条目来记录客户端 IP、主机名和客户端查找验证标志。最初,主机名设置为
NULL并且标志为假。此条目还用于来自同一原始 IP 的后续客户端 TCP 连接。 - 如果客户端 IP 条目的验证标志为 false,则服务器尝试 IP 到主机名到 IP DNS 的解析。如果成功,则使用解析后的主机名更新主机名,并将验证标志设置为 true。如果解决不成功,则采取的措施取决于错误是永久性的还是暂时性的。对于永久性故障,主机名保持不变
NULL,验证标志设置为 true。对于暂时性故障,主机名和验证标志保持不变。(在这种情况下,下次客户端从该 IP 连接时会发生另一次 DNS 解析尝试。) - 如果在处理来自给定 IP 地址的传入客户端连接时发生错误,服务器将更新该 IP 条目中的相应错误计数器。
资源组
MySQL 支持资源组的创建和管理,并允许将服务器内运行的线程分配给特定的组,以便线程根据组可用的资源执行。组属性启用对其资源的控制,以启用或限制组中线程的资源消耗。DBA 可以根据不同的工作负载修改这些属性。
目前,CPU时间是一种可管理的资源,以“虚拟CPU ”的概念为代表,包括CPU内核、超线程、硬件线程等。服务器在启动时确定有多少虚拟 CPU 可用,具有适当权限的数据库管理员可以将这些 CPU 与资源组相关联,并将线程分配给组。
例如,为了管理不需要以高优先级执行的批处理作业的执行,DBA 可以创建一个 Batch资源组,并根据服务器的繁忙程度向上或向下调整其优先级。(也许分配给该组的批处理作业应该在白天以较低的优先级运行,而在夜间以较高的优先级运行。)DBA 还可以调整该组可用的 CPU 集。可以启用或禁用组来控制线程是否可分配给它们。
客户端会话状态变化的服务器跟踪
MySQL 服务器实现了几个会话状态跟踪器。客户端可以启用这些跟踪器来接收其会话状态更改的通知。
跟踪器机制的一个用途是为 MySQL 连接器和客户端应用程序提供一种方法,以确定是否有任何会话上下文可用于允许会话从一个服务器迁移到另一个服务器。(要在负载均衡的环境中更改会话,在决定是否可以进行切换时,需要检测是否有会话状态需要考虑。)
跟踪器机制的另一个用途是允许应用程序知道何时可以将事务从一个会话移动到另一个会话。事务状态跟踪实现了这一点,这对于可能希望将事务从繁忙的服务器移动到负载较少的服务器的应用程序非常有用。例如,管理客户端连接池的负载平衡连接器可以在池中的可用会话之间移动事务。
但是,不能在任意时间进行会话切换。如果会话处于已完成读取或写入的事务的中间,则切换到不同的会话意味着原始会话上的事务回滚。仅当事务尚未在其中执行任何读取或写入时,才必须进行会话切换。
服务器关机流程
服务器关闭过程如下:
启动关闭过程。
这可以通过多种方式发起。例如,具有
SHUTDOWN权限的用户可以执行mysqladmin shutdown命令。 mysqladmin可以在 MySQL 支持的任何平台上使用。如有必要,服务器会创建一个关闭线程。
根据关闭的启动方式,服务器可能会创建一个线程来处理关闭过程。如果客户端请求关闭,则会创建关闭线程。如果关闭是接收
SIGTERM信号的结果 ,则信号线程可能会自行处理关闭,或者它可能会创建一个单独的线程来执行此操作。如果服务器尝试创建关闭线程但无法创建(例如,如果内存已用完),则会发出出现在错误日志中的诊断消息:1
Error: Can't create thread to kill server
服务器停止接受新连接。
为了防止在关闭期间启动新活动,服务器通过关闭它通常侦听连接的网络接口的处理程序来停止接受新的客户端连接:TCP/IP 端口、Unix 套接字文件、Windows 命名管道和Windows 上的共享内存。
服务器终止当前活动。
对于与客户端连接相关联的每个线程,服务器断开与客户端的连接并将线程标记为已终止。当线程注意到它们被如此标记时,它们就会死亡。空闲连接的线程很快就会消亡。当前正在处理语句的线程会定期检查它们的状态,并且需要更长的时间才能死亡。
对于具有打开事务的线程,事务将回滚。如果一个线程正在更新非事务表,操作如多行
UPDATE或INSERT可以离席了部分更新,因为操作可以完成前终止。如果服务器是复制源服务器,它会像对待其他客户端线程一样对待与当前连接的副本关联的线程。也就是说,每一个都被标记为被杀死并在下一次检查其状态时退出。
如果服务器是副本服务器,它会在将客户端线程标记为已终止之前停止复制 I/O 和 SQL 线程(如果它们处于活动状态)。允许 SQL 线程完成其当前语句(以避免导致复制问题),然后停止。如果此时 SQL 线程处于事务中间,则服务器会等待,直到当前复制事件组(如果有)执行完毕,或者直到用户发出
KILL QUERYorKILL CONNECTION语句。服务器关闭或关闭存储引擎。
在此阶段,服务器刷新表缓存并关闭所有打开的表。
每个存储引擎为其管理的表执行任何必要的操作。
InnoDB将其缓冲池刷新到磁盘(除非innodb_fast_shutdown是 2),将当前 LSN 写入表空间,并终止其自己的内部线程。MyISAM刷新表的任何挂起的索引写入。服务器退出。
为了向管理进程提供信息,服务器返回以下列表中描述的退出代码之一。括号中的短语表示 systemd 为响应代码而采取的操作,适用于 systemd 用于管理服务器的平台。
- 0 = 成功终止(未重新启动)
- 1 = 未成功终止(未重新启动)
- 2 = 不成功的终止(重新启动完成)
4.2 数据目录
MySQL 服务器管理的信息存储在称为数据目录的目录下。以下列表简要描述了通常在数据目录中找到的项目,并提供其他信息的交叉引用:
数据目录子目录。数据目录下的每个子目录都是一个数据库目录,对应一个服务器管理的数据库。所有 MySQL 安装都有特定的标准数据库:
- 该
mysql目录对应于mysql系统表,其中包含 MySQL 服务器运行时所需的信息。该数据库包含数据字典表和系统表。 - 该
performance_schema目录对应于 Performance Schema,它提供用于在运行时检查服务器内部执行的信息。 - 该
sys目录对应于sys表,它提供了一组对象来帮助更轻松地解释性能表信息。 - 该
ndbinfo目录对应于ndbinfo存储特定于 NDB Cluster 的信息的数据库(仅存在于构建为包含 NDB Cluster 的安装)。 - 服务器写入的日志文件。
InnoDB表空间和日志文件。- 默认/自动生成的 SSL 和 RSA 证书和密钥文件。
- 服务器进程 ID 文件(在服务器运行时)。
mysqld-auto.cnf存储持久化全局系统变量设置 的文件。
通过重新配置服务器,可以将前面列表中的某些项目重新定位到其他位置。此外,该 --datadir选项还可以更改数据目录本身的位置。对于给定的 MySQL 安装,检查服务器配置以确定项目是否已被移动。
4.3 系统表
该mysql模式是系统表。它包含存储 MySQL 服务器运行时所需信息的表。一个广泛的分类是 mysql模式包含存储数据库对象元数据的数据字典表和用于其他操作目的的系统表。以下讨论将系统表集进一步细分为更小的类别。
数据字典表
这些表构成数据字典,其中包含有关数据库对象的元数据。
1 | 重要的 |
catalogs: 目录信息。character_sets:有关可用字符集的信息。check_constraints:有关CHECK在表上定义的约束的信息 。见 第 13.1.20.6 节,“检查约束”。collations:有关每个字符集的排序规则的信息。column_statistics:列值的直方图统计信息。请参阅 第 8.9.6 节,“优化器统计信息”。column_type_elements:有关列使用的类型的信息。columns:有关表中列的信息。dd_properties:标识数据字典属性的表,例如其版本。服务器使用它来确定是否必须将数据字典升级到更新的版本。events:有关事件调度程序事件的信息。请参阅第 25.4 节,“使用事件调度程序”。如果使用该--skip-grant-tables选项启动服务器 ,则事件调度程序被禁用并且表中注册的事件不会运行。请参见 第 25.4.2 节,“事件调度程序配置”。foreign_keys,foreign_key_column_usage: 外键信息。index_column_usage:有关索引使用的列的信息。index_partitions:有关索引使用的分区的信息。index_stats:用于存储ANALYZE TABLE执行时生成的动态索引统计信息。indexes:关于表索引的信息。innodb_ddl_log:存储 DDL 日志以用于崩溃安全 DDL 操作。parameter_type_elements:有关存储过程和函数参数的信息,以及有关存储函数的返回值的信息。parameters:有关存储过程和函数的信息。请参见 第 25.2 节,“使用存储的例程”。resource_groups:有关资源组的信息。请参见第 5.1.16 节,“资源组”。routines:有关存储过程和函数的信息。请参见 第 25.2 节,“使用存储的例程”。schemata:关于模式的信息。在 MySQL 中,schema 就是一个数据库,因此该表提供了有关数据库的信息。st_spatial_reference_systems:有关空间数据可用空间参考系统的信息。table_partition_values:有关表分区使用的值的信息。table_partitions:有关表使用的分区的信息。table_stats:ANALYZE TABLE执行时生成的动态表统计信息。tables:有关数据库中表的信息。tablespace_files:有关表空间使用的文件的信息。tablespaces:有关活动表空间的信息。triggers:有关触发器的信息。view_routine_usage:关于视图和它们使用的存储函数之间的依赖关系的信息。view_table_usage:用于跟踪视图与其底层表之间的依赖关系。
1 | mysql>use information_schema; |
授予系统表
这些系统表包含有关用户帐户及其拥有的权限的授权信息。
从 MySQL 8.0 开始,授权表是InnoDB (事务)表。以前,这些是 MyISAM(非事务性)表。授权表存储引擎的变化是 MySQL 8.0 中对帐户管理语句(如CREATE USER和 ) 行为的伴随变化的基础GRANT。以前,命名多个用户的帐户管理语句可能对某些用户成功而对其他用户失败。这些语句现在是事务性的,要么对所有命名用户成功,要么回滚,如果发生任何错误,则无效。
user:用户帐户、全局权限和其他非权限列。global_grants:为用户分配动态全局权限;请参阅 静态与动态权限。db: 数据库级权限。tables_priv: 表级权限。columns_priv: 列级权限。procs_priv: 存储过程和函数权限。proxies_priv: 代理用户权限。default_roles:此表列出了在用户连接并验证或执行后要激活的默认角色SET ROLE DEFAULT。role_edges:此表列出了角色子图的边。给定的
user表行可能指的是用户帐户或角色。服务器可以通过查询role_edges表以获取有关身份验证 ID 之间关系的信息来区分行是代表用户帐户、角色还是两者。password_history:有关密码更改的信息。
1 | mysql> use performance_schema; |
对象信息系统表
这些系统表包含有关组件、可加载函数和服务器端插件的信息:
component:使用INSTALL COMPONENT. 此表中列出的任何组件都在服务器启动序列期间由加载程序服务安装。请参见第 5.5.1 节 “安装和卸载组件”。func:使用CREATE FUNCTION. 在正常启动序列期间,服务器加载此表中注册的函数。如果使用该--skip-grant-tables选项启动服务器 ,则表中注册的函数不会加载且不可用。请参见第 5.7.1 节,“安装和卸载可加载函数”。plugin:使用INSTALL PLUGIN. 在正常启动序列期间,服务器加载在此表中注册的插件。如果使用该--skip-grant-tables选项启动服务器 ,则表中注册的插件不会加载且不可用。请参阅第 5.6.1 节,“安装和卸载插件”。
日志系统表
服务器使用这些系统表进行日志记录:
general_log: 一般查询日志表。slow_log: 慢查询日志表。
日志表使用CSV存储引擎。
服务器端帮助系统表
这些系统表包含服务器端帮助信息:
help_category:有关帮助类别的信息。help_keyword:与帮助主题相关的关键字。help_relation:帮助关键字和主题之间的映射。help_topic: 帮助主题内容。
时区系统表
这些系统表包含时区信息:
time_zone:时区 ID 以及它们是否使用闰秒。time_zone_leap_second:闰秒发生时。time_zone_name:时区 ID 和名称之间的映射。time_zone_transition,time_zone_transition_type: 时区说明。
复制系统表
服务器使用这些系统表来支持复制:
gtid_executed:用于存储 GTID 值的表。请参阅 mysql.gtid_executed 表。ndb_binlog_index:NDB Cluster 复制的二进制日志信息。仅当服务器在NDBCLUSTER支持下构建时才会创建此表 。请参阅 第 23.7.4 节,“NDB Cluster 复制模式和表”。slave_master_info,slave_relay_log_info,slave_worker_info: 用于在副本服务器上存储复制信息。请参阅 第 17.2.4 节,“中继日志和复制元数据存储库”。
刚刚列出的所有表都使用 InnoDB存储引擎。
优化器系统表
这些系统表供优化器使用:
innodb_index_stats,innodb_table_stats: 用于InnoDB持久优化器统计信息。请参见第 15.8.10.1 节,“配置持久优化器统计参数”。server_cost,engine_cost: 优化器成本模型使用的表包含有关查询执行期间发生的操作的成本估计信息。server_cost包含一般服务器操作的优化器成本估算。engine_cost包含对特定存储引擎特定操作的估计。请参阅 第 8.9.5 节,“优化器成本模型”。
其他系统表
其他系统表不符合上述类别:
audit_log_filter,audit_log_user: 如果安装了 MySQL Enterprise Audit,这些表提供审计日志过滤器定义和用户帐户的持久存储。请参阅 审核日志表。firewall_group_allowlist,firewall_groups,firewall_memebership,firewall_users,firewall_whitelist: 如果安装了 MySQL Enterprise Firewall,这些表为防火墙使用的信息提供持久存储。请参阅 第 6.4.7 节,“MySQL 企业防火墙”。servers: 由FEDERATED存储引擎使用。请参阅 第 16.8.2.2 节,“使用 CREATE SERVER 创建联合表”。innodb_dynamic_metadata:由InnoDB存储引擎用于存储快速变化的表元数据,例如自动递增计数器值和索引树损坏标志。替换驻留在InnoDB系统表空间中的数据字典缓冲区表 。
4.4 服务器日志
MySQL Server 有几个日志,可以帮助您找出正在发生的活动。
| 日志类型 | 写入日志的信息 |
|---|---|
| 错误日志 | 启动、运行或停止mysqld时遇到的问题 |
| 一般查询日志 | 已建立的客户端连接和从客户端收到的语句 |
| 二进制日志 | 更改数据的语句(也用于复制) |
| 中继日志 | 从复制源服务器收到的数据更改 |
| 慢查询日志 | long_query_time执行时间超过几秒的查询 |
| DDL 日志(元数据日志) | DDL 语句执行的元数据操作 |
默认情况下,服务器在数据目录中为所有启用的日志写入文件。您可以通过刷新日志来强制服务器关闭并重新打开日志文件(或在某些情况下切换到新的日志文件)。当您发出FLUSH LOGS语句时会发生日志刷新 ;使用 or参数执行 mysqladmin;或使用 或 选项执行 mysqldump。请参阅 第 13.7.8.3 节,“FLUSH 语句”,第 4.5.2 节,“mysqladmin — MySQL 服务器管理程序”和 第 4.5.4 节,“mysqldump — 数据库备份程序”。此外,当二进制日志的大小达到 flush-logs``refresh--flush-logs--master-datamax_binlog_size 系统变量。
您可以在运行时控制常规查询和慢速查询日志。您可以启用或禁用日志记录,或更改日志文件名。您可以告诉服务器将一般查询和慢速查询条目写入日志表、日志文件或两者。
中继日志仅用于副本,以保存来自复制源服务器的数据更改,这些更改也必须在副本上进行。
选择通用查询日志和慢查询日志输出目的地
MySQL Server 提供对写入到通用查询日志和慢速查询日志的输出目的地的灵活控制(如果这些日志已启用)。日志条目可能的目的地包括日志文件或general_log与 slow_log在表mysql 系统数据库。可以选择文件输出、表输出或两者。
错误日志
错误日志包含mysqld 启动和关闭时间的记录。它还包含诊断消息,例如在服务器启动和关闭期间以及服务器运行期间发生的错误、警告和注释。例如,如果mysqld注意到一个表需要自动检查或修复,它会在错误日志中写入一条消息。
根据错误日志配置,错误消息还可以填充 Performance Schema error_log表,为日志提供 SQL 接口并允许查询其内容。
错误日志具体配置可以查看官方文档:https://dev.mysql.com/doc/refman/8.0/en/error-log.html
一般查询日志
一般查询日志是对mysqld正在做什么的一般记录 。当客户端连接或断开连接时,服务器将信息写入此日志,并记录从客户端收到的每个 SQL 语句。当您怀疑客户端有错误并想确切知道客户端发送给mysqld 的内容时,通用查询日志非常有用。
显示客户端何时连接的每一行还包括 指示用于建立连接的协议。 是 (不使用 SSL 建立的 TCP/IP 连接)、(使用 SSL 建立的 TCP/IP 连接)、(Unix 套接字文件连接)、(Windows 命名管道连接)或(Windows 共享内存连接)之一。
mysqld按照它接收语句的顺序将语句写入查询日志,这可能与它们执行的顺序不同。这种日志顺序与二进制日志的顺序相反,二进制日志的语句是在执行之后但在释放任何锁之前写入的。此外,查询日志可能包含仅选择数据的语句,而这些语句永远不会写入二进制日志。
在复制源服务器上使用基于语句的二进制日志记录时,其副本接收的语句将写入每个副本的查询日志。如果客户端使用mysqlbinlog实用程序读取事件并将它们传递给服务器,则语句将写入源的查询日志 。
但是,当使用基于行的二进制日志记录时,更新是作为行更改而不是 SQL 语句发送的,因此这些语句永远不会在binlog_formatis 时写入查询日志 ROW。当此变量设置为 时MIXED,给定的更新也可能不会写入查询日志
默认情况下,一般查询日志处于禁用状态。
二进制日志
二进制日志包含描述数据库更改的“事件”,例如表创建操作或表数据的更改。它还包含可能进行更改的语句的事件(例如, DELETE不匹配任何行的 a),除非使用基于行的日志记录。二进制日志还包含有关每个语句花费多长时间更新数据的信息。二进制日志有两个重要目的:
- 对于复制,复制源服务器上的二进制日志提供了要发送到副本的数据更改记录。源将其二进制日志中包含的信息发送到其副本,这些副本会复制这些事务以进行与源所做的相同数据更改。请参见 第 17.2 节,“复制实现”。
- 某些数据恢复操作需要使用二进制日志。恢复备份后,将重新执行备份后记录的二进制日志中的事件。这些事件使数据库从备份点开始更新。请参见 第 7.5 节“时间点(增量)恢复”。
二进制日志不用于诸如SELECT或 SHOW不修改数据的语句 。要记录所有语句(例如,识别有问题的查询),请使用一般查询日志。
运行启用了二进制日志记录的服务器会使性能稍慢。但是,二进制日志在使您能够设置复制和恢复操作方面的好处通常超过这种轻微的性能下降。
二进制日志对意外停止具有弹性。仅记录或回读完整的事件或事务。
默认情况下,二进制日志在每次写入时同步到磁盘 ( sync_binlog=1)。如果 sync_binlog未启用,并且操作系统或机器(不仅是 MySQL 服务器)崩溃,则二进制日志的最后一条语句可能会丢失。为了防止这种情况,启用 sync_binlog系统变量以在每个*N*提交组之后将二进制日志同步到磁盘 。请参阅 第 5.1.8 节,“服务器系统变量”。最安全的值 sync_binlog是 1(默认值),但这也是最慢的。
InnoDBXA 事务中的两阶段提交支持确保二进制日志和 InnoDB数据文件同步。但是,MySQL 服务器还应配置为InnoDB在提交事务之前将二进制日志和日志同步到磁盘。该InnoDB日志由缺省同步,并sync_binlog=1 保证了二进制日志是同步的。InnoDBXA 事务中隐式支持两阶段提交的效果 sync_binlog=1是在崩溃后重新启动时,在执行事务回滚后,MySQL 服务器扫描最新的二进制日志文件以收集事务*xid*值并计算在事务中的最后一个有效位置二进制日志文件。MySQL服务器然后告诉InnoDB完成任何已成功写入二进制日志的准备好的事务,并将二进制日志截断到最后一个有效位置。这确保了二进制日志反映了InnoDB表的确切数据 ,因此副本与源保持同步,因为它没有收到已回滚的语句。
如果 MySQL 服务器在崩溃恢复时发现二进制日志比它应该的要短,那么它至少缺少一个成功提交的InnoDB事务。如果sync_binlog=1磁盘/文件系统在被请求时进行实际同步(有些没有),则不应发生这种情况,因此服务器会打印错误消息。在这种情况下,此二进制日志不正确,应从源数据的新快照重新启动复制
从 MySQL 8.0.20 开始,您可以在 MySQL 服务器实例上启用二进制日志事务压缩。启用二进制日志事务压缩后,事务负载将使用 zstd 算法进行压缩,然后作为单个事件 (a Transaction_payload_event)写入服务器的二进制日志文件 。
压缩的事务有效负载在复制流中发送到副本、其他组复制组成员或客户端(如mysqlbinlog )时保持压缩状态 。它们不会被接收器线程解压缩,并且会以压缩状态写入中继日志。因此,二进制日志事务压缩可以节省事务发起方和接收方(以及他们的备份)的存储空间,并在服务器实例之间发送事务时节省网络带宽。
当需要检查压缩的交易有效负载中包含的单个事件时,将对其进行解压缩。例如,Transaction_payload_event由应用程序线程解压缩以将其包含的事件应用到接收方。恢复时也进行解压,重放事务时由mysqlbinlog进行,SHOW BINLOG EVENTSandSHOW RELAYLOG EVENTS语句进行解压。
以下类型的事件被排除在二进制日志事务压缩之外,因此总是未压缩地写入二进制日志:
- 与交易的 GTID 相关的事件(包括匿名 GTID 事件)。
- 其他类型的控制事件,如视图更改事件和心跳事件。
- 事件事件以及包含它们的所有交易。
- 非交易事件以及包含它们的所有交易。涉及非事务性和事务性存储引擎混合的事务不会压缩其有效负载。
- 使用基于语句的二进制日志记录的事件。二进制日志事务压缩仅适用于基于行的二进制日志格式。
二进制日志加密可用于包含压缩事务的二进制日志文件。
具有压缩负载的事务可以像任何其他事务一样回滚,也可以通过通常的过滤选项在副本上过滤掉它们。二进制日志事务压缩可以应用于 XA 事务。
慢查询日志
慢查询日志由long_query_time执行时间超过几秒钟并且至少 min_examined_row_limit需要检查行的 SQL 语句组成 。慢查询日志可用于查找需要很长时间执行的查询,因此可以进行优化。但是,检查长而缓慢的查询日志可能是一项耗时的任务。为了使这更容易,您可以使用 mysqldumpslow命令来处理慢查询日志文件并总结其内容。
服务器日志维护
MySQL 服务器可以创建多个不同的日志文件来帮助您查看正在发生的活动。但是,您必须定期清理这些文件,以确保日志不会占用太多磁盘空间。
在 Linux (Red Hat) 安装中,您可以使用该 mysql-log-rotate脚本进行日志维护。如果您从 RPM 发行版安装 MySQL,则该脚本应该已自动安装。如果您使用二进制日志进行复制,请小心使用此脚本。在确定所有副本都处理了二进制日志的内容之前,不应删除二进制日志。
二进制日志文件在服务器的二进制日志到期后自动删除。文件的删除可以在启动和刷新二进制日志时进行。默认的二进制日志有效期为 30 天。要指定替代到期期限,请使用 binlog_expire_logs_seconds系统变量。
4.5 组件
MySQL Server 包括一个基于组件的基础表,用于扩展服务器功能。组件提供可用于服务器和其他组件的服务。(就服务使用而言,服务器是一个组件,等同于其他组件。)组件之间仅通过它们提供的服务进行交互。
MySQL 发行版包括几个实现服务器扩展的组件:
- 用于配置错误日志记录的组件。请参阅 第 5.4.2 节“错误日志”和 第 5.5.3 节“错误日志组件”。
- 用于检查密码的组件。请参阅 第 6.4.3 节,“密码验证组件”。
- 密钥环组件为敏感信息提供安全存储。请参阅第 6.4.4 节,“MySQL 密钥环”。
- 使应用程序能够将它们自己的消息事件添加到审计日志的组件。请参阅 第 6.4.6 节,“审计消息组件”。
- 实现用于访问查询属性的可加载功能的组件。请参见第 9.6 节 “查询属性”。
4.6 服务器插件
MySQL 支持允许创建服务器插件的插件 API。插件可以在服务器启动时加载,或者在运行时加载和卸载而无需重新启动服务器。该接口支持的插件包括但不限于存储引擎、INFORMATION_SCHEMA表格、全文解析插件和服务器扩展。
MySQL 发行版包括几个实现服务器扩展的插件:
用于验证客户端尝试连接到 MySQL 服务器的插件。插件可用于多种身份验证协议。请参见第 6.2.17 节,“可插拔身份验证”。
一个连接控制插件,使管理员能够在一定数量的连续失败客户端连接尝试后引入增加的延迟。请参阅 第 6.4.2 节,“连接控制插件”。
密码验证插件实施密码强度策略并评估潜在密码的强度。请参阅 第 6.4.3 节,“密码验证组件”。
半同步复制插件实现了一个复制功能接口,只要至少有一个副本响应了每个事务,就允许源继续进行。请参阅第 17.4.10 节,“半同步复制”。
组复制使您能够跨一组 MySQL 服务器实例创建一个高度可用的分布式 MySQL 服务,数据一致性、冲突检测和解决以及组成员服务都是内置的。请参阅 第 18 章,组复制。
MySQL 企业版包含一个线程池插件,该插件管理连接线程,通过有效管理大量客户端连接的语句执行线程来提高服务器性能。请参阅第 5.6.3 节,“MySQL 企业线程池”。
MySQL 企业版包括一个审计插件,用于监控和记录连接和查询活动。请参阅第 6.4.5 节,“MySQL 企业审计”。
MySQL 企业版包含一个防火墙插件,该插件实现了应用程序级防火墙,使数据库管理员能够根据与已接受语句模式的许可列表的匹配来允许或拒绝 SQL 语句执行。请参阅 第 6.4.7 节,“MySQL 企业防火墙”。
查询重写插件检查 MySQL 服务器收到的语句,并可能在服务器执行它们之前重写它们。请参阅第 5.6.4 节“重写器查询重写插件”和 第 5.6.5 节“ddl_rewriter 插件”。
版本令牌支持创建和同步服务器令牌,应用程序可以使用这些令牌来防止访问不正确或过时的数据。Version Tokens 基于一个插件库,它实现了一个
version_tokens插件和一组可加载的函数。请参阅第 5.6.6 节,“版本令牌”。密钥环插件为敏感信息提供安全存储。请参阅第 6.4.4 节,“MySQL 密钥环”。
在 MySQL 8.0.24 中,MySQL Keyring 开始从插件过渡到使用组件基础设施,使用名为插件的插件来促进
daemon_keyring_proxy_plugin插件和组件服务 API 之间的桥梁。请参阅第 5.6.8 节,“密钥环代理桥接插件”。X 插件扩展了 MySQL 服务器,使其能够用作文档存储。运行 X 插件使 MySQL 服务器能够使用 X 协议与客户端通信,该协议旨在将 MySQL 的 ACID 兼容存储能力公开为文档存储。请参阅第 20.5 节,“X 插件”。
克隆允许
InnoDB从本地或远程 MySQL 服务器实例克隆数据。请参阅 第 5.6.7 节,“克隆插件”。测试框架插件测试服务器服务。有关这些插件的信息,请参阅https://dev.mysql.com/doc/index-other.html 上MySQL Server Doxygen 文档的 Plugins for Testing Plugin Services 部分 。
mysql企业线程池
MySQL 企业版包括 MySQL 企业线程池,使用服务器插件实现。MySQL Server 中的默认线程处理模型使用每个客户端连接一个线程来执行语句。随着越来越多的客户端连接到服务器并执行语句,整体性能会下降。线程池插件提供了一种替代线程处理模型,旨在减少开销并提高性能。该插件实现了一个线程池,通过有效管理大量客户端连接的语句执行线程来提高服务器性能。
线程池解决了每个连接使用一个线程的模型的几个问题:
- 太多的线程堆栈使 CPU 缓存在高度并行的执行工作负载中几乎无用。线程池促进线程堆栈重用以最小化 CPU 缓存占用空间。
- 由于并行执行的线程过多,上下文切换开销很高。这也对操作系统调度程序提出了挑战。线程池控制活动线程的数量,以将 MySQL 服务器内的并行度保持在它可以处理的级别,并且适合执行 MySQL 的服务器主机。
- 并行执行的事务过多会增加资源争用。在 中
InnoDB,这增加了持有中央互斥锁的时间。线程池控制事务何时开始,以确保不会有太多并行执行。
重写器查询重写插件
MySQL 支持查询重写插件,可以在服务器执行之前检查并可能修改服务器收到的 SQL 语句。
ddl_rewriter 插件
MySQL 8.0.16 及更高版本包含一个ddl_rewriter 插件,该插件CREATE TABLE 在解析和执行服务器收到的语句之前修改它们。该插件删除了ENCRYPTION、 DATA DIRECTORY和INDEX DIRECTORY子句,这在从 SQL 转储文件中恢复表时可能会有所帮助,这些文件是从加密的数据库或将其表存储在数据目录之外的数据库创建的。例如,该插件可以将此类转储文件恢复到未加密的实例或无法访问数据目录之外的路径的环境中。
版本令牌
MySQL 包括版本令牌,该功能支持创建和同步服务器令牌,应用程序可以使用这些令牌来防止访问不正确或过时的数据。
Version Tokens 接口具有以下特征:
- 版本标记是由用作键或标识符的名称加上值组成的对。
- 可以锁定版本令牌。应用程序可以使用令牌锁向其他协作应用程序指示令牌正在使用中并且不应被修改。
- 每个服务器建立版本令牌列表(例如,指定服务器分配或操作状态)。此外,与服务器通信的应用程序可以注册它自己的令牌列表,这些令牌指示它要求服务器处于的状态。应用程序向不在所需状态的服务器发送的 SQL 语句会产生错误。这是给应用程序的一个信号,它应该寻找处于所需状态的不同服务器来接收 SQL 语句。
克隆插件
MySQL 8.0.17 中引入的克隆插件允许在本地或从远程 MySQL 服务器实例克隆数据。克隆数据是存储在InnoDB其中的数据的物理快照,其中包括模式、表、表空间和数据字典元数据。克隆的数据包含一个功能齐全的数据目录,它允许使用克隆插件进行 MySQL 服务器配置。
本地克隆操作
本地克隆操作将数据从启动克隆操作的 MySQL 服务器实例克隆到运行 MySQL 服务器实例的同一服务器或节点上的目录。

远程克隆操作
远程克隆操作涉及启动克隆操作的本地 MySQL 服务器实例(“接收者”)和源数据所在的远程 MySQL 服务器实例( “捐赠者”)。当在接受者上启动远程克隆操作时,克隆的数据通过网络从捐赠者传输到接受者。默认情况下,远程克隆操作会删除收件人数据目录中的数据并将其替换为克隆的数据。或者,您可以将数据克隆到收件人的不同目录中,以避免删除现有数据。

与远程克隆操作相比,本地克隆操作克隆的数据没有区别。这两个操作克隆相同的数据。
克隆插件支持复制。除了克隆数据外,克隆操作还从提供者处提取和传输复制坐标并将其应用于接收者,这使得可以使用克隆插件来配置组复制成员和副本。使用克隆插件进行配置比复制大量事务更快、更有效。组复制成员还可以配置为使用克隆插件作为恢复的替代方法,以便成员自动选择最有效的方式从种子成员检索组数据。
Keyring 代理桥接插件
MySQL Keyring 最初使用服务器插件实现密钥库功能,但开始过渡到使用 MySQL 8.0.24 中的组件基础表。转换包括修改密钥环插件的底层实现以使用组件基础设施。使用daemon_keyring_proxy_plugin作为插件和组件服务 API 之间桥梁的命名插件可以促进这一点,并使密钥环插件能够继续使用,而不会改变用户可见的特征。
daemon_keyring_proxy_plugin 是内置的,无需执行任何操作即可安装或启用它。
锁定服务
MySQL 发行版提供了一个可在两个级别访问的锁定接口:
- 在 SQL 级别,作为一组可加载的函数,每个函数映射到对服务例程的调用。
- 作为 C 语言接口,可作为来自服务器插件或可加载函数的插件服务调用。
锁定接口具有以下特点:
锁具有三个属性:锁命名空间、锁名称和锁模式:
- 锁由命名空间和锁名称的组合标识。通过在不同的命名空间中创建锁,命名空间使不同的应用程序能够使用相同的锁名称而不会发生冲突。例如,如果应用程序A和B使用的名称空间
ns1和ns2,分别,每个应用可以使用锁的名称lock1和lock2不与其它应用干扰。 - 锁定模式是读或写。读锁是共享的:如果一个会话在给定的锁标识符上有一个读锁,其他会话可以在同一个标识符上获得一个读锁。写锁是排他的:如果一个会话在给定的锁标识符上有一个写锁,其他会话不能在同一个标识符上获得读或写锁。
- 锁由命名空间和锁名称的组合标识。通过在不同的命名空间中创建锁,命名空间使不同的应用程序能够使用相同的锁名称而不会发生冲突。例如,如果应用程序A和B使用的名称空间
命名空间和锁名称必须是非
NULL、非空的,并且最大长度为 64 个字符。指定为 的命名空间或锁名称NULL、空字符串或长度超过 64 个字符的字符串会导致ER_LOCKING_SERVICE_WRONG_NAME错误。锁定接口将命名空间和锁定名称视为二进制字符串,因此比较区分大小写。
锁定接口提供获取锁和释放锁的功能。调用这些函数不需要特殊权限。权限检查是调用应用程序的责任。
如果没有立即可用,可以等待锁。锁获取调用采用一个整数超时值,该值指示在放弃之前等待获取锁的秒数。如果在没有成功获取锁的情况下达到超时,
ER_LOCKING_SERVICE_TIMEOUT则会发生错误。如果超时为 0,则没有等待,如果不能立即获取锁,则调用会产生错误。锁定接口检测不同会话中的锁定获取调用之间的死锁。在这种情况下,锁定服务选择一个调用者并以
ER_LOCKING_SERVICE_DEADLOCK错误终止其锁定获取请求 。此错误不会导致事务回滚。为了在死锁的情况下选择会话,锁定服务更喜欢持有读锁的会话而不是持有写锁的会话。一个会话可以通过一个锁获取调用来获取多个锁。对于给定的调用,锁获取是原子的:如果获取了所有锁,则调用成功。如果获取任何锁失败,则调用不会获取任何锁并失败,通常带有
ER_LOCKING_SERVICE_TIMEOUTorER_LOCKING_SERVICE_DEADLOCK错误。一个会话可以为同一个锁标识符(命名空间和锁名称组合)获取多个锁。这些锁实例可以是读锁、写锁或两者的混合。
会话中获取的锁通过调用释放锁函数显式释放,或在会话终止(正常或异常)时隐式释放。事务提交或回滚时不会释放锁。
在一个会话中,给定命名空间的所有锁在释放时都会一起释放。
钥匙圈服务
MySQL Server 支持密钥环服务,使内部组件和插件能够安全地存储敏感信息以供以后检索。MySQL 发行版提供了一个可在两个级别访问的密钥环接口:
- 在 SQL 级别,作为一组可加载的函数,每个函数映射到对服务例程的调用。
- 作为 C 语言接口,可作为来自服务器插件或可加载函数的插件服务调用。
“记录”在密钥库中包含的数据(密钥本身)和一个唯一的标识符,通过该键被访问。标识符有两部分:
key_id:密钥 ID 或名称。key_id以 开头的值mysql_由 MySQL 服务器保留。user_id:会话有效用户ID。如果没有用户上下文,则此值可以是NULL。该值实际上不必是 “用户”;含义取决于应用程序。实现密钥环函数接口的函数将 的值
CURRENT_USER()作为user_id值传递给密钥环服务函数。
4.7 服务器可加载函数
MySQL 支持可加载函数,即不是内置的函数,但可以在运行时(在启动期间或以后)加载以扩展服务器功能,或卸载以删除功能。
可加载函数以前称为用户定义函数 (UDF)。这个术语有点用词不当,因为 “用户定义”也可以应用于其他类型的函数,例如存储函数(一种使用 SQL 编写的存储对象)和通过修改服务器源代码添加的本机函数。
MySQL 发行版包括可全部或部分实现这些服务器功能的可加载函数:
- 组复制使您能够跨一组 MySQL 服务器实例创建一个高度可用的分布式 MySQL 服务,数据一致性、冲突检测和解决以及组成员服务都是内置的。请参阅 第 18 章,组复制。
- MySQL 企业版包括基于 OpenSSL 库执行加密操作的函数。请参阅 第 6.6 节,“MySQL 企业加密”。
- MySQL 企业版包括为屏蔽和去标识化操作提供 SQL 级 API 的函数。请参阅 第 6.5.1 节,“MySQL 企业数据屏蔽和去标识化元素”。
- MySQL 企业版包括审计日志,用于监控和记录连接和查询活动。请参阅第 6.4.5 节,“MySQL 企业审计”和第 6.4.6 节,“审计消息组件”。
- MySQL 企业版包括一个防火墙功能,它实现了一个应用程序级防火墙,使数据库管理员能够根据与接受语句的模式匹配来允许或拒绝 SQL 语句执行。请参阅第 6.4.7 节,“MySQL 企业防火墙”。
- 查询重写器检查 MySQL 服务器接收的语句,并可能在服务器执行它们之前重写它们。请参阅 第 5.6.4 节,“重写器查询重写插件”
- 版本令牌支持创建和同步服务器令牌,应用程序可以使用这些令牌来防止访问不正确或过时的数据。请参阅 第 5.6.6 节,“版本令牌”。
- MySQL Keyring 为敏感信息提供安全存储。请参阅第 6.4.4 节,“MySQL 密钥环”。
- 锁定服务为应用程序提供了一个锁定接口。请参阅第 5.6.9.1 节,“锁定服务”。
- 函数提供对查询属性的访问。请参见 第 9.6 节 “查询属性”。
4.8 调试
如果您正在使用 MySQL 中的一些非常新的功能,您可以尝试使用该 选项运行mysqld--skip-new(这将禁用所有新的、可能不安全的功能)。请参阅 第 B.3.3.3 节,“如果 MySQL 持续崩溃怎么办”。
如果mysqld不想启动,请确认您没有my.cnf干扰您的设置的文件!您可以my.cnf 使用mysqld –print-defaults检查您的参数,并通过以mysqld –no-defaults …开头来避免使用它们。
如果mysqld开始占用 CPU 或内存,或者它“挂起”,您可以使用mysqladmin processlist status来查明是否有人正在执行需要很长时间的查询。如果遇到性能问题或新客户端无法连接的问题,在某些窗口中运行mysqladmin -i10 processlist status可能是个好主意 。
命令mysqladmin debug将有关使用中的锁、已用内存和查询使用情况的一些信息转储到 MySQL 日志文件。这可能有助于解决一些问题。即使您没有编译 MySQL 进行调试,此命令也提供了一些有用的信息!
如果问题是某些表变得越来越慢,您应该尝试使用OPTIMIZE TABLE或 myisamchk优化表 。请参阅 第 5 章,MySQL 服务器管理。您还应该使用EXPLAIN.
编译 MySQL 进行调试
如果您有一些非常具体的问题,您可以随时尝试调试 MySQL。为此,您必须使用该-DWITH_DEBUG=1选项配置 MySQL 。您可以通过执行以下操作来检查 MySQL 是否通过调试编译: mysqld –help。如果该 --debug标志与选项一起列出,那么您已启用调试。mysqladmin ver还将mysqld版本列为mysql … –debug在这种情况下。
创建跟踪文件
如果mysqld服务器没有启动或者很容易崩溃,你可以尝试创建一个trace文件来查找问题。
要做到这一点,你必须有一个编译了调试支持的mysqld。您可以通过执行来检查这一点mysqld -V。如果版本号以 结尾-debug,则编译时支持跟踪文件。(在 Windows 上,调试服务器被命名为 mysqld-debug而不是 mysqld。)
使用 WER 和 PDB 创建 Windows 故障转储
程序数据库文件(带有后缀pdb)包含在MySQL的ZIP 存档调试二进制文件和测试套件分发版中。这些文件提供了在出现问题时调试 MySQL 安装的信息。这是标准 MSI 或 Zip 文件的单独下载。
1 | 笔记 |
PDB 文件包含有关mysqld能够创建更详细的跟踪和转储文件的其他工具的更多详细信息 。您可以将这些与WinDbg或 Visual Studio 一起使用来调试 mysqld。
gdb下调试mysqld
在大多数系统上,如果mysqld崩溃,您还可以 从gdb启动mysqld以获取更多信息 。
如果您希望能够调试mysqld线程,则必须使用 Linux 上的 一些较旧的gdb版本。在这种情况下,您一次只能激活一个线程。 run --one-thread
在gdb下运行mysqld 时, NPTL 线程(Linux 上的新线程库)可能会导致问题。一些症状是:
- mysqld在启动期间挂起(在写入之前
ready for connections)。 - mysqld在
pthread_mutex_lock()orpthread_mutex_unlock()调用期间崩溃 。
使用堆栈跟踪
在某些操作系统上,如果mysqld意外终止,错误日志会包含堆栈跟踪。您可以使用它来找出mysqld在哪里(也许是为什么) 死亡。请参阅第 5.4.2 节,“错误日志”。要获得堆栈跟踪,您不能使用 gcc 选项编译mysqld-fomit-frame-pointer。
使用服务器日志查找mysqld错误原因
请注意,在启用常规查询日志的情况下启动mysqld之前,您应该使用myisamchk检查所有表 。
如果mysqld死掉或挂起,您应该在启用常规查询日志的情况下启动 mysqld。当mysqld 再次终止时,您可以检查日志文件的末尾以查找杀死mysqld的查询。
如果您使用默认的通用查询日志文件,日志将存储在数据库目录中,因为 *host_name*.log在大多数情况下,它是日志文件中的最后一个查询杀死了 mysqld,但如果可能,您应该通过重新启动mysqld并执行找到的查询来验证这一点来自mysql命令行工具。如果这有效,您还应该测试所有未完成的复杂查询。
您还可以EXPLAIN在所有SELECT需要很长时间的语句上尝试该命令 , 以确保mysqld正确使用索引。
您可以通过在启用慢查询日志的情况下启动mysqld来查找需要很长时间执行的查询。
如果您mysqld restarted在错误日志中找到文本(通常是一个名为 的文件 *host_name*.err),您可能已经找到了导致mysqld失败的查询 。如果发生这种情况,您应该使用myisamchk检查所有表(请参阅 第 5 章MySQL 服务器管理),并测试 MySQL 日志文件中的查询以查看是否有失败。如果您发现这样的查询,请先尝试升级到最新的 MySQL 版本。
如果您使用系统变量集启动mysqld, 如果表被标记为“未正确关闭”或“崩溃” myisam_recover_options,MySQL 会自动检查并尝试修复 MyISAM表。如果发生这种情况,MySQL 会在hostname.err文件中 写入一个条目,'Warning: Checking table ...'然后Warning: Repairing table是表是否需要修复。如果您收到很多这些错误,而mysqld之前没有意外死亡,那么就出了问题,需要进一步调查。
当服务器检测到MyISAM表损坏时,它会将附加信息写入错误日志,例如源文件的名称和行号,以及访问该表的线程列表。例子:Got an error from thread_id=1, mi_dynrec.c:368。这是包含在错误报告中的有用信息。
如果mysqld确实意外死亡,这不是一个好兆头,但在这种情况下,您不应调查 Checking table...消息,而应尝试找出mysqld死亡的原因。
如果您遇到表损坏,则制作测试用例
以下过程适用于 MyISAM表。有关遇到InnoDB 表损坏时应采取的步骤的信息,请参阅第 1.6 节 “如何报告错误或问题”。
如果您遇到损坏的MyISAM 表或如果mysqld在某些更新语句后总是失败,您可以通过执行以下操作来测试该问题是否可重现:
- 使用mysqladmin shutdown停止 MySQL 守护进程。
- 对表进行备份,以防万一修理造成不好的影响。
- 使用myisamchk -s database/*.MYI检查所有表。使用myisamchk -r database/ *
table*.MYI修复任何损坏的表 。 - 对表进行第二次备份。
- 如果您需要更多空间,请从 MySQL 数据目录中删除(或移走)任何旧的日志文件。
- 在启用二进制日志的情况下 启动mysqld。如果你想找到一个导致mysqld崩溃的语句 ,你应该在启用通用查询日志的情况下启动服务器。请参阅 第 5.4.3 节“通用查询日志”和 第 5.4.4 节“二进制日志”。
- 当你得到一个崩溃的表时,停止 mysqld服务器。
- 恢复备份。
- 在未启用二进制日志的情况下重新启动mysqld服务器 。
- 用mysqlbinlog binary-log-file重新执行语句| mysql。二进制日志保存在 MySQL 数据库目录中,名称为 .
hostname-bin.*NNNNNN* - 如果表再次损坏,或者您可以使用上述命令使 mysqld死亡,则您发现了一个可重现的错误。使用第 1.6 节“如何报告错误或问题”中给出的说明,将表和二进制日志通过 FTP 传输到我们的错误数据库 。如果您是支持客户,您可以使用 MySQL 客户支持中心 ( https://www.mysql.com/support/ ) 将问题告知 MySQL 团队并尽快修复。
详细说明可以参考官网:https://dev.mysql.com/doc/refman/8.0/en/debugging-server.html
调试 MySQL 客户端
为了能够使用集成调试包调试 MySQL 客户端,您应该使用 -DWITH_DEBUG=1
LOCK_ORDER 工具
MySQL 服务器是一个多线程应用程序,它使用许多内部锁定和与锁定相关的原语,例如互斥锁、rwlocks(包括 prlocks 和 sxlocks)、条件和文件。在服务器内,与锁相关的对象集随着新功能的实现和代码重构而发生变化,以提高性能。与任何使用锁定原语的多线程应用程序一样,当同时持有多个锁时,在执行过程中始终存在遇到死锁的风险。对于 MySQL 来说,死锁的影响是灾难性的,导致服务完全丢失。
从 MySQL 8.0.17 开始,为了能够检测锁定获取死锁并强制执行运行时执行是免费的,MySQL 支持 LOCK_ORDER 工具。这使得锁定顺序依赖图能够被定义为服务器设计的一部分,以及服务器运行时检查以确保锁定获取是非循环的并且执行路径符合该图。
本节提供有关使用 LOCK_ORDER 工具的信息,但仅限于基本级别。有关完整的详细信息,请参阅https://dev.mysql.com/doc/index-other.html上的 MySQL Server Doxygen 文档的锁定顺序部分。
DBUG 包
MySQL 服务器和大多数 MySQL 客户端都是使用最初由 Fred Fish 创建的 DBUG 包编译的。当你为调试配置 MySQL 时,这个包可以让你获得程序正在做什么的跟踪文件。
5. Mysql语句结构
5.1 语言结构
本章讨论了在使用 MySQL 时编写SQL语句的以下元素的规则:
- 文字值,例如字符串和数字
- 标识符,如数据库、表和列名
- 关键字和保留字
- 用户定义和系统变量
- 表达式
- 查询属性
- 评论
字符串
字符串是包含在单引号 ( ') 或双引号 ( ") 字符中的字节或字符序列。
表 9.1 特殊字符转义序列
| 转义序列 | 由序列表示的字符 |
|---|---|
\0 |
一个 ASCII NUL ( X'00') 字符 |
\' |
单引号 ( ') 字符 |
\" |
双引号 ( ") 字符 |
\b |
退格字符 |
\n |
换行(换行)字符 |
\r |
一个回车符 |
\t |
一个制表符 |
\Z |
ASCII 26 (Control+Z);见表后面的注释 |
\\ |
反斜杠 ( \) 字符 |
\% |
一个%字符;见表后面的注释 |
\_ |
一个_字符;见表后面的注释 |
数字文字
数字字面量包括精确值(int和 DECIMAL)字面量和近似值(float)字面量。
日期和时间文字
日期和时间值可以用多种格式表示,例如带引号的字符串或数字,具体取决于值的确切类型和其他因素。例如,在上下文中,其中的MySQL预计日期时,它解释任何的 '2015-07-21','20150721'以及20150721作为一个日期。
十六进制文字
十六进制文字值使用或 符号书写 ,其中包含十六进制数字 ( , )。数字和任何前导的字母都无关紧要。前导区分大小写,不能写为. X'*val*'``0x*val*val0..9``A..F``X``0x``0X
位值文字
位值文字使用或 符号编写 。 是使用零和一写入的二进制值。任何领先的字母 都无关紧要。前导区分大小写,不能写为. b'*val*'``0b*val*valb``0b``0B
布尔文字
常数TRUE和 分别FALSE计算为1和 0。常量名可以用任何字母大小写。
NULL值
该NULL值表示“无数据。” NULL可以写成任何字母。
请注意,该NULL值不同于0数字类型的值或字符串类型的空字符串。
标识符
MySQL 中的某些对象,包括数据库、表、索引、列、别名、视图、存储过程、分区、表空间、资源组和其他对象名称,称为标识符。本节描述 MySQL 中标识符的允许语法。
关键字和保留字
关键字是在 SQL 中具有重要意义的词。某些关键字,如SELECT, DELETE或 BIGINT,被保留,需要用作标识符,例如表和列名特殊待遇。对于内置函数的名称,这也可能是正确的。
用户定义的变量
您可以在一个语句中将一个值存储在用户定义的变量中,然后在另一个语句中引用它。这使您能够将值从一个语句传递到另一个语句。
用户变量写为 ,其中变量名由字母数字字符、 、 和 组成。如果将用户变量名称引用为字符串或标识符(例如, 、 或),则用户变量名称可以包含其他字符。 @*var_name*var_name.``_``$``@'my-var'``@"my-var"``@my-var``
查询属性
SQL 语句最明显的部分是语句的文本。从 MySQL 8.0.23 开始,客户端还可以定义适用于发送到服务器执行的下一条语句的查询属性:
- 在发送语句之前定义属性。
- 属性存在直到语句执行结束,此时属性集被清除。
- 虽然存在属性,但可以在服务器端访问它们。
可以使用查询属性的方式示例:
- Web 应用程序生成生成数据库查询的页面,并且对于每个查询必须跟踪生成它的页面的 URL。
- 应用程序为每个查询传递额外的处理信息,以供诸如审计插件或查询重写插件之类的插件使用。
注释
MySQL Server 支持三种注释样式:
- 从一个
#字符到行尾。 - 从一个
--序列到行尾。在 MySQL 中,--(双破折号)注释样式要求第二个破折号后跟至少一个空格或控制字符(例如空格、制表符、换行符等)。 - 从一个
/*序列到下一个*/序列,就像在 C 编程语言中一样。此语法使注释可以扩展到多行,因为开始和结束序列不需要在同一行上。
5.2 字符集、排序规则、Unicode
**一般的字符集和排序规则 **
一个 字符集 是一组 符号和编码。 一种 整理 是一组规则 用于比较字符集中的字符。 让我们做 用一个虚构字符集的例子来区分清楚。
假设我们有一个包含四个字母的字母表: A, B, a, b. 我们给每个字母一个数字: A = 0, B = 1, a = 2, b= 3. 信 A是一个符号,数字 0 是 编码 为 A,以及 所有四个字母及其编码的组合是 字符集 。
假设我们要比较两个字符串值, A和 B. 最简单的方法 这样做是为了查看编码:0 表示 A 和 1 为 B. 因为 0 小于 1,所以我们说 A小于 B. 我们所拥有的 刚刚完成的是对我们的字符集应用排序规则。 整理 是一组规则(在这种情况下只有一个规则): “ 比较 编码。 ” 我们称之为最简单的可能 整理一个 二进制文件 整理。
但是如果我们想说小写和大写 字母是等价的吗? 那么我们至少会有两条规则:(1) 处理小写字母 a和 b相当于 A和 B; (2) 然后比较编码。 我们称之为 一个 不区分大小写 整理。 它比二进制排序规则稍微复杂一些。
在现实生活中,大多数字符集都有很多字符:不仅仅是 A和 B但整个字母表, 有时多个字母或东方书写系统 数千个字符,以及许多特殊符号和 标点符号。 同样在现实生活中,大多数排序规则都有很多 规则,不仅仅是区分大小写,还有 是否区分重音( “ 重音 ” 是 附加到字符的标记,如德语 Ö), 并且对于多字符映射(例如 Ö = OE在两者之一 德国校对)。
MySQL 中的字符集和排序规则
MySQL Server 支持多种字符集,包括几个 Unicode 字符集。 要显示可用的字符集, 使用 INFORMATION_SCHEMA CHARACTER_SETS表或 SHOW CHARACTER SET陈述。
元数据的 UTF-8
元数据 是 “ 的 关于数据的数据。 ” 任何 描述 数据库——而不是 作为 的 内容 数据库 ——是 元数据。 因此列名、数据库名、用户名、版本 名称,并且大部分字符串来自 SHOW是元数据。 这也是 表中的内容为真 INFORMATION_SCHEMA因为那些表 定义包含有关数据库对象的信息。
元数据的表示必须满足以下要求:
- 所有元数据必须在相同的字符集中。 否则,无论是
SHOW声明也不SELECT声明,表INFORMATION_SCHEMA会工作正确,因为同一列中的不同行这些操作的结果将具有不同的特征套。 - 元数据必须包括所有语言的所有字符。 否则,用户将无法命名列和表使用他们自己的语言。
指定字符集和排序规则
字符集和排序规则有四个级别的默认设置:服务器、数据库、表和列。以下部分的描述可能看起来很复杂,但在实践中已经发现,多级违约会导致自然而明显的结果。
字符集问题不仅会影响数据存储,还会影响客户端程序和 MySQL 服务器之间的通信。如果您希望客户端程序使用不同于默认字符集的字符集与服务器通信,则需要指明是哪个字符集。例如,要使用utf8mb4 Unicode 字符集,请在连接到服务器后发出以下语句:
1 | SET NAMES 'utf8mb4'; |
字符串文字字符集和校对
每个字符串文字都有一个字符集和一个排序规则。
配置应用字符集和排序规则
对于使用默认 MySQL 字符集和排序规则 ( utf8mb4, utf8mb4_0900_ai_ci)存储数据的应用程序,不需要特殊配置。如果应用程序需要使用不同的字符集或排序规则存储数据,您可以通过多种方式配置字符集信息:
- 指定每个数据库的字符设置。例如,使用一个数据库的应用程序可能使用默认值
utf8mb4,而使用另一个数据库的应用程序可能使用sjis。 - 在服务器启动时指定字符设置。这会导致服务器对所有不进行其他安排的应用程序使用给定的设置。
- 如果您从源代码构建 MySQL,请在配置时指定字符设置。这会导致服务器使用给定设置作为所有应用程序的默认设置,而不必在服务器启动时指定它们。
错误信息字符集
服务器构造错误消息如下:
- 消息模板使用 UTF-8 (
utf8mb3)。 - 消息模板中的参数将替换为适用于特定错误发生的值:
- 诸如表名或列名之类的标识符在内部使用 UTF-8,因此它们按原样复制。
- 字符(非二进制)字符串值从其字符集转换为 UTF-8。
- 对于
0x20to 范围内的字节,二进制字符串值按原样复制0x7E,并\x对该范围外的字节使用十六进制编码。例如,如果对于一个试图插入发生重复键错误0x41CF9F成VARBINARY唯一的列,得到的错误消息使用UTF-8与一些字节的十六进制编码。
在 SQL 语句中使用 COLLATE
使用该COLLATE子句,您可以覆盖用于比较的任何默认排序规则。
该COLLATE子句具有高优先级(高于||),因此以下两个表达式是等效的:
1 | x || y COLLATE z |
3 字节和 4 字节 Unicode 字符集之间的转换
在utf8mb3和utf8mb4 字符集的区别如下:
utf8mb3仅支持基本多语言平面 (BMP) 中的字符。utf8mb4此外还支持位于 BMP 之外的补充字符。utf8mb3每个字符最多使用三个字节。utf8mb4每个字符最多使用四个字节。
1 | 笔记 |
表可以从被转换utf8mb3到 utf8mb4通过使用ALTER TABLE。假设一个表具有以下定义:
1 | CREATE TABLE t1 ( |
以下语句转换t1为 use utf8mb4:
1 | ALTER TABLE t1 |
从utf8mb3to 转换时utf8mb4的问题是列或索引键的最大长度以字节为单位保持不变 。因此,它在字符方面更小, 因为字符的最大长度是四个字节而不是三个字节。对于 CHAR, VARCHAR和 TEXT数据类型,将你的MySQL表时观看了这些问题:
- 检查
utf8mb3列的所有定义并确保它们不超过存储引擎的最大长度。 - 检查
utf8mb3列上的所有索引并确保它们不超过存储引擎的最大长度。有时最大值会因存储引擎的增强而改变。
5.3 数据类型
1. 数字数据类型
对于整数数据类型, M表示 最大显示宽度。 最大显示宽度为 255。 显示宽度与类型可以取值的范围无关 存储。
对于浮点和定点数据类型, *M*是可以存储总位数。
从 MySQL 8.0.17 开始,不推荐使用 display width 属性 对于整数数据类型; 你应该期待它的支持 在未来版本的 MySQL 中删除。
如果您指定 ZEROFILL对于数字列, MySQL 会自动添加 UNSIGNED 列的属性。
从 MySQL 8.0.17 开始, ZEROFILL属性是 不推荐用于数字数据类型; 你应该期待支持 它将在未来版本的 MySQL 中删除。 考虑使用 产生此属性效果的替代方法。 为了 例如,应用程序可以使用 LPAD()零垫功能 数字达到所需的宽度,或者他们可以存储 格式化的数字 CHAR 列。
允许的数值数据类型 UNSIGNED 属性也允许 SIGNED. 然而,这些 数据类型默认是有符号的,所以 SIGNED属性没有效果。
整数类型(精确值)——INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT
MySQL 支持 SQL 标准整数类型 INTEGER(或者 INT) 和 SMALLINT. 作为标准的延伸, MySQL 还支持整数类型 TINYINT, MEDIUMINT, 和 BIGINT. 下表显示了 每个整数类型所需的存储和范围。
表 11.1 MySQL 支持的整数类型所需的存储和范围
| 类型 | 存储(字节) | 有符号的最小值 | 最小值无符号 | 最大值有符号 | 最大值无符号 |
|---|---|---|---|---|---|
TINYINT |
1 | -128 |
0 |
127 |
255 |
SMALLINT |
2 | -32768 |
0 |
32767 |
65535 |
MEDIUMINT |
3 | -8388608 |
0 |
8388607 |
16777215 |
INT |
4 | -2147483648 |
0 |
2147483647 |
4294967295 |
BIGINT |
8 | -263 |
0 |
263-1 |
264-1 |
定点类型(精确值)- DECIMAL、NUMERIC
这 DECIMAL和 NUMERIC类型存储精确的数字数据值。 这些类型用于 保持精确的精度很重要,例如 货币数据。 在 MySQL 中, NUMERIC是 实施为 DECIMAL,所以下面 关于的评论 DECIMAL同样适用于 NUMERIC.
在一个 DECIMAL列声明, 可以(并且通常是)指定精度和比例。 为了 例子:
1 | salary DECIMAL(5,2) |
在这个例子中, 5是精度和 2是规模。 精度代表 为值存储的有效位数,以及 scale 表示可以存储的位数 小数点后。 标准 SQL 要求 DECIMAL(5,2)是 能够存储任何五位数和两位小数的值,所以 可以存储在 salary 列范围从 -999.99到 999.99.
在标准 SQL 中,语法 DECIMAL(M)是 相当于 DECIMAL(M,0). 同样,语法 DECIMAL是等价的 到 DECIMAL(M,0), 允许实现决定的值 M. MySQL 支持这两个 的变体形式 DECIMAL句法。 默认的 的价值 M是 10。 如果比例为 0, DECIMAL值不包含 小数点或小数部分。
**浮点类型(近似值)- FLOAT、DOUBLE **
这 FLOAT和 DOUBLE类型 表示近似的数字数据值。 MySQL 使用四个字节 用于单精度值和 8 个字节用于双精度 值。
为了 FLOAT,SQL 标准允许 精度的可选规范(但不是范围 指数),以关键字后的位为单位 FLOAT在括号内; ; 那是, FLOAT(p). MySQL 也支持这个可选的精度规范,但是 中的精度值 FLOAT(p)仅用于确定存储大小。 从 0 到 23 的精度 结果是 4 字节的单精度 FLOAT柱子。 从 24 到 53 的精度导致 8 字节 双精度 DOUBLE柱子。
MySQL 允许使用非标准语法: FLOAT(M,D) 要么 REAL(M,D) 要么 DOUBLE PRECISION(M,D). 这里, (M,D) 意味着比值可以存储多达 M总位数,其中 D数字可能在小数点之后 观点。 例如,定义为的列 FLOAT(7,4)显示为 -999.9999. MySQL 在以下情况下执行舍入 存储值,所以如果你插入 999.00009 成 FLOAT(7,4)列,近似 结果是 999.0001.
从 MySQL 8.0.17 开始,非标准的 FLOAT(M,D) 和 DOUBLE(M,D)语法已被弃用,您应该期待对它的支持 在未来版本的 MySQL 中删除。 因为浮点值是近似值而不是存储为 精确值,尝试在比较中将它们视为精确值可能会 导致问题。 他们也受制于平台或 实现依赖。
**位值类型——BIT **
这 BIT数据类型用于存储位 值。 一种 BIT(M)启用 储存 M-位值。 M范围从 1 到 64。 要指定位值, b’value’符号 可以使用。 value是一个二进制值 用零和一书写。 例如, b’111’和 b’10000000’分别代表 7 和 128。
如果你给一个值赋值 BIT(M)列 小于 M位长,值 在左边用零填充。 例如,赋值 的 b’101’到 BIT(6) 列实际上与分配相同 b’000101’
**超出范围和溢出处理 **
当 MySQL 在一个数字列中存储一个值时 列数据类型的允许范围,结果取决于当时生效的 SQL 模式:
如果启用了严格的 SQL 模式,MySQL 将拒绝 超出范围的值有错误,插入失败,在符合 SQL标准。
如果没有启用限制模式,MySQL 会裁剪该值 到列数据类型范围的适当端点 并存储结果值。
当将超出范围的值分配给整数列时, MySQL存储代表对应的值 列数据类型范围的端点。
当一个浮点或定点列被分配一个 超出指定范围的值(或 默认)精度和比例,MySQL 存储值 表示该范围的相应端点。
假设一个表 t1有这个定义:
1 | CREATE TABLE t1 (i1 TINYINT, i2 TINYINT UNSIGNED); |
启用严格 SQL 模式后,会出现超出范围的错误:
1 | mysql> SET sql_mode = 'TRADITIONAL'; |
如果未启用严格的 SQL 模式,则会发生带有警告的剪辑:
1 | mysql> SET sql_mode = ''; |
2.日期和时间数据类型
表示时间值的日期和时间数据类型是 DATE, TIME, DATETIME, TIMESTAMP, 和 YEAR. 每个时间类型都有一个 有效值的范围,以及一个 “ 零 ” 值 当您指定一个 MySQL 不能的无效值时可以使用 代表。 这 TIMESTAMP和 DATETIME类型有特殊 自动更新行为。
下表显示了每种类型的“零”值的格式。 “零”值是特殊的,但您可以使用表中显示的值显式存储或引用它们。 您也可以使用更容易编写的值“0”或 0 来执行此操作。 对于包含日期部分(DATE、DATETIME 和 TIMESTAMP)的时间类型,使用这些值可能会产生警告或错误。 确切的行为取决于启用了严格和 NO_ZERO_DATE SQL 模式中的哪一个(如果有); 请参阅第 5.1.11 节,“服务器 SQL 模式”。
| Data Type | “Zero” Value |
|---|---|
DATE |
'0000-00-00' |
TIME |
'00:00:00' |
DATETIME |
'0000-00-00 00:00:00' |
TIMESTAMP |
'0000-00-00 00:00:00' |
YEAR |
0000 |
尽管 MySQL 尝试以多种格式解释值,日期部分必须始终按年-月-日顺序给出。
日期和时间格式
DATE
支持的范围是 ‘1000-01-01’到 ‘9999-12-31’. MySQL 显示 DATE值在 ‘YYYY-MM-DD’ 格式,但允许将值分配给 DATE使用任一列 字符串或数字
DATETIME(fsp)
日期和时间的组合。 支持的范围是 ‘1000-01-01 00:00:00.000000’到 ‘9999-12-31 23:59:59.999999’. MySQL 显示 DATETIME值在 ‘YYYY-MM-DD hh:mm:ss[.fraction]’ 格式,但允许将值分配给 DATETIME使用任一列 字符串或数字。
一个可选的 fsp中的价值 可以给出从 0 到 6 的范围来指定小数秒 精确。 值为 0 表示没有 小数部分。 如果省略,则默认精度为 0。 自动初始化并更新到当前日期 和时间 DATETIME列 可以使用指定 DEFAULT和 ON UPDATE列定义子句
TIMESTAMP(fsp)
一个时间戳。 范围是 ‘1970-01-01 00:00:01.000000’UTC 至 ‘2038-01-19 03:14:07.999999’世界标准时间。 TIMESTAMP值被存储 作为自纪元以来的秒数 ( ‘1970-01-01 00:00:00’世界标准时间)。 一种 TIMESTAMP不能代表 价值 ‘1970-01-01 00:00:00’因为 相当于从纪元和值开始的 0 秒 0 保留用于表示 ‘0000-00-00 00:00:00’, “ 零 ” TIMESTAMP价值。 一个可选的 fsp中的价值 可以给出从 0 到 6 的范围来指定小数秒 精确。 值为 0 表示没有 小数部分。 如果省略,则默认精度为 0。
TIME(fsp)
一个时间。 范围是 ‘-838:59:59.000000’ 到 ‘838:59:59.000000’. MySQL 显示 TIME值在 ‘hh:mm:ss[.fraction]’ 格式,但允许将值分配给 TIME使用任一列 字符串或数字。 一个可选的 fsp中的价值 可以给出从 0 到 6 的范围来指定小数秒 精确。 值为 0 表示没有 小数部分。 如果省略,则默认精度为 0。
YEAR(4)
4 位数字格式的年份。 MySQL 显示 YEAR值在 YYYY格式,但允许 赋值给 YEAR使用字符串或数字的列。 值显示为 1901到 2155, 要么 0000.
这 SUM()和 AVG()聚合函数不 处理时间值。 (他们将值转换为数字, 在第一个非数字字符后丢失所有内容。)工作 围绕这个问题,转换为数字单位,执行 聚合操作,并转换回时间值。 例子:
1 | SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(time_col))) FROM tbl_name; |
这 DATEtype 用于带有日期的值 部分但没有时间部分。 MySQL检索并显示 DATE值在 ‘YYYY-MM-DD’格式。 支持的范围是 ‘1000-01-01’ 到 ‘9999-12-31’.
这 DATETIME类型用于值 包含日期和时间部分。 MySQL检索并显示 DATETIME值在 ‘YYYY-MM-DD hh:mm:ss’格式。 支持的范围是 ‘1000-01-01 00:00:00’到 ‘9999-12-31 23:59:59’.
这 TIMESTAMP数据类型用于值 包含日期和时间部分。 TIMESTAMP有一个范围 ‘1970-01-01 00:00:01’UTC 至 ‘2038-01-19 03:14:07’世界标准时间。
一种 DATETIME或者 TIMESTAMPvalue 可以包含一个尾随小数秒部分,最多可达 微秒(6 位)精度。 特别是,任何分数 插入到一个值的一部分 DATETIME要么 TIMESTAMP列被存储而不是 丢弃。 包含小数部分后,格式为 这些值是 ‘YYYY-MM-DD hh:mm:ss[.fraction]’, 范围为 DATETIME值是 ‘1000-01-01 00:00:00.000000’到 ‘9999-12-31 23:59:59.999999’,以及范围 为了 TIMESTAMP值是 ‘1970-01-01 00:00:01.000000’到 ‘2038-01-19 03:14:07.999999’. 小数部分应该总是 与其余时间用小数点隔开; 没有其他 小数秒分隔符被识别。 信息 关于 MySQL 中的小数秒支持
请注意 MySQL 中日期值解释的某些属性:
MySQL 允许对指定为字符串的值使用“宽松”格式,其中任何标点字符都可以用作日期部分或时间部分之间的分隔符。在某些情况下,这种语法可能具有欺骗性。例如,
'10:11:12'由于 ,诸如此类的值可能看起来像时间值:,但'2010-11-12'如果在日期上下文中使用,则被解释为年份。该值'10:45:15'被转换为'0000-00-00'因为'45'不是有效月份。在日期和时间部分与小数秒部分之间识别的唯一分隔符是小数点。
服务器要求月和日值是有效的,而不仅仅是分别在 1 到 12 和 1 到 31 的范围内。禁用严格模式后,
'2004-04-31'将转换为 无效日期'0000-00-00'并生成警告。启用严格模式后,无效日期会产生错误。要允许此类日期,请启用ALLOW_INVALID_DATES.MySQL 不接受
TIMESTAMP在日或月列中包含零的值或无效日期的值。此规则的唯一例外是特殊的“零”值'0000-00-00 00:00:00',如果 SQL 模式允许此值。确切的行为取决于NO_ZERO_DATE是否启用了严格 SQL 模式和 SQL 模式;包含 2 位数年份值的日期不明确,因为世纪未知。MySQL 使用以下规则解释 2 位年份值:
- 范围内的年份值
00-69变为2000-2069。 - 范围内的年份值
70-99变为1970-1999。
- 范围内的年份值
具有 2 位数年份的日期值不明确,因为世纪未知。此类值必须解释为 4 位数字形式,因为 MySQL 在内部使用 4 位数字存储年份。
TIME类型
MySQLTIME以*'hh:mm:ss'*格式(或大小 *'hhh:mm:ss'*时值的格式)检索和显示值 。TIME值范围从 '-838:59:59'到 '838:59:59'。小时部分可能如此之大,因为该TIME类型不仅可以用于表示一天中的某个时间(必须小于 24 小时),还可以表示经过的时间或两个事件之间的时间间隔(可能远大于 24小时,甚至是负数)。
MySQL 可以识别TIME多种格式的值,其中一些格式可以包含高达微秒(6 位)精度的尾随小数秒部分。
为TIME列指定缩写值时要小心 。MySQL 将TIME带有冒号的缩写值解释 为一天中的时间。也就是说,'11:12'意味着 '11:12:00',而不是 '00:11:12'。MySQL 使用最右边的两个数字代表秒(即,作为经过时间而不是一天中的时间)的假设来解释没有冒号的缩写值。例如,您可能认为 '1112'and1112是指 '11:12:00'(11 点后 12 分钟),但 MySQL 将它们解释为'00:11:12'(11 分 12 秒)。同样,'12'和 12被解释为 '00:00:12'。
YEAR类型
该YEAR类型是用于表示年份值的 1 字节类型。它可以声明为 YEAR隐式显示宽度为 4 个字符,或等效为YEAR(4)显式显示宽度。
TIMESTAMP 和 DATETIME 的自动初始化和更新
TIMESTAMP和 DATETIME列可以自动初始化并更新为当前日期和时间(即当前时间戳)。
对于表中的任何TIMESTAMP或 DATETIME列,您可以将当前时间戳指定为默认值、自动更新值或两者:
- 自动初始化的列被设置为未指定列值的插入行的当前时间戳。
- 当行中任何其他列的值从其当前值更改时,自动更新的列会自动更新为当前时间戳。如果所有其他列都设置为其当前值,则自动更新的列保持不变。要防止自动更新的列在其他列更改时更新,请将其显式设置为其当前值。要在其他列未更改时更新自动更新的列,请将其显式设置为应具有的值(例如,将其设置为
CURRENT_TIMESTAMP)。
时间值中的小数秒
MySQL 对TIME、DATETIME和 TIMESTAMP值具有小数秒支持 , 精度高达微秒(6 位)
日期和时间类型之间的转换
在某种程度上,您可以将值从一种时态类型转换为另一种时态类型。但是,可能会有一些价值的改变或信息的丢失。在所有情况下,时间类型之间的转换都受结果类型的有效值范围的约束。例如,虽然 DATE、 DATETIME和 TIMESTAMP值都可以使用相同的格式集指定,但这些类型并不都具有相同的值范围。 TIMESTAMP值不能早于1970UTC 或晚于 '2038-01-19 03:14:07'UTC。这意味着诸如 的日期'1968-01-01'虽然作为 aDATE或 DATETIME值有效,但作为 值无效 TIMESTAMP并被转换为0.
3. 字符串数据类型
字符串数据类型CHAR, VARCHAR, BINARY, VARBINARY, BLOB, TEXT, ENUM,和 SET。
字符列比较和排序基于分配给列的排序规则。对于 CHAR, VARCHAR, TEXT, ENUM,和 SET数据类型,可以使用二进制(声明一个柱_bin)归类或所述 BINARY属性原因比较和排序,以使用底层字符代码值,而不是一个词汇顺序。
BINARY[(M)] 这 BINARY类型类似于 这 CHAR类型,但存储 二进制字节字符串而不是非二进制字符串。 可选长度 M代表 以字节为单位的列长度。 如果省略, M默认为 1。
VARBINARY(M) 这 VARBINARY类型相似 到 VARCHAR类型,但是 存储二进制字节字符串而不是非二进制字符 字符串。 M代表最大值 以字节为单位的列长度。
BLOB[(M)] 一种 BLOB具有最大值的列 长度为 65,535 (2 16 − 1) 字节。 每个 BLOB价值是 使用 2 字节长度的前缀存储,指示 值中的字节数。 可选长度 M可以给 对于这种类型。 如果这样做,MySQL 会创建该列作为 最小的 BLOB类型大 足以容纳价值 M字节 长。
TEXT[(M)] [CHARACTER SET charset_name] [COLLATE collation_name] 一种 TEXT具有最大值的列 长度为 65,535 (2 16 − 1) 人物。 有效最大长度小于 值包含多字节字符。 每个 TEXT值使用一个存储 2 字节长度的前缀,表示其中的字节数 价值。 可选长度 M可以给 对于这种类型。 如果这样做,MySQL 会创建该列作为 最小的 TEXT类型大 足以容纳价值 M 长字符。
ENUM(‘value1’,’value2’,…) [CHARACTER SET charset_name] [COLLATE collation_name] 一个枚举。 一个只能有一个的字符串对象 值,从值列表中选择 ‘value1’, ‘value2’, …, NULL或者 特别的 ‘’错误值。 ENUM值被表示 内部为整数。 一个 ENUM列可以有一个 最多 65,535 个不同的元素。 个人最大支持长度 ENUM元素是 M<= 255 和 ( MX w) <= 1020,其中 M是元素文字长度和 w是所需的字节数 用于字符集中的最大长度字符。
SET(‘value1’,’value2’,…) [CHARACTER SET charset_name] [COLLATE collation_name] 一套。 一个可以有零个或多个值的字符串对象, 每个都必须从值列表中选择 ‘value1’, ‘value2’, … SET 值在内部表示为整数。 一种 SET列可以有一个 最多 64 个不同的成员。 个人最大支持长度 SET元素是 M<= 255 和 ( MX w) <= 1020,其中 M是元素文字长度和 w是所需的字节数 用于字符集中的最大长度字符。
CHAR 和 VARCHAR 类型
该CHAR和VARCHAR类型相似,但它们被存储和检索的方式不同。它们在最大长度和是否保留尾随空格方面也不同。
该CHAR和VARCHAR类型的声明,其长度指示要存储的最大字符数。例如, CHAR(30)最多可容纳 30 个字符。
一个长度CHAR列被固定在创建表声明的长度。长度可以是 0 到 255 之间的任何值。CHAR 存储值时,它们会用空格右填充到指定的长度。当CHAR被检索到的值,拖尾的空格被删除,除非 PAD_CHAR_TO_FULL_LENGTH启用SQL模式。
VARCHAR列中的 值是可变长度的字符串。长度可以指定为 0 到 65,535 之间的值。a 的有效最大长度 VARCHAR受最大行大小(65,535 字节,在所有列之间共享)和使用的字符集的约束。
与 相比CHAR, VARCHAR值存储为 1 字节或 2 字节长度的前缀加数据。长度前缀表示值中的字节数。如果值需要不超过 255 个字节,则列使用一个长度字节,如果值可能需要超过 255 个字节,则使用两个长度字节。
如果未启用严格 SQL 模式并且您为CHAR或VARCHAR列分配的值 超过该列的最大长度,则该值将被截断以适合并生成警告。对于非空格字符的截断,您可以使用严格的 SQL 模式导致发生错误(而不是警告)并禁止插入值。
对于VARCHAR列,超出列长度的尾随空格在插入之前被截断并生成警告,无论使用何种 SQL 模式。对于 CHAR列,无论 SQL 模式如何,都会以静默方式从插入的值中截断多余的尾随空格。
VARCHAR值在存储时不会被填充。根据标准 SQL,在存储和检索值时保留尾随空格。
下表说明之间的差别 CHAR和VARCHAR通过显示各种字符串值存储到的结果 CHAR(4)和VARCHAR(4) 列(假设该列使用单字节字符集,例如latin1)。
| 值 | CHAR(4) |
需要存储 | VARCHAR(4) |
需要存储 |
|---|---|---|---|---|
'' |
' ' |
4字节 | '' |
1 字节 |
'ab' |
'ab ' |
4字节 | 'ab' |
3 个字节 |
'abcd' |
'abcd' |
4字节 | 'abcd' |
5 字节 |
'abcdefgh' |
'abcd' |
4字节 | 'abcd' |
5 字节 |
显示为存储在表最后一行的值仅适用 于不使用严格 SQL 模式的情况;如果启用了严格模式,则不会存储超过列长度的值 ,并导致错误。
InnoDB将长度大于或等于 768 字节的固定长度字段编码为可变长度字段,可以在页外存储。例如, CHAR(255)如果字符集的最大字节长度大于 3,则列可以超过 768 个字节,就像使用utf8mb4.
对于删除尾随填充字符或比较忽略它们的情况,如果列具有需要唯一值的索引,则将仅在尾随填充字符数上不同的值插入到列中会导致重复键错误。例如,如果表包含'a',则尝试存储'a '会导致重复键错误。
BINARY 和 VARBINARY 类型
该BINARY和VARBINARY 类型相似,CHAR并且 VARCHAR,不同的是它们存储二进制字符串,而非非二进制字符串。也就是说,它们存储字节串而不是字符串。这意味着它们具有binary字符集和排序规则,并且比较和排序基于值中字节的数值。
所允许的最大长度是相同的 BINARY,并VARBINARY因为它是为CHAR和 VARCHAR,不同之处在于对于长度BINARY和VARBINARY 以字节为单位而不是字符被测量。
BLOB 和 TEXT 类型
一种 BLOB是一个二进制大对象,可以容纳 可变数量的数据。 四个 BLOB 类型是 TINYBLOB, BLOB, MEDIUMBLOB, 和 LONGBLOB. 这些仅在它们可以使用的值的最大长度上有所不同 抓住。 四个 TEXT类型是 TINYTEXT, TEXT, MEDIUMTEXT, 和 LONGTEXT. 这些对应于四个 BLOB类型和 具有相同的最大长度和存储要求。
BLOB值被视为二进制字符串 (字节字符串)。 他们有 binary字符集和排序规则,以及比较和排序是 基于列值中字节的数值。 TEXT值被视为非二进制字符串 (字符串)。 他们有一个字符集,而不是 binary, 并对值进行排序和比较 基于字符集的排序规则。
ENUM 类型
一个 ENUM是一个带有值的字符串对象 从枚举的允许值列表中选择 在表创建时明确地在列规范中。
该ENUM类型具有以下优点:
- 在列具有有限的可能值集的情况下压缩数据存储。您指定为输入值的字符串会自动编码为数字。
- 可读的查询和输出。这些数字被转换回查询结果中的相应字符串。
枚举值必须是带引号的字符串文字。例如,您可以创建一个包含ENUM如下列的表 :
1 | CREATE TABLE shirts ( |
将 100 万行插入到这个表中 'medium'需要 100 万字节的存储空间,而如果将实际字符串存储'medium'在 VARCHAR列中则需要 600 万字节。
每个枚举值都有一个索引:
列规范中列出的元素分配有索引号,从 1 开始。
空字符串错误值的索引值为 0。这意味着您可以使用以下
SELECT语句查找ENUM分配了无效值的行:1
mysql> SELECT * FROM tbl_name WHERE enum_col=0;
NULL值的索引是NULL。这里的术语“索引”指的是枚举值列表中的一个位置。它与表索引无关。
例如,指定为的列ENUM('Mercury', 'Venus', 'Earth')可以具有此处显示的任何值。还显示了每个值的索引。
| 价值 | 指数 |
|---|---|
NULL |
NULL |
'' |
0 |
'Mercury' |
1 |
'Venus' |
2 |
'Earth' |
3 |
一ENUM列最多可以有 65,535 个不同的元素。
如果ENUM在数字上下文中检索值,则返回列值的索引。例如,您可以ENUM像这样从列中检索数值 :
1 | mysql> SELECT enum_col+0 FROM tbl_name; |
ENUM创建表时, 会自动从表定义中的成员值中删除尾随空格 。
检索时,存储在ENUM 列中的值将使用列定义中使用的字母大小写显示。请注意,ENUM可以为列分配字符集和排序规则。对于二进制或区分大小写的排序规则,在为列分配值时会考虑字母大小写。
如果将数字存储到ENUM列中,则该数字将被视为可能值的索引,并且存储的值是具有该索引的枚举成员。(然而,这并不能一起工作 LOAD DATA,如果数值是引用,它仍然解释为索引,如果在枚举值的列表中没有匹配的字符串,它把所有的输入为字符串)。由于这些原因,不建议ENUM使用看起来像数字的枚举值来定义列,因为这很容易变得混乱。例如,下面的列具有的字符串值枚举成员 '0','1'和 '2',但数值索引值 1,2以及 3:
1 | numbers ENUM('0','1','2') |
如果您存储2,它将被解释为索引值,并变为'1'(索引为 2 的值)。如果您存储'2',它会匹配一个枚举值,因此它存储为 '2'。如果您存储'3',则它不匹配任何枚举值,因此将其视为索引并变为'2'(索引为 3 的值)。
1 | mysql> INSERT INTO t (numbers) VALUES(2),('2'),('3'); |
我们强烈建议您不要不使用数字作为枚举值,因为它不保存在存储在适当 TINYINT或 SMALLINT类型,很容易混淆,如果你的字符串和基本数值(这可能是不一样的)引用ENUM错误的 值。如果确实使用数字作为枚举值,请始终将其括在引号中。如果省略引号,则将数字视为索引。
如果启用了严格的 SQL 模式,定义中的重复值会导致警告或错误。
SET 类型
一种 SET是一个可以为零的字符串对象 或多个值,每个值都必须从一个列表中选择 创建表时指定的允许值。 SET由多个组成的列值 集合成员用逗号分隔的成员指定 ( ,)。 这样做的结果是 SET成员价值不应该是自己 包含逗号。
一种 SET列可以有一个最大值 64 个不同的成员。
定义中的重复值会导致警告或错误 如果启用了严格的 SQL 模式。
尾随空格被自动删除 SET表定义中的成员值 创建表时。
4. 空间数据类型
遵循 OGC 规范,MySQL 将空间扩展实现为具有几何类型环境的SQL的子集。该术语是指已使用一组几何类型进行扩展的 SQL 环境。几何值 SQL 列实现为具有几何类型的列。该规范描述了一组 SQL 几何类型,以及用于创建和分析几何值的这些类型的函数。
MySQL 空间扩展支持地理特征的生成、存储和分析:
- 表示空间值的数据类型
- 用于操作空间值的函数
- 空间索引以改善对空间列的访问时间
空间数据类型和函数可用于 MyISAM, InnoDB, NDB,和 ARCHIVE表。用于索引空间列,MyISAM并InnoDB 支持SPATIAL和非SPATIAL索引。其他存储引擎支持非SPATIAL索引
一个地理特征是具有位置世上任何东西。一个特性可以是:
- 一个实体。例如,一座山、一座池塘、一座城市。
- 空间。例如,镇区,热带地区。
- 一个可定义的位置。例如,十字路口,作为两条街道相交的特定地方。
一些文档使用术语地理空间特征来指代地理特征。
几何是表示地理特征的另一个词。最初几何这个词的 意思是地球的测量。另一个含义来自制图,指的是制图师用来绘制世界地图的几何特征。
此处的讨论将这些术语视为同义词: 地理特征、 地理空间特征、 特征或 几何。最常用的术语是几何,定义为 一个点或点的集合,代表世界上任何具有位置的事物。
以下材料涵盖了这些主题:
- MySQL模型中实现的空间数据类型
- OpenGIS几何模型中空间扩展的基础
- 表示空间数据的数据格式
- 如何在 MySQL 中使用空间数据
- 空间数据索引的使用
- MySQL 与 OpenGIS 规范的区别
一些空间数据类型保存单个几何值:
GEOMETRYPOINTLINESTRINGPOLYGON
GEOMETRY可以存储任何类型的几何值。其他单值类型(POINT、 LINESTRING和POLYGON)将它们的值限制为特定的几何类型。
其他空间数据类型保存值的集合:
MULTIPOINTMULTILINESTRINGMULTIPOLYGONGEOMETRYCOLLECTION
GEOMETRYCOLLECTION可以存储任何类型的对象的集合。其他集合类型(MULTIPOINT、 MULTILINESTRING和 MULTIPOLYGON)将集合成员限制为具有特定几何类型的成员。
两种标准空间数据格式用于表示几何查询中的对象:
- 众所周知的文本 (WKT) 格式
- 众所周知的二进制 (WKB) 格式
5. Json数据类型
MySQL 支持JSON由RFC 7159定义的本机数据类型,可以有效访问 JSON(JavaScript Object Notation)文档中的数据。该 JSON数据类型提供了这些优点超过存储在字符串列JSON格式的字符串:
- 自动验证存储在
JSON列中的 JSON 文档 。无效的文档会产生错误。 - 优化的存储格式。存储在
JSON列中的JSON 文档 被转换为允许快速读取文档元素的内部格式。当服务器稍后必须读取以这种二进制格式存储的 JSON 值时,不需要从文本表示中解析该值。二进制格式的结构使服务器能够直接通过键或数组索引查找子对象或嵌套值,而无需读取文档中它们之前或之后的所有值。
存储JSON文档所需的空间与LONGBLOBor 大致相同LONGTEXT;有关更多信息,请参见 第 11.7 节,“数据类型存储要求”。请务必记住,存储在JSON列中的任何 JSON 文档的大小都限于max_allowed_packet系统变量的值。(当服务器在内存中内部处理 JSON 值时,它可以大于此值;服务器存储它时会应用限制。)您可以使用该JSON_STORAGE_SIZE()函数获取存储 JSON 文档所需的空间量 ;请注意,对于一个JSON 列,存储大小——以及由此函数返回的值——是列在对其执行任何部分更新之前使用的大小。
JSON列与其他二进制类型的列一样,不直接索引;相反,您可以在从列中提取标量值的生成列上创建索引 JSON。
MySQL 优化器还会在与 JSON 表达式匹配的虚拟列上查找兼容索引。
在 MySQL 8.0.17 及更高版本中,InnoDB 存储引擎支持 JSON 数组上的多值索引。
MySQL NDB Cluster 8.0 支持JSON列和 MySQL JSON 函数,包括在从列生成的JSON列上创建索引作为无法索引JSON列的解决方法。JSON每个NDB表最多支持3列 。
6. 其他
磁盘上表数据的存储要求取决于几个因素。不同的存储引擎表示数据类型和存储原始数据不同。表数据可能会针对一列或整行进行压缩,从而使表或列的存储需求计算变得复杂。
尽管磁盘上的存储布局存在差异,但用于交流和交换表行信息的内部 MySQL API 使用适用于所有存储引擎的一致数据结构。
6. Mysql引擎
存储引擎是 MySQL 组件,用于处理不同表类型的 SQL 操作。InnoDB是默认和最通用的存储引擎,Oracle 建议将它用于表,除非是特殊用例。(CREATE TABLEMySQL 8.0 中的语句InnoDB默认创建表。)
MySQL Server 使用可插拔存储引擎架构,使存储引擎能够加载到正在运行的 MySQL 服务器中和从中卸载。
要确定您的服务器支持哪些存储引擎,请使用该 SHOW ENGINES语句。Support列中的值表示是否可以使用引擎。的值YES, NO或DEFAULT表示发动机可用,不可用,或可用与当前被设置为默认的存储引擎。
MySQL 8.0 支持的存储引擎
InnoDB:MySQL 8.0 中的默认存储引擎。InnoDB是 MySQL 的事务安全(符合 ACID)存储引擎,具有提交、回滚和崩溃恢复功能来保护用户数据。InnoDB行级锁定(不升级到更粗粒度的锁定)和 Oracle 风格的一致非锁定读取增加了多用户并发性和性能。InnoDB将用户数据存储在聚集索引中,以减少基于主键的常见查询的 I/O。为了维护数据完整性,InnoDB还支持FOREIGN KEY引用完整性约束。有关 的更多信息InnoDB,请参阅 第 15 章InnoDB 存储引擎。MyISAM:这些表占用空间很小。 表级锁定 限制了读/写工作负载的性能,因此它通常用于 Web 和数据仓库配置中的只读或以读取为主的工作负载。Memory:将所有数据存储在 RAM 中,以便在需要快速查找非关键数据的环境中进行快速访问。这种发动机以前称为HEAP发动机。它的用例正在减少;InnoDB其缓冲池内存区域提供了一种通用且持久的方式来将大部分或所有数据保存在内存中,并NDBCLUSTER为庞大的分布式数据集提供快速的键值查找。CSV:它的表格实际上是具有逗号分隔值的文本文件。CSV 表允许您以 CSV 格式导入或转储数据,以便与读写相同格式的脚本和应用程序交换数据。由于 CSV 表未编入索引,因此您通常InnoDB在正常操作期间将数据保存在表中,并且仅在导入或导出阶段使用 CSV 表。Archive:这些紧凑的、未索引的表用于存储和检索大量很少引用的历史、存档或安全审计信息。Blackhole:Blackhole 存储引擎接受但不存储数据,类似于 Unix/dev/null设备。查询总是返回一个空集。这些表可用于复制配置,其中 DML 语句被发送到副本服务器,但源服务器不保留自己的数据副本。NDB(也称为NDBCLUSTER):这种集群数据库引擎特别适用于需要尽可能高的正常运行时间和可用性的应用程序。Merge:使 MySQL DBA 或开发人员能够对一系列相同的MyISAM表进行逻辑分组并将它们作为一个对象引用。适用于 VLDB 环境,例如数据仓库。Federated:提供链接单独的 MySQL 服务器以从多个物理服务器创建一个逻辑数据库的能力。非常适合分布式或数据集市环境。Example:这个引擎作为 MySQL 源代码中的一个例子,说明了如何开始编写新的存储引擎。它主要是开发人员感兴趣的。存储引擎是一个什么都不做的 “存根”。您可以使用此引擎创建表,但不能在其中存储或从中检索数据。
您不限于对整个服务器或架构使用相同的存储引擎。您可以为任何表指定存储引擎。例如,应用程序可能主要使用 InnoDB表,其中一个CSV 表用于将数据导出到电子表格,而一些 MEMORY表用于临时工作区。
| 特征 | MyISAM | Memory | InnoDB | Archive | NDB |
|---|---|---|---|---|---|
| B树索引 | 是的 | 是的 | 是的 | 不 | 不 |
| 备份/时间点恢复(注 1) | 是的 | 是的 | 是的 | 是的 | 是的 |
| 集群数据库支持 | 不 | 不 | 不 | 不 | 是的 |
| 聚集索引 | 不 | 不 | 是的 | 不 | 不 |
| 压缩数据 | 是(注 2) | 不 | 是的 | 是的 | 不 |
| 数据缓存 | 不 | 不适用 | 是的 | 不 | 是的 |
| 加密数据 | 是(注 3) | 是(注 3) | 是(注 4) | 是(注 3) | 是(注 3) |
| 外键支持 | 不 | 不 | 是的 | 不 | 是(注 5) |
| 全文检索索引 | 是的 | 不 | 是(注 6) | 不 | 不 |
| 地理空间数据类型支持 | 是的 | 不 | 是的 | 是的 | 是的 |
| 地理空间索引支持 | 是的 | 不 | 是(注 7) | 不 | 不 |
| 哈希索引 | 不 | 是的 | 否(注 8) | 不 | 是的 |
| 索引缓存 | 是的 | 不适用 | 是的 | 不 | 是的 |
| 锁定粒度 | 表 | 表 | 行 | 行 | 行 |
| MVCC | 不 | 不 | 是的 | 不 | 不 |
| 复制支持(注 1) | 是的 | 有限(注9) | 是的 | 是的 | 是的 |
| 存储限制 | 256TB | 内存 | 64TB | 没有任何 | 384EB |
| T树索引 | 不 | 不 | 不 | 不 | 是的 |
| 交易 | 不 | 不 | 是的 | 不 | 是的 |
| 更新数据字典的统计信息 | 是的 | 是的 | 是的 | 是的 | 是的 |
您可以通过设置default_storage_engine变量为当前会话设置默认存储引擎 :
1 | SET default_storage_engine=NDBCLUSTER; |
https://forums.mysql.com/list.php?21`MyISAM`提供了 一个专门讨论存储引擎的论坛。
6.1 InnoDB存储引擎
6.1.1 InnoDB 简介
InnoDB是一种兼顾高可靠性和高性能的通用存储引擎。在 MySQL 5.6 中,InnoDB是默认的 MySQL 存储引擎。除非您配置了不同的默认存储引擎,否则发出CREATE TABLE不带ENGINE 子句的语句会创建一个InnoDB表。
InnoDB 的主要优势
- 它的 DML 操作遵循 ACID 模型,事务具有提交、回滚和崩溃恢复功能,以保护用户数据。
- 行级锁定和 Oracle 风格的一致读取提高了多用户并发性和性能。
InnoDB表将您的数据排列在磁盘上以优化基于主键的查询。每个InnoDB表都有一个称为聚集索引的主键索引,用于组织数据以最小化主键查找的 I/O。- 为维护数据完整性,
InnoDB支持FOREIGN KEY约束。使用外键,检查插入、更新和删除以确保它们不会导致相关表之间的不一致。
1 | DML补充说明: |
6.1.2. InnoDB 存储引擎特性
| 特征 | 支持 |
|---|---|
| B树索引 | 是的 |
| 备份/时间点恢复(在服务器中实现,而不是在存储引擎中。) | 是的 |
| 集群数据库支持 | 不 |
| 聚集索引 | 是的 |
| 压缩数据 | 是的 |
| 数据缓存 | 是的 |
| 加密数据 | 是(通过加密函数在服务器中实现;在 MySQL 5.7 及更高版本中,支持静态数据加密。) |
| 外键支持 | 是的 |
| 全文检索索引 | 是(MySQL 5.6 及更高版本支持 FULLTEXT 索引。) |
| 地理空间数据类型支持 | 是的 |
| 地理空间索引支持 | 是(MySQL 5.7 及更高版本支持地理空间索引。) |
| 哈希索引 | 否(InnoDB 在内部利用哈希索引来实现其自适应哈希索引功能。) |
| 索引缓存 | 是的 |
| 锁定粒度 | 排 |
| MVCC | 是的 |
| 复制支持(在服务器中实现,而不是在存储引擎中。) | 是的 |
| 存储限制 | 64TB |
| T树索引 | 不 |
| 交易 | 是的 |
| 更新数据字典的统计信息 | 是的 |
1. MVCC
1 | MVCC |
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
什么是当前读和快照读?
在学习MVCC多版本并发控制之前,我们必须先了解一下,什么是MySQL InnoDB下的当前读和快照读?
- 当前读 像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
- 快照读 像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。
当前读,快照读和MVCC的关系
- 准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念
- 而在MySQL中,实现这么一个MVCC理想概念,我们就需要MySQL提供具体的功能去实现它,而快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能。而相对而言,当前读就是悲观锁的具体功能实现
- 要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC模型在MySQL中的具体实现则是由 3个隐式字段,undo日志 ,Read View 等去完成的,具体可以看下面的MVCC实现原理
MVCC能解决什么问题,好处是?
数据库并发场景有三种,分别为:
- 读-读:不存在任何问题,也不需要并发控制
- 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
- 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
MVCC带来的好处是?
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 所以MVCC可以为数据库解决以下问题
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
- 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
2. MVCC 实现原理
MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的。所以我们先来看看这个三个point的概念
隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
- DB_TRX_ID 6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
- DB_ROLL_PTR 7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
- DB_ROW_ID 6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
- 实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了
如上图,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本
undo日志
undo log主要分为两种:
- insert undo log 代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
- update undo log 事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
1 | purge |
对MVCC有帮助的实质是update undo log ,undo log实际上就是存在rollback segment中旧记录链,它的执行流程如下:
一、 比如一个有个事务插入persion表插入了一条新记录,记录如下,name为Jerry, age为24岁,隐式主键是1,事务ID和回滚指针,我们假设为NULL

二、 现在来了一个事务1对该记录的name做出了修改,改为Tom
- 在事务1修改该行(记录)数据时,数据库会先对该行加排他锁
- 然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本
- 拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它
- 事务提交后,释放锁

三、 又来了个事务2修改person表的同一个记录,将age修改为30岁
- 在事务2修改该行数据时,数据库也先为该行加锁
- 然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
- 修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
- 事务提交,释放锁

从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录(当然就像之前说的该undo log的节点可能是会purge线程清除掉,向图中的第一条insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)
作者:爱情小傻蛋 链接:https://www.jianshu.com/p/8845ddca3b23
版本链
我们先来理解一下版本链的概念。在InnoDB引擎表中,它的聚簇索引记录中有两个必要的隐藏列:
trx_id这个id用来存储的每次对某条聚簇索引记录进行修改的时候的事务id。
roll_pointer每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入操作的undo日志没有这个属性,因为它没有老版本)

比如现在有个事务id是60的执行的这条记录的修改语句。

此时在undo日志中就存在版本链。

ReadView
说了版本链我们再来看看ReadView。已提交读和可重复读的区别就在于它们生成ReadView的策略不同。
ReadView中主要就是有个列表来存储我们系统中当前活跃着的读写事务,也就是begin了还未提交的事务。通过这个列表来判断记录的某个版本是否对当前事务可见。假设当前列表里的事务id为[80,100]。
如果你要访问的记录版本的事务id为50,比当前列表最小的id80小,那说明这个事务在之前就提交了,所以对当前活动的事务来说是可访问的。
如果你要访问的记录版本的事务id为70,发现此事务在列表id最大值和最小值之间,那就再判断一下是否在列表内,如果在那就说明此事务还未提交,所以版本不能被访问。如果不在那说明事务已经提交,所以版本可以被访问。
如果你要访问的记录版本的事务id为110,那比事务列表最大id100都大,那说明这个版本是在ReadView生成之后才发生的,所以不能被访问。
这些记录都是去版本链里面找的,先找最近记录,如果最近这一条记录事务id不符合条件,不可见的话,再去找上一个版本再比较当前事务的id和这个版本事务id看能不能访问,以此类推直到返回可见的版本或者结束。
举个例子 ,在已提交读隔离级别下:
比如此时有一个事务id为100的事务,修改了name,使得的name等于小明2,但是事务还没提交。则此时的版本链是

那此时另一个事务发起了select 语句要查询id为1的记录,那此时生成的ReadView 列表只有[100]。那就去版本链去找了,首先肯定找最近的一条,发现trx_id是100,也就是name为小明2的那条记录,发现在列表内,所以不能访问。
这时候就通过指针继续找下一条,name为小明1的记录,发现trx_id是60,小于列表中的最小id,所以可以访问,直接访问结果为小明1。
那这时候我们把事务id为100的事务提交了,并且新建了一个事务id为110也修改id为1的记录,并且不提交事务

这时候版本链就是

这时候之前那个select事务又执行了一次查询,要查询id为1的记录。
这个时候关键的地方来了
如果你是已提交读隔离级别,这时候你会重新一个ReadView,那你的活动事务列表中的值就变了,变成了[110]。
按照上的说法,你去版本链通过trx_id对比查找到合适的结果就是小明2。
如果你是**可重复读隔离级别,这时候你的ReadView还是第一次select时候生成的ReadView,**也就是列表的值还是[100]。所以select的结果是小明1。所以第二次select结果和第一次一样,所以叫可重复读!
也就是说已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。
这就是Mysql的MVCC,通过版本链,实现多版本,可并发读-写,写-读。通过ReadView生成策略的不同实现不同的隔离级别。
转载至:https://baijiahao.baidu.com/s?id=1629409989970483292&wfr=spider&for=pc
作者:yes的练级攻略
3. 事务的四种隔离级别
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。下面通过事例一一阐述它们的概念与联系。
Read uncommitted
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。
事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。
分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。
那怎么解决脏读呢?Read committed!读提交,能解决脏读问题。
Read committed
读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…
分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。
那怎么解决可能的不可重复读问题?Repeatable read !
Repeatable read
重复读,就是在开始读取数据(事务开启)时,不再允许修改操作
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。
分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
什么时候会出现幻读?
事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。
那怎么解决幻读问题?Serializable!
Serializable 序列化
Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
值得一提的是:大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。Mysql的默认隔离级别是Repeatable read。
本文链接:https://blog.csdn.net/qq_33290787/article/details/51924963
4. 事务并发可能出现的情况
脏读(Dirty Read)
一个事务读到了另一个未提交事务修改过的数据

会话B开启一个事务,把id=1的name为武汉市修改成温州市,此时另外一个会话A也开启一个事务,读取id=1的name,此时的查询结果为温州市,会话B的事务最后回滚了刚才修改的记录,这样会话A读到的数据是不存在的,这个现象就是脏读。(脏读只在读未提交隔离级别才会出现)
不可重复读(Non-Repeatable Read)
一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。(不可重复读在读未提交和读已提交隔离级别都可能会出现)

会话A开启一个事务,查询id=1的结果,此时查询的结果name为武汉市。接着会话B把id=1的name修改为温州市(隐式事务,因为此时的autocommit为1,每条SQL语句执行完自动提交),此时会话A的事务再一次查询id=1的结果,读取的结果name为温州市。会话B再此修改id=1的name为杭州市,会话A的事务再次查询id=1,结果name的值为杭州市,这种现象就是不可重复读。
幻读(Phantom)
一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。(幻读在读未提交、读已提交、可重复读隔离级别都可能会出现)

事务的隔离级别
MySQL的事务隔离级别一共有四个,分别是读未提交、读已提交、可重复读以及可串行化。
MySQL的隔离级别的作用就是让事务之间互相隔离,互不影响,这样可以保证事务的一致性。
隔离级别比较:可串行化>可重复读>读已提交>读未提交
隔离级别对性能的影响比较:可串行化>可重复读>读已提交>读未提交
由此看出,隔离级别越高,所需要消耗的MySQL性能越大(如事务并发严重性),为了平衡二者,一般建议设置的隔离级别为可重复读,MySQL默认的隔离级别也是可重复读。
读未提交(READ UNCOMMITTED)

在读未提交隔离级别下,事务A可以读取到事务B修改过但未提交的数据。
可能发生脏读、不可重复读和幻读问题,一般很少使用此隔离级别。
读已提交(READ COMMITTED)

在读已提交隔离级别下,事务B只能在事务A修改过并且已提交后才能读取到事务B修改的数据。
读已提交隔离级别解决了脏读的问题,但可能发生不可重复读和幻读问题,一般很少使用此隔离级别。
可重复读(REPEATABLE READ)

在可重复读隔离级别下,事务B只能在事务A修改过数据并提交后,自己也提交事务后,才能读取到事务B修改的数据。
可重复读隔离级别解决了脏读和不可重复读的问题,但可能发生幻读问题。
提问:为什么上了写锁(写操作),别的事务还可以读操作?
因为InnoDB有MVCC机制(多版本并发控制),可以使用快照读,而不会被阻塞。
可串行化(SERIALIZABLE)




各种问题(脏读、不可重复读、幻读)都不会发生,通过加锁实现(读锁和写锁)。


转载至:https://developer.aliyun.com/article/743691
作者:张德Talk
5. 什么是幻读?以及如何解决幻读问题?
为了便于说明问题,这一篇文章,我们就先使用一个小一点儿的表。
1 | CREATE TABLE `t` ( |
这个表除了主键id外,还有一个索引c,初始化语句在表中插入了6行数据。
下面的语句序列,是怎么加锁的,加的锁又是什么时候释放的呢?
1 | begin; |
比较好理解的是,这个语句会命中d=5的这一行,对应的主键id=5,因此在select 语句执行完成后,id=5这一行会加一个写锁,而且由于两阶段锁协议,这个写锁会在执行commit语句的时候释放。
由于字段d上没有索引,因此这条查询语句会做全表扫描。那么,其他被扫描到的,但是不满足条件的5行记录上,会不会被加锁呢?
我们知道,InnoDB的默认事务隔离级别是可重复读,所以本文接下来没有特殊说明的部分,都是设定在可重复读隔离级别下。
幻读是什么?
现在,我们就来分析一下,如果只在id=5这一行加锁,而其他行的不加锁的话,会怎么样。
下面先来看一下这个场景(注意:这是我假设的一个场景):

可以看到,session A里执行了三次查询,分别是Q1、Q2和Q3。它们的SQL语句相同,都是select * from t where d=5 for update。这个语句的意思你应该很清楚了,查所有d=5的行,而且使用的是当前读,并且加上写锁。现在,我们来看一下这三条SQL语句,分别会返回什么结果。
Q1只返回id=5这一行;
在T2时刻,session B把id=0这一行的d值改成了5,因此T3时刻Q2查出来的是id=0和id=5这两行;
在T4时刻,session C又插入一行(1,1,5),因此T5时刻Q3查出来的是id=0、id=1和id=5的这三行。
其中,Q3读到id=1这一行的现象,被称为“幻读”。也就是说,幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。 这里,我需要对“幻读”做一个说明:
在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。
上面session B的修改结果,被session A之后的select语句用“当前读”看到,不能称为幻读。幻读仅专指“新插入的行”。
因为这三个查询都是加了for update,都是当前读。而当前读的规则,就是要能读到所有已经提交的记录的最新值。并且,session B和sessionC的两条语句,执行后就会提交,所以Q2和Q3就是应该看到这两个事务的操作效果,而且也看到了,这跟事务的可见性规则并不矛盾。
幻读有什么问题?
首先是语义上的。session A在T1时刻就声明了,“我要把所有d=5的行锁住,不准别的事务进行读写操作”。而实际上,这个语义被破坏了。
如果现在这样看感觉还不明显的话,我再往session B和session C里面分别加一条SQL语句,你再看看会出现什么现象。

session B的第二条语句update t set c=5 where id=0,语义是“我把id=0、d=5这一行的c值,改成了5”。
由于在T1时刻,session A 还只是给id=5这一行加了行锁, 并没有给id=0这行加上锁。因此,session B在T2时刻,是可以执行这两条update语句的。这样,就破坏了 session A 里Q1语句要锁住所有d=5的行的加锁声明。
session C也是一样的道理,对id=1这一行的修改,也是破坏了Q1的加锁声明。
数据一致性问题
我们知道,锁的设计是为了保证数据的一致性。而这个一致性,不止是数据库内部数据状态在此刻的一致性,还包含了数据和日志在逻辑上的一致性。
为了说明这个问题,我给session A在T1时刻再加一个更新语句,即:update t set d=100 where d=5。

update的加锁语义和select …for update 是一致的,所以这时候加上这条update语句也很合理。session A声明说“要给d=5的语句加上锁”,就是为了要更新数据,新加的这条update语句就是把它认为加上了锁的这一行的d值修改成了100。
现在,我们来分析一下图3执行完成后,数据库里会是什么结果。
经过T1时刻,id=5这一行变成 (5,5,100),当然这个结果最终是在T6时刻正式提交的;
经过T2时刻,id=0这一行变成(0,5,5);
经过T4时刻,表里面多了一行(1,5,5);
其他行跟这个执行序列无关,保持不变。
这样看,这些数据也没啥问题,但是我们再来看看这时候binlog里面的内容。
T2时刻,session B事务提交,写入了两条语句;
T4时刻,session C事务提交,写入了两条语句;
T6时刻,session A事务提交,写入了update t set d=100 where d=5 这条语句。
我统一放到一起的话,就是这样的:
update t set d=5 where id=0; /(0,0,5)/ update t set c=5 where id=0; /(0,5,5)/
insert into t values(1,1,5); /(1,1,5)/ update t set c=5 where id=1; /(1,5,5)/
update t set d=100 where d=5;/所有d=5的行,d改成100/ 好,你应该看出问题了。这个语句序列,不论是拿到备库去执行,还是以后用binlog来克隆一个库,这三行的结果,都变成了 (0,5,100)、(1,5,100)和(5,5,100)。
也就是说,id=0和id=1这两行,发生了数据不一致。这个问题很严重,是不行的。
到这里,我们再回顾一下,这个数据不一致到底是怎么引入的?
我们分析一下可以知道,这是我们假设“select * from t where d=5 for update这条语句只给d=5这一行,也就是id=5的这一行加锁”导致的。
所以我们认为,上面的设定不合理,要改。
那怎么改呢?我们把扫描过程中碰到的行,也都加上写锁,再来看看执行效果。

由于session A把所有的行都加了写锁,所以session B在执行第一个update语句的时候就被锁住了。需要等到T6时刻session A提交以后,session B才能继续执行。
这样对于id=0这一行,在数据库里的最终结果还是 (0,5,5)。在binlog里面,执行序列是这样的:
insert into t values(1,1,5); /(1,1,5)/ update t set c=5 where id=1; /(1,5,5)/
update t set d=100 where d=5;/所有d=5的行,d改成100/
update t set d=5 where id=0; /(0,0,5)/ update t set c=5 where id=0; /(0,5,5)/ 可以看到,按照日志顺序执行,id=0这一行的最终结果也是(0,5,5)。所以,id=0这一行的问题解决了。
但同时你也可以看到,id=1这一行,在数据库里面的结果是(1,5,5),而根据binlog的执行结果是(1,5,100),也就是说幻读的问题还是没有解决。为什么我们已经这么“凶残”地,把所有的记录都上了锁,还是阻止不了id=1这一行的插入和更新呢?
原因很简单。在T3时刻,我们给所有行加锁的时候,id=1这一行还不存在,不存在也就加不上锁。
也就是说,即使把所有的记录都加上锁,还是阻止不了新插入的记录,这也是为什么“幻读”会被单独拿出来解决的原因。
现在你知道了,产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB只好引入新的锁,也就是间隙锁(Gap Lock)。
顾名思义,间隙锁,锁的就是两个值之间的空隙。比如文章开头的表t,初始化插入了6个记录,这就产生了7个间隙。

这样,当你执行 select * from t where d=5 for update的时候,就不止是给数据库中已有的6个记录加上了行锁,还同时加了7个间隙锁。这样就确保了无法再插入新的记录。
也就是说这时候,在一行行扫描的过程中,不仅将给行加上了行锁,还给行两边的空隙,也加上了间隙锁。
现在你知道了,数据行是可以加上锁的实体,数据行之间的间隙,也是可以加上锁的实体。但是间隙锁跟我们之前碰到过的锁都不太一样。
比如行锁,分成读锁和写锁。下图就是这两种类型行锁的冲突关系。
也就是说,跟行锁有冲突关系的是“另外一个行锁”。
但是间隙锁不一样,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。间隙锁之间都不存在冲突关系。
这句话不太好理解,我给你举个例子:

这里session B并不会被堵住。因为表t里并没有c=7这个记录,因此session A加的是间隙锁(5,10)。而session B也是在这个间隙加的间隙锁。它们有共同的目标,即:保护这个间隙,不允许插入值。但,它们之间是不冲突的。
间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间。也就是说,我们的表t初始化以后,如果用select * from t for update要把整个表所有记录锁起来,就形成了7个next-key lock,分别是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。
你可能会问说,这个supremum从哪儿来的呢?
这是因为+∞是开区间。实现上,InnoDB给每个索引加了一个不存在的最大值supremum,这样才符合我们前面说的“都是前开后闭区间”。
间隙锁和next-key lock的引入,帮我们解决了幻读的问题,但同时也带来了一些“困扰”。
作者:ITWords 原文链接:https://blog.csdn.net/new_buff_007/article/details/104249866
6. MySQL-InnoDB-MVCC多版本并发控制
相关概念
1.read view, 快照snapshot
淘宝数据库内核月报/2017/10/01/ 此文虽然是以PostgreSQL进行的说明, 但并不影响理解, 在”事务快照的实现”该部分有细节需要注意: 事务快照是用来存储数据库的事务运行情况。一个事务快照的创建过程可以概括为: 查看当前所有的未提交并活跃的事务,存储在数组中 选取未提交并活跃的事务中最小的XID,记录在快照的xmin中 选取所有已提交事务中最大的XID,加1后记录在xmax中
注意: 上文中在PostgreSQL中snapshot的概念, 对应MySQL中, 其实就是你在网上看到的read view,快照这些概念;
2.read view 主要是用来做可见性判断的, 比较普遍的解释便是”本事务不可见的当前其他活跃事务”, 但正是该解释, 可能会造成一节理解上的误区, 所以此处提供两个参考, 供给大家避开理解误区:
1 | read view中的`高水位low_limit_id`可以参考 https://github.com/zhangyachen/zhangyachen.github.io/issues/68, https://www.zhihu.com/question/66320138 |
3.另外, 对于read view快照的生成时机, 也非常关键, 正是因为生成时机的不同, 造成了RC,RR两种隔离级别的不同可见性;
- 在innodb中(默认repeatable read级别), 事务在begin/start transaction之后的第一条select读操作后, 会创建一个快照(read view), 将当前系统中活跃的其他事务记录记录起来;
- 在innodb中(默认read committed级别), 事务中每条select语句都会创建一个快照(read view);
4.undo-log
- Undo log是InnoDB MVCC事务特性的重要组成部分。当我们对记录做了变更操作时就会产生undo记录,Undo记录默认被记录到系统表空间(ibdata)中,但从5.6开始,也可以使用独立的Undo 表空间。
- Undo记录中存储的是老版本数据,当一个旧的事务需要读取数据时,为了能读取到老版本的数据,需要顺着undo链找到满足其可见性的记录。当版本链很长时,通常可以认为这是个比较耗时的操作(例如bug#69812)。
- 大多数对数据的变更操作包括INSERT/DELETE/UPDATE,其中INSERT操作在事务提交前只对当前事务可见,因此产生的Undo日志可以在事务提交后直接删除(谁会对刚插入的数据有可见性需求呢!!),而对于UPDATE/DELETE则需要维护多版本信息,在InnoDB里,UPDATE和DELETE操作产生的Undo日志被归成一类,即update_undo
- 另外, 在回滚段中的undo logs分为:
insert undo log和update undo log- insert undo log : 事务对insert新记录时产生的undolog, 只在事务回滚时需要, 并且在事务提交后就可以立即丢弃。
- update undo log : 事务对记录进行delete和update操作时产生的undo log, 不仅在事务回滚时需要, 一致性读也需要,所以不能随便删除,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被purge线程删除。
5.InnoDB存储引擎在数据库每行数据的后面添加了三个字段
- 6字节的
事务ID(DB_TRX_ID)字段: 用来标识最近一次对本行记录做修改(insert|update)的事务的标识符, 即最后一次修改(insert|update)本行记录的事务id。 至于delete操作,在innodb看来也不过是一次update操作,更新行中的一个特殊位将行表示为deleted, 并非真正删除。 - 7字节的
回滚指针(DB_ROLL_PTR)字段: 指写入回滚段(rollback segment)的undo logrecord (撤销日志记录记录)。 如果一行记录被更新, 则undo logrecord 包含 ‘重建该行记录被更新之前内容’ 所必须的信息。 - 6字节的
DB_ROW_ID字段: 包含一个随着新行插入而单调递增的行ID, 当由innodb自动产生聚集索引时,聚集索引会包括这个行ID的值,否则这个行ID不会出现在任何索引中。 结合聚簇索引的相关知识点, 我的理解是, 如果我们的表中没有主键或合适的唯一索引, 也就是无法生成聚簇索引的时候, InnoDB会帮我们自动生成聚集索引, 但聚簇索引会使用DB_ROW_ID的值来作为主键; 如果我们有自己的主键或者合适的唯一索引, 那么聚簇索引中也就不会包含 DB_ROW_ID 了 。 关于聚簇索引, 《高性能MySQL》中的篇幅对我来说已经够用了, 稍后会整理一下以前的学习笔记, 然后更新上来。
6.可见性比较算法(这里每个比较算法后面的描述是建立在rr级别下,rc级别也是使用该比较算法,此处未做描述)
设要读取的行的最后提交事务id(即当前数据行的稳定事务id)为 trx_id_current
当前新开事务id为 new_id
当前新开事务创建的快照read view 中最早的事务id为up_limit_id, 最迟的事务id为low_limit_id(注意这个low_limit_id=未开启的事务id=当前最大事务id+1)
比较:
- 1.
trx_id_current < up_limit_id, 这种情况比较好理解, 表示, 新事务在读取该行记录时, 该行记录的稳定事务ID是小于, 系统当前所有活跃的事务, 所以当前行稳定数据对新事务可见, 跳到步骤5. - 2.
trx_id_current >= trx_id_last, 这种情况也比较好理解, 表示, 该行记录的稳定事务id是在本次新事务创建之后才开启的, 但是却在本次新事务执行第二个select前就commit了,所以该行记录的当前值不可见, 跳到步骤4。 - 3.
trx_id_current <= trx_id_current <= trx_id_last, 表示: 该行记录所在事务在本次新事务创建的时候处于活动状态,从up_limit_id到low_limit_id进行遍历,如果trx_id_current等于他们之中的某个事务id的话,那么不可见, 调到步骤4,否则表示可见。 - 4.从该行记录的 DB_ROLL_PTR 指针所指向的回滚段中取出最新的undo-log的版本号, 将它赋值该
trx_id_current,然后跳到步骤1重新开始判断。 - 5.将该可见行的值返回。
小结
- 一般我们认为MVCC有下面几个特点:
- 每行数据都存在一个版本,每次数据更新时都更新该版本
- 修改时Copy出当前版本, 然后随意修改,各个事务之间无干扰
- 保存时比较版本号,如果成功(commit),则覆盖原记录, 失败则放弃copy(rollback)
- 就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道, 因为这看起来正是,在提交的时候才能知道到底能否提交成功
- 而InnoDB实现MVCC的方式是:
- 事务以排他锁的形式修改原始数据
- 把修改前的数据存放于undo log,通过回滚指针与主数据关联
- 修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)
- 二者最本质的区别是: 当修改数据时是否要
排他锁定,如果锁定了还算不算是MVCC?
- Innodb的实现真算不上MVCC, 因为并没有实现核心的多版本共存,
undo log中的内容只是串行化的结果, 记录了多个事务的过程, 不属于多版本共存。但理想的MVCC是难以实现的, 当事务仅修改一行记录使用理想的MVCC模式是没有问题的, 可以通过比较版本号进行回滚, 但当事务影响到多行数据时, 理想的MVCC就无能为力了。 - 比如, 如果事务A执行理想的MVCC, 修改Row1成功, 而修改Row2失败, 此时需要回滚Row1, 但因为Row1没有被锁定, 其数据可能又被事务B所修改, 如果此时回滚Row1的内容,则会破坏事务B的修改结果,导致事务B违反ACID。 这也正是所谓的
第一类更新丢失的情况。 - 也正是因为InnoDB使用的MVCC中结合了排他锁, 不是纯的MVCC, 所以第一类更新丢失是不会出现了, 一般说更新丢失都是指第二类丢失更新。
多版本和二级索引
InnoDB多版本并发控制 (MVCC) 处理二级索引与聚簇索引不同。聚集索引中的记录就地更新,它们隐藏的系统列指向撤消日志条目,可以从中重建记录的早期版本。与聚集索引记录不同,二级索引记录不包含隐藏的系统列,也不会就地更新。
当二级索引列被更新时,旧的二级索引记录被删除标记,新记录被插入,并最终被删除标记记录被清除。当二级索引记录被删除标记或二级索引页被更新的事务更新时,InnoDB在聚集索引中查找数据库记录。在聚集索引中,DB_TRX_ID检查记录,如果在启动读取事务后修改了记录,则从撤消日志中检索记录的正确版本。
如果二级索引记录被标记为删除或二级索引页被更新的事务更新, 则不使用覆盖索引技术。不是从索引结构返回值,而是InnoDB在聚集索引中查找记录。
但是,如果启用了 索引条件下推 (ICP)优化,并且WHERE可以仅使用索引中的字段评估部分条件,则 MySQL 服务器仍会将这部分WHERE条件下推到存储引擎,在那里使用指数。如果没有找到匹配的记录,则避免聚集索引查找。如果找到匹配的记录,即使是在删除标记的记录中,也会在 InnoDB聚集索引中查找该记录。
https://github.com/zhangyachen/zhangyachen.github.io/issues/68:
每一行额外包含三个隐藏字段:
- DB_TRX_ID:事务ID。行的创建时间和删除时间记录的就是此值。
- DB_ROLL_PTR:指向当前记录项的undo信息。
- DB_ROW_ID::随着新行插入单调递增的一个字段。当由innodb自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,不然的话聚集索引中不包括这个值。
- 在insert操作时,创建时间 = DB_ROW_ID,这时,“删除时间 ”是未定义的。
- 在update操作时,复制新增行的“创建时间”=DB_ROW_ID,删除时间未定义,旧数据行“创建时间”不变,删除时间=该事务的DB_ROW_ID。
- 在delete操作时,相应数据行的“创建时间”不变,删除时间 = 该事务的DB_ROW_ID。
- select操作对两者都不修改,只读相应的数据。
关于low_limit_id,up_limit_id的理解: up_limit_id:当前已经提交的事务号 + 1,事务号 < up_limit_id ,对于当前Read View都是可见的。理解起来就是创建Read View视图的时候,之前已经提交的事务对于该事务肯定是可见的。 low_limit_id:目前已出现过的事务id的最大值+1,事务号 >= low_limit_id,对于当前Read View都是不可见的。理解起来就是在创建Read View视图之后创建的事务对于该事务肯定是不可见的。
另外,trx_ids为活跃事务id列表,即Read View初始化时当前未提交的事务列表。所以当进行RR读的时候,trx_ids中的事务对于本事务是不可见的(除了自身事务,自身事务对于表的修改对于自己当然是可见的)。理解起来就是创建RV时,将当前活跃事务ID记录下来,后续即使他们提交对于本事务也是不可见的。
example
| 步骤 | 1 | 2 | 3 |
|---|---|---|---|
| 一 | begin | ||
| 二 | begin | ||
| 三 | insert into test(score) values(1607); 假设此时事务号21 | ||
| 四 | insert into test(score) values(1607); 此时事务号22 | ||
| 五 | 此时创建读视图,up_limit_id = 21, low_limit_id = 23 活跃事务列表为(21,22) |
||
| 六 | insert into test(score) values(1620); 事务号为23 | ||
| 七 | insert into test(score) values(1621); 事务号为24 | ||
| 八 | insert into test(score) values(1622); 事务号为25 | ||
| 九 | select * from test; 此时的up_limit_id 为21,low_limit_id 为26,活跃事务列表为(21,22),故21,22在活跃事务列表不可见 ![]() |
||
| 十 | select * from test; 此时low_limit_id为26,up_limit_id 为21,活跃事务列表是(21,22) 22本事务自身可见。21的在活跃事务列表不可见。23,24不在活跃事务列表,可见 ![]() |
||
| 十一 | select * from test; 事务内readview不变,low_limit_id = 23,up_limit_id = 21,活跃事务列表 (21,22)。故21自身可见,22在活跃事务列表不可见。>=23的都不可见![]() |
注意的几点:
- Read View视图是在进行RR读之前创建的,而不是在事务刚
begin时创建的。如果Read View视图是在事务刚begin时创建的,那么在步骤四中事务22的Read View就定下来了(up_limit_id = 21,low_limit_id = 23),那么在步骤十中就看不到3中提交的数据了,因为事务号23,24,25大于等于事务22.low_limit_id - 事务内Read View一旦创建就不变化了。
- 在第十步中按我之前的理解,3中insert的数据是在2中begin之后插入的,按理说2是看不到3中insert插入的数据的。但是事务保证的是两次select的数据是一致的,所以Read View是在第一次select时创建的,所以3中insert的数据是在2中可以看到。
7.ICP
ICP主要用于在使用索引查询数据时进行优化。没有ICP优化时,存储引擎通过索引定位数据并读取这些被定位的数据返回给MySQL服务器,MySQL服务器会根据WHERE条件对这些数据进行过滤。当启用了ICP优化后,WHERE语句中那些仅仅使用优化器选中的索引列中的字段会被MySQL服务器推送给存储引擎,在存储引擎使用索引定位数据并根据索引中的字段进行WHERE条件过滤,然后在读取表中数据返回给MySQL服务器,MySQL服务器会对WHERE语句中剩下的条件进行过滤。有了ICP可以减少存储引擎访问表数据的次数,也减少了MySQL服务器访问存储引擎的次数。
索引条件下推优化的适用性取决于以下条件:
- 当需要访问全表行时
range, ICP 用于ref、eq_ref、 和ref_or_null访问方法。 - ICP 可用于
InnoDB和MyISAM表。(例外:MySQL 5.6 中的分区表不支持 ICP;此问题已在 MySQL 5.7 中解决。) - 对于
InnoDB表,ICP 仅用于二级索引。ICP 的目标是减少全行读取的次数,从而减少 I/O 操作。对于InnoDB聚集索引,完整的记录已经读入InnoDB缓冲区。在这种情况下使用 ICP 不会减少 I/O。 - 不能下推引用子查询的条件。
- 不能下推引用存储函数的条件。存储引擎不能调用存储的函数。
- 无法按下触发条件。
要了解此优化的工作原理,请首先考虑未使用索引条件下推时索引扫描的进行方式:
- 获取下一行,首先通过读取索引元组,然后通过使用索引元组定位并读取整个表行。
- 测试
WHERE适用于该表的条件部分。根据测试结果接受或拒绝该行。
使用索引条件下推,扫描是这样进行的:
- 获取下一行的索引元组(但不是完整的表行)。
- 测试
WHERE适用于该表的条件部分,并且可以仅使用索引列进行检查。如果不满足条件,则处理下一行的索引元组。 - 如果满足条件,则使用索引元组定位并读取整个表行。
- 测试
WHERE适用于该表的条件的其余部分。根据测试结果接受或拒绝该行。
EXPLAIN使用索引条件下推时,输出显示 Using index condition在 Extra列中。它不显示,Using index 因为当必须读取完整的表行时这不适用。
假设一个表包含有关人员及其地址的信息,并且该表的索引定义为 INDEX (zipcode, lastname, firstname)。如果我们知道一个人的zipcode价值但不确定姓氏,我们可以这样搜索:
1 | SELECT * FROM people |
MySQL 可以使用索引来扫描带有 zipcode='95054'. 第二部分 ( lastname LIKE '%etrunia%') 不能用于限制必须扫描的行数,因此如果没有索引条件下推,此查询必须检索所有具有 zipcode='95054'.
使用索引条件下推,MySQLlastname LIKE '%etrunia%'在读取完整表行之前检查该 部分。这避免读取与匹配zipcode条件但不匹配条件的 索引元组对应的完整行 lastname。
在基于磁盘的存储引擎中,索引查找分两步完成,如图所示:

索引条件下推优化尝试通过检查索引记录是否满足可以检查它们的部分 WHERE 条件来减少全记录读取的次数:

获得多少速度取决于 - 有多少记录将被过滤掉 - 读取它们的成本
概念介绍
Index Condition Pushdown (ICP)是MySQL 5.6 版本中的新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式。 a 当关闭ICP时,index 仅仅是data access 的一种访问方式,存储引擎通过索引回表获取的数据会传递到MySQL Server 层进行where条件过滤。 b 当打开ICP时,如果部分where条件能使用索引中的字段,MySQL Server 会把这部分下推到引擎层,可以利用index过滤的where条件在存储引擎层进行数据过滤,而非将所有通过index access的结果传递到MySQL server层进行where过滤. 优化效果:ICP能减少引擎层访问基表的次数和MySQL Server 访问存储引擎的次数,减少io次数,提高查询语句性能。
实践案例 a 环境准备 数据库版本 5.6.16 关闭缓存 set query_cache_size=0; set query_cache_type=OFF; 测试数据下载地址
b 当开启ICP时
1 | mysql> SET profiling = 1; |
此时情况下根据MySQL的最左前缀原则, first_name 可以使用索引,last_name采用了like 模糊查询,不能使用索引。 c 关闭ICP
1 | mysql> set optimizer_switch='index_condition_pushdown=off'; |
当开启ICP时 查询在sending data环节时间消耗是 0.000189s
1 | mysql> show profile cpu,block io for query 1; |
当关闭ICP时 查询在sending data环节时间消耗是 0.000735s
1 | mysql> show profile cpu,block io for query 2; |
从上面的profile 可以看出ICP 开启时整个sql 执行时间是未开启的2/3,sending data 环节的时间消耗前者仅是后者的1/4。
ICP 开启时的执行计划 含有 Using index condition 标示 ,表示优化器使用了ICP对数据访问进行优化。
ICP的原理简单说来就是将可以利用索引筛选的where条件在存储引擎一侧进行筛选,而不是将所有index access的结果取出放在server端进行where筛选。
原理
以上面的查询为例,在没有ICP时,首先通过索引前缀从存储引擎中读出224条first_name为Mary的记录,然后在server段用where筛选last_name的like条件;而启用ICP后,由于last_name的like筛选可以通过索引字段进行,那么存储引擎内部通过索引与where条件的对比来筛选掉不符合where条件的记录,这个过程不需要读出整条记录,同时只返回给server筛选后的6条记录,因此提高了查询性能。
下面通过图两种查询的原理详细解释。
关闭ICP:

在不支持ICP的系统下,索引仅仅作为data access使用。
开启ICP:

在ICP优化开启时,在存储引擎端首先用索引过滤可以过滤的where条件,然后再用索引做data access,被index condition过滤掉的数据不必读取,也不会返回server端。
6.1.3. InnoDB表
InnoDB架构
下图显示了构成InnoDB存储引擎架构的内存和磁盘结构。


6.1.4. InnoDB 内存结构
1. 缓冲池Buffer Pool
缓冲池是主内存中的一个区域,用于在 InnoDB访问时缓存表和索引数据。缓冲池允许直接从内存访问经常使用的数据,从而加快处理速度。在专用服务器上,多达 80% 的物理内存通常分配给缓冲池。
为了提高大量读取操作的效率,缓冲池被划分为可能包含多行的页面。为了缓存管理的效率,缓冲池被实现为页面的链表;很少使用的数据使用最近最少使用 (LRU) 算法的变体从缓存中老化。
了解如何利用缓冲池将经常访问的数据保存在内存中是 MySQL 调优的一个重要方面。
缓冲池 LRU 算法
缓冲池使用 LRU 算法的变体作为列表进行管理。当需要空间向缓冲池添加新页面时,最近最少使用的页面会被逐出,并将新页面添加到列表中间。此中点插入策略将列表视为两个子列表:
- 在头部,最近访问的新(“年轻”)页面 的子列表
- 在尾部,最近访问过的旧页面的子列表

该算法将经常使用的页面保留在新的子列表中。旧的子列表包含不太常用的页面;这些页面是驱逐的候选页面。
默认情况下,算法操作如下:
- 缓冲池的 3/8 专用于旧子列表。
- 列表的中点是新子列表尾部与旧子列表头部相交的边界。
- 当
InnoDB将页面读入缓冲池时,它最初将它插入到中点(旧子列表的头部)。可以读取页面,因为它是用户启动的操作(例如 SQL 查询)所必需的,或者是由 自动执行的预读操作的一部分InnoDB。 - 访问旧子列表中的页面使其 “年轻”,将其移动到新子列表的头部。如果页面是因为用户启动的操作需要它而被读取,则第一次访问会立即发生,并且页面会变年轻。如果页面是由于预读操作而读取的,则第一次访问不会立即发生,并且在页面被逐出之前可能根本不会发生。
- 随着数据库的运行,缓冲池中未被访问的页面会通过向列表尾部移动来“老化”。新旧子列表中的页面随着其他页面的更新而老化。旧子列表中的页面也会随着页面插入中点而老化。最终,一个未使用的页面到达旧子列表的尾部并被驱逐。
默认情况下,查询读取的页面会立即移动到新的子列表中,这意味着它们在缓冲池中停留的时间更长。例如,为mysqldump操作或SELECT没有WHERE子句的 语句 执行的表扫描可以将大量数据带入缓冲池并驱逐等量的旧数据,即使新数据不再使用。类似地,由预读后台线程加载且仅访问一次的页面被移动到新列表的头部。这些情况会将经常使用的页面推送到旧的子列表,在那里它们会被逐出。
InnoDB标准监视器输出在BUFFER POOL AND MEMORY有关缓冲池 LRU 算法操作的部分中包含多个字段。
缓冲池配置
您可以配置缓冲池的各个方面以提高性能。
- 理想情况下,您将缓冲池的大小设置为尽可能大的值,从而为服务器上的其他进程留出足够的内存来运行而不会产生过多的分页。缓冲池越大,就越
InnoDB像内存数据库,从磁盘读取数据一次,然后在后续读取期间从内存访问数据。缓冲池大小是使用innodb_buffer_pool_size配置选项配置的。 - 在具有足够内存的 64 位系统上,您可以将缓冲池拆分为多个部分,以最大程度地减少并发操作之间对内存结构的争用。有关详细信息,请参阅第 14.8.3.1 节,“配置多个缓冲池实例”。
- 您可以将经常访问的数据保留在内存中,而不管操作的活动突然激增,这些操作会将大量不常访问的数据带入缓冲池。有关详细信息,请参阅 第 14.8.3.2 节,“使缓冲池扫描抵抗”。
- 您可以控制如何以及何时执行预读请求以异步地将页面预取到缓冲池中,以预测可能很快就会需要这些页面。有关详细信息,请参阅第 14.8.3.3 节,“配置 InnoDB 缓冲池预取(预读)”。
- 您可以控制何时发生后台刷新以及是否根据工作负载动态调整刷新速率。有关详细信息,请参阅 第 14.8.3.4 节,“配置缓冲池刷新”。
- 您可以配置如何
InnoDB保留当前缓冲池状态以避免服务器重新启动后的长时间预热。有关详细信息,请参阅 第 14.8.3.5 节,“保存和恢复缓冲池状态”。
补充说明

应用系统分层架构,为了加速数据访问,会把最常访问的数据,放在缓存(cache)里,避免每次都去访问数据库。
操作系统,会有缓冲池(buffer pool)机制,避免每次访问磁盘,以加速数据的访问。
MySQL作为一个存储系统,同样具有缓冲池(buffer pool)机制,以避免每次查询数据都进行磁盘IO。
今天,和大家聊一聊InnoDB的缓冲池。
InnoDB的缓冲池缓存什么?有什么用?
缓存表数据与索引数据,把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘IO,起到加速访问的作用。
速度快,那为啥不把所有数据都放到缓冲池里?
凡事都具备两面性,抛开数据易失性不说,访问快速的反面是存储容量小:
(1)缓存访问快,但容量小,数据库存储了200G数据,缓存容量可能只有64G;
(2)内存访问快,但容量小,买一台笔记本磁盘有2T,内存可能只有16G;
因此,只能把“最热”的数据放到“最近”的地方,以“最大限度”的降低磁盘访问。
如何管理与淘汰缓冲池,使得性能最大化呢?
在介绍具体细节之前,先介绍下“预读”的概念。
什么是预读?
磁盘读写,并不是按需读取,而是按页读取,一次至少读一页数据(一般是4K),如果未来要读取的数据就在页中,就能够省去后续的磁盘IO,提高效率。
预读为什么有效?
数据访问,通常都遵循“集中读写”的原则,使用一些数据,大概率会使用附近的数据,这就是所谓的“局部性原理”,它表明提前加载是有效的,确实能够减少磁盘IO。
按页(4K)读取,和InnoDB的缓冲池设计有啥关系?
(1)磁盘访问按页读取能够提高性能,所以缓冲池一般也是按页缓存数据;
(2)预读机制启示了我们,能把一些“可能要访问”的页提前加入缓冲池,避免未来的磁盘IO操作;
InnoDB是以什么算法,来管理这些缓冲页呢?
最容易想到的,就是LRU(Least recently used)。
画外音:memcache,OS都会用LRU来进行页置换管理,但MySQL的玩法并不一样。
传统的LRU是如何进行缓冲页管理?
最常见的玩法是,把入缓冲池的页放到LRU的头部,作为最近访问的元素,从而最晚被淘汰。这里又分两种情况:
(1)页已经在缓冲池里,那就只做“移至”LRU头部的动作,而没有页被淘汰;
(2)页不在缓冲池里,除了做“放入”LRU头部的动作,还要做“淘汰”LRU尾部页的动作;

如上图,假如管理缓冲池的LRU长度为10,缓冲了页号为1,3,5…,40,7的页。
假如,接下来要访问的数据在页号为4的页中:

(1)页号为4的页,本来就在缓冲池里;
(2)把页号为4的页,放到LRU的头部即可,没有页被淘汰;
画外音:为了减少数据移动,LRU一般用链表实现。
假如,再接下来要访问的数据在页号为50的页中:

(1)页号为50的页,原来不在缓冲池里;
(2)把页号为50的页,放到LRU头部,同时淘汰尾部页号为7的页;
**传统的LRU缓冲池算法十分直观,OS,memcache等很多软件都在用,MySQL为啥这么矫情,不能直接用呢?
这里有两个问题:**
(1)预读失效;
(2)缓冲池污染;
什么是预读失效?
由于预读(Read-Ahead),提前把页放入了缓冲池,但最终MySQL并没有从页中读取数据,称为预读失效。
如何对预读失效进行优化?
要优化预读失效,思路是:
(1)让预读失败的页,停留在缓冲池LRU里的时间尽可能短;
(2)让真正被读取的页,才挪到缓冲池LRU的头部;
以保证,真正被读取的热数据留在缓冲池里的时间尽可能长。
具体方法是:
(1)将LRU分为两个部分:
- 新生代(new sublist)
- 老生代(old sublist)
(2)新老生代收尾相连,即:新生代的尾(tail)连接着老生代的头(head);
(3)新页(例如被预读的页)加入缓冲池时,只加入到老生代头部:
- 如果数据真正被读取(预读成功),才会加入到新生代的头部
- 如果数据没有被读取,则会比新生代里的“热数据页”更早被淘汰出缓冲池

举个例子,整个缓冲池LRU如上图:
(1)整个LRU长度是10;
(2)前70%是新生代;
(3)后30%是老生代;
(4)新老生代首尾相连;

假如有一个页号为50的新页被预读加入缓冲池:
(1)50只会从老生代头部插入,老生代尾部(也是整体尾部)的页会被淘汰掉;
(2)假设50这一页不会被真正读取,即预读失败,它将比新生代的数据更早淘汰出缓冲池;

假如50这一页立刻被读取到,例如SQL访问了页内的行row数据:
(1)它会被立刻加入到新生代的头部;
(2)新生代的页会被挤到老生代,此时并不会有页面被真正淘汰;
改进版缓冲池LRU能够很好的解决“预读失败”的问题。
画外音:但也不要因噎废食,因为害怕预读失败而取消预读策略,大部分情况下,局部性原理是成立的,预读是有效的。
新老生代改进版LRU仍然解决不了缓冲池污染的问题。
什么是MySQL缓冲池污染?
当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL性能急剧下降,这种情况叫缓冲池污染。
例如,有一个数据量较大的用户表,当执行:
select * from user where name like “%shenjian%”;
虽然结果集可能只有少量数据,但这类like不能命中索引,必须全表扫描,就需要访问大量的页:
(1)把页加到缓冲池(插入老生代头部);
(2)从页里读出相关的row(插入新生代头部);
(3)row里的name字段和字符串shenjian进行比较,如果符合条件,加入到结果集中;
(4)…直到扫描完所有页中的所有row…
如此一来,所有的数据页都会被加载到新生代的头部,但只会访问一次,真正的热数据被大量换出。
怎么这类扫码大量数据导致的缓冲池污染问题呢?
MySQL缓冲池加入了一个“老生代停留时间窗口”的机制:
(1)假设T=老生代停留时间窗口;
(2)插入老生代头部的页,即使立刻被访问,并不会立刻放入新生代头部;
(3)只有满足“被访问”并且“在老生代停留时间”大于T,才会被放入新生代头部;

继续举例,假如批量数据扫描,有51,52,53,54,55等五个页面将要依次被访问。

如果没有“老生代停留时间窗口”的策略,这些批量被访问的页面,会换出大量热数据。

加入“老生代停留时间窗口”策略后,短时间内被大量加载的页,并不会立刻插入新生代头部,而是优先淘汰那些,短期内仅仅访问了一次的页。

而只有在老生代呆的时间足够久,停留时间大于T,才会被插入新生代头部。
总结
(1)缓冲池(buffer pool)是一种常见的降低磁盘访问的机制;
(2)缓冲池通常以页(page)为单位缓存数据;
(3)缓冲池的常见管理算法是LRU,memcache,OS,InnoDB都使用了这种算法;
(4)InnoDB对普通LRU进行了优化:
- 将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,页被访问,才进入新生代,以解决预读失效的问题
- 页被访问,且在老生代停留时间超过配置阈值的,才进入新生代,以解决批量数据访问,大量热数据淘汰的问题
思路,比结论重要。
作者:58沈剑_架构师之路 链接:https://juejin.cn/post/6844903874172551181
2. Change Buffer
Change Buffer是一种特殊的数据结构,当二级索引页不在缓冲池中时,它会缓存对二级索引页的 更改 。可能由INSERT、 UPDATE或 DELETE操作 (DML)导致的缓冲更改 稍后在其他读取操作将页面加载到缓冲池时合并。

与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页。稍后在其他操作将受影响的页面读入缓冲池时合并缓存的更改,可避免大量随机访问 I/O,而这些 I/O 将需要将二级索引页面从磁盘读入缓冲池。
在系统大部分空闲时或在缓慢关闭期间运行的清除操作会定期将更新的索引页写入磁盘。与将每个值立即写入磁盘相比,清除操作可以更有效地为一系列索引值写入磁盘块。
当有许多受影响的行和许多二级索引要更新时,更改缓冲区合并可能需要几个小时。在此期间,磁盘 I/O 增加,这可能会导致磁盘绑定查询的显着减慢。在提交事务后,甚至在服务器关闭并重新启动之后,更改缓冲区合并也可能继续发生。
在内存中,更改缓冲区占据了缓冲池的一部分。在磁盘上,更改缓冲区是系统表空间的一部分,当数据库服务器关闭时,索引更改会在其中缓冲。
缓存在更改缓冲区中的数据类型由 innodb_change_buffering变量控制。
如果索引包含降序索引列或主键包含降序索引列,则二级索引不支持更改缓冲。
当页面加载到缓冲池时,缓冲的更改会被合并,更新的页面稍后会刷新到磁盘。这UPDATEDELETEInnoDB主线程在服务器几乎空闲时和缓慢关闭期间合并缓冲的更改 。
因为它可以减少磁盘读取和写入,所以更改缓冲对于 I/O 密集型工作负载最有价值;例如,具有大量 DML 操作(如批量插入)的应用程序受益于更改缓冲。
但是,更改缓冲区占用了缓冲池的一部分,减少了可用于缓存数据页的内存。如果工作集几乎适合缓冲池,或者如果您的表具有相对较少的二级索引,则禁用更改缓冲可能会很有用。如果工作数据集完全适合缓冲池,则更改缓冲不会施加额外的开销,因为它仅适用于不在缓冲池中的页面。
补充说明
上篇《缓冲池(buffer pool),彻底懂了!》介绍了InnoDB缓冲池的工作原理。
简单回顾一下:

(1)MySQL数据存储包含 内存 与磁盘两个部分;
(2)内存缓冲池(buffer pool)以页为单位,缓存最热的数据页(data page)与索引页(index page);
(3)InnoDB以变种LRU算法管理缓冲池,并能够解决“预读失效”与“缓冲池污染”的问题;
画外音:细节详见《缓冲池(buffer pool),彻底懂了!》。
毫无疑问,对于读请求,缓冲池能够减少磁盘IO,提升性能。问题来了,那写请求呢?
情况一
假如要修改页号为4的索引页,而这个页正好在缓冲池内。

如上图序号1-2:
(1)直接修改缓冲池中的页,一次内存操作;
(2)写入redo log,一次磁盘顺序写操作;
这样的效率是最高的。
画外音:像写日志这种顺序写,每秒几万次没问题。
是否会出现一致性问题呢?
并不会。
(1)读取,会命中缓冲池的页;
(2)缓冲池LRU数据淘汰,会将“脏页”刷回磁盘;
(3)数据库异常奔溃,能够从redo log中恢复数据;
什么时候缓冲池中的页,会刷到磁盘上呢?
定期刷磁盘,而不是每次刷磁盘,能够降低磁盘IO,提升MySQL的性能。
画外音:批量写,是常见的优化手段。
情况二
假如要修改页号为40的索引页,而这个页正好**不**在缓冲池内。

此时麻烦一点,如上图需要1-3:
(1)先把需要为40的索引页,从磁盘加载到缓冲池,一次磁盘随机读操作;
(2)修改缓冲池中的页,一次内存操作;
(3)写入redo log,一次磁盘顺序写操作;
没有命中缓冲池的时候,至少产生一次磁盘IO,对于写多读少的业务场景,是否还有优化的空间呢?
这即是InnoDB考虑的问题,又是本文将要讨论的写缓冲(change buffer)。
*画外音:从名字容易看出,写缓冲是降低磁盘IO,提升数据库写性能的一种机制。* 什么是InnoDB的写缓冲?
在MySQL5.5之前,叫插入缓冲(insert buffer),只针对insert做了优化;现在对delete和update也有效,叫做写缓冲(change buffer)。
它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。
1 | *画外音:R了狗了,这个句子,好长。* |
InnoDB加入写缓冲优化,上文“情况二”流程会有什么变化?
假如要修改页号为40的索引页,而这个页正好不在缓冲池内。

加入写缓冲优化后,流程优化为:
(1)在写缓冲中记录这个操作,一次内存操作;
(2)写入redo log,一次磁盘顺序写操作;
其性能与,这个索引页在缓冲池中,相近。
1 | *画外音:可以看到,40这一页,并没有加载到缓冲池中。* |
是否会出现一致性问题呢?
也不会。
(1)数据库异常奔溃,能够从redo log中恢复数据;
(2)写缓冲不只是一个内存结构,它也会被定期刷盘到写缓冲系统表空间;
(3)数据读取时,有另外的流程,将数据合并到缓冲池;
不妨设,稍后的一个时间,有请求查询索引页40的数据。

此时的流程如序号1-3:
(1)载入索引页,缓冲池未命中,这次磁盘IO不可避免;
(2)从写缓冲读取相关信息;
(3)恢复索引页,放到缓冲池LRU里;
1 | *画外音:可以看到,40这一页,在真正被读取时,才会被加载到缓冲池中。* |
还有一个遗漏问题,为什么写缓冲优化,仅适用于非唯一普通索引页呢?
InnoDB里,聚集索引(clustered index)和普通索引(secondary index)的异同,《1分钟了解MyISAM与InnoDB的索引差异》有详尽的叙述,不再展开。
如果索引设置了唯一(unique)属性,在进行修改操作时,InnoDB必须进行唯一性检查。也就是说,索引页即使不在缓冲池,磁盘上的页读取无法避免(否则怎么校验是否唯一?),此时就应该直接把相应的页放入缓冲池再进行修改,而不应该再整写缓冲这个幺蛾子。
除了数据页被访问,还有哪些场景会触发刷写缓冲中的数据呢?
还有这么几种情况,会刷写缓冲中的数据:
(1)有一个后台线程,会认为数据库空闲时;
(2)数据库缓冲池不够用时;
(3)数据库正常关闭时;
(4)redo log写满时;
1 | *画外音:几乎不会出现redo log写满,此时整个数据库处于无法写入的不可用状态。* |
什么业务场景,适合开启InnoDB的写缓冲机制?
先说什么时候不适合,如上文分析,当:
(1)数据库都是唯一索引;
(2)或者,写入一个数据后,会立刻读取它;
这两类场景,在写操作进行时(进行后),本来就要进行进行页读取,本来相应页面就要入缓冲池,此时写缓存反倒成了负担,增加了复杂度。
什么时候适合使用写缓冲,如果:
(1)数据库大部分是非唯一索引;
(2)业务是写多读少,或者不是写后立刻读取;
可以使用写缓冲,将原本每次写入都需要进行磁盘IO的SQL,优化定期批量写磁盘。
作者:58沈剑_架构师之路 链接:https://juejin.cn/post/6844903875271475213
3. 自适应哈希索引
自适应哈希索引能够InnoDB在具有适当组合的工作负载和足够的缓冲池内存的系统上执行更像内存数据库,而不会牺牲事务功能或可靠性。自适应哈希索引由innodb_adaptive_hash_index 变量启用 ,或在服务器启动时由 关闭 --skip-innodb-adaptive-hash-index。
根据观察到的搜索模式,使用索引键的前缀构建哈希索引。前缀可以是任意长度,也可能只有 B 树中的某些值出现在哈希索引中。哈希索引是针对经常访问的索引页面按需构建的。
对于某些工作负载,哈希索引查找的加速大大超过了监视索引查找和维护哈希索引结构的额外工作。对自适应哈希索引的访问有时会成为繁重工作负载下的争用源,例如多个并发连接。带有LIKE运算符和% 通配符的查询 也往往不会受益。对于无法从自适应哈希索引中受益的工作负载,将其关闭可减少不必要的性能开销。由于很难提前预测自适应哈希索引是否适合特定系统和工作负载,请考虑在启用和禁用它的情况下运行基准测试。
自适应哈希索引功能是分区的。每个索引都绑定到一个特定的分区,每个分区都由一个单独的闩锁保护。分区由innodb_adaptive_hash_index_parts 变量控制 。该 innodb_adaptive_hash_index_parts 变量默认设置为 8。最大设置为 512。
4. 日志缓冲区
日志缓冲区是保存要写入磁盘上日志文件的数据的内存区域。日志缓冲区大小由innodb_log_buffer_size变量定义 。默认大小为 16MB。日志缓冲区的内容会定期刷新到磁盘。大型日志缓冲区使大型事务能够运行,而无需在事务提交之前将重做日志数据写入磁盘。因此,如果您有更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘 I/O。
关于磁盘I/O可以参看这篇文章:https://tech.meituan.com/2017/05/19/about-desk-io.html
6.1.5 InnoDB磁盘结构
1.表
InnoDB表是使用CREATE TABLE语句创建的 ;例如:
1 | CREATE TABLE t1 (a INT, b CHAR (20), PRIMARY KEY (a)) ENGINE=InnoDB; |
ENGINE=InnoDB当InnoDB定义为默认存储引擎时不需要 该子句 ,默认情况下它是。
行格式
InnoDB表 的行格式决定了其行在磁盘上的物理存储方式。 InnoDB支持四种行格式,每种格式具有不同的存储特性。支持行格式包括 REDUNDANT,COMPACT, DYNAMIC,和COMPRESSED。该DYNAMIC行格式是默认的。
表的行格式决定了其行的物理存储方式,这反过来又会影响查询和 DML 操作的性能。随着单个磁盘页面中容纳更多行,查询和索引查找可以更快地工作,缓冲池中需要的缓存内存更少,写出更新值所需的 I/O 也更少。
每个表中的数据分为页。构成每个表的页面排列在称为 B 树索引的树数据结构中。表数据和二级索引都使用这种类型的结构。表示整个表的 B 树索引称为聚集索引,它根据主键列进行组织。聚集索引数据结构的节点包含行中所有列的值。二级索引结构的节点包含索引列和主键列的值。
可变长度列是列值存储在 B 树索引节点中的规则的一个例外。太长而不适合 B 树页面的可变长度列存储在单独分配的磁盘页面上,称为溢出页面。此类列称为页外列。页外列的值存储在溢出页面的单向链接列表中,每个这样的列都有自己的一个或多个溢出页面列表。根据列长度,可变长度列值的全部或前缀存储在 B 树中,以避免浪费存储空间和读取单独的页面。
该InnoDB存储引擎支持四名的格式:REDUNDANT,COMPACT, DYNAMIC,和COMPRESSED。
表 15.15 InnoDB 行格式概述
| 行格式 | 紧凑的存储特性 | 增强的可变长度列存储 | 大索引键前缀支持 | 压缩支持 | 支持的表空间类型 |
|---|---|---|---|---|---|
REDUNDANT |
不 | 不 | 不 | 不 | 系统,每个表文件,一般 |
COMPACT |
是的 | 不 | 不 | 不 | 系统,每个表文件,一般 |
DYNAMIC |
是的 | 是的 | 是的 | 不 | 系统,每个表文件,一般 |
COMPRESSED |
是的 | 是的 | 是的 | 是的 | 每个表文件,一般 |
以下主题描述行格式存储特征以及如何定义和确定表的行格式。
冗余行格式
该REDUNDANT格式提供与旧版本 MySQL 的兼容性。
使用REDUNDANT行格式的表在 B 树节点内的索引记录中存储可变长度列值(VARCHAR、 VARBINARY和 BLOB和 TEXT类型)的前 768 个字节,其余部分存储在溢出页上。大于或等于 768 字节的固定长度列被编码为可变长度列,可以在页外存储。例如,CHAR(255)如果字符集的最大字节长度大于 3,则列可以超过 768 个字节,就像使用utf8mb4.
如果列的值是 768 字节或更少,则不使用溢出页,并且可能会节省一些 I/O,因为该值完全存储在 B 树节点中。这适用于相对较短的BLOB列值,但可能会导致 B 树节点填充数据而不是键值,从而降低其效率。具有许多BLOB列的表可能会导致 B 树节点变得太满,并且包含的行太少,从而使整个索引的效率低于行较短或列值存储在页外的情况。
冗余行格式存储特性
该REDUNDANT行格式有如下存储特性:
- 每个索引记录包含一个 6 字节的标头。标头用于将连续记录链接在一起,并用于行级锁定。
- 聚集索引中的记录包含所有用户定义列的字段。此外,还有一个 6 字节的事务 ID 字段和一个 7 字节的滚动指针字段。
- 如果没有为表定义主键,则每个聚集索引记录还包含一个 6 字节的行 ID 字段。
- 每个二级索引记录包含为聚集索引键定义的所有不在二级索引中的主键列。
- 记录包含指向记录的每个字段的指针。如果一条记录的字段总长度小于128字节,则指针为1字节;否则,两个字节。指针数组称为记录目录。指针指向的区域是记录的数据部分。
- 在内部,固定长度的字符列如
CHAR(10)in 以固定长度格式存储。尾随空格不会从VARCHAR列中截断 。 - 大于或等于 768 字节的固定长度列被编码为可变长度列,可以在页外存储。例如,
CHAR(255)如果字符集的最大字节长度大于 3,则列可以超过 768 个字节,就像使用utf8mb4. - SQL
NULL值在记录目录中保留一或两个字节。如果 SQLNULL值存储在可变长度列中,则在记录的数据部分保留零字节。对于定长列,该列的定长保留在记录的数据部分。为NULL值保留固定空间允许将列从NULL非NULL值就地更新, 而不会导致索引页碎片。
紧凑行格式
与REDUNDANT行格式相比,COMPACT行格式减少了约 20% 的行存储空间,代价是增加了某些操作的 CPU 使用。如果您的工作负载是典型的受缓存命中率和磁盘速度限制的工作负载,则COMPACT格式化可能会更快。如果工作负载受 CPU 速度限制,紧凑格式可能会更慢。
使用COMPACT行格式的表在B 树节点内的索引记录中存储可变长度列值(VARCHAR、 VARBINARY和 BLOB和 TEXT类型)的前 768 个字节,其余部分存储在溢出页上。大于或等于 768 字节的固定长度列被编码为可变长度列,可以在页外存储。例如, 如果字符集的最大字节长度大于 3,则列可以超过 768 个字节,就像使用. CHAR(255)``utf8mb4
如果列的值是 768 字节或更少,则不使用溢出页,并且可能会节省一些 I/O,因为该值完全存储在 B 树节点中。这适用于相对较短的BLOB列值,但可能会导致 B 树节点填充数据而不是键值,从而降低其效率。具有许多BLOB列的表可能会导致 B 树节点变得太满,并且包含的行太少,从而使整个索引的效率低于行较短或列值存储在页外的情况。
动态行格式
该DYNAMIC行格式提供相同的存储特性的COMPACT行格式,但增加了增强的长期可变长度列和支持大型索引键前缀的存储能力。
当一个表用 ROW_FORMAT=DYNAMIC,InnoDB 可以存储完全离页的长可变长度列值(for VARCHAR、 VARBINARY和 BLOB和 TEXT类型),聚集索引记录只包含一个指向溢出页的 20 字节指针。大于或等于 768 字节的固定长度字段被编码为可变长度字段。例如, CHAR(255)如果字符集的最大字节长度大于 3,则列可以超过 768 个字节,就像使用utf8mb4.
列是否存储在页外取决于页面大小和行的总大小。当一行太长时,选择最长的列进行页外存储,直到聚集索引记录适合B 树页面。 TEXT并且 BLOB小于或等于 40 字节的列存储在行中。
该DYNAMIC行格式保持在索引节点存储整个行,如果它符合的效率(如做的 COMPACT和REDUNDANT 格式),但是DYNAMIC行格式避免填充B-树节点具有大量数据的问题字节长的列。该DYNAMIC行格式是基于这样的思想,如果一个长的数据值的一部分被存储关闭页,它通常是最有效的存储关闭页整个值。使用DYNAMIC格式,较短的列可能会保留在 B 树节点中,从而最大限度地减少给定行所需的溢出页数。
该DYNAMIC行的格式支持索引键的前缀可达3072个字节。
压缩行格式
该COMPRESSED行格式提供相同的存储特性和功能的 DYNAMIC行格式,但增加了对表和索引数据压缩的支持。
该COMPRESSED行格式使用类似的内部细节关闭页存储为DYNAMIC行格式,从表和索引数据的附加存储和性能的考虑被压缩,并使用较小的页大小。对于COMPRESSED行格式,该 KEY_BLOCK_SIZE选项控制在聚集索引中存储多少列数据,以及在溢出页上放置多少列数据。
定义表的行格式
InnoDB表 的默认行格式由innodb_default_row_format 变量定义 ,其默认值为DYNAMIC。当ROW_FORMATtable 选项未明确定义或ROW_FORMAT=DEFAULT指定时,将使用默认行格式 。
可以使用or 语句中的ROW_FORMATtable 选项 显式定义表的行格式 。例如: CREATE TABLEALTER TABLE
1 | CREATE TABLE t1 (c1 INT) ROW_FORMAT=DYNAMIC; |
MySQL InnoDB 行记录格式(ROW_FORMAT)
一、行记录格式的分类和介绍
在早期的InnoDB版本中,由于文件格式只有一种,因此不需要为此文件格式命名。随着InnoDB引擎的发展,开发出了不兼容早期版本的新文件格式,用于支持新的功能。为了在升级和降级情况下帮助管理系统的兼容性,以及运行不同的MySQL版本,InnoDB开始使用命名的文件格式。

Antelope: 先前未命名的,原始的InnoDB文件格式。它支持两种行格式:COMPACT 和 REDUNDANT。MySQL5.6的默认文件格式。可以与早期的版本保持最大的兼容性。不支持 Barracuda 文件格式。
Barracuda: 新的文件格式。它支持InnoDB的所有行格式,包括新的行格式:COMPRESSED 和 DYNAMIC。与这两个新的行格式相关的功能包括:InnoDB表的压缩,长列数据的页外存储和索引建前缀最大长度为3072字节。
在 msyql 5.7.9 及以后版本,默认行格式由innodb_default_row_format变量决定,它的默认值是DYNAMIC,也可以在 create table 的时候指定ROW_FORMAT=DYNAMIC。用户可以通过命令 SHOW TABLE STATUS LIKE'table_name' 来查看当前表使用的行格式,其中 row_format 列表示当前所使用的行记录结构类型。
PS:如果要修改现有表的行模式为compressed或dynamic,必须先将文件格式设置成Barracuda:set global innodb_file_format=Barracuda;,再用ALTER TABLE tablename ROW_FORMAT=COMPRESSED;去修改才能生效。
1 | mysql> show variables like "innodb_file_format"; |
1 | mysql> show table status like "test%"\G |
二、InnoDB行存储
InnoDB表的数据存储在页(page)中,每个页可以存放多条记录。这些页以树形结构组织,这颗树称为B树索引。表中数据和辅助索引都是使用B树结构。维护表中所有数据的这颗B树索引称为聚簇索引,通过主键来组织的。聚簇索引的叶子节点包含行中所有字段的值,辅助索引的叶子节点包含索引列和主键列。
变长字段是个例外,例如对于BLOB和VARCHAR类型的列,当页不能完全容纳此列的数据时,会将此列的数据存放在称为溢出页(overflow page)的单独磁盘页上,称这些列为页外列(off-page column)。这些列的值存储在以单链表形式存在的溢出页列表中,每个列都有自己溢出页列表。某些情况下,为了避免浪费存储空间和消除读取分隔页,列的所有或前缀数据会存储在B+树索引中。
三、Compact 和 Redundant
(一)Compact
Compact行记录是在MySQL5.0中引入的,为了高效的存储数据,简单的说,就是为了让一个页(Page)存放的行数据越多,这样性能就越高。行记录格式如下:

变长字段长度列表:变长字段长度最大不超过2字节(MySQL数据库varcahr类型的最大长度限制为65535)
NULL标识位:该位指示了该行数据中是否有NULL值,有则用1。
记录头信息:固定占用5字节(40位)

- 列N数据:实际存储每列的数据,NULL不占该部分任何空间,即NULL占有NULL标志位,实际存储不占任何空间。
PS:每一行数据除了用户定义的例外,还有两个隐藏列,事物ID列和回滚指针列,分别位6字节和7字节的大小,若InnoDB表没有定义主键,每行还未增加一个6字节的rowid列。
(二)Redundant
MySQL5.0之前的行记录格式:

字段偏移列表:同样是按照列的顺序逆序放置的,若列的长度小于255字节,用1字节表示,若大于255字节,用2字节表示。
记录头信息:占用6字节(48位)

(三)行溢出数据
当
行记录的长度没有超过行记录最大长度时,所有数据都会存储在当前页。当
行记录的长度超过行记录最大长度时,变长列(variable-length column)会选择外部溢出页(overflow page,一般是Uncompressed BLOB Page)进行存储。
1 | Compact` + `Redundant`:保留前`768Byte`在当前页(`B+Tree叶子节点`),其余数据存放在`溢出页`。`768Byte`后面跟着`20Byte`的数据,用来存储`指向溢出页的指针。 |
(四)概述
对于 Compact 和 Redundant 行格式,InnoDB将变长字段(VARCHAR, VARBINARY, BLOB 和 TEXT)的前786字节存储在B+树节点中,其余的数据存放在溢出页(off-page),如下图:

上面所讲的讲的blob或变长大字段类型包括blob,text,varchar,其中varchar列值长度大于某数N时也会存溢出页,在latin1字符集下N值可以这样计算:innodb的块大小默认为16kb,由于innodb存储引擎表为索引组织表,树底层的叶子节点为一双向链表,因此每个页中至少应该有两行记录,这就决定了innodb在存储一行数据的时候不能够超过8k,减去其它列值所占字节数,约等于N。
使用Antelope文件格式,若字段的值小于等于786字节,不需要溢出页,因为字段的值都在B+树节点中,所以会降低I/O操作。这对于相对较短的BLOB字段有效,但可能由于B+树节点存储过多的数据而导致效率低下。
四、Compressed 和 Dynamic
InnoDB1.0x开始引入心的文件格式(file format,用户可以理解位新的页格式)——Barracuda(图1),这个新的格式拥有两种新的行记录格式:Compressed和Dynamic。
新的两种记录格式对于存放BLOB中的数据采用了完全的行溢出的方式。如图:

Dynamic行格式,列存储是否放到off-page页,主要取决于行大小,他会把行中最长的一列放到off-page,直到数据页能存放下两行。TEXT或BLOB列<=40bytes时总是存在于数据页。这种方式可以避免compact那样把太多的大列值放到B-tree Node(数据页中只存放20个字节的指针,实际的数据存放在Off Page中,之前的Compact 和 Redundant 两种格式会存放768个字前缀字节)。
Compressed物理结构上与Dynamic类似,Compressed行记录格式的另一个功能就是存储在其中的行数据会以zlib的算法进行压缩,因此对于BLOB、TEXT、VARCHAR这类大长度数据能够进行有效的存储(减少40%,但对CPU要求更高)。
转载至:https://www.cnblogs.com/wilburxu/p/9435818.html
作者:WilburXu
主键
建议您为您创建的每个表定义一个主键。选择主键列时,选择具有以下特征的列:
- 最重要的查询引用的列。
- 永远不会留空的列。
- 从不具有重复值的列。
- 插入后很少更改值的列。
查看 InnoDB 表属性
要查看InnoDB表的属性,请发出一条SHOW TABLE STATUS 语句:
1 | mysql> SHOW TABLE STATUS FROM test LIKE 't%' \G; |
您还可以InnoDB通过查询InnoDB信息架构系统表来访问表属性:
1 | mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME='test/t1' \G |
外部创建表
在InnoDB 外部创建表有不同的原因;也就是说,在数据目录之外创建表。例如,这些原因可能包括空间管理、I/O 优化或将表放置在具有特定性能或容量特征的存储设备上。
InnoDB 支持以下外部创建表的方法:
您可以InnoDB通过DATA DIRECTORY 在CREATE TABLE语句中指定子句在外部目录中创建表。
1 | CREATE TABLE t1 (c1 INT PRIMARY KEY) DATA DIRECTORY = '/external/directory'; |
将表从 MyISAM 转换为 InnoDB
- 调整 MyISAM 和 InnoDB 的内存使用
- 处理过长或过短的事务
- 处理死锁
- 存储布局
- 转换现有表
- 克隆表的结构
- 传输数据
- 存储要求
- 定义主键
- 应用程序性能注意事项
- 了解与 InnoDB 表关联的文件
InnoDB 中的 AUTO_INCREMENT 处理
InnoDB提供了一个可配置的锁定机制,可以显着提高使用AUTO_INCREMENT列向表中添加行的SQL语句的可伸缩性和性能。 要对InnoDB表使用AUTO_INCREMENT机制,必须将AUTO_INCREMENT列定义为索引的一部分,以便可以对表执行相当于索引的SELECT MAX(ai_col)查找以获取最大列值。 通常,这是通过使列成为某些表索引的第一列来实现的。
本节介绍AUTO_INCREMENT锁定模式的行为,对不同AUTO_INCREMENT锁定模式设置的使用含义,以及InnoDB如何初始化AUTO_INCREMENT计数器。
InnoDB AUTO_INCREMENT 锁定模式
本节介绍用于生成自动递增值的AUTO_INCREMENT锁定模式的行为,以及每种锁定模式如何影响复制。 自动递增锁定模式在启动时使用innodb_autoinc_lock_mode配置参数进行配置。 以下术语用于描述innodb_autoinc_lock_mode设置:
- “INSERT-like” statements(类INSERT语句)
所有可以向表中增加行的语句,包括
INSERT,INSERT ... SELECT,REPLACE,REPLACE ... SELECT, andLOAD DATA.包括“simple-inserts”, “bulk-inserts”, and “mixed-mode” inserts. - “Simple inserts”
可以预先确定要插入的行数(当语句被初始处理时)的语句。 这包括没有嵌套子查询的单行和多行INSERT和REPLACE语句,但不包括
INSERT ... ON DUPLICATE KEY UPDATE - “Bulk inserts”
事先不知道要插入的行数(和所需自动递增值的数量)的语句。 这包括
INSERT ... SELECT,REPLACE ... SELECT和LOAD DATA语句,但不包括纯INSERT。 InnoDB在处理每行时一次为AUTO_INCREMENT列分配一个新值。 - “Mixed-mode inserts”
这些是“Simple inserts”语句但是指定一些(但不是全部)新行的自动递增值。 示例如下,其中c1是表t1的AUTO_INCREMENT列:
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');另一种类型的“Mixed-mode inserts”是INSERT ... ON DUPLICATE KEY UPDATE,其在最坏的情况下实际上是INSERT语句随后又跟了一个UPDATE,其中AUTO_INCREMENT列的分配值不一定会在 UPDATE 阶段使用
innodb_autoinc_lock_mode 变量 有三种可能的设置 。设置为 0、1 或 2,分别表示 “传统”、“连续”或 “交错”锁定模式。从 MySQL 8.0 开始,交错锁模式 ( innodb_autoinc_lock_mode=2) 是默认设置。在 MySQL 8.0 之前,连续锁定模式是默认的 ( innodb_autoinc_lock_mode=1)。
MySQL 8.0 中交错锁模式的默认设置反映了从基于语句的复制更改为基于行的复制作为默认复制类型。基于语句的复制需要连续的自增锁模式,以保证给定的SQL语句序列以可预测和可重复的顺序分配自增值,而基于行的复制对SQL语句的执行顺序不敏感.
innodb_autoinc_lock_mode = 0(“传统”锁定模式)传统的锁定模式提供与
innodb_autoinc_lock_mode引入变量之前存在的相同行为 。由于语义可能存在差异,提供传统锁定模式选项是为了向后兼容、性能测试和解决“混合模式插入”问题。在这种锁定模式下,所有“类似 INSERT ”的语句都会获得一个特殊的表级
AUTO-INC锁定,用于插入到带有AUTO_INCREMENT列的表中。此锁通常保持到语句的末尾(而不是事务的末尾),以确保为给定的INSERT语句序列以可预测和可重复的顺序分配自动增量值,并确保自动增量值由任何给定语句赋值都是连续的。在基于语句的复制的情况下,这意味着在副本服务器上复制 SQL 语句时,自动增量列使用的值与源服务器上的值相同。多个
INSERT语句的执行结果 是确定性的,副本复制与源上相同的数据。如果多个INSERT语句生成的自动增量值被交错,则两个并发INSERT语句的结果 将是不确定的,并且无法使用基于语句的复制可靠地传播到副本服务器。为清楚起见,请考虑使用此表的示例:
1
2
3
4
5CREATE TABLE t1 (
c1 INT(11) NOT NULL AUTO_INCREMENT,
c2 VARCHAR(10) DEFAULT NULL,
PRIMARY KEY (c1)
) ENGINE=InnoDB;假设有两个事务在运行,每个事务都将行插入到带有
AUTO_INCREMENT列的表中 。一个事务使用INSERT ... SELECT插入 1000 行的语句,另一个事务使用 插入一行的简单INSERT语句:1
2Tx1: INSERT INTO t1 (c2) SELECT 1000 rows from another table ...
Tx2: INSERT INTO t1 (c2) VALUES ('xxx');InnoDB无法预先知道从Tx1SELECT中的INSERT语句中检索了多少行 ,并且随着 语句的进行,它一次分配一个自动增量值。使用表级锁,一直保持到语句的末尾,一次只能执行一条INSERT引用 table 的语句t1,并且不同语句的自增数的生成不会交错。Tx1INSERT ... SELECT语句生成的自增值 是连续的,使用的(单个)自增值INSERTTx2 中的语句小于或大于所有用于 Tx1 的语句,具体取决于哪个语句先执行。只要 SQL 语句在从二进制日志重放时(使用基于语句的复制时,或在恢复场景中)以相同的顺序执行,结果与 Tx1 和 Tx2 首次运行时的结果相同。因此,在语句结束之前一直持有的表级锁使
INSERT使用自动增量的语句安全用于基于语句的复制。但是,当多个事务同时执行插入语句时,这些表级锁会限制并发性和可伸缩性。在前面的示例中,如果没有表级锁,则用于
INSERTin Tx2的 自增列的值取决于语句的执行时间。如果INSERTTx2 的INSERT在 Tx1 的运行时执行(而不是在它开始之前或完成之后),则这两个INSERT语句分配的特定自增值 是不确定的,并且可能因运行而异。
innodb_autoinc_lock_mode = 1(“连续”锁定模式)这是默认的锁定模式.在这个模式下,“bulk inserts”仍然使用AUTO-INC表级锁,并保持到语句结束.这适用于所有
INSERT ... SELECT,REPLACE ... SELECT和LOAD DATA语句。同一时刻只有一个语句可以持有AUTO-INC锁. “Simple inserts”(要插入的行数事先已知)通过在mutex(轻量锁)的控制下获得所需数量的自动递增值来避免表级AUTO-INC锁, 它只在分配过程的持续时间内保持,而不是直到语句完成。 不使用表级AUTO-INC锁,除非AUTO-INC锁由另一个事务保持。 如果另一个事务保持AUTO-INC锁,则“简单插入”等待AUTO-INC锁,如同它是一个“批量插入”。这种锁定模式确保,在存在
INSERT行数未知的 语句时(并且随着语句的进行分配自动递增编号),任何“INSERT-like ” 语句分配的所有自动递增值都是连续,并且操作对于基于语句的复制是安全的。简而言之,这种锁定模式显着提高了可伸缩性,同时可以安全地用于基于语句的复制。此外,与“传统” 锁定模式一样,任何给定语句分配的自动递增编号都是连续的。对于任何使用自动增量的语句,与“传统”模式相比,语义 上 没有任何变化,只有一个重要的例外。
例外情况是“混合模式插入”,其中用户为
AUTO_INCREMENT多行“简单插入”中的某些(但不是全部)行提供列的显式值 。对于此类插入,InnoDB分配比要插入的行数更多的自动增量值。但是,所有自动分配的值都是连续生成的(因此高于)由最近执行的前一条语句生成的自动增量值。“多余”的数字丢失。innodb_autoinc_lock_mode = 2(“交错”锁定模式)在这种锁模式下,没有 “
INSERT-like ” 语句使用表级AUTO-INC锁,可以同时执行多条语句。这是最快和最具扩展性的锁模式,但在使用基于语句的复制或从二进制日志重放 SQL 语句的恢复场景时,它是不安全的。在这种锁定模式下,自动递增值保证在所有并发执行的“
INSERT-like ” 语句中是唯一的并且单调递增 。但是,由于多个语句可以同时生成数字(即,数字的分配在语句之间交错),为任何给定语句插入的行生成的值可能不连续。如果执行的唯一语句是提前知道要插入的行数的“简单插入”,则为单个语句生成的数字没有间隙,“混合模式插入”除外 。但是,当执行“批量插入”时,任何给定语句分配的自动增量值中可能存在间隙。
2. 索引
聚集索引和二级索引
每个InnoDB表都有一个称为聚集索引的特殊索引,用于存储行数据。通常,聚集索引与主键同义。为了从查询、插入和其他数据库操作中获得最佳性能,了解如何InnoDB使用聚集索引来优化常见查找和 DML 操作非常重要。
聚集索引以外的索引称为二级索引。在 中InnoDB,二级索引中的每条记录都包含该行的主键列,以及为二级索引指定的列。 InnoDB使用此主键值搜索聚集索引中的行。
如果主键很长,二级索引会占用更多的空间,所以主键短是有利的。
InnoDB 索引的物理结构
除空间索引外,InnoDB 索引都是B 树数据结构。空间索引使用 R 树,它是用于索引多维数据的专用数据结构。索引记录存储在其 B 树或 R 树数据结构的叶页中。索引页的默认大小为 16KB。页大小由innodb_page_sizeMySQL 实例初始化时的设置决定 。
当新记录插入到InnoDB 聚集索引中时, InnoDB尝试保留 1/16 的页面空闲空间以供将来插入和更新索引记录。如果按顺序(升序或降序)插入索引记录,则生成的索引页大约为 15/16。如果以随机顺序插入记录,则页面从 1/2 到 15/16 已满。
InnoDB创建或重建 B 树索引时执行批量加载。这种创建索引的方法称为排序索引构建。该 innodb_fill_factor变量定义了在排序索引构建期间填充的每个 B 树页面上的空间百分比,剩余空间保留用于将来的索引增长。空间索引不支持排序索引构建。有关更多信息,请参阅 第 15.6.2.3 节,“排序索引构建”。一个 innodb_fill_factor100个叶子在聚簇索引页的空间1/16的设置免费为未来的指数增长。
排序索引构建
InnoDB在创建或重建索引时执行批量加载而不是一次插入一个索引记录。这种索引创建方法也称为排序索引构建。空间索引不支持排序索引构建。
索引构建分为三个阶段。
在第一阶段, 扫描聚集索引,生成索引条目并添加到排序缓冲区。当排序缓冲区变满时,条目被排序并写出到临时中间文件。此过程也称为 “运行”。
在第二阶段,将一次或多次运行写入临时中间文件,对文件中的所有条目执行归并排序。
在第三个也是最后一个阶段,排序后的条目被插入到 B 树中。
在引入排序索引构建之前,使用插入 API 将索引条目一次一条地插入到 B 树中。此方法涉及打开 B 树 游标以查找插入位置,然后使用乐观插入将条目插入 B 树页面 。如果由于页面已满而导致插入失败, 则会执行悲观插入,这涉及打开 B 树游标并根据需要拆分和合并 B 树节点,以便为条目找到空间。这种“自上而下”的弊端 建立索引的方法是搜索插入位置的成本和不断分裂和合并 B 树节点。
排序索引构建使用“自下而上”建立索引的方法。使用这种方法,对最右侧叶页的引用保存在 B 树的所有级别。分配必要 B 树深度的最右侧叶页,并根据其排序顺序插入条目。一旦叶页已满,节点指针将附加到父页,并为下一次插入分配同级叶页。这个过程一直持续到所有条目都被插入,这可能会导致插入到根级别。分配同级页时,释放对先前固定的叶页的引用,新分配的叶页成为最右侧的叶页和新的默认插入位置。
为未来的指数增长保留 B 树页面空间
要为将来的索引增长留出空间,您可以使用该 innodb_fill_factor变量来保留 B 树页面空间的百分比。例如,设置 innodb_fill_factor为 80 会在排序索引构建期间保留 B 树页面中 20% 的空间。此设置适用于 B 树叶页和非叶页。它不适用于用于TEXT或 BLOB条目的外部页面 。保留的空间量可能与配置的不完全相同,因为该 innodb_fill_factor值被解释为提示而不是硬限制。
全文索引 支持排序索引构建 。以前,SQL 用于将条目插入到全文索引中。
对于压缩表,之前的索引创建方法将条目附加到压缩页和未压缩页。当修改日志(代表压缩页面上的可用空间)变满时,将重新压缩压缩页面。如果由于空间不足导致压缩失败,页面将被拆分。使用排序索引构建,条目仅附加到未压缩的页面。当一个未压缩的页面变满时,它就会被压缩。自适应填充用于确保在大多数情况下压缩成功,但如果压缩失败,页面将被拆分并再次尝试压缩。这个过程一直持续到压缩成功。
在排序索引构建期间禁用重做日志记录。相反,有一个 检查点来确保索引构建可以承受意外退出或失败。检查点强制将所有脏页写入磁盘。在排序索引构建期间,页面清理器线程会定期收到信号以刷新 脏页面,以确保可以快速处理检查点操作。通常,当干净页面的数量低于设置的阈值时,页面清理器线程会刷新脏页面。对于排序索引构建,脏页会立即刷新以减少检查点开销并并行化 I/O 和 CPU 活动。
排序索引构建可能会导致 优化器统计信息与以前的索引创建方法生成的统计信息不同。统计数据的差异(预计不会影响工作负载性能)是由于用于填充索引的算法不同。
MySQL索引创建原理:Sorted Index Builds
在早期的MySQL版本,创建索引是需要复制整表数据的;在5.5版本中,MySQL利用快速创建索引(fast index creation)技术,优化了索引的创建算法,使其执行时不需要再进行复制表数据的操作,同时允许在创建索引时,其他事务并发读;而在5.6以上的版本,在创建索引时允许其他事务并发读写,并将此索引创建方式称为Sorted Index Builds。以下介绍一下5.5前后版本,MySQL在索引创建方式上的优化变迁。
一、5.5以前(表复制) 当需要创建索引时,MySQL会根据创建的索引,按照所需的表结构新建一个空表,然后锁定源表,将数据一行一行地插入到空表中,同时更新维护空表上的索引。当数据全部复制完毕,索引也就建成了,最后源表被删除,而新建的表则更名为源表的名字。 这种方式弊端非常多,不但需要复制全表数据,十分耗时,而且在复制数据期间,源表是被锁定的状态,排斥任何读写操作。
二、5.5以后(Sorted Index Builds) 当需要创建索引时,分为三个阶段: 1.扫描源表,即主键聚集索引,将要创建的索引的条目(即索引键和主键),先存放在排序缓冲区(sort buffer)。当排序缓冲区写满后,里面的索引条目将会进行一次排序,并写出到一个临时中间文件; 2.经过多次写满排序缓冲区并写出到临时中间文件,主键聚集索引遍历完后,在临时中间文件中将会对所有索引条目进行一次合并排序; 3.最后排好序的索引条目插入到目标B树索引,索引创建完成。
三、相比变复制的方式,Sorted Index Builds带来了哪些好处? 在引入Sorted Index Builds之前,创建索引的过程是一遍扫描源表,一遍将得到的数据插入到新建的空表中,也就是边扫描边插入B树索引中。这种方式由于索引键的插入是乱序的,所以在插入的过程中需要不断地寻找和确认插入的位置,期间必然伴随着索引页的分裂和合并。 而Sorted Index Builds的方式,由于事先已经将索引条目排序,插入B树索引时,只需要顺序地往索引页里插入数据,再索引页写满时,再向右分配一个索引页即可,整个索引创建过程中,都是在写入最右的叶子节点,无需去寻找确认插入的位置,也不会引发索引页的分裂合并。
四、Sorted Index Builds和redo日志 Sorted Index Builds不会写redo记录,而是用一个检查点来将脏页刷到磁盘。而页清理线程会及时地将索引脏页刷到磁盘,以减少执行检查点所花时间和开销。 同时也是由于创建索引不记录redo,所以在xtrabackup备份期间,如果执行了创建索引操作,备份将会终止,因为xtrabackup无法从redo中获取创建索引的操作,在利用备份apply-log时也就无法重现创建索引的操作。
作者:遇星 原文链接:https://blog.csdn.net/weixin_39004901/article/details/84938358
3. 表空间
系统表空间
系统表空间是更改缓冲区的存储区域。如果表是在系统表空间中创建的,而不是在每个表文件或通用表空间中创建,则它还可能包含表和索引数据。在以前的 MySQL 版本中,系统表空间包含InnoDB数据字典。在 MySQL 8.0 中,InnoDB将元数据存储在 MySQL 数据字典中。在以前的 MySQL 版本中,系统表空间还包含双写缓冲区存储区。自 MySQL 8.0.20 起,此存储区域驻留在单独的双写文件中。
系统表空间可以有一个或多个数据文件。默认情况下,ibdata1会在数据目录中创建一个名为 的系统表空间数据文件 。系统表空间数据文件的大小和数量由innodb_data_file_path启动选项定义。
增加系统表空间的大小
增加系统表空间大小的最简单方法是将其配置为自动扩展。
减少 InnoDB 系统表空间的大小
不支持减小现有系统表空间的大小。实现更小的系统表空间的唯一选择是将数据从备份恢复到使用所需系统表空间大小配置创建的新 MySQL 实例。
File-Per-Table 表空间
每个表的文件表空间包含单个InnoDB表的数据和索引 ,并存储在文件系统上的单个数据文件中。
从历史上看,所有的InnoDB 表和indexes 是存储在system 表空间。这个整体的方法是针对机器是整个用于数据库处理,精心策划的数据增长,任何磁盘存储分配给MySQL 不会被其他目的需要.
InnoDB的file-per-table tablespace功能提供一个更加灵活的选择,每个InnoDB 表和他的索引是存储在一个单独的.ibd文件。每个这样的.ibd文件代表一个单独的表空间。
这个功能是通过innodb_file_per_table 配置选项,默认启用在5.6.6和更高版本
File-Per-Table Tablespaces 存在的优点:
- 截断或删除在 file-per-table 表空间中创建的表后,磁盘空间将返回给操作系统。截断或删除存储在共享表空间中的表会在共享表空间数据文件中创建空闲空间,该空间只能用于
InnoDB数据。换句话说,共享表空间数据文件在表被截断或删除后不会缩小。 ALTER TABLE对共享表空间中的表进行 表复制操作会增加表空间占用的磁盘空间量。此类操作可能需要与表中的数据加上索引一样多的额外空间。该空间不会像用于 file-per-table 表空间那样释放回操作系统。- TRUNCATE TABLE 选项是更快的 当运行在存储在file-per-table tablepaces的表
- File-per-table 表空间数据文件可以在单独的存储设备上创建,用于 I/O 优化、空间管理或备份目的。
- 你可以移动单独的InnoDB 表相比整个数据库
- 您可以从另一个 MySQL 实例导入驻留在 file-per-table 表空间中的表。
- 当发生数据损坏、备份或二进制日志不可用或 MySQL 服务器实例无法重新启动时,存储在单个表空间数据文件中的表可以节省时间并提高成功恢复的机会。
- 在 file-per-table 表空间中创建的表支持
DYNAMIC与COMPRESSED行格式相关的功能,而系统表空间不支持这些功能。 - 共享表空间中的表的大小受 64TB 表空间大小限制。相比之下,每个表每个文件的表空间都有 64TB 的大小限制,这为单个表的大小增长提供了足够的空间。
File-Per-Table Tablespaces 存在的缺点:
file-per-table tablespaces 每个表有一个未使用的空间, 只能被相同表的行使用。这个会浪费空间
fsync 操作必须允许在每个打开的表相比在一个单独的文件。因为 这里有一个单独的fsync 操作在每个文件,
写操作在多个表不能合成一个单独的I/O操作。 这个可能需要InnoDB 执行更多数量得到fsync操作
mysqld 必须每个表一个文件句柄, 这可能会影响性能 如果你有大量的表
当每个表都有自己的数据文件时,需要更多的文件描述符。
存在更多碎片的可能性,这会阻碍
DROP TABLE和表扫描性能。但是,如果管理碎片,每个表的文件表空间可以提高这些操作的性能。删除驻留在 file-per-table 表空间中的表时会扫描缓冲池,这对于大型缓冲池可能需要几秒钟。使用广泛的内部锁执行扫描,这可能会延迟其他操作。
该
innodb_autoextend_increment变量定义了在自动扩展共享表空间文件变满时扩展其大小的增量大小,不适用于 file-per-table 表空间文件,这些文件无论innodb_autoextend_increment设置如何都会自动扩展 。每个表的初始文件表空间扩展是少量的,之后扩展以 4MB 的增量发生。
通用表空间
通用表空间是InnoDB 使用CREATE TABLESPACE语法创建的共享表空间。
通用表空间提供以下功能:
- 与系统表空间类似,通用表空间是能够为多个表存储数据的共享表空间。
- 通用表空间比file-per-table 表空间具有潜在的内存优势 。服务器在表空间的生命周期内将表空间元数据保存在内存中。与在每个单独的文件表空间中的相同数量的表相比,较少的通用表空间中的多个表为表空间元数据消耗的内存更少。
- 一般的表空间数据文件可以放在一个相对于或独立于MySQL数据目录的目录中,它为您提供了许多文件每表表空间的数据文件和存储管理能力 。与 file-per-table 表空间一样,将数据文件放置在 MySQL 数据目录之外的能力允许您单独管理关键表的性能,为特定表设置 RAID 或 DRBD,或将表绑定到特定磁盘,例如。
- 通用表空间支持所有表行格式和相关功能。
- 该
TABLESPACE选项可用于CREATE TABLE在通用表空间、每个表文件表空间或系统表空间中创建表。 - 该
TABLESPACE选项可用于ALTER TABLE在通用表空间、每个表文件表空间和系统表空间之间移动表。
撤销表空间
撤消表空间包含撤消日志,这是包含有关如何撤消事务对聚集索引记录的最新更改的信息的记录集合。
初始化 MySQL 实例时会创建两个默认的 undo 表空间。默认撤消表空间在初始化时创建,以为在接受 SQL 语句之前必须存在的回滚段提供位置。至少需要两个撤消表空间来支持撤消表空间的自动截断。
默认撤消表空间在innodb_undo_directory 变量定义的位置创建。如果 innodb_undo_directory变量未定义,则在数据目录中创建默认的撤消表空间。默认撤消表空间数据文件命名为 undo_001和undo_002。数据字典中定义的对应撤销表空间名称是innodb_undo_001和 innodb_undo_002。
临时表空间
InnoDB 使用会话临时表空间和全局临时表空间。
会话临时表空间
会话临时表空间存储用户创建的临时表和优化器在InnoDB配置为磁盘内部临时表的存储引擎时创建的内部临时表。从 MySQL 8.0.16 开始,用于磁盘内部临时表的存储引擎是InnoDB.
会话临时表空间在第一次请求创建磁盘临时表时从临时表空间池中分配给会话。一个会话最多分配两个表空间,一个用于用户创建的临时表,另一个用于优化器创建的内部临时表。分配给会话的临时表空间用于会话创建的所有磁盘临时表。当会话断开连接时,其临时表空间被截断并释放回池中。服务器启动时会创建一个包含 10 个临时表空间的池。池的大小永远不会缩小,并且表空间会根据需要自动添加到池中。在正常关闭或中止初始化时删除临时表空间池。会话临时表空间文件在创建时大小为五页,并且具有.ibt 文件扩展名。
为会话临时表空间保留了 40 万个空间 ID。因为每次服务器启动时都会重新创建会话临时表空间池,所以当服务器关闭时会话临时表空间的空间 ID 不会保留,并且可能会被重用。
在 Linux 上优化表空间分配
从 MySQL 8.0.22 开始,您可以优化如何InnoDB 在 Linux 上为每个表文件和通用表空间分配空间。默认情况下,当需要额外空间时, InnoDB将页面分配给表空间并将 NULL 物理写入这些页面。如果频繁分配新页面,此行为会影响性能。从 MySQL 8.0.22 开始,您可以innodb_extend_and_initialize在 Linux 系统上禁用 以避免将 NULL 物理写入新分配的表空间页面。当 innodb_extend_and_initialize被禁用,空间使用分配给表空间文件 posix_fallocate()调用,而不必物理写的NULL该预留空间。
4. 双写缓存区doublewrite
双写缓冲区是InnoDB的三大特性之一,还有两个是 Buffer Pool简称BP、自适应Hash索引。双写缓冲区是一个存储区域,在 InnoDB将页面写入InnoDB数据文件中的适当位置之前,从缓冲池中写入页面 。如果 在页面写入过程中存在操作系统、存储子系统或意外的mysqld进程退出,则 InnoDB可以在崩溃恢复期间从双写缓冲区中找到该页面的良好副本。
注意:系统恢复后,MySQL 可以根据redolog 进行恢复,而mysql在恢复的过程中是检查page的checksum,checksum就是pgae的最后事务号,发生partial page write 问题时,page已经损坏,找不到该page中的事务号,就无法恢复。
虽然数据被写入两次,但双写缓冲区不需要两倍的 I/O 开销或两倍的 I/O 操作。数据以一个大的顺序块写入双写缓冲区,只需fsync()调用一次操作系统
在 MySQL 8.0.20 之前,doublewrite 缓冲区存储区位于InnoDB系统表空间中。从 MySQL 8.0.20 开始,双写缓冲区存储区域位于双写文件中。
从 MySQL 8.0.23 开始,InnoDB自动加密属于加密表空间的双写文件页面(请参阅第 15.13 节,“InnoDB静态数据加密”)。同样,属于页面压缩表空间的双写文件页面被压缩。因此,双写文件可以包含不同的页面类型,包括未加密和未压缩的页面、加密的页面、压缩的页面以及加密和压缩的页面。
5. 重做日志redo log
重做日志是一种基于磁盘的数据结构,用于在崩溃恢复期间纠正不完整事务写入的数据。在正常操作期间,重做日志对由 SQL 语句或低级 API 调用产生的更改表数据的请求进行编码。在初始化期间和接受连接之前,会自动重放在意外关闭之前未完成更新数据文件的修改。
默认情况下,重做日志在磁盘上由两个名为ib_logfile0和 的 文件物理表示ib_logfile1。MySQL 以循环方式写入重做日志文件。重做日志中的数据根据受影响的记录进行编码;这些数据统称为重做。通过重做日志的数据通道由不断增加的LSN值表示。
重做日志刷新的组提交
InnoDB,像任何其他 符合 ACID的数据库引擎一样,在事务提交之前刷新事务的重做日志。InnoDB 使用组提交 功能将多个刷新请求组合在一起,以避免每次提交一次刷新。使用组提交, InnoDB向日志文件发出一次写入以对大约同时提交的多个用户事务执行提交操作,从而显着提高吞吐量。
重做日志归档
在备份操作正在进行时,复制重做日志记录的备份实用程序有时可能无法跟上重做日志生成的速度,从而导致由于这些记录被覆盖而丢失重做日志记录。这个问题最常发生在备份操作期间有大量 MySQL 服务器活动时,并且重做日志文件存储介质的运行速度比备份存储介质更快。MySQL 8.0.17 中引入的重做日志归档功能通过将重做日志记录顺序写入归档文件以及重做日志文件来解决此问题。备份实用程序可以根据需要从存档文件中复制重做日志记录,从而避免潜在的数据丢失。
性能注意事项
由于额外的写入活动,激活重做日志归档通常具有较小的性能成本。
在 Unix 和类 Unix 操作系统上,假设没有持续的高更新率,性能影响通常很小。在 Windows 上,假设相同,性能影响通常会更高一些。
如果有持续的高更新率并且重做日志存档文件与重做日志文件位于同一存储介质上,则由于复合写入活动,性能影响可能更加显着。
如果持续的高更新率并且重做日志存档文件位于比重做日志文件慢的存储介质上,则性能会受到任意影响。
写入重做日志归档文件不会妨碍正常的事务日志记录,除非重做日志归档文件存储介质的运行速度比重做日志文件存储介质慢得多,并且存在大量持久化重做日志块的积压等待写入重做日志归档文件。在这种情况下,事务日志记录率降低到可以由重做日志存档文件所在的较慢存储介质管理的水平。
Redo Log是循环写的,如下图:
- writepos记录了当前写的位置,一边写位置一边往前推进,当writepos与checkpoint重叠的时候就表示logfile写满了,绿色部分表示是空闲的空间,红色部分是写了redo log的空间;
- checkpoint处标识了当前的
LSN,每当系统崩溃重启,都会从当前checkpoint这个位置执行重做日志,根据重做日志逐个确认数据页是否没问题,有问题就通过redo log进行修复。

6. 撤销日志Undo Logs
撤消日志是与单个读写事务相关联的撤消日志记录的集合。撤消日志记录包含有关如何撤消事务对聚集索引 记录的最新更改的信息。如果另一个事务需要查看原始数据作为一致性读取操作的一部分,则未修改的数据将从撤消日志记录中检索。撤消日志存在于 撤消日志段中,而撤消日志段包含在 回滚段中。回滚段驻留在 撤销表空间和全局临时表空间中。
7. 补充说明
双写缓冲区是InnoDB的三大特性之一,还有两个是 Buffer Pool简称BP、自适应Hash索引。doublewrite缓冲区是一个存储区,在该存储区中,InnoDB将页面写入InnoDB数据文件中的适当位置之前,先从缓冲池中刷新页面 。如果在页面写入过程中存在操作系统,存储子系统或意外的mysqld进程退出,则InnoDB可以在崩溃恢复期间从doublewrite缓冲区中找到页面的良好副本。注意:系统恢复后,MySQL 可以根据redolog 进行恢复,而mysql在恢复的过程中是检查page的checksum,checksum就是pgae的最后事务号,发生partial page write 问题时,page已经损坏,找不到该page中的事务号,就无法恢复。 为什么需要双写?个人理解宏观上还是与InnoDB需要支持事务(ACID)特性有关,而底层的原因是为了解决Partial Write Page问题。
1 | 介绍双写之前我们有了解部分页面写入问题: |
之前在分析Mysql - InnoDB引擎对事务ACID的实现原理分析时个人认为已经透彻的分析了事务的实现过程,而为了实现事务InnoDB引入了比较多的组件,设计的特别复杂,InnoDB级别包括:(行锁、临建锁、间隙锁)锁和加锁规则、MVCC、redo log、undo log、视图(Read View)。而官方文档也在隔离型和持久性上面明确指向了数据双写机制,如下图:

InnoDB的页大小默认为16K,可以使用参数innodb_page_size设置, 可设置的值有: 64KB,32KB,16KB(默认),8KB和4KB。并且在数据校验时也针对页进行计算,即他们是一个整个对待,包括把数据持久化到磁盘的操作。而计算机的硬件和操作系统在极端情况下(比如断电、系统崩溃)时,刚写入了4K或8K数据,那么就不能保证该操作的原子性,称为部分页面写问题(Partial Write Page)。
此时就引入了双写缓存区的机制,当发生极端情况时,可以从系统表空间的Double Write Buffer【磁盘上】进行恢复,下面是InnoDB的架构图、双写和恢复流程图。为了方便对比,将组件放在了相同的位置:


这样在极端情况下也能解决 Partial Write page问题了,但是如果我自己的系统本身数据要求没有那么高(比如日志数据库),这样的话毕竟双写是有一定的性能开销的。可以通过参数innodb_doublewrite = 0进行关闭,设置为1表示开启。官方认为,尽管需要写入两次数据,但是写缓冲区不需要两次的 io开销或操作,因为只需要调用一次操作系统的fsync() 就可以将批量数据顺序写入磁盘 -> 系统表空间的Double Write Buffer(如上图),这里是顺序写而不是随机写(性能可以保证),当然前提是配置刷盘策略参数innodb_flush_method为默认的O_DIRECT。
作者:it_lihongmin 原文链接:https://blog.csdn.net/it_lihongmin/article/details/115192102
一双写是什么
双写是InnoDB在表空间上的128个页(2个区)是2MB; 其原理: 为了解决部分页面写入问题,当mysql将脏数据刷新到数据文件的时候,先使用memcopy将脏数据复制到内存中的双写缓冲区,之后通过双写缓冲区再分2次,每次写入1MB到共享表空间,然后马上调用fsync函数,同步到磁盘上,避免过程缓存带来的问题,在这个中,doublewrite是顺序写,开销不大,在完成doublewrite后,在将写buffer写入各表空间文件,字节是离散写入。 如果发生了情况(断电),InnoDB再次启动后,发现了一个页面数据已经损坏,此时就可以从双写缓冲区中进行数据恢复了。
二双写的反对是什么?什么位置 表空间上的双写缓冲区实际上也是一个文件,写DWB会导致系统有更多的fsync,而硬盘的fsync性能,所以它会降低mysql的整体性能。但是并不会降低到原来的 50%。这主要是因为: 1)双写是一个连接的存储空间,所以硬盘在写数据的时候是顺序写,而不是随机写,这样的性能提升。 2)数据从双写缓冲区写到真正的段中的时候,系统会自动合并连接刷新空间的方式,每次可以刷新多个页面;
三双写在恢复的时候是如何工作的? If there’s a partial page write to the doublewrite buffer itself, the original page will still be on disk in its real location.- –如果是写doublewrite buffer本身失败,那么这些数据不会被写到磁盘,InnoDB此时会从磁盘载入原始的数据,然后通过InnoDB的事务日志来计算出正确的数据,重新 写入到doublewrite buffer. When InnoDB recovers, it will use the original page instead of the corrupted copy in the doublewrite buffer. However, if the doublewrite buffer succeeds and the write to the page’s real location fails, InnoDB will use the copy in the doublewrite buffer during recovery. –如果 doublewrite buffer写成功的话,但是写磁盘失败,InnoDB就不用通过事务日志来计算了,而是直接用buffer的数据再写一遍. InnoDB 知道页面何时损坏,因为每个页面最后都有一个校验和;校验和是最后写入的内容,因此如果页面的内容与校验和不匹配,则页面已损坏。因此,在恢复时,InnoDB 只是读取双写缓冲区中的每个页面并验证校验和。如果页面的校验和不正确,它会从其原始位置读取页面。 ——在恢复的时候,InnoDB 直接比较页面的校验和,如果不对的话,就从硬盘存储原始数据,再由事务日志开始推演正确的数据。所以 InnoDB 的恢复通常需要不需要的时间。
四我们是否一定需要双写?在某些情况下,双写 缓冲区确实不是必需的——例如,您可能想在从站上禁用它。此外,一些文件系统(如 ZFS)自己也做同样的事情,所以 InnoDB 这样做是多余的。您可以通过将 InnoDB_doublewrite 设置为 0 来禁用双写缓冲区。
五如何使用双写 InnoDB_doublewrite=1表示启动双写 显示状态如’InnoDB_dblwr%’可以查询双写的使用情况; 相关参数与状态 双写的使用情况: 显示状态如“%InnoDB_dblwr%”;
InnoDB_dblwr_pages_written 从bp刷新到DBWB的个数 InnoDB_dblwr_writes 写文件的 每次写操作合并页的个数= InnoDB_dblwr_pages_written/InnoDB_dblwr_writes
6.1.6 索引和Transaction模型
为了实现大规模,繁忙或高度可靠的数据库应用程序,从其他数据库系统移植大量代码,或调整 MySQL 性能,了解InnoDB锁定和InnoDB事务模型非常重要。
1. InnoDB 锁定
共享锁和互斥锁
InnoDB实现标准的行级锁定,其中有两种类型的锁:共享(S)锁和排他(X)锁。
如果事务T1在行r上持有共享(S)锁,则来自某些不同事务T2的对行r的锁请求将按以下方式处理:
T2对S锁定的请求可以立即获得批准。结果,T1和T2都在r上保持S锁定。T2对X锁定的请求无法立即获得批准。
如果事务T1在行r上拥有排他(X)锁,则不能立即批准来自某个不同事务T2的对r上任一类型的锁的请求。相反,事务T2必须 await 事务T1释放对行r的锁定。
1 | 共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 |
1 | 共享锁:可以加共享锁,不能加排它锁 |
Intention Locks
InnoDB支持多重粒度锁定,它允许行锁和 table 锁并存。例如,诸如锁 table…写之类的语句在指定的 table 上具有排他锁(X锁)。为了使在多个粒度级别上的锁定切实可行,InnoDB使用intention locks。意向锁是 table 级锁,指示事务稍后对 table 中的行需要哪种类型的锁(共享锁或排他锁)。有两种类型的意图锁:
例如,选择…锁定共享模式设置IS锁,而选择…更新设置IX锁。
意向锁定协议如下:
- 在事务可以获取 table 中某行的共享锁之前,它必须首先获取该 table 中的
IS锁或更强的锁。 - 在事务可以获取 table 中某行的排它锁之前,它必须首先获取该 table 中的
IX锁。
table 级锁类型的兼容性汇总在以下矩阵中。
X |
IX |
S |
IS |
|
|---|---|---|---|---|
X |
冲突 | 冲突 | 冲突 | 冲突 |
IX |
冲突 | 兼容 | 冲突 | 兼容 |
S |
冲突 | 冲突 | 兼容 | 兼容 |
IS |
冲突 | 兼容 | 兼容 | 兼容 |
如果锁与现有锁兼容,则将其授予请求的事务,但如果与现有锁冲突,则不授予该请求。事务 await 直到冲突的现有锁被释放。如果锁定请求与现有锁定发生冲突并且由于会导致deadlock而无法被授予,则会发生错误。
除了全 table 请求(例如锁 table…写)之外,意图锁不会阻止任何其他操作。意图锁定的主要目的是 table 明有人正在锁定 table 中的行,或者打算锁定 table 中的行。
Record Locks
记录锁定是对索引记录的锁定。例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;阻止任何其他事务插入,更新或删除t.c1的值为10的行。
记录锁始终锁定索引记录,即使没有定义索引的 table 也是如此。在这种情况下,InnoDB将创建一个隐藏的聚集索引,并将该索引用于记录锁定。
Gap Locks
间隙锁定是对索引记录之间的间隙的锁定,或者是对第一个或最后一个索引记录之前的间隙的锁定。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;防止其他事务将15的值插入到t.c1列中,无论该列中是否已经有这样的值,因为该范围中所有现有值之间的间隙都被锁定。
间隙可能跨越单个索引值,多个索引值,甚至为空。
间隙锁是性能和并发性之间权衡的一部分,并且在某些事务隔离级别而非其他级别中使用。
对于使用唯一索引来锁定唯一行来锁定行的语句,不需要间隙锁定。 (这不包括搜索条件仅包含多列唯一索引的某些列的情况;在这种情况下,会发生间隙锁定.)例如,如果id列具有唯一索引,则以下语句仅使用具有id值 100 的行的索引记录锁,其他会话是否在前面的间隙中插入行都没有关系:
Next-Key锁
下一个键锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。
插入意向锁
插入意图锁定是INSERT在行插入之前通过操作设置的一种间隙锁定 。这个锁定以插入到相同索引间隙中的多个事务如果没有插入间隙中相同位置时不需要等待对方的方式发信号通知插入意图。假设有索引记录的值为4和7.分别尝试插入5和6的值的事务分别在获得对插入行的排它锁之前用插入意向锁来锁定4和7之间的间隔,但不要相互阻塞,因为行是非冲突的。
AUTO-INC锁定
一个AUTO-INC锁是通过交易将与表中取得一个特殊的表级锁 AUTO_INCREMENT列。在最简单的情况下,如果一个事务正在向表中插入值,则任何其他事务都必须等待自己插入到该表中,以便第一个事务插入的行接收连续的主键值。
2. InnoDB事务模型
在InnoDB,所有的用户活动发生在一个事务。如果启用模式,则每个SQL语句将自行形成一个事务。默认情况下,MySQL的为会每个启用的新连接启动会话,所以如果该语句没有返回错误, MySQL 将在每个 SQL 语句之后进行提交。如果语句返回错误,则提交或返回滚行为创建错误。
一直的非锁定读区MVCC
一个 一致读 意思是 InnoDB使用多版本控制 向查询提供数据库中某个点的快照 时间。 查询会看到事务所做的更改 在该时间点之前提交,并且没有进行任何更改 稍后或未提交的事务。 这条规则的例外是 查询看到先前语句所做的更改 在同一个交易中。 此异常会导致以下情况 异常:如果您更新表中的某些行, SELECT看到最新版本 更新的行,但它也可能会看到任何旧版本 行。 如果其他会话同时更新同一张表, 异常意味着您可能会看到表格处于以下状态 从未存在于数据库中。
锁定读取
如果您查询数据,然后在其中插入或更新相关数据 相同的交易,常规的 SELECT声明没有提供足够的保护。 其他交易 可以更新或删除您刚刚查询的相同行。 InnoDB支持两种类型 锁定读取 该 提供额外的安全:
SELECT … FOR SHARE
在读取的任何行上设置共享模式锁。 其他 会话可以读取行,但不能修改它们,直到 你的交易提交。 如果这些行中的任何一行被更改 通过另一个尚未提交的事务,您的 查询等待,直到该事务结束,然后使用 最新值。
SELECT … FOR UPDATE
对于搜索遇到的索引记录,锁定行并 任何关联的索引条目,就像您发出 UPDATE这些行的语句。 其他 事务被阻止更新这些行,从 正在做 SELECT … FOR SHARE,或从 在某些事务隔离级别读取数据。 一致读取忽略在记录上设置的任何锁 存在于阅读视图中。 (旧版本的记录不能 锁定; 它们通过应用重建 撤销日志 上 记录的内存副本。) SELECT … FOR UPDATE需要 SELECT特权,至少 中的一个 DELETE, LOCK TABLES, 要么 UPDATE特权。
3. InnoDB中不同SQL语句设置的锁
一个 锁定读 ,一个 UPDATE, 或 DELETE一般设置记录锁 在处理 SQL 时扫描的每条索引记录上 陈述。 有没有没关系 WHERE语句中的条件 排除该行。 InnoDB不记得了 精确的 WHERE条件,但只知道哪个 索引范围被扫描。 锁通常是 下一个键锁 也 块在 插入 “ 间隙 ” 紧接之前 记录。 然而, 间隙锁定 可以显式禁用,这会导致下一个键锁定不 使用。
4. 幻读
当同一个查询在不同时间产生不同的行集时, 所谓的幻影问题就会发生在一个事务中。例如,如果 a SELECT执行两次,但第二次返回第一次没有返回的行,则该行是“幻影”行。
为了防止幻读,InnoDB使用称为下一个键锁定的算法,该算法将索引行锁定与间隙锁定相结合。InnoDB执行行级锁定的方式是,当它搜索或扫描表索引时,它会在遇到的索引记录上设置共享锁或排他锁。因此,行级锁实际上是索引记录锁。此外,索引记录上的 next-key 锁也会影响 索引记录之前的“间隙”。也就是说,next-key 锁是一个索引记录锁加上一个在索引记录之前的间隙上的间隙锁。如果一个会话在记录上有共享锁或排他锁R在一个索引中,另一个会话不能R在索引顺序之前的间隙中插入新的索引记录 。
5. 死锁
死锁是不同事务无法继续进行的情况,因为每个事务都持有另一个需要的锁。因为两个事务都在等待资源变得可用,所以它们都不会释放它持有的锁。
当事务锁定多个表中的行(通过诸如UPDATE或 之类的 语句SELECT ... FOR UPDATE)但顺序相反时,可能会发生死锁 。当这些语句锁定索引记录和间隙的范围时,也可能发生死锁,每个事务由于时间问题而获取一些锁而不是其他锁。
为了减少死锁的可能性,使用事务而不是LOCK TABLES语句;将插入或更新数据的事务保持得足够小,以至于它们不会长时间保持打开状态;当不同的事务更新多个表或大范围的行时,SELECT ... FOR UPDATE在每个事务中使用相同的操作顺序(如 );在SELECT ... FOR UPDATE和 UPDATE ... WHERE 语句中使用的列上创建索引。死锁的可能性不受隔离级别的影响,因为隔离级别改变了读操作的行为,而死锁的发生是因为写操作。
当启用死锁检测(默认)并且死锁确实发生时,InnoDB检测条件并回滚其中一个事务(受害者)。如果使用innodb_deadlock_detect变量禁用死锁检测 ,则 InnoDB依赖 innodb_lock_wait_timeout设置在死锁情况下回滚事务。因此,即使您的应用程序逻辑是正确的,您仍然必须处理必须重试事务的情况。要查看InnoDB用户事务中的最后一个死锁,请使用 SHOW ENGINE INNODB STATUS。如果频繁的死锁突出了事务结构或应用程序错误处理的问题,则启用 innodb_print_all_deadlocks将所有死锁的信息打印到 mysqld错误日志。
当启用死锁检测(默认)时, InnoDB自动检测事务 死锁并回滚一个或多个事务以打破死锁。 InnoDB尝试选择要回滚的小事务,其中事务的大小由插入、更新或删除的行数决定。
6. 事务调度
InnoDB使用争用感知事务调度 (CATS) 算法来确定等待锁定的事务的优先级。当多个事务在等待同一个对象上的锁时,CATS 算法确定哪个事务先收到锁。
CATS 算法通过分配调度权重来优先考虑等待事务,该权重是根据事务阻塞的事务数计算的。例如,如果两个事务正在等待对同一个对象的锁定,则阻塞最多事务的事务被分配更大的调度权重。如果权重相等,则优先考虑等待时间最长的事务。
1 | 笔记 |
6.1.7 配置
在继续进行启动配置之前,请查看以下与存储相关的注意事项。
在某些情况下,您可以通过将数据和日志文件放在单独的物理磁盘上来提高数据库性能。您还可以将原始磁盘分区(原始设备)用于
InnoDB数据文件,这可能会加快 I/O。InnoDB是一个事务安全(符合 ACID)的存储引擎,具有提交、回滚和崩溃恢复功能来保护用户数据。 但是,如果底层操作系统或硬件不像宣传的那样工作,它就不能这样做。许多操作系统或磁盘子系统可能会延迟或重新排序写入操作以提高性能。在某些操作系统上,非常fsync()应该等到文件的所有未写入数据都已刷新的系统调用实际上可能会在数据刷新到稳定存储之前返回。因此,操作系统崩溃或断电可能会破坏最近提交的数据,或者在最坏的情况下,甚至会因为写入操作已重新排序而损坏数据库。如果数据完整性对您很重要,请在生产中使用任何东西之前执行 “即插即用”测试。在 macOS 上,InnoDB使用特殊的fcntl()文件刷新方法。在 Linux 下,建议禁用回写缓存。在 ATA/SATA 磁盘驱动器上,这样的命令
hdparm -W0 /dev/hda可能会禁用回写缓存。 请注意,某些驱动器或磁盘控制器可能无法禁用回写缓存。关于
InnoDB保护用户数据的恢复功能,InnoDB使用文件刷新技术,该技术涉及称为双写缓冲区的结构 ,默认情况下启用 (innodb_doublewrite=ON)。双写缓冲区增加了意外退出或断电后恢复的安全性,并通过减少fsync()操作需求来提高大多数 Unix 品种的性能 。innodb_doublewrite如果您担心数据完整性或可能的故障,建议保持启用该选项。有关双写缓冲区的信息,请参阅第 15.11.1 节,“InnoDB 磁盘 I/O”.在将 NFS 与 一起使用之前
InnoDB,请查看将 NFS 与 MySQL一起使用中概述的潜在问题 。
从 MySQL 8.0.20 开始,双写缓冲区存储区域驻留在双写文件中,这为双写页面的存储位置提供了灵活性。在以前的版本中,doublewrite 缓冲区存储区域驻留在系统表空间中。
配置缓冲池刷新
InnoDB在后台执行某些任务,包括从缓冲池中刷新脏页。脏页是那些已被修改但尚未写入磁盘上数据文件的页。
在 MySQL 8.0 中,缓冲池刷新由页面清理线程执行。页面清理线程的数量由innodb_page_cleaners变量控制,该 变量的默认值为 4。但是,如果页面清理线程的数量超过缓冲池实例的数量, innodb_page_cleaners则会自动设置为与 相同的值 innodb_buffer_pool_instances。
自适应冲洗
InnoDB使用自适应刷新算法,根据重做日志生成速度和当前刷新速率动态调整刷新速率。目的是通过确保刷新活动与当前工作负载保持同步来平滑整体性能。自动调整刷新率有助于避免由于缓冲池刷新引起的 I/O 活动突发影响可用于普通读取和写入活动的 I/O 容量时可能发生的吞吐量突然下降。
例如,尖锐的检查点通常与生成大量重做条目的写入密集型工作负载相关联,可能会导致吞吐量的突然变化。当InnoDB想要重用日志文件的一部分时,会出现一个尖锐的检查点。在执行此操作之前,必须刷新日志文件该部分中包含重做条目的所有脏页。如果日志文件已满,则会出现尖锐的检查点,从而导致吞吐量暂时降低。即使innodb_max_dirty_pages_pct 未达到阈值,也可能发生这种情况 。
保存和恢复缓冲池状态
为了减少重新启动服务器后的预热时间,InnoDB在服务器关闭时为每个缓冲池保存最近使用的页面的百分比,并在服务器启动时恢复这些页面。存储的最近使用页面的百分比由innodb_buffer_pool_dump_pct 配置选项定义 。
重新启动繁忙的服务器后,通常会有一个吞吐量稳定增加的预热期,因为缓冲池中的磁盘页面被带回内存(因为查询、更新相同的数据等)。在启动时恢复缓冲池的能力通过重新加载重启前缓冲池中的磁盘页面而不是等待 DML 操作访问相应的行来缩短预热时间。此外,I/O 请求可以大批量执行,从而使整体 I/O 更快。页面加载在后台进行,不会延迟数据库启动。
从核心文件中排除缓冲池页面
核心文件记录正在运行的进程的状态和内存映像。由于缓冲池驻留在主内存中,并且正在运行的进程的内存映像被转储到核心文件中,因此具有大缓冲池的系统可以在mysqld进程死亡时生成大核心文件。
由于多种原因,大型核心文件可能会出现问题,包括写入它们所需的时间、它们消耗的磁盘空间量以及与传输大型文件相关的挑战。
为 InnoDB 配置线程并发
InnoDB使用操作系统 线程来处理来自用户事务的请求。(事务InnoDB在提交或回滚之前可能会发出许多请求 。)在具有多核处理器的现代操作系统和服务器上,上下文切换是高效的,大多数工作负载运行良好,对并发线程数没有任何限制。
在有助于最小化线程之间的上下文切换的情况下,InnoDB可以使用多种技术来限制并发执行的操作系统线程的数量(从而限制在任何时间处理的请求数量)。当InnoDB收到来自用户会话的新请求时,如果并发执行的线程数处于预先定义的限制,则新请求会在再次尝试之前休眠一小段时间。休眠后无法重新调度的请求被放入先进先出队列并最终被处理。等待锁的线程不计入并发执行的线程数。
在 Linux 上使用异步 I/O
InnoDB使用 Linux 上的异步 I/O 子系统(本机 AIO)对数据文件页面执行预读和写入请求。此行为由innodb_use_native_aio 配置选项控制,该 选项仅适用于 Linux 系统并默认启用。在其他类 Unix 系统上, InnoDB仅使用同步 I/O。历史上, InnoDB仅在 Windows 系统上使用异步 I/O。在 Linux 上使用异步 I/O 子系统需要该libaio库。
配置索引页合并阈值
您可以配置MERGE_THRESHOLD索引页的值。如果在 删除行或操作缩短行时索引页的“ page-full ”百分比低于该MERGE_THRESHOLD值 UPDATE,则 InnoDB尝试将索引页与相邻索引页合并。默认 MERGE_THRESHOLD值为 50,这是之前的硬编码值。最小值 MERGE_THRESHOLD为 1,最大值为 50。
当索引页的“ page-full ”百分比低于默认MERGE_THRESHOLD设置50% 时 , InnoDB尝试将索引页与相邻页合并。如果两个页面都接近 50% 已满,页面合并后很快就会发生页面拆分。如果这种合并拆分行为频繁发生,则会对性能产生不利影响。为了避免频繁的合并拆分,您可以降低该MERGE_THRESHOLD值,以便 InnoDB尝试以较低的“页面满”百分比进行页面合并 。以较低的页面满百分比合并页面在索引页面中留下更多空间并有助于减少合并拆分行为。
该MERGE_THRESHOLD索引页面可以为表或个别指标进行定义。
自动配置的缓冲池大小
| 检测到的服务器内存 | 缓冲池大小 |
|---|---|
| 小于 1GB | 128MiB(默认值) |
| 1GB 至 4GB | detected server memory * 0.5 |
| 大于 4GB | detected server memory * 0.75 |
自动配置的日志文件大小
| 缓冲池大小 | 日志文件大小 |
|---|---|
| 小于 8GB | 512MB |
| 8GB 至 128GB | 1024MB |
| 大于 128GB | 2048MB |
| 缓冲池大小 | 日志文件数 |
|---|---|
| 小于 8GB | 圆形( buffer pool size) |
| 8GB 至 128GB | 圆形(*buffer pool size** 0.75) |
| 大于 128GB | 64 |
更多配置可以参考官网文档:https://dev.mysql.com/doc/refman/8.0/en/innodb-configuration.html
6.1.8 表和页面压缩
本节提供有关 InnoDB表压缩和 InnoDB页面压缩功能的信息。页面压缩功能也称为 透明页面压缩。
使用 的压缩功能InnoDB,您可以创建以压缩形式存储数据的表。压缩有助于提高原始性能和可扩展性。压缩意味着磁盘和内存之间传输的数据更少,占用的磁盘和内存空间也更少。具有二级索引的表的好处被放大 ,因为索引数据也被压缩。压缩对于SSD存储设备尤其重要,因为它们的容量往往低于 HDD设备。
压缩表可以在 file-per-table 表 空间或 一般表空间中创建。表压缩不适用于 InnoDB系统表空间。系统表空间(空间 0, .ibdata 文件)可以包含用户创建的表,但它也包含从不压缩的内部系统数据。因此,压缩仅适用于存储在 file-per-table 或通用表空间中的表(和索引)。
压缩表的限制
- 压缩表不能存储在
InnoDB系统表空间中。 - 通用表空间可以包含多个表,但压缩表和未压缩表不能在同一个通用表空间中共存。
- 压缩适用于整个表及其所有关联的索引,而不是单个行,尽管有子句 name
ROW_FORMAT。 InnoDB不支持压缩临时表。当innodb_strict_mode启用(默认值),CREATE TEMPORARY TABLE如果返回错误ROW_FORMAT=COMPRESSED或KEY_BLOCK_SIZE指定。如果innodb_strict_mode禁用,则会发出警告并使用非压缩行格式创建临时表。同样的限制适用于ALTER TABLE对临时表的操作。
大多数情况下,InnoDB 数据存储和压缩中描述的内部优化可 确保系统在处理压缩数据时运行良好。但是,由于压缩效率取决于数据的性质,因此您可以做出影响压缩表性能的决策:
- 要压缩的表。
- 要使用的压缩页面大小。
- 是否根据运行时性能特征调整缓冲池的大小,例如系统压缩和解压缩数据所花费的时间。工作负载更像是 数据仓库 (主要是查询)还是 OLTP系统(查询和DML 的混合)。
- 如果系统对压缩表执行 DML 操作,并且数据的分布方式导致运行时出现代价高昂的 压缩失败,您可以调整其他高级配置选项。
数据库压缩与应用程序压缩
决定是在应用程序中还是在表中压缩数据;不要对相同的数据使用两种类型的压缩。当您压缩应用程序中的数据并将结果存储在压缩表中时,额外的空间节省是极不可能的,双重压缩只会浪费 CPU 周期。
MySQL InnoDB支持数据压缩,有两种数据压缩方式,第一种为表压缩,通常也称之为行格式压缩,另外一种是页压缩(Page Compression),又叫做透明页压缩(Transparent Page Compression),是一种页面级别的数据压缩,页压缩对操作系统及文件系统有一定的要求
INNODB表压缩
压缩前提
表压缩能提升性能,减少存储空间,主要是用在字符类型比较大的表上(VARCHAR,VARBINARY和BLOB和TEXT类型),且读多写少的情况下,如果你的应用是io密集型的,不是cpu密集型的,那么压缩会带来很多性能的提升,例如:数据仓库。
1 | innodb_file_format = Barracuda --模式支持压缩 |
压缩原理
InnoDB支持两种文件格式 Antelope(羚羊)和Barracuda(梭鱼):
Antelope :是5.6之前的文件格式,支持InnoDB表的COMPACT和REDUNDANT行格式,共享表空间默认为Antelope
Barracuda:是最新的文件格式,支持所有innodb行格式,包括最新的COMPRESSED和DYNAMIC行格式。
ROW_FORMAT值:
| ROW_FORMAT | 支持索引前缀 | 独立表空间压缩 | 系统表空间压缩 |
|---|---|---|---|
| COMPRESSED | 3072字节 | 支持 | 不支持 |
| DYNAMIC | 3072字节 | 不支持 | 不支持 |
| COMPACT | 768字节 | 不支持 | 支持 |
| REDUNDANT | 768字节 | 不支持 | 支持 |
压缩算法
压缩算法采用LZ77,在这个算法下,如果压缩效率好点的话,压缩后的大小和未压缩的数据大小比如在25-50%左右,在这种情况下就会有效地通过消耗一些CPU来减少IO操作,增大吞吐量,可以通过调节压缩程度(innodb_compression_level参数)来权衡压缩比和CPU使用率。
innodb_compression_level:默认值为6,可选值0-9,数值越大表示压缩程度越大,消耗的CPU也越多。
innodb_compression_failure_threshold_pct:默认为5,可取值0-100,表示更新一个压缩表时,指定一个压缩失败的临界值。当超过这个临界值,mysql会为每个压缩页添加额外的空间来避免再次压缩失败。值为0表示禁用监控压缩效率,改为动态调整。
innodb_compression_pad_pct_max:重新压缩时为每个压缩页额外分配的空间比例,默认50,可取值0-75.这个参数值只有当参数innodb_compression_failure_threshold_pct非0时才生效。
转载至:https://cloud.tencent.com/developer/article/1056453
INNODB页压缩原理
当一个页被写入磁盘时,InnoDB使用指定的压缩算法对该页进行压缩,然后将压缩后的数据写入磁盘,此时打孔机制会将压缩后的页尾空闲块进行释放,以减少磁盘空间占用。如果压缩失败,则按照原样写入数据。
在Linux操作系统中,文件系统块大小是打孔的单位尺寸,因此,页压缩后的数据必须小于等于InnoDB页大小减去文件系统块大小的值,才能使页压缩生效。举个例子,InnoDB页大小16K,文件系统块大小4K,页数据必须被压缩到小于等于12K,才能使用打孔机制,减少磁盘空间占用。
页压缩的限制和使用注意事项
- 如果文件系统块大小或者压缩单元大小乘以2大于innodb页大小时,页压缩将自动失效。
- 共享表空间(系统表空间,临时表空间,通用表空间)中的表不支持页压缩。
- undo log 和 redo log 表空间不支持页压缩。
- 空间索引 R-tree 页 不支持页压缩。
- 行格式压缩过的表(ROW_FORMAT=COMPRESSED),不支持页压缩。
- 在InnoDB崩溃恢复过程中,更新的页面将以未压缩的形式写出。
- 在一个不支持压缩算法的服务器上加载页压缩后的表空间,会导致I/O错误。
- 使用一个较大的innodb页大小,比如64KB,文件系统块大小4KB,这样的配置能够提高压缩效果,但是同时也会导致写放大,需要更大的buffer pool,增加I/O消耗。
转载至:https://www.mytecdb.com/blogDetail.php?id=116
6.1.9 磁盘 I/O 和文件空间管理
作为 DBA,您必须管理磁盘 I/O 以防止 I/O 子系统变得饱和,并管理磁盘空间以避免填满存储设备。该ACID设计模型需要一定量的I / O可能似乎是多余的,但有助于确保数据的可靠性。在这些约束范围内, InnoDB尝试优化数据库工作和磁盘文件的组织以最小化磁盘 I/O 量。有时,I/O 会推迟到数据库不忙,或者直到所有内容都需要进入一致状态,例如在快速关闭后重新启动数据库期间。
本节讨论使用默认类型的 MySQL 表(也称为InnoDB表)的I/O 和磁盘空间的主要注意事项 :
- 控制用于提高查询性能的后台 I/O 量。
- 启用或禁用以额外 I/O 为代价提供额外持久性的功能。
- 将表组织成许多小文件、几个大文件或两者的组合。
- 平衡重做日志文件的大小与日志文件变满时发生的 I/O 活动。
- 如何重组表以获得最佳查询性能。
1. InnoDB 磁盘 I/O
InnoDB在可能的情况下使用异步磁盘 I/O,通过创建多个线程来处理 I/O 操作,同时在 I/O 仍在进行时允许其他数据库操作继续进行。在 Linux 和 Windows 平台上,InnoDB使用可用的操作系统和库函数来执行“原生”异步 I/O。在其他平台上,InnoDB仍然使用 I/O 线程,但线程实际上可能会等待 I/O 请求完成;这种技术被称为“模拟” 异步 I/O。
预读
如果InnoDB可以确定很有可能很快就需要数据,它会执行预读操作以将该数据带入缓冲池,以便它在内存中可用。对连续数据发出几个大的读取请求比发出几个小的、分散的请求更有效。中有两种预读启发式方法InnoDB:
- 在顺序(顺序)预读中,如果
InnoDB注意到对表空间中某个段的访问模式是顺序的,它会提前将一批数据库页面读取发布到 I/O 系统。 - 在随机预读中,如果
InnoDB注意到表空间中的某些区域似乎正在被完全读入缓冲池,它会将剩余的读取发布到 I/O 系统。
线性预读是一种技术,它根据缓冲池中按顺序访问的页面来预测可能很快需要哪些页面。您可以InnoDB使用配置参数通过调整触发异步读取请求所需的顺序页面访问次数来控制何时执行预读操作 innodb_read_ahead_threshold。在添加此参数之前,InnoDB只会在读取当前extent的最后一页时计算是否对整个nextextent发出异步预取请求。
配置参数 innodb_read_ahead_threshold 控制InnoDB检测顺序页面访问模式的敏感程度。如果从一个extent 中顺序读取的页数大于或等于 innodb_read_ahead_threshold,则 InnoDB启动整个后续extent 的异步预读操作。 innodb_read_ahead_threshold可以设置为 0-64 之间的任何值。默认值为 56。值越大,访问模式检查越严格。例如,如果您将该值设置为 48,则InnoDB 仅当顺序访问当前范围中的 48 页时才触发线性预读请求。如果值为 8,InnoDB即使顺序访问区中的少至 8 页,也会触发异步预读。你可以在MySQL配置文件中设置这个参数的值,也可以用SET GLOBAL语句动态改变它 ,这需要足够的权限来设置全局系统变量。
随机预读是一种技术,它根据缓冲池中已有的页面来预测何时可能很快需要页面,而不管这些页面的读取顺序如何。如果在缓冲池中找到来自相同范围的 13 个连续页面,则 InnoDB异步发出请求以预取该范围的剩余页面。
随机预读方式则是表示当同一个extent中的一些page在buffer pool中发现时,Innodb会将该extent中的剩余page一并读到buffer pool中。
由于随机预读方式给innodb code带来了一些不必要的复杂性,同时在性能也存在不稳定性,在5.5中已经将这种预读方式废弃,默认是OFF。若要启用此功能,即将配置变量设置innodb_random_read_ahead为ON。
双写缓冲区
InnoDB使用一种新颖的文件刷新技术,该技术涉及一种称为doublewrite buffer的结构 ,在大多数情况下默认启用 ( innodb_doublewrite=ON)。它增加了意外退出或断电后恢复的安全性,并通过减少fsync()操作需求来提高大多数 Unix 品种的性能。
在将页面InnoDB 写入数据文件之前,首先将它们写入称为双写缓冲区的存储区域。只有在对双写缓冲区的写入和刷新完成后,InnoDB才会将页面写入数据文件中的适当位置。如果在页面写入过程中存在操作系统、存储子系统或意外的 mysqld进程退出(导致页面撕裂的 情况),InnoDB稍后可以在恢复期间从双写缓冲区中找到该页面的良好副本。
2. 文件空间管理
您使用innodb_data_file_path 配置选项在配置文件中定义的数据文件 构成InnoDB 系统表空间。这些文件在逻辑上连接起来形成系统表空间。没有使用条纹。您无法定义您的表在系统表空间中的分配位置。在新创建的系统表空间中,InnoDB从第一个数据文件开始分配空间。
为了避免将所有表和索引存储在系统表空间中所带来的问题,您可以启用 innodb_file_per_table 配置选项(默认),它将每个新创建的表存储在单独的表空间文件中(带有扩展名 .ibd)。对于以这种方式存储的表,磁盘文件中的碎片较少,并且当表被截断时,空间将返回给操作系统,而不是仍然由系统表空间内的 InnoDB 保留。
您还可以将表存储在 常规表空间中。通用表空间是使用CREATE TABLESPACE 语法创建的共享表空间。它们可以在 MySQL 数据目录之外创建,能够保存多个表,并支持所有行格式的表。
页、区、段和表空间
每个表空间由数据库页组成 。MySQL 实例中的每个表空间都具有相同的页面大小。默认情况下,所有表空间的页大小为 16KB;您可以通过innodb_page_size在创建 MySQL 实例时指定该选项将页面大小减少到 8KB 或 4KB 。您还可以将页面大小增加到 32KB 或 64KB。有关更多信息,请参阅 innodb_page_size文档。
页被分成 区段大小为1MB用于尺寸的页面到16KB(64次连续的16KB的页面,或128 8KB页或256 4KB页)。对于 32KB 的页面大小,扩展区大小为 2MB。对于 64KB 的页面大小,扩展区大小为 4MB。在 “文件”表空间内被称为 段在 InnoDB。(这些段与回滚段不同,回滚段实际上包含许多表空间段。)
3. InnoDB 检查点
使日志文件非常大可能会减少检查点期间的磁盘 I/O 。将日志文件的总大小设置为与缓冲池一样大甚至更大通常是有意义的。
4. 对表进行碎片整理
对二级索引的随机插入或删除会导致索引变得碎片化。碎片化意味着磁盘上索引页的物理顺序与页上记录的索引顺序不接近,或者分配给索引的 64 页块中有许多未使用的页。
碎片化的一个症状是表占用的空间比它“应该”占用的空间多。具体是多少,很难确定。所有InnoDB数据和索引都存储在B-trees 中,它们的填充因子可能在 50% 到 100% 之间变化。碎片化的另一个症状是像这样的表扫描花费的时间比它“应该”花费的时间 要多
为了加快索引扫描,您可以定期执行 “空” ALTER TABLE 操作,这会导致 MySQL 重建表:
1 | ALTER TABLE tbl_name ENGINE=INNODB |
另一种执行碎片整理操作的方法是使用 mysqldump将表转储到文本文件,删除表,然后从转储文件中重新加载它。
如果索引中的插入总是升序并且只从末尾删除记录,则InnoDB 文件空间管理算法保证索引中不会出现碎片。
6.1.10 在线 DDL
DDL 是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。
在线 DDL 功能支持即时和就地表更改以及并发 DML。此功能的好处包括:
- 在繁忙的生产环境中提高响应能力和可用性,在这种环境中,让表在几分钟或几小时内不可用是不切实际的。
- 对于就地操作,能够在使用
LOCK子句的DDL 操作期间调整性能和并发之间的平衡。请参阅 LOCK 子句。 - 比表复制方法更少的磁盘空间使用和 I/O 开销。
该LOCK子句可用于就地执行的操作,可用于微调操作期间对表的并发访问程度。仅 LOCK=DEFAULT支持立即执行的操作。该ALGORITHM子句主要用于性能比较,并在遇到任何问题时作为旧表复制行为的后备。例如:
- 为避免在就地
ALTER TABLE操作期间意外使表不可用于读取、写入或两者,请在语句上指定一个子句,ALTER TABLE例如LOCK=NONE(permit reads and writes) 或LOCK=SHARED(permit readings)。如果请求的并发级别不可用,操作将立即停止。 - 要比较算法之间的性能,请运行带有
ALGORITHM=INSTANT、ALGORITHM=INPLACE和 的语句ALGORITHM=COPY。您还可以在old_alter_table启用配置选项的情况下运行语句以强制使用ALGORITHM=COPY. - 为避免
ALTER TABLE通过复制表的操作占用服务器,请包含ALGORITHM=INSTANT或ALGORITHM=INPLACE。如果它不能使用指定的算法,则语句立即停止。
在创建索引时,该表仍可用于读取和写入操作。该 CREATE INDEX语句仅在访问该表的所有事务完成后才结束,以便索引的初始状态反映该表的最新内容。
对添加二级索引的在线 DDL 支持意味着您通常可以通过创建没有二级索引的表,然后在加载数据后添加二级索引来加快创建和加载表及关联索引的整个过程。
列操作
当使用该INSTANT算法添加列时,以下限制适用 :
- 添加列不能在同一语句中与其他
ALTER TABLE不支持ALGORITHM=INSTANT. - 一列只能添加为表格的最后一列。不支持将一列添加到其他列中的任何其他位置。
- 不能将列添加到使用
ROW_FORMAT=COMPRESSED. - 不能将列添加到包含
FULLTEXT索引的表中 。 - 不能将列添加到临时表。临时表仅支持
ALGORITHM=COPY. - 不能将列添加到驻留在数据字典表空间中的表。
- 添加列时不评估行大小限制。但是,在表中插入和更新行的 DML 操作期间会检查行大小限制。
在线 DDL 改进了 MySQL 操作的几个方面:
- 访问该表的应用程序响应更快,因为在 DDL 操作正在进行时可以继续对表进行查询和 DML 操作。减少锁定和等待 MySQL 服务器资源会带来更大的可扩展性,即使对于 DDL 操作中不涉及的操作也是如此。
- 即时操作只修改数据字典中的元数据。表上没有元数据锁,表数据不受影响,操作瞬间完成。并发 DML 不受影响。
- 在线操作避免了与表复制方法相关的磁盘 I/O 和 CPU 周期,从而最大限度地减少了数据库的整体负载。最小化负载有助于在 DDL 操作期间保持良好的性能和高吞吐量。
- 与表复制操作相比,联机操作将更少的数据读入缓冲池,从而减少了从内存中清除频繁访问的数据。在 DDL 操作后清除经常访问的数据可能会导致暂时的性能下降。
LOCK 子句
默认情况下,MySQL 在 DDL 操作期间使用尽可能少的锁定。如果LOCK需要,可以为就地操作和一些复制操作指定该子句以强制执行更多限制性锁定。如果该 LOCK子句指定了比特定 DDL 操作所允许的限制更少的锁定级别,则该语句将失败并显示错误。 LOCK下面按限制性从低到高的顺序对条款进行了描述:
LOCK=NONE:允许并发查询和 DML。
例如,将此子句用于涉及客户注册或购买的表,以避免在冗长的 DDL 操作期间使表不可用。
LOCK=SHARED:允许并发查询但阻止 DML。
例如,在数据仓库表上使用该子句,您可以将数据加载操作延迟到 DDL 操作完成,但不能长时间延迟查询。
LOCK=DEFAULT:允许尽可能多的并发(并发查询、DML 或两者)。省略
LOCK子句与指定LOCK=DEFAULT.当您不希望 DDL 语句的默认锁定级别导致表的任何可用性问题时,请使用此子句。
LOCK=EXCLUSIVE:阻止并发查询和 DML。
如果主要关注的是在尽可能短的时间内完成 DDL 操作,并且不需要并发查询和 DML 访问,请使用此子句。如果服务器应该空闲,您也可以使用此子句,以避免意外的表访问。
在线 DDL 操作可以被视为三个阶段:
阶段 1:初始化
在初始化阶段,服务器会根据存储引擎能力、语句中指定的操作以及用户指定
ALGORITHM和LOCK选项来确定操作期间允许的并发量 。在此阶段,使用共享的可升级元数据锁来保护当前表定义。阶段 2:执行
在这个阶段,语句被准备和执行。元数据锁是否升级为独占取决于初始化阶段评估的因素。如果需要独占元数据锁,则仅在语句准备期间短暂使用。
阶段 3:提交表定义
在提交表定义阶段,元数据锁升级为独占以驱逐旧表定义并提交新表定义。一旦被授予,独占元数据锁定的持续时间很短。
由于上面概述的独占元数据锁要求,在线 DDL 操作可能必须等待在表上持有元数据锁的并发事务提交或回滚。在 DDL 操作之前或期间启动的事务可以在被更改的表上持有元数据锁。在长时间运行或非活动事务的情况下,在线 DDL 操作可能会超时等待独占元数据锁定。此外,在线 DDL 操作请求的未决独占元数据锁会阻止表上的后续事务。
DDL 操作的性能很大程度上取决于该操作是否立即执行、就地执行以及是否重建表。
为了评估一个DDL操作的相对表现,可以比较使用的结果ALGORITHM=INSTANT, ALGORITHM=INPLACE和 ALGORITHM=COPY。还可以使用old_alter_tableenabled运行语句以强制使用ALGORITHM=COPY.
在对大表运行 DDL 操作之前,请检查操作是快还是慢,如下所示:
- 克隆表结构。
- 用少量数据填充克隆表。
- 对克隆的表运行 DDL 操作。
- 检查“行影响”值是否为零。非零值表示操作复制表数据,这可能需要特殊规划。例如,您可能会在预定的停机时间期间执行 DDL 操作,或者一次在每个副本服务器上执行一个。
因为有一些处理工作涉及记录并发 DML 操作所做的更改,然后在最后应用这些更改,所以在线 DDL 操作总体上可能比阻止其他会话访问表的表复制机制花费更长的时间。原始性能的降低与使用该表的应用程序的更好响应相平衡。在评估更改表结构的技术时,请根据网页加载时间等因素考虑最终用户对性能的看法。
使用在线 DDL 简化 DDL 语句
在引入在线 DDL之前,通常的做法是将许多 DDL 操作组合成一条ALTER TABLE 语句。因为每个ALTER TABLE 语句都涉及复制和重建表,所以一次对同一个表进行多次更改会更有效,因为这些更改都可以通过对表的单个重建操作完成。缺点是涉及 DDL 操作的 SQL 代码更难维护和在不同的脚本中重用。如果每次的具体变化都不同,您可能必须ALTER TABLE为每个略有不同的场景构建一个新的复合体。
在线 DDL 操作失败通常是由于以下情况之一造成的:
- 一个
ALGORITHM子句指定的算法不与特定类型的DDL操作或存储引擎的兼容。 - 一个
LOCK子句指定低程度的锁定(的SHARED或NONE),其不与特定类型的DDL操作兼容。 - 等待表上的排他锁时会发生超时 ,这可能在 DDL 操作的初始和最后阶段短暂需要。
- 该
tmpdir或innodb_tmpdir文件系统运行的磁盘空间,而MySQL索引的创建过程中,在磁盘上写入临时排序文件。 - 操作时间长,并发DML修改表太多,导致临时在线日志的大小超过了
innodb_online_alter_log_max_size配置选项的值 。这种情况会导致DB_ONLINE_LOG_TOO_BIG错误。 - 并发 DML 对原始表定义允许的表进行更改,但新表定义不允许。该操作仅在最后失败,当 MySQL 尝试应用并发 DML 语句的所有更改时。例如,您可能会在创建唯一索引时将重复值插入到列中,或者您可能
NULL在该列上创建主键索引时将值插入 到 该列中。并发 DML 所做的更改优先,并且ALTER TABLE操作被有效回滚。
6.1.11 静态数据加密
InnoDB支持文件表表 空间、通用 表空间、mysql系统表空间、重做日志和撤消日志的静态数据加密 。
从 MySQL 8.0.16 开始,还支持为模式和通用表空间设置加密默认值,这允许 DBA 控制是否加密在这些模式和表空间中创建的表。
InnoDB使用两层加密密钥架构,由主加密密钥和表空间密钥组成。当表空间被加密时,表空间密钥被加密并存储在表空间头中。当应用程序或经过身份验证的用户想要访问加密的表空间数据时, InnoDB使用主加密密钥来解密表空间密钥。表空间密钥的解密版本永远不会改变,但可以根据需要更改主加密密钥。此操作称为主密钥轮换。
静态数据加密功能依赖于用于主加密密钥管理的密钥环组件或插件。
主密钥轮换
主加密密钥应定期轮换,并且每当您怀疑密钥已被泄露时。
主密钥轮换是一个原子的、实例级的操作。每次轮换主加密密钥时,MySQL 实例中的所有表空间密钥都会重新加密并保存回各自的表空间标头。作为原子操作,一旦启动轮换操作,所有表空间键的重新加密必须成功。如果主密钥轮换因服务器故障而中断,InnoDB则在服务器重新启动时向前滚动操作。有关更多信息,请参阅 加密和恢复。
轮换主加密密钥只会更改主加密密钥并重新加密表空间密钥。它不会解密或重新加密关联的表空间数据。
加密和恢复
如果在加密操作期间发生服务器故障,则在重新启动服务器时将前滚该操作。对于一般表空间,加密操作在后台线程中从最后处理的页面恢复。
如果在主密钥轮换期间发生服务器故障, InnoDB则在服务器重新启动时继续操作。
必须在存储引擎初始化之前加载密钥环组件或插件,以便在InnoDB初始化和恢复活动访问表空间数据之前,可以从表空间标头中检索解密表空间数据页所需的信息。(请参阅 加密先决条件。)
当InnoDB初始化和恢复开始时,主密钥轮换操作恢复。由于服务器故障,某些表空间密钥可能已经使用新的主加密密钥进行了加密。InnoDB从每个表空间头读取加密数据,如果数据表明表空间密钥是使用旧的主加密密钥加密的,InnoDB则从密钥环中检索旧密钥并使用它来解密表空间密钥。 InnoDB然后使用新的主加密密钥重新加密表空间密钥,并将重新加密的表空间密钥保存回表空间标头。
加密限制
- 高级加密标准 (AES) 是唯一受支持的加密算法。
InnoDB表空间加密使用电子码本 (ECB) 块加密模式进行表空间密钥加密,使用密码块链接 (CBC) 块加密模式进行数据加密。填充不用于 CBC 块加密模式。相反,InnoDB确保要加密的文本是块大小的倍数。 - 仅对file-per-table 表 空间、 通用 表空间和
mysql系统表空间支持加密 。MySQL 8.0.13 中引入了对通用表空间的加密支持。mysql从 MySQL 8.0.16 开始,对系统表空间的加密支持 可用。其他表空间类型(包括InnoDB系统表空间)不支持加密。 - 您不能将表从加密的file-per-table 表 空间、 通用 表空间或
mysql系统表空间移动或复制 到不支持加密的表空间类型。 - 您不能将表从加密表空间移动或复制到未加密表空间。但是,允许将表从未加密的表空间移动到加密的表空间。例如,您可以将表从未加密的file-per-table或 通用 表空间移动或复制 到加密的通用表空间。
- 默认情况下,表空间加密仅适用于表空间中的数据。可以通过启用
innodb_redo_log_encrypt和 来加密重做日志和撤消日志数据innodb_undo_log_encrypt。 - 不允许更改驻留在或以前驻留在加密表空间中的表的存储引擎。
6.1.12 启动选项和系统变量
- 可以在服务器启动时通过命名或使用
--skip-前缀禁用为真或假的系统变量 。例如,可以启用或禁用所述InnoDB自适应散列索引,则可以使用--innodb-adaptive-hash-index或--skip-innodb-adaptive-hash-index在命令行上,或innodb_adaptive_hash_index或skip_innodb_adaptive_hash_index在选项文件。 - 可以 在命令行或 选项文件中指定采用数值的系统变量 。
--*var_name*=*value*``*var_name*=*value* - 许多系统变量可以在运行时更改(请参阅 第 5.1.9.2 节,“动态系统变量”)。
- 有关信息
GLOBAL和SESSION变量的作用域修饰符,请参阅SET声明文件。 - 某些选项控制
InnoDB数据文件的位置和布局 。 第 15.8.1 节,“InnoDB 启动配置”解释了如何使用这些选项。 - 一些您最初可能不会使用的选项有助于
InnoDB根据机器容量和数据库工作负载调整性能特征 。 - 有关指定选项和系统变量的更多信息,请参阅第 4.2.2 节 “指定程序选项”。
详情请参考:https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html
6.1.13 INFORMATION_SCHEMA 表
本节提供InnoDB INFORMATION_SCHEMA表的信息和使用示例 。
InnoDB INFORMATION_SCHEMA 表提供有关InnoDB存储引擎各个方面的元数据、状态信息和统计信息。您可以InnoDB INFORMATION_SCHEMA通过SHOW TABLES在INFORMATION_SCHEMA数据库上发出一条语句 来查看表 列表:
1 | mysql> SHOW TABLES FROM INFORMATION_SCHEMA LIKE 'INNODB%'; |
压缩表
有两对InnoDB INFORMATION_SCHEMA关于压缩的表可以深入了解压缩的整体工作情况:
INNODB_CMP并INNODB_CMP_RESET提供有关压缩操作次数和执行压缩所用时间的信息。INNODB_CMPMEM并INNODB_CMPMEM_RESET提供有关为压缩分配内存的方式的信息。
事务和锁定信息
一张INFORMATION_SCHEMA表和两张 Performance Schema 表使您能够监视 InnoDB事务并诊断潜在的锁定问题:
INNODB_TRX:该INFORMATION_SCHEMA表提供当前在内部执行的每个事务的信息InnoDB,包括事务状态(例如,它是正在运行还是等待锁定)、事务何时开始以及事务正在执行的特定 SQL 语句。data_locks:这个 Performance Schema 表为每个持有锁和每个被阻塞等待释放锁的锁请求包含一行:有一行各举行了锁,无论是持有锁的事务的状态(
INNODB_TRX.TRX_STATE是RUNNING,LOCK WAIT,ROLLING BACK或COMMITTING)。InnoDB 中等待另一个事务释放锁(
INNODB_TRX.TRX_STATEisLOCK WAIT)的每个事务都被一个阻塞锁请求阻塞。该阻塞锁请求是针对另一个事务以不兼容模式持有的行或表锁。锁请求总是有一种模式与阻止请求的持有锁的模式不兼容(读 vs. 写、共享 vs. 独占)。被阻塞的事务在另一个事务提交或回滚之前无法继续,从而释放请求的锁。对于每个被阻塞的事务,
data_locks包含一行描述事务请求的每个锁,以及它正在等待的锁。
data_lock_waits:这个 Performance Schema 表指示哪些事务正在等待给定的锁,或者给定的事务正在等待哪个锁。该表包含每个阻塞事务的一行或多行,指示它请求的锁以及阻塞该请求的任何锁。该REQUESTING_ENGINE_LOCK_ID值指的是一个事务请求的锁,该BLOCKING_ENGINE_LOCK_ID值指的是阻止第一个事务继续进行的锁(由另一个事务持有)。对于任何给定的阻塞事务, 中的所有行都data_lock_waits具有相同的值REQUESTING_ENGINE_LOCK_ID和不同的值BLOCKING_ENGINE_LOCK_ID。
模式对象表
表名表示提供的数据类型:
INNODB_TABLES提供有关InnoDB表的元数据。INNODB_COLUMNS提供有关InnoDB表列的元数据。INNODB_INDEXES提供有关InnoDB索引的元数据。INNODB_FIELDS提供有关InnoDB索引的关键列(字段)的元数据。INNODB_TABLESTATS提供有关InnoDB从内存数据结构派生的表的低级状态信息的视图 。INNODB_DATAFILES为InnoDB每个表文件和通用表空间提供数据文件路径信息。INNODB_TABLESPACES提供有关InnoDB每个表文件、通用和撤消表空间的元数据。INNODB_TABLESPACES_BRIEF提供有关InnoDB表空间的元数据子集。INNODB_FOREIGN提供有关在InnoDB表上定义的外键的元数据。INNODB_FOREIGN_COLS提供有关在表上定义的外键列的元数据InnoDB。
FULLTEXT 索引表
表格概览
INNODB_FT_CONFIG:提供有关表的FULLTEXT索引和相关处理的元数据InnoDB。INNODB_FT_BEING_DELETED:提供INNODB_FT_DELETED表格的快照 ;它仅在OPTIMIZE TABLE维护操作期间使用。当OPTIMIZE TABLE运行时,该INNODB_FT_BEING_DELETED表被清空,并且DOC_ID值是从除去INNODB_FT_DELETED表。由于 的内容INNODB_FT_BEING_DELETED通常具有较短的生命周期,因此该表用于监视或调试的效用有限。有关OPTIMIZE TABLE在带有FULLTEXT索引的表上 运行的信息 ,请参阅 第 12.10.6 节,“微调 MySQL 全文搜索”。INNODB_FT_DELETED:存储从表的FULLTEXT索引中删除的行InnoDB。为避免索引的 DML 操作期间昂贵的索引重组InnoDBFULLTEXT,有关新删除词的信息单独存储,在您进行文本搜索时从搜索结果中过滤掉,并仅在您OPTIMIZE TABLE为InnoDB表发出语句时从主搜索索引中删除.INNODB_FT_DEFAULT_STOPWORD:保存在 表上创建索引时默认使用 的停用词列表。FULLTEXT``InnoDB有关该
INNODB_FT_DEFAULT_STOPWORD表的信息 ,请参阅第 12.10.4 节,“全文停用词”。INNODB_FT_INDEX_TABLE:提供有关用于处理针对表FULLTEXT索引的 文本搜索的倒排索引的信息InnoDB。INNODB_FT_INDEX_CACHE:提供有关FULLTEXT索引中新插入行的标记信息 。为避免 DML 操作期间昂贵的索引重组,新索引词的信息单独存储,仅在OPTIMIZE TABLE运行时、服务器关闭时或缓存大小超过innodb_ft_cache_size或innodb_ft_total_cache_size系统变量。
缓冲池表
该InnoDB INFORMATION_SCHEMA缓冲池表提供缓冲关于内页池状态信息和元数据InnoDB缓冲池。
指标表
该INNODB_METRICS表提供有关InnoDB性能和资源相关计数器的信息。
临时表信息表
INNODB_TEMP_TABLE_INFO提供有关InnoDB在InnoDB实例中处于活动状态的用户创建的临时表的信息。它不提供有关InnoDB优化器使用的内部临时表的信息 。
检索 InnoDB 表空间元数据
该INFORMATION_SCHEMA.FILES表提供有关所有InnoDB表空间类型的元数据,包括每个表的文件表空间、 通用表空间、 系统表空间、 临时表表空间和撤消表空间(如果存在)。
本节提供了InnoDB特定的使用示例。有关INFORMATION_SCHEMA.FILEStable提供的数据的更多信息
此查询InnoDB 从与INFORMATION_SCHEMA.FILES表InnoDB空间相关的表字段中检索有关系统表空间的 元数据。 INFORMATION_SCHEMA.FILES不相关的字段InnoDB总是返回 NULL,并从查询中排除。
与 Performance Schema 的集成
您可以InnoDB 使用 MySQL Performance Schema 功能分析某些内部操作 。这种类型的调优主要面向评估优化策略以克服性能瓶颈的专家用户。DBA 还可以使用此功能进行容量规划,以查看他们的典型工作负载是否遇到 CPU、RAM 和磁盘存储的特定组合的任何性能瓶颈;如果是,则判断是否可以通过增加系统某些部分的容量来提高性能。
互斥锁是代码中使用的一种同步机制,用于强制在给定时间只有一个线程可以访问公共资源。当服务器中执行的两个或多个线程需要访问同一资源时,这些线程会相互竞争。第一个获得互斥锁的线程会导致其他线程等待,直到锁被释放。
对于InnoDB已检测的互斥锁,可以使用Performance Schema监视互斥锁等待 。例如,在 Performance Schema 表中收集的等待事件数据可以帮助识别具有最多等待或最长总等待时间的互斥锁。
6.1.14 监视器
有两种类型的InnoDB监视器:
- 标准
InnoDB监视器显示以下类型的信息:- 主后台线程完成的工作
- 信号量等待
- 有关最近的外键和死锁错误的数据
- 锁定等待交易
- 活动事务持有的表和记录锁
- 挂起的 I/O 操作和相关统计信息
- 插入缓冲区和自适应哈希索引统计信息
- 重做日志数据
- 缓冲池统计
- 行操作数据
- 该
InnoDB锁监控打印附加锁信息作为标准的一部分InnoDB监视器输出。
锁监视器与标准监视器相同,只是它包括附加的锁信息。为定期输出启用任一监视器会打开相同的输出流,但如果启用了锁定监视器,则该流包含额外信息。例如,如果您启用标准监视器和锁定监视器,则会打开单个输出流。该流包含额外的锁定信息,直到您禁用锁定监视器。
6.1.15 备份和恢复
安全数据库管理的关键是定期备份。根据您的数据量、MySQL 服务器的数量和数据库工作负载,您可以单独或组合使用这些备份技术:热备份 与MySQL 企业备份; 通过在 MySQL 服务器关闭时复制文件进行冷备份; 使用 mysqldump 的逻辑备份用于较小的数据量或记录模式对象的结构。热备份和冷备份是 复制实际数据文件的物理备份,可以直接被 mysqld服务器使用以加快恢复速度。
热备份
该mysqlbackup命令,MySQL企业备份组件的一部分,让您备份运行MySQL实例,包括InnoDB表,以最小的中断操作,同时生产数据库的一致性快照。当mysqlbackup正在复制 InnoDB表时,对InnoDB表的读取和写入 可以继续。MySQL Enterprise Backup 还可以创建压缩备份文件,并备份表和数据库的子集。结合MySQL二进制日志,用户可以进行时间点恢复。MySQL Enterprise Backup 是 MySQL Enterprise 订阅的一部分。
冷备份
如果您可以关闭 MySQL 服务器,则可以进行物理备份,其中包含用于InnoDB管理其表的所有文件 。使用以下程序:
- 执行缓慢关闭MySQL服务器,并确保它停止没有错误。
- 将所有
InnoDB数据文件(ibdata文件和.ibd文件)复制到安全的地方。 - 将所有
InnoDB日志文件(ib_logfile文件)复制到安全的地方。 - 将您的
my.cnf一个或多个配置文件复制到一个安全的地方。
使用 mysqldump 进行逻辑备份
备份的时候将数据库备份成SQL(包含drop,create,insert等语句),恢复的时候直接导入即可。
除了物理备份之外,建议您通过使用mysqldump转储表来定期创建逻辑备份 。二进制文件可能会在您不注意的情况下损坏。转储的表存储在人类可读的文本文件中,因此更容易发现表损坏。此外,由于格式更简单,严重数据损坏的机会更小。mysqldump 还可以--single-transaction 选择在不锁定其他客户端的情况下制作一致的快照。
复制与InnoDB表一起工作,因此您可以使用 MySQL 复制功能在需要高可用性的数据库站点上保留数据库的副本。
恢复
要将InnoDB数据库从进行物理备份的时间恢复到现在,您必须在启用二进制日志记录的情况下运行 MySQL 服务器,甚至在进行备份之前也是如此。要在还原备份后实现时间点恢复,您可以应用备份后发生的二进制日志中的更改。
从数据损坏或磁盘故障中恢复
如果您的数据库损坏或发生磁盘故障,您必须使用备份执行恢复。在损坏的情况下,首先找到一个没有损坏的备份。恢复基本备份后,使用mysqlbinlog和 mysql从二进制日志文件进行时间点恢复,以恢复备份后发生的更改。
在某些数据库损坏的情况下,转储、删除和重新创建一个或几个损坏的表就足够了。您可以使用该 CHECK TABLE语句来检查表是否损坏,尽管CHECK TABLE自然无法检测到所有可能的损坏类型。
在某些情况下,明显的数据库页面损坏实际上是由于操作系统损坏了自己的文件缓存,而磁盘上的数据可能没问题。最好先尝试重新启动计算机。这样做可以消除似乎是数据库页面损坏的错误。如果 MySQL 由于InnoDB一致性问题仍然无法启动,请参阅 第 15.21.3 节,“强制 InnoDB 恢复”以了解在恢复模式下启动实例的步骤,这允许您转储数据。
InnoDB 崩溃恢复
要从意外的 MySQL 服务器退出中恢复,唯一的要求是重新启动 MySQL 服务器。 InnoDB自动检查日志并将数据库前滚到现在。 InnoDB自动回滚崩溃时存在的未提交事务。
InnoDB 和 MySQL 复制
在副本上的存储引擎与源上的存储引擎不同的情况下,可以使用复制。例如,您可以InnoDB将对源上的 MyISAM表的修改复制到 副本上的表。
在源上失败的事务不会影响复制。MySQL 复制基于二进制日志,其中 MySQL 写入修改数据的 SQL 语句。失败的事务(例如,由于外键违规,或者因为回滚)不会写入二进制日志,因此不会发送到副本。
6.1.16 内存缓存插件
InnoDB memcached 插件的好处
本节概述了daemon_memcached插件的优点 。InnoDB表和memcached的组合比 单独 使用它们具有优势。
直接访问
InnoDB存储引擎避免了SQL的解析和规划开销。在与 MySQL 服务器相同的进程空间中 运行memcached避免了来回传递请求的网络开销。
使用memcached协议写入的数据透明写入
InnoDB表中,无需经过MySQL SQL层。在更新非关键数据时,您可以控制写入频率以实现更高的原始性能。通过memcached 协议请求的数据从
InnoDB表中透明查询 ,无需经过MySQL SQL层。对相同数据的后续请求由
InnoDB缓冲池提供。缓冲池处理内存缓存。您可以使用InnoDB配置选项调整数据密集型操作的性能。数据可以是非结构化的或结构化的,这取决于应用程序的类型。您可以为数据创建一个新表,或使用现有表。
InnoDB可以将多个列值组合和分解为单个 memcached项值,从而减少应用程序中所需的字符串解析和连接量。例如,您可以将字符串值存储2|4|6|8在memcached 缓存中,并InnoDB根据分隔符将值拆分,然后将结果存储在四个数字列中。内存和磁盘之间的传输是自动处理的,简化了应用程序逻辑。
数据存储在 MySQL 数据库中,以防止崩溃、中断和损坏。
您可以
InnoDB通过 SQL访问底层表进行报告、分析、即席查询、批量加载、多步事务计算、并集和交集等集合操作,以及其他适合 SQL 的表达能力和灵活性的操作。您可以通过将
daemon_memcached源服务器上的插件与 MySQL 复制结合使用来确保高可用性 。memcached与 MySQL 的集成提供了一种使内存中数据持久化的方法,因此您可以将其用于更重要的数据类型。您可以在应用程序中使用更多
add、incr和类似的写入操作,而不必担心数据可能会丢失。您可以停止和启动 memcached服务器而不会丢失对缓存数据所做的更新。为了防止意外中断,您可以利用InnoDB崩溃恢复、复制和备份功能。该方法
InnoDB确实快速 的主键查找是天作之合memcached的单项查询。daemon_memcached与等效的 SQL 查询相比,插件使用的直接、低级数据库访问路径对于键值查找要高效得多。memcached 的序列化功能可以将复杂的数据结构、二进制文件甚至代码块转换为可存储的字符串,提供了一种将此类对象放入数据库的简单方法。
因为你可以通过SQL访问底层数据,可以生成报告,搜索或跨多个密钥更新,功能和通话功能,如
AVG()与MAX()上memcached的数据。使用memcached本身,所有这些操作都是昂贵或复杂的 。您不需要在启动时手动将数据加载到 memcached中。当应用程序请求特定键时,值会自动从数据库中检索,并使用
InnoDB缓冲池缓存在内存中 。由于memcached占用的 CPU 相对较少,并且其内存占用易于控制,因此它可以在同一系统上与 MySQL 实例一起舒适地运行。
因为数据一致性是由用于常规
InnoDB表的机制强制执行的,所以您不必担心过时的memcached数据或在缺少键的情况下查询数据库的回退逻辑。
1 | 【摘要】Memcached是一个分布式的内存对象缓存系统,通常用于动态Web应用以减轻数据库负载。 Memcached是基于一个存储键对的hashmap,当表格满了以后,就使用LRU(最近最小使用)算法机制替换掉。 Memcached使用了libevent来均衡任何数量的打开链接,使用非阻塞的网络I/O, 对内部对象实现引用计数(因此,针对多样的客户端,对象可以处在多样的状态), 使用自己的页块分配. |
内存缓存架构
InnoDB 分布式缓存插件实现的memcached作为一个MySQL插件守护进程访问该InnoDB存储引擎直接绕过MySQL的SQL层。
下图说明了daemon_memcached与 SQL 相比,应用程序如何通过插件访问数据。

daemon_memcached插件的 特点:
memcached作为mysqld的守护程序插件 。双方的mysqld和 memcached的在同一个进程空间中运行,具有非常低的延迟对数据的访问。
直接访问
InnoDB表,绕过 SQL 解析器、优化器,甚至 Handler API 层。标准的memcached协议,包括基于文本的协议和二进制协议。该
daemon_memcached插件通过了memcapable 命令的所有 55 项兼容性测试。多列支持。您可以将多个列映射到键值存储的 “值”部分,列值由用户指定的分隔符分隔。
默认情况下,使用memcached协议直接读取和写入数据
InnoDB,让 MySQL 使用InnoDB缓冲池管理内存缓存 。默认设置代表了数据库应用程序的高可靠性和最少意外的组合。例如,默认设置避免数据库端未提交的数据,或为memcachedget请求返回的陈旧数据 。高级用户可以将系统配置为传统的 memcached服务器,所有数据只缓存在memcached引擎中(内存缓存),或者使用 “ memcached引擎”(内存缓存)和
InnoDBmemcached引擎(InnoDB作为后端持久化)的组合贮存)。控制数据如何往往之间来回传递
InnoDB和memcached的 通过操作innodb_api_bk_commit_interval,daemon_memcached_r_batch_size以及daemon_memcached_w_batch_size配置选项。批量大小选项默认值为 1,以获得最大的可靠性。通过 配置参数 指定memcached选项 的能力
daemon_memcached_option。例如,您可以更改memcached侦听的端口、减少最大同时连接数、更改键值对的最大内存大小或启用错误日志的调试消息。的
innodb_api_trx_level配置选项控制事务 隔离级别上通过处理的查询memcached的。尽管 memcached没有事务的概念 ,但您可以使用此选项来控制 memcached看到由daemon_memcached插件使用的表上发出的 SQL 语句引起的更改的 时间。默认情况下,innodb_api_trx_level设置为READ UNCOMMITTED。该
innodb_api_enable_mdl选项可用于在 MySQL 级别锁定表,以便DDL无法通过 SQL 接口删除或更改映射表 。如果没有锁,该表可以从 MySQL 层删除,但会保留在InnoDB存储中,直到 memcached或其他一些用户停止使用它。“ MDL ”代表“元数据锁定”。
设置 InnoDB 内存缓存插件
可以参考这篇文章:https://dev.mysql.com/doc/refman/8.0/en/innodb-memcached-setup.html
InnoDB memcached 多获取和范围查询支持
该daemon_memcached插件支持多个 get 操作(在单个memcached查询中获取多个键值对 )和范围查询。
在单个memcached查询中获取多个键值对的 能力通过减少客户端和服务器之间的通信流量来提高读取性能。对于 InnoDB,这意味着更少的事务和开表操作。
InnoDB memcached 插件的安全注意事项
由于memcached默认不使用认证机制,可选的SASL认证不如传统DBMS安全措施强,所以只将非敏感数据保留在使用daemon_memcached插件的MySQL实例中,并隔离使用此配置的任何服务器来自潜在的入侵者。不允许memcached从 Internet 访问这些服务器;只允许从受防火墙保护的 Intranet 内访问,最好是从您可以限制其成员资格的子网访问。
不同于传统的分布式缓存,该 daemon_memcached插件可以让你通过调用产生的数据值的控制耐久性 add,set, incr,等等。默认情况下,通过memcached接口写入的数据存储到磁盘,并调用get从磁盘返回最近的值。尽管默认行为不能提供最佳的原始性能,但与InnoDB表的 SQL 接口相比,它仍然很快。
当您获得使用该daemon_memcached插件的经验时 ,您可以考虑放宽非关键类数据的持久性设置,冒着在中断事件中丢失一些更新值的风险,或者返回稍微过时的数据。
持久性和原始性能之间的权衡之一是提交新数据和更改数据的频率 。如果数据很关键,则应立即提交,以便在意外退出或中断的情况下是安全的。如果数据不太重要,例如在意外退出后重置的计数器或您可以承受丢失的日志数据,您可能更喜欢较低频率提交时可用的更高原始吞吐量。
补充
memcache 本身是一款分布式的高速缓存系统,以 key-value 的形式常驻内存,一般用来做网站或者数据库的缓存使用。
特别是对以下场景非常适合用 memcache 来做缓存:
频繁访问的数据
安全性要求比较低的数据
更新比较频繁的小表(用户状态表、物品库存等)
MySQL memcached api
MySQL 5.6 —— 开始支持
MySQL 5.6 把 memcache 功能以插件形式集成到 MySQL 数据库中,称为 memcached api。
这样一来,memcache 的数据以 InnoDB 关系表的形式同步于磁盘,解决了 memcache 的几个问题:
解决了 memcache 的数据持久化的问题;
可以很方便的以 SQL 语句的形式直接访问 memcache 的数据;
不需要单独安装 memcache,安装 MySQL 即可使用。
MySQL 5.7 —— 深入优化
MySQL 5.7 对 memcached api 做了深入优化,官方数据显示使用 memcached api,在只读的场景中,QPS 提升到 100W。
MySQL 8.0 —— 新增特性
MySQL 8.0 发布后,又在功能上给 memcached api 增加了两个新特性。
- 批量获取 KEY
相比原来一次只能读取一个 Key 来讲,减少了客户端与 MySQL 服务端的数据交互次数。
- 可以对 KEY 进行 RANGE 操作
可以直接类似于 select * from t1 where id between 10 and 20 这样的范围检索语句。
转载至:https://opensource.actionsky.com/20200706-mysql/
6.1.17 故障排除
以下一般准则适用于故障排除 InnoDB问题:
当操作失败或您怀疑存在错误时,请查看 MySQL 服务器错误日志(请参阅第 5.4.2 节,“错误日志”)。 服务器错误消息参考提供
InnoDB了您可能遇到的一些常见特定错误的故障排除信息 。如果失败与死锁有关 ,请在
innodb_print_all_deadlocks启用该选项的情况下运行, 以便将每个死锁的详细信息打印到 MySQL 服务器错误日志。有关死锁的信息,请参阅第 15.7.5 节,“InnoDB 中的死锁”。如果问题与
InnoDB数据字典有关,请参阅 第 15.21.4 节,“对 InnoDB 数据字典操作进行故障排除”。故障排除时,通常最好从命令提示符运行 MySQL 服务器,而不是通过 mysqld_safe或作为 Windows 服务。然后您可以看到mysqld打印到控制台的内容,从而更好地了解正在发生的事情。在 Windows 上,使用将输出定向到控制台窗口的 选项启动mysqld
--console。启用
InnoDB监视器以获取有关问题的信息(请参阅 第 15.17 节,“InnoDB 监视器”)。如果问题与性能有关,或者您的服务器似乎挂起,您应该启用标准监视器来打印有关InnoDB. 如果问题出在锁上,请启用锁监视器。如果问题与表创建、表空间或数据字典操作有关,请参阅 InnoDB 信息模式系统表以检查InnoDB内部数据字典的内容。InnoDB``InnoDB在以下条件下临时启用标准 监视器输出:- 长时间的信号量等待
InnoDB在缓冲池中找不到空闲块- 超过 67% 的缓冲池被锁堆或自适应哈希索引占用
如果您怀疑某个表已损坏,请
CHECK TABLE在该表上运行 。
1. I/O 问题故障排除
InnoDBI/O问题 的排查步骤取决于问题发生的时间:在MySQL服务器启动期间,或在正常操作期间由于文件系统级别的问题导致DML或DDL语句失败。
初始化问题
如果在InnoDB尝试初始化其表空间或其日志文件时出现问题,请删除由InnoDB:all ibdatafiles 和 all ib_logfilefiles创建的所有 文件。如果您已经创建了一些 InnoDB表,还要.ibd从 MySQL 数据库目录中删除所有 文件。然后InnoDB再次尝试创建数据库。对于最简单的故障排除,请从命令提示符启动 MySQL 服务器,以便您查看发生了什么。
运行时问题
如果InnoDB在文件操作过程中打印操作系统错误,通常问题有以下解决方案之一:
- 确保
InnoDB数据文件目录和InnoDB日志目录存在。 - 确保mysqld具有在这些目录中创建文件的访问权限。
- 确保mysqld可以读取正确的
my.cnf或my.ini选项文件,以便它以您指定的选项开始。 - 确保磁盘未满并且您没有超过任何磁盘配额。
- 确保您为子目录和数据文件指定的名称不冲突。
- 仔细检查
innodb_data_home_dir和innodb_data_file_path值的语法 。特别是,选项中的任何MAX值innodb_data_file_path都是硬限制,超过该限制会导致致命错误。
2. 强制 InnoDB 恢复
要调查数据库页面损坏,您可以使用 SELECT ... INTO OUTFILE. 通常,通过这种方式获得的数据大部分是完整的。严重损坏可能会导致语句或 后台操作意外退出或断言,甚至导致前 滚恢复崩溃。在这种情况下,您可以使用该 选项强制启动存储引擎,同时阻止后台操作运行,以便转储您的表。例如,您可以在重新启动服务器之前将以下行添加到选项文件的部分: SELECT * FROM *tbl_name*``InnoDB``InnoDBinnodb_force_recoveryInnoDB``[mysqld]
3. 数据字典操作故障排除
有关表定义的信息存储在 InnoDB 数据字典中。如果您四处移动数据文件,字典数据可能会变得不一致。
如果数据字典损坏或一致性问题阻止您启动InnoDB,请参阅 第 15.21.3 节,“强制 InnoDB 恢复”以获取有关手动恢复的信息。
4. 错误处理
以下项目描述了如何InnoDB 执行错误处理。InnoDB有时只回滚失败的语句,有时它会回滚整个事务。
如果表空间中的文件空间
Table is full不足,则会发生MySQL 错误并InnoDB回滚 SQL 语句。一个事务死锁 导致
InnoDB要 回滚整个 事务。发生这种情况时重试整个事务。锁定等待超时导致
InnoDB回滚当前语句(等待锁定并遇到超时的语句)。要回滚整个事务,请在--innodb-rollback-on-timeout启用的情况下启动服务器 。如果使用默认行为,则重试该语句,如果--innodb-rollback-on-timeout已启用,则重试整个事务 。死锁和锁等待超时在繁忙的服务器上都是正常的,应用程序有必要意识到它们可能发生并通过重试来处理它们。您可以通过在事务期间对数据的第一次更改和提交之间做尽可能少的工作来降低它们的可能性,因此锁定的时间尽可能短,行数尽可能少。有时,在不同事务之间拆分工作可能是实用且有帮助的。
如果您未
IGNORE在语句中指定选项,则重复键错误会回滚 SQL语句。A
row too long error回滚 SQL 语句。其他错误大多由MySQL代码层(
InnoDB存储引擎层以上)检测,并回滚相应的SQL语句。不会在单个 SQL 语句的回滚中释放锁。
6.1.18 限制
本节描述了InnoDB 表、索引、表空间和InnoDB存储引擎其他方面的 限制。
一个表最多可以包含 1017 列。虚拟生成的列包含在此限制中。
一个表最多可以包含 64 个 二级索引。
对于
InnoDB使用DYNAMIC或COMPRESSED行格式的表, 索引键前缀长度限制为 3072 字节 。对于
InnoDB使用REDUNDANT或COMPACT行格式的表, 索引键前缀长度限制为 767 字节 。例如,假设一个字符集和每个字符最多 4 个字节,您可能会在 a或 列 上超过 191 个字符的列前缀索引达到此限制 。TEXT``VARCHAR``utf8mb4尝试使用超过限制的索引键前缀长度会返回错误。
如果您在创建 MySQL 实例时通过指定选项将
InnoDB页面大小减少到 8KB 或 4KBinnodb_page_size,则索引键的最大长度会按比例降低,基于 16KB 页面大小的 3072 字节限制。即当页大小为 8KB 时,最大索引键长度为 1536 字节,页大小为 4KB 时为 768 字节。适用于索引键前缀的限制也适用于全列索引键。
多列索引最多允许 16 列。超过限制会返回错误。
1
ERROR 1070 (42000): Too many key parts specified; max 16 parts allowed
对于 4KB、8KB、16KB 和 32KB 页面大小,最大行大小(不包括在页外存储的任何可变长度列)略小于页面的一半。例如,默认
innodb_page_size16KB的最大行大小 约为 8000 字节。但是,对于InnoDB64KB的页面大小,最大行大小约为 16000 字节。LONGBLOB和LONGTEXT列必须小于 4GB,并且总行大小(包括BLOB和TEXT列)必须小于 4GB。如果一行的长度小于半页,则所有行都将本地存储在该页中。如果超过半页,则选择可变长度列用于外部页外存储,直到该行适合半页,如 第 15.11.2 节,“文件空间管理”中所述。
尽管
InnoDB内部支持大于 65,535 字节的行大小,但 MySQL 本身对所有列的组合大小强加了 65,535 的行大小限制。请参阅 第 8.4.7 节,“表列数和行大小的限制”。在一些较旧的操作系统上,文件必须小于 2GB。这不是
InnoDB限制。如果您需要一个大型系统表空间,请使用多个较小的数据文件而不是一个大型数据文件对其进行配置,或者将表数据分布在每个表的文件和通用表空间数据文件中。InnoDB日志文件 的组合最大大小为512GB。最小表空间大小略大于 10MB。最大表空间大小取决于
InnoDB页面大小。表 15.31 InnoDB 最大表空间大小
InnoDB 页面大小 最大表空间大小 4KB 16TB 8KB 32TB 16KB 64TB 32KB 128TB 64KB 256TB 最大表空间大小也是表的最大大小。
表空间文件的路径(包括文件名)不能超过
MAX_PATHWindows 上的限制。在 Windows 10 之前,MAX_PATH限制为 260 个字符。从 Windows 10 版本 1607 开始,MAX_PATH常见的 Win32 文件和目录功能的限制已删除,但您必须启用新行为。有关与并发读写事务相关的限制,请参阅第 15.6.6 节,“撤消日志”。
6.2 MyISAM 存储引擎
1. 简介
每个MyISAM表存储在磁盘上的两个文件。这些文件的名称以表名开头,并具有指示文件类型的扩展名。所述数据文件具有一个 .MYD(MYData)扩展。索引文件具有.MYI (MYIndex)扩展。表定义存储在 MySQL 数据字典中。
MyISAM 表具有以下特征:
所有数据值都以低字节在前存储。这使得数据机和操作系统独立。二进制可移植性的唯一要求是机器使用二进制补码有符号整数和 IEEE 浮点格式。这些要求在主流机器中被广泛使用。二进制兼容性可能不适用于有时具有特殊处理器的嵌入式系统。
先存储数据低字节没有明显的速度损失;表行中的字节通常是未对齐的,按顺序读取未对齐的字节比以相反的顺序读取更多的处理。此外,与其他代码相比,服务器中获取列值的代码对时间并不重要。
所有数字键值都以高字节优先存储,以便更好地压缩索引。
支持大文件的文件系统和操作系统支持大文件(最多 63 位文件长度)。
表中有 (2 32 ) 2 (1.844E+19) 行的限制
MyISAM。每个
MyISAM表的最大索引数为 64。每个索引的最大列数为 16。
最大密钥长度为 1000 字节。这也可以通过更改源代码和重新编译来更改。对于长度超过 250 字节的密钥,使用比默认值 1024 字节更大的密钥块大小。
当按排序顺序插入行时(如使用
AUTO_INCREMENT列时),索引树将被拆分,以便高节点仅包含一个键。这提高了索引树中的空间利用率。AUTO_INCREMENT支持每表 一列的内部处理。MyISAM自动更新此列用于INSERT和UPDATE操作。这使AUTO_INCREMENT列更快(至少 10%)。序列顶部的值在删除后不会重复使用。(当一AUTO_INCREMENT列被定义为多列索引的最后一列时,从序列顶部删除的值的重用确实会发生。)AUTO_INCREMENT可以使用ALTER TABLE或 myisamchk重置该值 。将删除与更新和插入混合使用时,动态大小的行的碎片化程度要小得多。这是通过自动组合相邻删除的块并在删除下一个块时扩展块来完成的。
MyISAM支持并发插入:如果一个表在数据文件中间没有空闲块,你可以INSERT在其他线程从表中读取的同时向其中添加新行。由于删除行或更新具有比当前内容更多的数据的动态长度行,可能会出现空闲块。当所有空闲块都用完(填满)时,以后的插入将再次并发。见 第 8.11.3 节,“并发插入”。您可以将数据文件和索引文件放在不同物理设备上的不同目录中,以通过
DATA DIRECTORY和INDEX DIRECTORYtable 选项提高速度CREATE TABLE。见第 13.1.20 节,“CREATE TABLE 语句”。NULL索引列中允许使用值。每个密钥需要 0 到 1 个字节。每个字符列可以有不同的字符集。请参阅 第 10 章,字符集、排序规则、Unicode。
MyISAM索引文件中 有一个标志,指示表是否已正确关闭。如果 mysqld以myisam_recover_options系统变量集启动,则MyISAM表在打开时会自动检查,如果表未正确关闭,则会修复。如果您使用该
--update-state选项运行myisamchk 将表标记为已检查。myisamchk –fast只检查那些没有这个标记的表。myisamchk –analyze存储密钥部分以及整个密钥的统计信息。
myisampack可以打包
BLOB和VARCHAR列。
MyISAM 还支持以下功能:
- 支持真实
VARCHAR类型;一VARCHAR列以存储在一个或两个字节中的长度开始。 - 带有
VARCHAR列的表可能具有固定或动态的行长。 - 表中
VARCHAR和CHAR列 的长度总和 可能高达 64KB。 - 任意长度限制
UNIQUE。
2. 键所需的空间
MyISAM表使用 B 树索引。您可以粗略计算索引文件的大小为 (key_length+4)/0.67,对所有键求和。这是最坏的情况,当所有键都按排序顺序插入并且表没有任何压缩键时。
字符串索引是空间压缩的。如果第一个索引部分是一个字符串,它也被前缀压缩。如果字符串列有很多尾随空格或者VARCHAR是不总是使用全长的列,则空间压缩会使索引文件小于最坏情况下的数字 。前缀压缩用于以字符串开头的键。如果有许多具有相同前缀的字符串,则前缀压缩会有所帮助。
在MyISAM表中,您还可以通过PACK_KEYS=1在创建表时指定table 选项来为压缩数字添加前缀。数字首先与高字节一起存储,因此当您有许多具有相同前缀的整数键时,这会有所帮助。
3. 表存储格式
MyISAM支持三种不同的存储格式。其中两个,固定格式和动态格式,是根据您使用的列类型自动选择的。第三种压缩格式只能使用myisampack实用程序创建 。当您对没有or 列的表 使用CREATE TABLE或 时 ,您可以强制表格式为或 使用 表选项。
静态(定长)表特性
静态格式是MyISAM 表的默认格式。当表不包含可变长度列它用于(VARCHAR, VARBINARY, BLOB,或 TEXT)。每行都使用固定数量的字节存储。
在三种MyISAM存储格式中,静态格式是最简单和最安全的(最不容易损坏)。由于可以轻松地在磁盘上找到数据文件中的行,它也是最快的磁盘格式:要根据索引中的行号查找行,请将行号乘以行长计算行位置。此外,在扫描表时,每次磁盘读取操作都可以很容易地读取恒定数量的行。
如果您的计算机在 MySQL 服务器写入固定格式MyISAM文件时崩溃,则安全性得到证明 。在这种情况下, myisamchk可以轻松确定每一行的开始和结束位置,因此它通常可以回收除部分写入的行之外的所有行。MyISAM表索引总是可以根据数据行重建。
静态格式表具有以下特征:
CHAR和VARCHAR列被空格填充到指定的列宽,尽管列类型没有改变。BINARY和VARBINARY列用0x00字节填充到列宽。NULL列在行中需要额外的空间来记录它们的值是否为NULL。每NULL列多占一位,四舍五入到最接近的字节。- 很快。
- 易于缓存。
- 崩溃后易于重建,因为行位于固定位置。
- 除非您删除大量行并希望将可用磁盘空间返回给操作系统,否则重组是不必要的。为此,请使用
OPTIMIZE TABLE或 myisamchk -r。 - 通常需要比动态格式表更多的磁盘空间。
动态表特性
如果使用动态存储格式MyISAM 表包含任何可变长度列(VARCHAR, VARBINARY, BLOB,或 TEXT),或者如果表用所创建的ROW_FORMAT=DYNAMIC表的选项。
动态格式比静态格式稍微复杂一点,因为每一行都有一个标题,指示它的长度。当更新导致行变长时,行可能会变得碎片化(存储在不连续的片段中)。
您可以使用OPTIMIZE TABLE或 myisamchk -r对表进行碎片整理。如果您在一个还包含一些可变长度列的表中具有经常访问或更改的固定长度列,那么将可变长度列移动到其他表以避免碎片化可能是一个好主意。
动态格式表具有以下特征:
- 除了长度小于 4 的字符串列之外,所有字符串列都是动态的。
- 每行前面都有一个位图,指示哪些列包含空字符串(对于字符串列)或零(对于数字列)。这不包括包含
NULL值的列。如果删除尾随空格后字符串列的长度为零,或者数字列的值为零,则将其标记在位图中而不保存到磁盘。非空字符串保存为长度字节加上字符串内容。 NULL列在行中需要额外的空间来记录它们的值是否为NULL。每NULL列多占一位,四舍五入到最接近的字节。- 通常需要比固定长度表少得多的磁盘空间。
- 每行仅使用所需的空间。但是,如果一行变大,则会根据需要将其拆分为多个部分,从而导致行碎片化。例如,如果您使用扩展行长度的信息更新行,则该行会变得碎片化。在这种情况下,您可能需要不时运行
OPTIMIZE TABLE或 myisamchk -r以提高性能。使用myisamchk -ei获取表统计信息。 - 崩溃后比静态格式表更难重建,因为行可能被分成许多部分并且链接(片段)可能会丢失。
压缩表
压缩存储格式是使用myisampack工具生成的只读格式。压缩表可以用myisamchk解压缩 。
压缩表具有以下特点:
- 压缩表占用很少的磁盘空间。这可以最大限度地减少磁盘使用,这在使用慢速磁盘(例如 CD-ROM)时很有帮助。
- 每行都单独压缩,因此访问开销非常小。根据表中最大的行,行的标题占用一到三个字节。每列的压缩方式不同。每列通常有一个不同的哈夫曼树。一些压缩类型是:
- 可用于固定长度或动态长度的行。
4. MyISAM 表问题
即使MyISAM表格式非常可靠(SQL 语句对表所做的所有更改都在语句返回之前写入),但如果发生以下任何事件,您仍然可能会损坏表:
- 该mysqld的进程在写中间被杀害。
- 发生意外的计算机关机(例如,计算机已关闭)。
- 硬件故障。
- 您正在使用外部程序(例如 myisamchk)来修改同时由服务器修改的表。
- MySQL 或
MyISAM代码中的软件错误。
您可以MyISAM使用该CHECK TABLE语句检查表的健康状况,并MyISAM使用 REPAIR TABLE. 当 mysqld未运行时,您还可以使用myisamchk命令检查或修复表。
如果您的表经常损坏,您应该尝试确定发生这种情况的原因。要知道的最重要的事情是该表是否因服务器意外退出而损坏。您可以通过restarted mysqld在错误日志中查找最近的消息来轻松验证这一点。如果有这样的消息,表损坏很可能是服务器死机的结果。否则,在正常操作期间可能会发生损坏。这是一个错误。您应该尝试创建一个可重现的测试用例来演示问题。
每个MyISAM索引文件(.MYIfile)在头部都有一个计数器,可以用来检查表是否被正确关闭。如果您从myisamchkCHECK TABLE或 myisamchk收到以下警告 ,则表示此计数器已不同步:
1 | clients are using or haven't closed the table properly |
此警告并不一定意味着该表已损坏,但您至少应该检查该表。
计数器的工作原理如下:
- 在 MySQL 中第一次更新表时,索引文件头中的计数器会增加。
- 在进一步更新期间,计数器不会更改。
- 当表的最后一个实例关闭时(因为
FLUSH TABLES执行了 操作或因为表缓存中没有空间),如果表在任何时候被更新,计数器就会递减。 - 当您修理桌子或检查桌子并发现它没问题时,计数器将重置为零。
- 为避免与可能检查表的其他进程交互时出现问题,如果计数器为零,则在关闭时不会递减计数器。
换句话说,只有在以下情况下,计数器才会变得不正确:
一个
MyISAM表被复制,而第一发布LOCK TABLES和FLUSH TABLES。MySQL 在更新和最终关闭之间崩溃了。(该表可能仍然没问题,因为 MySQL 总是为每个语句之间的所有内容发出写入。)
一个表在被mysqld使用的同时被myisamchk –recover或myisamchk –update-state修改。
多个mysqld服务器正在使用该表,一个服务器在该表被另一台服务器使用时执行了
REPAIR TABLE或CHECK TABLE。在此设置中,使用 是安全的CHECK TABLE,尽管您可能会收到来自其他服务器的警告。但是,REPAIR TABLE应该避免,因为当一台服务器用新的数据文件替换数据文件时,其他服务器并不知道这一点。通常,在多个服务器之间共享数据目录是一个坏主意。
5. 崩溃恢复
一、怎样检查表的错误
为了检查一张表,使用下列命令:
myisamchk tbl_name
这能找出所有错误的99.99%。它不能找出的是仅仅涉及数据文件的损坏(这很不常见)。如果你想要检查一张表,你通常应该没有选项地运行myisamchk或用-s或–silent选项的任何一个。
myisamchk -e tbl_name
它做一个完全彻底的数据检查(-e意思是“扩展检查”)。它对每一行做每个键的读检查以证实他们确实指向正确的行。这在一个有很多键的大表上可能花很长时间。myisamchk通常将在它发现第一个错误以后停止。如果你想要获得更多的信息,你能增加–verbose(-v)选项。这使得myisamchk继续一直到最多20个错误。在一般使用中,一个简单的myisamchk(没有除表名以外的参数)就足够了。
myisamchk -e -i tbl_name
像前面的命令一样,但是-i选项告诉myisamchk还打印出一些统计信息。
二、怎样修复表
一张损坏的表的症状通常是查询意外中断并且你能看到例如这些错误:
“tbl_name.frm”被锁定不能改变。
不能找到文件“tbl_name.MYI”(Errcode :### )。
从表处理器的得到错误###(此时,错误135是一个例外)。
意外的文件结束。
记录文件被毁坏。
在这些情况下,你必须修复表。myisamchk通常能检测并且修复出错的大部分东西。
修复过程包含最多4个阶段,在下面描述。在你开始前,你应该cd到数据库目录和检查表文件的权限,确保他们可被运行mysqld的Unix用户读取(和你,因为你需要存取你正在检查的文件)。如果它拒绝你修改文件,他们也必须是可被你写入的。
阶段1:检查你的表
运行
myisamchk *.MYI
或(myisamchk -e *.MYI,如果你有更多的时间)。使用-s(沉默)选项禁止不必要的信息。
你必须只修复那些myisamchk报告有一个错误的表。对这样的表,继续到阶段2。
如果在检查时,你得到奇怪的错误(例如out of memory错误),或如果myisamchk崩溃,到阶段3。
阶段2 :简单安全的修复
首先,试试myisamchk -r -q tbl_name(-r -q意味着“快速恢复模式”)。这将试图不接触数据文件来修复索引文件。如果数据文件包含它应有的一切和在数据文件指向正确地点的删除连接,这应该管用并且表可被修复。开始修理下一张表。否则,使用下列过程:
在继续前做数据文件的一个备份。
使用myisamchk -r tbl_name(-r意味着“恢复模式”)。这将从数据文件中删除不正确的记录和已被删除的记录并重建索引文件。
如果前面的步骤失败,使用myisamchk –safe-recover tbl_name。安全恢复模式使用一个老的恢复方法,处理常规恢复模式不行的少数情况(但是更慢)。
如果在修复时,你得到奇怪的错误(例如out of memory错误),或如果myisamchk崩溃,到阶段3。
阶段3 :困难的修理
如果在索引文件的第一个16K块被破坏,或包含不正确的信息,或如果索引文件丢失,你只应该到这个阶段 。在这种情况下,创建一个新的索引文件是必要的。按如下这样做:
把数据文件移更安全的地方。
使用表描述文件创建新的(空)数据和索引文件:
shell> mysql db_name mysql> DELETE FROM tbl_name; mysql> quit
将老的数据文件拷贝到新创建的数据文件之中。(不要只是将老文件移回新文件之中;你要保留一个副本以防某些东西出错。)
回到阶段2。现在myisamchk -r -q应该工作了。(这不应该是一个无限循环)。
阶段4:非常困难的修复
只有描述文件也破坏了,你才应该到达这个阶段。这应该从未发生过,因为在表被创建以后,描述文件就不再改变了。
从一个备份恢复描述文件并且回到阶段3。你也可以恢复索引文件并且回到阶段2。对后者,你应该用myisamchk -r启动。
如果你没有一个备份但是确切地知道表是怎样被创建的,在另一个数据库中创建表的一个拷贝。删除新的数据文件,然后从其他数据库将描述和索引文件移到破坏的数据库中。这给了你新的描述和索引文件,但是让数据文件独自留下来了。回到阶段2并且尝试重建索引文件。
转载至:https://blog.51cto.com/liuer/1036540
6. MyISAM补充说明
Mysql 中 MyISAM 和 InnoDB 的区别有哪些?
InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;
InnoDB 是聚集索引,MyISAM 是非聚集索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
如何选择:
是否要支持事务,如果要请选择 InnoDB,如果不需要可以考虑 MyISAM;
如果表中绝大多数都只是读查询,可以考虑 MyISAM,如果既有读写也挺频繁,请使用InnoDB。
系统奔溃后,MyISAM恢复起来更困难,能否接受,不能接受就选 InnoDB;
MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的。如果你不知道用什么存储引擎,那就用InnoDB,至少不会差。
先看下《高性能MySQL》中对于他们的评价:
InnoDB:MySQL默认的事务型引擎,也是最重要和使用最广泛的存储引擎。它被设计成为大量的短期事务,短期事务大部分情况下是正常提交的,很少被回滚。InnoDB的性能与自动崩溃恢复的特性,使得它在非事务存储需求中也很流行。除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。
MyISAM:在MySQL 5.1 及之前的版本,MyISAM是默认引擎。MyISAM提供的大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM并不支持事务以及行级锁,而且一个毫无疑问的缺陷是崩溃后无法安全恢复。正是由于MyISAM引擎的缘故,即使MySQL支持事务已经很长时间了,在很多人的概念中MySQL还是非事务型数据库。尽管这样,它并不是一无是处的。对于只读的数据,或者表比较小,可以忍受修复操作,则依然可以使用MyISAM(但请不要默认使用MyISAM,而是应该默认使用InnoDB)
6.3 MEMORY 存储引擎
何时使用 MEMORY 或 NDB Cluster
希望部署使用MEMORY存储引擎存储重要、高可用或频繁更新数据的应用程序的开发人员 应该考虑 NDB Cluster 是否是更好的选择。MEMORY引擎的典型用例 涉及以下特征:
- 涉及临时、非关键数据的操作,例如会话管理或缓存。当 MySQL 服务器停机或重启时,
MEMORY表中的数据丢失。 - 内存存储,可实现快速访问和低延迟。数据量可以完全容纳在内存中,而不会导致操作系统换出虚拟内存页面。
- 只读或以读取为主的数据访问模式(有限更新)。
NDB Cluster 提供MEMORY与具有更高性能级别的引擎相同的功能 ,并提供以下附加功能 MEMORY:
- 行级锁定和多线程操作,用于客户端之间的低争用。
- 即使使用包含写入的语句混合也具有可扩展性。
- 用于数据持久性的可选磁盘支持操作。
- 无共享架构和多主机操作,无单点故障,实现 99.999% 的可用性。
- 跨节点自动分发数据;应用程序开发人员无需制定自定义分片或分区解决方案。
- 支持不支持的可变长度数据类型(包括
BLOB和TEXT)MEMORY。
分区
MEMORY 表不能分区。
性能特点
MEMORY性能受到处理更新时单线程执行和表锁开销导致的争用的限制。这会在负载增加时限制可伸缩性,特别是对于包含写入的语句混合。
尽管在内存中处理MEMORY 表,但它们不一定比InnoDB繁忙服务器上的表、通用查询或读/写工作负载下的表快 。特别是,执行更新所涉及的表锁定会减慢MEMORY来自多个会话的表的并发使用。
根据对MEMORY表执行的查询类型 ,您可以将索引创建为默认哈希数据结构(用于根据唯一键查找单个值)或通用 B 树数据结构(用于所有类型的涉及等式、不等式或范围运算符(例如小于或大于)的查询)。以下部分说明了创建这两种索引的语法。一个常见的性能问题是在 B 树索引更高效的工作负载中使用默认哈希索引。
用户创建的临时表
MEMORY表内容存储在内存中,这是MEMORY表与服务器在处理查询时动态创建的内部临时表共享的属性。但是,这两种表的不同之处在于MEMORY表不受存储转换的影响,而内部临时表是:
- 如果内部临时表变得太大,服务器会自动将其转换为磁盘存储,如 第 8.4.4 节“MySQL 中的内部临时表使用”中所述。
- 用户创建的
MEMORY表永远不会转换为磁盘表。
Memory存储引擎表和临时表的区别
临时表分两类:系统使用临时表,create temporary table 建立的临时表。无论哪种表,只有当前session是可见的。而Memory表是所有线程都可以使用的。 系统使用临时表又分为两类:查过限制使用Myisam临时表,未超过限制使用Memory表。
使用场景
- 用于查找或者是映射表,例如邮编和地区的对应表
- 用于保存数据分析中产生的中间表
- 用于缓存周期性聚合数据的结果表
注意一点是:Memory数据易丢失,所以要求数据可再生
memory存储引擎是MySQL中的一类特殊的存储引擎。其使用存储在内存中的内容来创建表,而且所有数据也放在内存中。这些特性都与InnoDB,MyISAM存储引擎不同。 OK,这里我们讲解一些memory存储引擎的文件存储形式,索引类型,存储周期和优缺点。
每个基于memory存储引擎的表实际对应一个磁盘文件,该文件的文件名与表名相同,类型为frm类型。该文件只存储表的结构,而其数据文件,都是存储在内存中的,这样有利于对数据的快速的处理,提高整个表的处理效率。
值得注意的是:服务器需要有足够的内存来维持memory存储引擎的表的使用。如果不需要了,可以释放这些内存,甚至可以删除不需要的表。
Memory存储引擎默认使用哈希(HASH)索引,其速度比使用B型树(BTREE)索引快。如果我们需要使用B型树索引,可以在创建索引时选择使用。
这里来整理一个小的技巧:
Memory存储引擎通常很少用到,至少我是没有用到过。因为Memory表的所有数据都是存储在内存上的,如果内存出现异常会影响到数据的完整性。
如果重启机器或者关机,表中的所有数据都将消失,因此,基于Memory存储引擎的表的生命周期都比较短,一般都是一次性的。
Memory表的大小是受到限制的,表的大小主要取决于2个参数,分别是max_rows和max_heap_table_size。其中,max_rows可以在创建表时指定,max_heap_table_size的大小默认为16MB,可以按需要进行扩大。
因此,其基于内存中的特性,这类表的处理速度会非常快,但是,其数据易丢失,生命周期短。基于其这个缺陷,选择Memory存储引擎时需要特别小心。
作者:码农小杨 链接:https://www.jianshu.com/p/141f238abe86
6.4 CSV 存储引擎
在CSV使用逗号分隔值格式的文本文件存储引擎存储数据。
该CSV存储引擎总是被编译到MySQL服务器。
要检查CSV引擎的源代码,请查看storage/csvMySQL 源代码分发的目录。
创建CSV表时,服务器会创建一个纯文本数据文件,其名称以表名开头并带有.CSV扩展名。当您将数据存储到表中时,存储引擎将其以逗号分隔值格式保存到数据文件中。
1 | mysql> CREATE TABLE test (i INT NOT NULL, c CHAR(10) NOT NULL) |
创建CSV表还会创建相应的元文件,用于存储表的状态和表中存在的行数。此文件的名称与带有扩展名的表的名称相同CSM。
如果检查test.CSV通过执行上述语句创建的数据库目录中的文件,其内容应如下所示:
1 | "1","record one" |
这种格式可以被 Microsoft Excel 等电子表格应用程序读取,甚至写入。
该CSV存储引擎不支持索引。
该CSV存储引擎不支持分区。
您使用CSV 存储引擎创建的所有表的NOT NULL所有列都必须具有该属性。
1 | bash-3.2# vi test.CSV |
CSV文件用来存储数据,test_4521.sdi存储表元数据,CSM文件存储表的元数据如表状态和数据量
我们看看如果增加索引会发生什么
1 | MySQL [test]> create index idx_id on test(i); |
证明不支持索引
适用场景
1 | 适合做为数据交换的中间表(能够在服务器运行的时候,拷贝和拷出文件,可以将电子表格存储为CSV文件再拷贝到MySQL数据目录下,就能够在数据库中打开和使用。同样,如果将数据写入到CSV文件数据表中,其它web程序也可以迅速读取到数据。 |
作者:码农小杨 链接:https://www.jianshu.com/p/7ec7429a78d2
6.5 ARCHIVE 存储引擎
该ARCHIVE存储引擎产生大量未索引数据存储在一个非常小的足迹专用表。
表 16.5 ARCHIVE 存储引擎功能
| 特征 | 支持 |
|---|---|
| B树索引 | 不 |
| 备份/时间点恢复(在服务器中实现,而不是在存储引擎中。) | 是的 |
| 集群数据库支持 | 不 |
| 聚集索引 | 不 |
| 压缩数据 | 是的 |
| 数据缓存 | 不 |
| 加密数据 | 是(通过加密功能在服务器中实现。) |
| 外键支持 | 不 |
| 全文检索索引 | 不 |
| 地理空间数据类型支持 | 是的 |
| 地理空间索引支持 | 不 |
| 哈希索引 | 不 |
| 索引缓存 | 不 |
| 锁定粒度 | 排 |
| MVCC | 不 |
| 复制支持(在服务器中实现,而不是在存储引擎中。) | 是的 |
| 存储限制 | 没有任何 |
| T树索引 | 不 |
| 交易 | 不 |
| 更新数据字典的统计信息 | 是的 |
创建ARCHIVE表时,存储引擎会创建名称以表名开头的文件。数据文件的扩展名为.ARZ. .ARN在优化操作期间可能会出现一个 文件。
该ARCHIVE引擎支持 INSERT, REPLACE和 SELECT,而不是 DELETE或 UPDATE。它确实支持 ORDER BY操作、 BLOB列和空间数据类型(请参阅第 11.4.1 节,“空间数据类型”)。不支持地理空间参考系统。该ARCHIVE 发动机采用了行级锁。
该ARCHIVE引擎支持 AUTO_INCREMENTcolumn属性。该 AUTO_INCREMENT列可以具有唯一索引或非唯一索引。尝试在任何其他列上创建索引会导致错误。该ARCHIVE引擎还支持语句中的AUTO_INCREMENTtable 选项 CREATE TABLE来分别指定新表的初始序列值或重置现有表的序列值。
ARCHIVE不支持将值插入到AUTO_INCREMENT小于当前最大列值的列中。尝试这样做会导致 ER_DUP_KEY错误。
该ARCHIVE引擎将忽略 BLOB列,如果没有要求,并扫描他们过去在阅读它们。
该ARCHIVE存储引擎不支持分区。
存储:行在插入时被压缩。该ARCHIVE引擎使用 zlib无损数据压缩(参见 http://www.zlib.net/)。您可以使用 OPTIMIZE TABLE来分析表并将其打包成更小的格式(使用 的原因 OPTIMIZE TABLE,请参见本节后面部分)。该引擎还支持CHECK TABLE. 有几种类型的插入被使用:
- 一条
INSERT语句只是将行推入压缩缓冲区,并且该缓冲区会根据需要刷新。插入缓冲区受锁保护。ASELECT强制发生刷新。 - 批量插入仅在完成后可见,除非同时发生其他插入,在这种情况下可以部分看到它。A
SELECT永远不会导致批量插入的刷新,除非在加载时发生正常插入。
检索:检索时,行按需解压缩;没有行缓存。一个 SELECT操作执行一次完整的表扫描:当 aSELECT发生时,它找出当前可用的行数并读取该行数。SELECT作为一致读取执行。请注意,SELECT插入期间的大量 语句会降低压缩效果,除非仅使用批量插入。为了实现更好的压缩,您可以使用 OPTIMIZE TABLE或 REPAIR TABLE。ARCHIVE报告的表中 的行数 SHOW TABLE STATUS总是准确的。
从archive单词的解释我们大概可以明白这个存储引擎的用途,这个存储引擎基本上用于数据归档;它的压缩比非常的高,存储空间大概是innodb的10-15分之一所以它用来存储历史数据非常的适合,由于它不支持索引同时也不能缓存索引和数据,所以它不适合作为并发访问表的存储引擎。Archivec存储引擎使用行锁来实现高并发插入操作,但是它不支持事务,其设计目标只是提供高速的插入和压缩功能。
每个archive表在磁盘上存在两个文件
.frm(存储表定义)
.arz(存储数据)
1.archive存储引擎支持insert、replace和select操作,但是不支持update和delete。
2.archive存储引擎支持blob、text等大字段类型。支持auto_increment自增列同时自增列可以不是唯一索引。
3.archive支持auto_increment列,但是不支持往auto_increment列插入一个小于当前最大的值的值。
4.archive不支持索引所以无法在archive表上创建主键、唯一索引、和一般的索引。
由于高压缩和快速插入的特点Archive非常适合作为日志表的存储引擎,但是前提是不经常对该表进行查询操作。
转载至:https://www.cnblogs.com/chenmh/p/5821844.html
Archive表比MyISAM表要小大约75%,比支持事务处理的InnoDB表小大约83%。当数据量非常大的时候Archive的插入性能表现会较MyISAM为佳。
Archive表的性能是否可能超过MyISAM?答案是肯定的。根据MySQL工程师的资料,当表内的数据达到1.5GB这个量级,CPU又比较快的时候,Archive表的执行性能就会超越MyISAM表。因为这个时候,CPU会取代I/O子系统成为性能瓶颈。别忘了Archive表比其他任何类型的表执行的物理I/O操作都要少。
较小的空间占用也能在你移植MySQL数据的时候发挥作用。当你需要把数据从一台MySQL服务器转移到另一台的时候,Archive表可以方便地移植到新的MySQL环境,你只需将保存Archive表的底层文件复制过去就可以了。
使用场景 日志和数据采集类应用(不支持OLTP)
作者:码农小杨 链接:https://www.jianshu.com/p/c18d4da0a827
6.6 BLACKHOLE 存储引擎
该BLACKHOLE存储引擎作为一个 “黑洞”,它接受的数据,但它扔了出去,不存储。检索总是返回一个空结果:
1 | mysql> CREATE TABLE test(i INT, c CHAR(10)) ENGINE = BLACKHOLE; |
该BLACKHOLE存储引擎支持所有类型的索引。也就是说,您可以在表定义中包含索引声明。
该BLACKHOLE存储引擎不支持分区。
插入到BLACKHOLE表中不存储任何数据,但如果启用了基于语句的二进制日志记录,则 SQL 语句将被记录并复制到副本服务器。这可以用作中继器或过滤器机制。
源写入其二进制日志。的“虚设” 的mysqld过程充当复制品,施加的所需组合replicate-do-*和 replicate-ignore-*规则,并写入它自己的新的,过滤二进制日志。(请参阅 第 17.1.6 节,“复制和二进制日志记录选项和变量”。)此过滤日志提供给副本。
虚拟进程实际上不存储任何数据,因此在复制源服务器上运行额外的mysqld进程所产生的处理开销很小 。可以使用其他副本重复这种类型的设置。
INSERTBLACKHOLE表的触发器 按预期工作。但是,由于该BLACKHOLE表实际上并未存储任何数据,UPDATE并且 DELETE触发器未激活:FOR EACH ROW触发器定义中的子句不适用,因为没有行。
BLACKHOLE存储引擎的 其他可能用途包括:
- 验证转储文件语法。
- 通过比较
BLACKHOLE启用和不启用二进制日志记录的性能,测量二进制日志记录的开销。 BLACKHOLE本质上是一个 “无操作”的存储引擎,所以它可以用来寻找与存储引擎本身无关的性能瓶颈。
该BLACKHOLE发动机是交易感知的,在这个意义上,提交的事务都写入二进制日志和回滚事务都没有。
黑洞引擎和自动增量列
该BLACKHOLE引擎是一个空操作引擎。对表执行的任何操作BLACKHOLE 均无效。在考虑自动递增的主键列的行为时,应牢记这一点。引擎不会自动递增字段值,也不会保留自动递增字段状态。这对复制具有重要意义。
考虑以下所有三个条件都适用的复制场景:
- 在源服务器上有一个黑洞表,其中有一个作为主键的自动增量字段。
- 在副本上存在相同的表,但使用 MyISAM 引擎。
- 插入是在源表中执行的,无需在
INSERT语句本身中或通过使用SET INSERT_ID语句显式设置自动增量值 。
在这种情况下,复制失败,主键列上出现重复条目错误。
在基于语句的复制中, INSERT_ID上下文事件中的值始终相同。由于尝试为主键列插入具有重复值的行,因此复制失败。
在基于行的复制中,引擎为行返回的值对于每次插入总是相同的。这会导致副本尝试使用主键列的相同值重放两个插入日志条目,因此复制失败。
列过滤
使用基于行的复制 ( binlog_format=ROW) 时,支持表中缺少最后一列的副本,如 第 17.5.1.9 节,“在源和副本上具有不同表定义的复制”部分所述。
这种过滤在副本端起作用,即列在被过滤掉之前被复制到副本。至少有两种情况不需要将列复制到副本:
- 如果数据是机密的,那么副本服务器应该无权访问它。
- 如果源有很多副本,在发送到副本之前过滤可能会减少网络流量。
使用BLACKHOLE引擎可以实现源列过滤 。这以类似于如何实现源表过滤的方式执行 - 通过使用 BLACKHOLE引擎和 --replicate-do-tableor --replicate-ignore-table选项。
MySql-BlackHole:黑洞引擎
概念
像MyISAM、InnoDB,BlackHole是另一种MySQL引擎,从字面意思来看, 其表现就像一个黑洞,只进不出,进来就消失。换句话说,任何往其中写的数据都将丢失,有点像Linux的/dev/null 比如一个表test的引擎是BlackHole,任何对这个表的insert都将丢失, 对它的select永远返回空集,对应的数据目录下只有一个test.frm文件,且没有其他文件与之关联。
使用场景
一个并不保存任何数据的引擎,到底有什么意义? 关键在于,虽然其不保存数据,但对数据库的操作仍旧记录在binlog日志中。 这就带来一个好处,可以将其作为主从复制的中介,将原来从主库中同步的操作变为从作为中介的BlackHole引擎数据库中同步。
作为伪主库分担主库负担
众所周知,当从库比较多的时候,所有从库都从主库load数据将加重主库的负担。但如果是从BlackHole的伪主库中同步就可以减轻主库的负担。原有主从架构大概就像下面这样:

1 | 现在,BlackHole伪主库作为中介,变成这样: |

特别是,可以在伪主库中配置replicate-do和replicate-ignore规则,过滤不需要同步的表。
作为binlog日志收集器
其不保存实际数据,只记录binlog的特性,使得该引擎可用于binlog日志收集,便于数据库分析。 相关知识:binlog日志的format有三种:row,statement,mixed。 row的方式记录每一行被改变的记录,也就说,update将记录所有符合条件被修改的行,alter table更惨,相当于重建整个表,记录所有行的改变。所以这种格式下日志容易过大; statement的方式只记录改变数据的SQL,没有row方式的问题,但其会记录该SQL执行的上下文信息,有个不好的地方是,该上下文信息在另一端重现的时候,容易因为较复杂的信息负责出错。 mixed的方式综合row和statement的方式。
配置
在伪库中,需要如下配置: 配置默认类型为BlackHole,可以用 default_table_type = BLACKHOLE 或是 default-storage-engine = BLACKHOLE 打开binlog:log-bin = ms-mysql-bin 特别要配置:log-slave-update = 1,只有这样,主库中的操作才会同步到BlackHole的binlog中,否则,只有直接针对BlackHole的操作才会记录到binlog。 忽略InnoDB:skip-innodb,当建表语句带有engine=innodb时,将使用默认的BlackHole引擎。 需要提醒的是,当采用这种架构时,数据同步多了中间一层,需要进一步考虑延迟问题。
转载至:https://segmentfault.com/a/1190000012023635
MySQL的BlackHole引擎
MySQL在5.x系列提供了Blackhole引擎–“黑洞”. 其作用正如其名字一样:任何写入到此引擎的数据均会被丢弃掉, 不做实际存储;Select语句的内容永远是空。 和Linux中的 /dev/null 文件完成的作用完全一致。 那么, 一个不能存储数据的引擎有什么用呢?
在大规模的Mysql服务器集群中,如果是存在一台主服务,多台从服务器,在繁忙的业务中,意味着主服务器每操作一个事件,都要往自己的二进制日志中写数据,同时还要往多台从服务器发一次,N台服务器指向一台主服务器,那么需要主服务器发送N次,会启动N个线程,每个线程各自从线程里读二进制日志,那么会有大量的IO,本来是为主服务器减轻负担的,那么这样只能造成压力越来越大,那这样master主机就会为每台slave主机分配出一个binlog dump进程,这样的话会严重影响master的性能。


解决这种问题可以采用多级复制,主服务器还是保持主位置A,再拿一台服务器作为从服务器B,主服务器A只启动一个线程指向从服务器B,那么B服务器再作为其他N台服务器的主,那么B就启动了多个线程,怎么给B服务器减轻压力呢?
在主从之间添加一个分布式master,配置blackhole存储引擎,他起到一个中继的作用,他接收数据但丢其他而不是存储,只是会把master的二进制日志供下层的slave来读取。
第一,让B服务器不再执行查询操作; 第二,让B服务器不再执行写操作; 第三,负责多线程为每个从服务器提供数据,那么就不需要在B服务器存储数据了,但是需要提供二进制日志和中继日志,但B服务器又不需要数据库;
把blackhole引擎,用做slave,配置一些过滤规则,比如复制某些表、不复制某些表。然后也作为一个master,带多个slave。这样的好 处是省了一定的网络带宽,如果没有blackhole做中间环节,那么就需要把第一个master的所有日志都传递到各个slave上去。经过 blackhole这一个slave兼master过滤后再传递给多个slave,减少了带宽占用。而使用blackhole引擎的原因是它不占硬盘空 间,作为一个中转,只负责记日志、传日志。
要设置 default_storage_engine=blackhole 就能实现主从 使用不一致的存储引擎。
补充:如果使用blackhole引擎创建的表,在执行insert操作后,再查询,是没有数据的,因为创建的时候只有.frm表结构文件。
BLACKHOLE总结
- BLACKHOLE支持所有类型的索引
- BLACKHOLE 表不存储数据,如果复制基于SBR,语句可以记录并在从库执行;如果复制为RBR、MBR,UPDATE及DELETE操作将会跳过,不会记录也从库不执行。
- Insert触发器可以正常使用,Update、Delete触发器因为不存储数据不能触发,FOR EACH ROW 也不能触发。
- BLACKHOLE 表Auto Increment字段不会自动递增,也不保留自增字段的状态
- 结合复制replicate-do和replicate-ignore规则,可使用BLACKHOLE当做一个分发主服务器
- 可用来验证转储文件语法
- 测试binlog的开销量,通过对比 BLACKHOLE 与 不启动 binlog的性能
- 可能被用来查找与存储引擎自身不相关的性能瓶颈
6.7 MERGE 存储引擎
MERGE表 的替代方案是分区表,它将单个表的分区存储在单独的文件中,并使某些操作能够更有效地执行。
创建MERGE表时,MySQL.MRG在磁盘上创建一个 文件,其中包含MyISAM应用作一个表的基础表的名称。表的表格式MERGE存储在MySQL数据字典中。基础表不必与MERGE 表位于同一数据库中。
在MERGE 表上您可以使用SELECT, DELETE, UPDATE,和 INSERT,您必须对映射到表的表 具有SELECT、 DELETE和 UPDATE特权 。
1 | 笔记 |
一. 什么是MERGE引擎 MERGE存储引擎把一组MyISAM数据表当做一个逻辑单元来对待,让我们可以同时对他们进行查询。
二. 应用场景 如果需要把日志纪录不停的录入MySQL数据库,并且每天、每周或者每个月都创建一个单一的表,而且要时常进行来自多个表的合计查询,MERGE表这时会非常简单有效。
三. 举例 假设有如下两表
CREATE TABLE
t1(idint(10) unsigned NOT NULL AUTO_INCREMENT,logvarchar(45) , PRIMARY KEY (id) ) ENGINE=MyISAM;
CREATE TABLE
t2(idint(10) unsigned NOT NULL AUTO_INCREMENT,logvarchar(45) , PRIMARY KEY (id) ) ENGINE=MyISAM;
假设t1,t2中都有如下记录
| id | log |
| 1 | test1 |
| 2 | test2 |
| 3 | test3 |
建立MERGE表
CREATE TABLE
t(idint(10) unsigned NOT NULL AUTO_INCREMENT,logvarchar(45) NOT NULL, PRIMARY KEY (id) ) ENGINE=MERGE UNION=(t1, t2) INSERT_METHOD=LAST;
执行select * from t;将会得到如下结果
| id | log |
|---|---|
| 1 | test1 |
| 2 | test2 |
| 3 | test3 |
| 1 | test1 |
| 2 | test2 |
| 3 | test3 |
从效果上看,t1,t2的记录如同在一张表里一样被罗列了出来。当然,看了这个结果你一定会有一些疑问,在下一节里我们会慢慢解释。现在我们主要来解释一下上面MERGE表的建表语句。 1)ENGINE=MERGE 指明使用MERGE引擎,有些同学可能见到过ENGINE=MRG_MyISAM的例子,也是对的,它们是一回事。 2)UNION=(t1, t2) 指明了MERGE表中挂接了些哪表,可以通过alter table的方式修改UNION的值,以实现增删MERGE表子表的功能。 3)INSERT_METHOD=LAST INSERT_METHOD指明插入方式,取值可以是:0 不允许插入;FIRST 插入到UNION中的第一个表; LAST 插入到UNION中的最后一个表。 4)MERGE表及构成MERGE数据表结构的各成员数据表必须具有完全一样的结构。每一个成员数据表的数据列必须按照同样的顺序定义同样的名字和类型,索引也必须按照同样的顺序和同样的方式定义。
四. Cookie问答 1)建表时UNION指明的子表如果存在相同主键的记录会怎么样? 相同主键的记录会同时存在于MERGE中,就像第三节中的例子所示。但如果继续向MERGE表中插入数据,若数据主键已存在则无法插入。换言之,MERGE表只对建表之后的操作负责。
2)若MREGE后存在重复主键,按主键查询会是什么结果? 顺序查询,只出现一条查询记录即停止。比如第三节中的例子,如果执行
select * from t where id=1;
只会得到结果
| id | log |
|---|---|
| 1 | test1 |
3)直接删除一个子表会出现什么情况,正确删除的方式是怎样的? MERGE表会被破坏,正确方式是用alter table方式先将子表从MERGE表中去除,再删除子表。 以第三节中的例子为例,执行如下操作
alter table t ENGINE=MRG_MyISAM UNION=(t1) INSERT_METHOD=LAST;
可以从MERGE表中去除t2,这里你可以安全的对t2进行任何操作了。
4)误删子表时,如何恢复MERGE表? 误删子表时,MERGE表上将无法进行任何操作。 方法1,drop MERGE表,重建。重建时注意在UNION部分去掉误删的子表。 方法2,建立MERGE表时,会在数据库目录下生成一个.MRG文件,比如设表名为t,则文件名为t.MRG。 文件内容类似: t1 t2 INSERT_METHOD=LAST 指明了MGEGE表的子表构成及插入方式。 可以直接修改此文件,去掉误删表的表名。然后执行flush tables即可修复MERGE表。
1 | mysql> CREATE TABLE t1 ( |
5)MERGE的子表中之前有记录,且有自增主键,则MERGE表创建后,向其插入记录时主键以什么规则自增? 以各表中的AUTO_INCREMENT最大值做为下一次插入记录的主键值。 比如t1的自增ID至6,t2至4,则创建MERGE表后,插入的下一条记录ID将会是7
6)两个结构完全相同的但已存在数据的表,是否一定可以合成一个MEREGE表? 从实验的结果看,不是这样的,有时创建出的表,无法进行任何操作。 所以,推荐的使用方法是先有一个MERGE表,里面只包含一张表,当一个这个表的的大小增长到一定程度(比如200w)时,创建另一张空表,将其挂入MERGE表,然后继续插入记录。
7)删除MERGE表是否会对子表产生影响? 不会
8)MREGE表的子表的ENGIN是否有要求? 有的,必须是MyISAM表
附: 官方给出的关于MERGE表存在的一些问题 http://dev.mysql.com/doc/refm… 如果需要把日志纪录不停的录入MySQL数据库,并且每天、每周或者每个月都创建一个单一的表,而且要时常进行来自多个表的合计查询,MERGE表这时会非常简单有效。
MERGE 表的优缺点
MERGE 表格可以帮助您解决以下问题:
- 轻松管理一组日志表。比如你可以把不同月份的数据放到单独的表中,用myisampack压缩其中的一部分,然后创建一个
MERGE表,将它们合二为一。 - 获得更快的速度。您可以根据某些条件拆分大型只读表,然后将各个表放在不同的磁盘上。以
MERGE这种方式构建的表可能比使用单个大表快得多。 - 执行更有效的搜索。如果您确切地知道要查找什么,则可以只在其中一个基础表中搜索某些查询,并使用一个
MERGE表进行其他查询。您甚至可以拥有许多MERGE使用重叠表集的不同表。 - 执行更有效的维修。修复映射到
MERGE表的单个小表比修复单个大表更容易 。 - 立即将多个表映射为一个。一
MERGE,因为它使用的各个表的索引表不需要维护它自己的索引。因此,MERGE表集合的 创建或重新映射非常快。(MERGE即使没有创建索引,您在创建表时仍必须指定索引定义 。) - 如果您有一组表,您可以
MERGE从这些表中按需创建一个大表,您可以改为按需从这些表中创建一个 表。这更快,并节省了大量磁盘空间。 - 超出操作系统的文件大小限制。每个
MyISAM表都受此限制的约束,但MyISAM表的集合不受此限制。 - 您可以
MyISAM通过定义MERGE映射到单个表的表来为表 创建别名或同义词 。这样做应该没有真正显着的性能影响(只有几个间接调用和memcpy()每次读取的调用)。
MERGE表 的缺点是:
MyISAM一个MERGE表 只能使用相同的表。- 某些
MyISAM功能在MERGE表中不可用 。例如,您不能FULLTEXT在MERGE表上创建索引 。(您可以FULLTEXT在基础MyISAM表上创建 索引 ,但不能MERGE使用全文搜索来搜索表。) - 如果
MERGE表是非临时表,则所有基础MyISAM表都必须是非临时表。如果MERGE表是临时表,则MyISAM表可以是临时表和非临时表的任意组合。 MERGE表比MyISAM表使用更多的文件描述符 。如果 10 个客户端使用MERGE映射到 10 个表的表,则服务器使用 (10 × 10) + 10 个文件描述符。(10 个客户端中的每个客户端有 10 个数据文件描述符,客户端之间共享 10 个索引文件描述符。)- 索引读取速度较慢。当您读取索引时,
MERGE存储引擎需要对所有底层表发出读取,以检查哪个表最匹配给定的索引值。要读取下一个索引值,MERGE存储引擎需要搜索读取缓冲区以找到下一个值。只有当一个索引缓冲区用完时,存储引擎才需要读取下一个索引块。这使得MERGE索引在eq_ref搜索时慢得多,但在搜索时不会慢很多ref。
6.8 FEDERATED 存储引擎
该FEDERATED存储引擎可让您从远程MySQL数据库,而不使用复制或群集技术访问数据。查询本地FEDERATED表会自动从远程(联合)表中提取数据。本地表上不存储任何数据。
FEDERATED如果从源代码构建 MySQL, 要包含存储引擎,请使用 选项调用CMake-DWITH_FEDERATED_STORAGE_ENGINE。
该FEDERATED存储引擎默认情况下未在运行的服务器启用; 要启用 FEDERATED,您必须使用该--federated选项启动 MySQL 服务器二进制文件。
概述
当您使用标准存储引擎之一(例如MyISAM、CSV或 InnoDB)创建表时,该表由表定义和关联数据组成。创建 FEDERATED表时,表定义相同,但数据的物理存储在远程服务器上处理。
一个FEDERATED表由两个元素组成:
- 甲远程服务器与数据库表,其又由表定义(存储在MySQL数据字典)和相关联的表的。远程表的表类型可以是远程
mysqld服务器支持的任何类型,包括MyISAM或InnoDB。 - 甲本地服务器与数据库表,其中表定义相匹配,该远程服务器上的对应的表的。表定义存储在数据字典中。本地服务器上没有数据文件。相反,表定义包括一个指向远程表的连接字符串。
在FEDERATED本地服务器上的表上执行查询和语句时 ,通常会在本地数据文件中插入、更新或删除信息的操作被发送到远程服务器执行,在那里它们更新远程服务器上的数据文件或从远程服务器返回匹配的行。
FEDERATED表设置 的基本结构:

当客户端发出引用FEDERATED表的SQL语句时, 本地服务器(执行SQL语句的地方)和远程服务器(物理存储数据的地方)之间的信息流如下:
- 存储引擎查看
FEDERATED表中的每一列, 并构造一个适当的 SQL 语句来引用远程表。 - 该语句使用 MySQL 客户端 API 发送到远程服务器。
- 远程服务器处理该语句,本地服务器检索该语句产生的任何结果(受影响的行数或结果集)。
- 如果语句生成结果集,则每一列都将转换为
FEDERATED引擎期望的内部存储引擎格式, 并可用于将结果显示给发出原始语句的客户端。
本地服务器使用 MySQL 客户端 C API 函数与远程服务器通信。它调用 mysql_real_query()发送语句。为了读取结果集,它 mysql_store_result()使用 mysql_fetch_row().
要创建FEDERATED表,您应该按照以下步骤操作:
- 在远程服务器上创建表。或者,记下现有表的表定义,可能使用该
SHOW CREATE TABLE语句。 - 使用相同的表定义在本地服务器上创建表,但添加将本地表链接到远程表的连接信息。
例如,您可以在远程服务器上创建下表:
1 | CREATE TABLE test_table ( |
分布式跨库查询 mysql federated引擎的使用
分布式跨库查询时,可以尝试使用federated引擎,来创建远程表的映射,方便查询。
1.开启引擎
查询数据库是否支持
1 | SHOW ENGINES; |

有,说明支持,但是没有开启,开启一下:
配置文件添加:federated,如下:
1 | [mysqld] |
然后重启mysql,再次查询,发现已经开启:
2.场景
数据库1:阿里云 java4all,表product_stock;
数据库2:华为云 wangtest1,表user;
user表中有一个product_stock_id。
需求:需要跨库查询。
3.创建数据库表映射
在华为云的wangtest1数据库中,创建一个阿里云的java4all库的product_stock表的映射表。
1 | CREATE TABLE `product_stock` ( |
这里需要注意,数据库引擎的选择,要明确指定引擎ENGINE=FEDERATED,
创建完后,会发现,在wangtest1库中,也有了product_stock表:

此时,其实在华为云的wangtest1库中,就有了阿里云的java4all库中的product_stock这张表的映射了。我们可以看到,这张表外观看起来和正常的表是一样的,但是其实华为云这边这是存储了表结构,数据还是从阿里云拉取的。
我们尝试在阿里云修改数据,在华为云这边刷新,也会看到变化。反之也是可以的。在使用层面看来,这product_stock和本地原本就创建了的效果是一样的,各种查询都是支持的,但是不建议给映射表写的权限。
查询测试:
1 | SELECT * from user a ,product_stock ps |

4.注意事项
映射表的字段,要少于等于原表(远程表)字段。
远程表的数据库据密码,不能含有@字符,因为在创建映射表时,
CONNECTION='mysql://root:1xxx@1xx.xx.xx.xx:3306/java4all/product_stock',这里用户名密码,和数据库地址之间的分隔符是@,如果你的密码含有@,会导致解析出错。修改本地表结构,是不允许的,因为你这个表是映射远程表的,远程表没改,你改了,显然不行。如果远程表修改了,这个表需要重新映射。
删除本地映射表,对远程表无负作用。
转载至:https://cloud.tencent.com/developer/article/1406100
作者:IT云清
通过创建存储引擎为Federated 的表来实现远程共享服务器表数据。 Federated:能够将多个分离(不在同一台服务器上的机器)的MySQL服务器链接起来,从多个物理服务器创建一个逻辑数据库。十分适合于分布式环境或数据集市环境。
6.9 MySQL存储引擎架构概述
MySQL 可插拔存储引擎架构使数据库专业人员能够为特定应用程序需求选择专门的存储引擎,同时完全无需管理任何特定应用程序编码要求。MySQL 服务器架构将应用程序员和 DBA 与存储级别的所有低级实现细节隔离开来,提供一致且简单的应用程序模型和 API。因此,尽管不同存储引擎具有不同的功能,但应用程序不受这些差异的影响。
MySQL 可插拔存储引擎架构

可插拔存储引擎架构提供了一套标准的管理和支持服务,这些服务在所有底层存储引擎中都是通用的。存储引擎本身是数据库服务器的组件,它们实际对在物理服务器级别维护的底层数据执行操作。
这种高效的模块化架构为那些希望专门针对特定应用程序需求(例如数据仓库、事务处理或高可用性情况)的人提供了巨大的好处,同时享受使用一组独立于任何接口和服务的优势存储引擎。
应用程序员和 DBA 通过连接器 API 和存储引擎之上的服务层与 MySQL 数据库交互。如果应用程序更改带来了需要更改底层存储引擎的需求,或者需要添加一个或多个存储引擎来支持新需求,则无需进行重大的编码或流程更改即可使工作正常进行。MySQL 服务器架构通过提供适用于跨存储引擎的一致且易于使用的 API,使应用程序免受存储引擎底层复杂性的影响。
通用数据库服务器层
从技术角度来看,存储引擎中有哪些独特的支持基础设施组件?一些主要功能差异包括:
- 并发:某些应用程序比其他应用程序具有更细粒度的锁要求(例如行级锁)。选择正确的锁定策略可以减少开销,从而提高整体性能。该领域还包括对多版本并发控制或“快照” 读取等功能的支持。
- 事务支持:并非每个应用程序都需要事务,但对于那些需要事务的应用程序,有非常明确的要求,例如 ACID 合规性等。
- 引用完整性:需要让服务器通过 DDL 定义的外键强制执行关系数据库引用完整性。
- 物理存储:这涉及从表和索引的整体页面大小以及用于将数据存储到物理磁盘的格式的所有内容。
- 索引支持:不同的应用场景往往受益于不同的索引策略。每个存储引擎通常都有自己的索引方法,尽管有些(例如 B 树索引)对几乎所有引擎都是通用的。
- Memory Caches:不同的应用程序对某些内存缓存策略的响应比其他的更好,因此尽管一些内存缓存对于所有存储引擎(例如用于用户连接的那些)是通用的,但其他的只有在特定的存储引擎投入使用时才被唯一定义.
- 性能辅助:这包括用于并行操作、线程并发、数据库检查点、批量插入处理等的多个 I/O 线程。
- 杂项目标功能:这可能包括对地理空间操作的支持、对某些数据操作操作的安全限制以及其他类似功能。
每组可插拔存储引擎基础架构组件都旨在为特定应用程序提供一组选择性的好处。相反,避免使用一组组件功能有助于减少不必要的开销。理所当然地,了解特定应用程序的一组要求并选择合适的 MySQL 存储引擎会对整体系统效率和性能产生巨大影响。
7. Mysql优化
本章介绍如何优化 MySQL 性能并提供示例。优化涉及在多个级别上配置、调整和测量性能。根据您的工作角色(开发人员、DBA 或两者的组合),您可以在单个 SQL 语句、整个应用程序、单个数据库服务器或多个联网数据库服务器的级别进行优化。有时,您可以主动并提前计划性能,而有时您可能会在出现问题后对配置或代码问题进行故障排除。优化 CPU 和内存使用还可以提高可扩展性,允许数据库处理更多负载而不会减慢速度。
7.1 优化概述
数据库性能取决于数据库级别的多个因素,例如表、查询和配置设置。这些软件构造会导致硬件级别的 CPU 和 I/O 操作,您必须将其最小化并尽可能提高效率。在处理数据库性能时,您首先要学习软件方面的高级规则和指南,并使用挂钟时间来衡量性能。当您成为专家时,您会更多地了解内部发生的事情,并开始测量诸如 CPU 周期和 I/O 操作之类的事情。
典型用户的目标是从他们现有的软件和硬件配置中获得最佳的数据库性能。高级用户寻找机会改进 MySQL 软件本身,或开发自己的存储引擎和硬件设备来扩展 MySQL 生态系统。数据库性能取决于数据库级别的多个因素,例如表、查询和配置设置。这些软件构造会导致硬件级别的 CPU 和 I/O 操作,您必须将其最小化并尽可能提高效率。在处理数据库性能时,您首先要学习软件方面的高级规则和指南,并使用挂钟时间来衡量性能。当您成为专家时,您会更多地了解内部发生的事情,并开始测量诸如 CPU 周期和 I/O 操作之类的事情。
典型用户的目标是从他们现有的软件和硬件配置中获得最佳的数据库性能。高级用户寻找机会改进 MySQL 软件本身,或开发自己的存储引擎和硬件设备来扩展 MySQL 生态系统。
在数据库级别进行优化
使数据库应用程序快速运行的最重要因素是其基本设计:
- 表格的结构是否正确?特别是,列是否具有正确的数据类型,每个表是否具有适合工作类型的列?例如,执行频繁更新的应用程序通常有很多列很少的表,而分析大量数据的应用程序通常只有很少的列有很多表。
- 是否有合适的 索引来提高查询效率?
- 您是否为每个表使用了合适的存储引擎,并利用了您使用的每个存储引擎的优势和特性?特别是,选择事务性存储引擎(例如)
InnoDB或非 事务性存储引擎(例如)MyISAM对于性能和可伸缩性非常重要。 - 每个表是否使用适当的行格式?此选择还取决于用于表的存储引擎。特别是,压缩表使用较少的磁盘空间,因此需要较少的磁盘 I/O 来读取和写入数据。压缩可用于带有
InnoDB表的所有类型的工作负载 以及只读MyISAM表。 - 应用程序是否使用了适当的 锁定策略?例如,在可能的情况下允许共享访问,以便数据库操作可以并发运行,并在适当的时候请求独占访问,以便关键操作获得最高优先级。同样,存储引擎的选择很重要。该
InnoDB存储引擎处理大部分锁定问题,而不需要您的参与,允许在数据库更好的并发,减少试验和调整的金额,让您的代码。 - 用于缓存的 所有内存区域的大小是否正确?也就是说,大到足以容纳经常访问的数据,但又不能大到使物理内存过载并导致分页。要配置的主要内存区域是
InnoDB缓冲池和MyISAM密钥缓存。
1 | 注意 |
在硬件级别进行优化
随着数据库变得越来越繁忙,任何数据库应用程序最终都会达到硬件限制。DBA 必须评估是否可以调整应用程序或重新配置服务器以避免这些 瓶颈,或者是否需要更多的硬件资源。系统瓶颈通常来自以下来源:
- 磁盘查找。磁盘找到一条数据需要时间。使用现代磁盘,平均时间通常低于 10 毫秒,因此理论上我们每秒可以执行大约 100 次寻道。这个时间随着新磁盘的增加而缓慢改善,并且很难针对单个表进行优化。优化寻道时间的方法是将数据分布到多个磁盘上。
- 磁盘读写。当磁盘处于正确位置时,我们需要读取或写入数据。使用现代磁盘,一个磁盘可提供至少 10–20MB/s 的吞吐量。这比搜索更容易优化,因为您可以从多个磁盘并行读取。
- CPU 周期。当数据在主存中时,我们必须对其进行处理以获得我们的结果。与内存量相比,拥有大表是最常见的限制因素。但是对于小表,速度通常不是问题。
- 内存带宽。当 CPU 需要的数据超过 CPU 缓存的容量时,主内存带宽就成为瓶颈。对于大多数系统来说,这是一个不常见的瓶颈,但需要注意。
平衡便携性和性能
要在可移植 MySQL 程序中使用面向性能的 SQL 扩展,您可以将特定于 MySQL 的关键字包装在/*! */注释分隔符内的语句中。其他 SQL 服务器忽略注释的关键字。
7.2 查询优化器
根据表、列、索引和WHERE子句中的条件的详细信息,MySQL 优化器会考虑许多技术来有效地执行 SQL 查询中涉及的查找。可以在不读取所有行的情况下执行对大表的查询;可以在不比较每个行组合的情况下执行涉及多个表的连接。优化器选择执行最高效查询的一组操作称为“查询执行计划”,也称为 EXPLAIN计划。你的目标是认识到 EXPLAIN 表明查询优化良好的计划,并学习 SQL 语法和索引技术以在您看到一些低效操作时改进计划。
1. 理解查询执行计划
使用 EXPLAIN 优化查询
该EXPLAIN语句提供有关 MySQL 如何执行语句的信息:
EXPLAIN作品有SELECT,DELETE,INSERT,REPLACE,和UPDATE语句。- 当
EXPLAIN与可解释语句一起使用时,MySQL 会显示来自优化器的有关语句执行计划的信息。也就是说,MySQL 解释了它将如何处理语句,包括有关如何连接表和按什么顺序连接的信息。 - 当
EXPLAIN使用 with 而不是可解释语句时,它显示在命名连接中执行的语句的执行计划。 - 对于
SELECT语句,EXPLAIN生成可以使用 显示的附加执行计划信息SHOW WARNINGS。 EXPLAIN对于检查涉及分区表的查询很有用。- 该
FORMAT选项可用于选择输出格式。TRADITIONAL以表格格式显示输出。如果没有FORMAT选项,这是默认设置 。JSONformat 以 JSON 格式显示信息。
在EXPLAIN 的帮助下,您可以查看应该在表中添加索引的位置,以便通过使用索引查找行来更快地执行语句。您还可以 EXPLAIN用于检查优化器是否以最佳顺序连接表。要提示优化器使用与表在SELECT语句中命名的顺序相对应的连接顺序 ,请以SELECT STRAIGHT_JOIN而不只是开始语句SELECT。(请参阅 第 13.2.10 节,“SELECT 语句”。)但是, STRAIGHT_JOIN可能会阻止使用索引,因为它禁用了半连接转换。
优化程序跟踪有时可能会提供与EXPLAIN 的信息互补的信息。但是,优化器跟踪格式和内容可能会因版本而异。
如果您在认为应该使用索引时遇到索引未被使用的问题,请运行ANALYZE TABLE以更新表统计信息,例如键的基数,这可能会影响优化器所做的选择。
EXPLAIN 输出格式
该EXPLAIN语句提供有关 MySQL 如何执行语句的信息。 EXPLAIN作品有 SELECT, DELETE, INSERT, REPLACE,和 UPDATE语句。
EXPLAIN为SELECT语句中使用的每个表返回一行信息 。它按照 MySQL 在处理语句时读取它们的顺序列出输出中的表。这意味着 MySQL 从第一个表中读取一行,然后在第二个表中找到匹配的行,然后在第三个表中,依此类推。处理完所有表后,MySQL 输出选定的列并通过表列表回溯,直到找到具有更多匹配行的表。从此表中读取下一行,然后处理下一个表。
EXPLAIN 输出列
| 专栏 | JSON 名称 | 意义 |
|---|---|---|
id |
select_id |
该SELECT标识符 |
select_type |
无 | 该SELECT类型 |
table |
table_name |
输出行的表 |
partitions |
partitions |
匹配的分区 |
type |
access_type |
联接类型 |
possible_keys |
possible_keys |
可供选择的可能索引 |
key |
key |
实际选择的索引 |
key_len |
key_length |
所选密钥的长度 |
ref |
ref |
与索引比较的列 |
rows |
rows |
估计要检查的行数 |
filtered |
filtered |
按表条件过滤的行百分比 |
Extra |
无 | 附加信息 |
id(JSON名:select_id)SELECT标识符。这是SELECT查询中的序列号 。NULL如果该行引用其他行的联合结果,则该值可以是。在这种情况下,该table列显示的值类似于 表示该行引用具有 和值的行的 并集 。select_type(JSON 名称:无)SELECT类型,可以是下表中的任何一种。JSON 格式EXPLAIN将SELECT类型公开 为一个的属性query_block,除非它是SIMPLE或PRIMARY。JSON 名称(如果适用)也显示在表中。
select_type 价值 |
JSON 名称 | 意义 |
|---|---|---|
SIMPLE |
无 | 简单SELECT(不使用 UNION或子查询) |
PRIMARY |
无 | 最外面 SELECT |
UNION |
无 | 中的第二个或以后的SELECT语句 UNION |
DEPENDENT UNION |
dependent( true) |
a 中的第二个或后面的SELECT语句 UNION,取决于外部查询 |
UNION RESULT |
union_result |
的结果UNION。 |
SUBQUERY |
无 | 首先SELECT在子查询 |
DEPENDENT SUBQUERY |
dependent( true) |
首先SELECT在子查询中,依赖于外部查询 |
DERIVED |
无 | 派生表 |
DEPENDENT DERIVED |
dependent( true) |
派生表依赖于另一个表 |
MATERIALIZED |
materialized_from_subquery |
物化子查询 |
UNCACHEABLE SUBQUERY |
cacheable( false) |
无法缓存结果并且必须为外部查询的每一行重新评估的子查询 |
UNCACHEABLE UNION |
cacheable( false) |
UNION 属于不可缓存子查询的第二个或以后的选择(请参阅 UNCACHEABLE SUBQUERY) |
table(JSON名:table_name)输出行所引用的表的名称。这也可以是以下值之一
partitions(JSON名:partitions)查询将匹配记录的分区。该值
NULL用于非分区表。type(JSON名:access_type)联接类型。该
type列EXPLAIN输出介绍如何联接表。在 JSON 格式的输出中,这些作为access_type属性的值被找到。下面的列表描述了连接类型,从最好的类型到最差的类型const该表最多有一个匹配行,在查询开始时读取。因为只有一行,该行中该列的值可以被优化器的其余部分视为常量。const表非常快,因为它们只被读取一次。const用于将 一个PRIMARY KEY或UNIQUE索引的所有部分与常量值进行比较。在以下查询中,*tbl_name*可以用作const表:1
2
3
4SELECT * FROM tbl_name WHERE primary_key=1;
SELECT * FROM tbl_name
WHERE primary_key_part1=1 AND primary_key_part2=2;eq_ref对于前面表中的每个行组合,从该表中读取一行。除了
systemandconst类型之外,这是最好的连接类型。当连接使用索引的所有部分并且索引是一个PRIMARY KEY或UNIQUE NOT NULL索引时使用它。eq_ref可用于使用=运算符进行比较的索引列 。比较值可以是常量或表达式,该表达式使用在此表之前读取的表中的列。在以下示例中,MySQL 可以使用eq_ref连接来处理 *ref_table*:1
2
3
4
5
6SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;ref对于先前表中的每个行组合,从该表中读取具有匹配索引值的所有行。ref如果联接仅使用键的最左前缀或键不是 aPRIMARY KEY或UNIQUE索引(换句话说,如果联接无法根据键值选择单行),则使用。如果使用的键只匹配几行,这是一个很好的连接类型。ref可用于使用=or<=>运算符进行比较的索引列 。在以下示例中,MySQL 可以使用ref连接来处理 *ref_table*:1
2
3
4
5
6
7
8SELECT * FROM ref_table WHERE key_column=expr;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;fulltext连接是使用FULLTEXT索引执行的。-
这种连接类型类似于
ref,但另外,MySQL 会额外搜索包含NULL值的行。这种连接类型优化最常用于解析子查询。在以下示例中,MySQL 可以使用ref_or_null连接来处理*ref_table*:1
2SELECT * FROM ref_table
WHERE key_column=expr OR key_column IS NULL; -
此连接类型表示使用了索引合并优化。在这种情况下,
key输出行中的列包含所使用索引的列表,并key_len包含所使用索引 的最长关键部分的列表。 unique_subquery只是一个索引查找函数,完全替换子查询以提高效率。这种类型替代 了以下形式的
eq_ref一些IN子查询:1
value IN (SELECT primary_key FROM single_table WHERE some_expr)
-
这种联接类型类似于
unique_subquery. 它取代了IN子查询,但它适用于以下形式的子查询中的非唯一索引:1
value IN (SELECT key_column FROM single_table WHERE some_expr)
-
仅检索给定范围内的行,使用索引来选择行。的
key输出行中的列指示使用哪个索引。将key_len包含已使用的时间最长的关键部分。该ref列适用NULL于这种类型。range当使用=,<>,>,>=,<,<=,IS NULL,<=>,BETWEEN,LIKE, 或IN()运算符中的任何一个将键列与常量进行比较时,可以使用 :1
2
3
4
5
6
7
8
9
10
11SELECT * FROM tbl_name
WHERE key_column = 10;
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name
WHERE key_part1 = 10 AND key_part2 IN (10,20,30); -
该
index联接类型是一样的ALL,只是索引树被扫描。这有两种方式:- 如果索引是查询的覆盖索引,可以满足表中所有需要的数据,则只扫描索引树。在这种情况下,该
Extra列显示Using index。仅索引扫描通常比ALL索引的大小通常小于表数据的大小要快 。 - 使用从索引中读取来执行全表扫描以按索引顺序查找数据行。
Uses index不会出现在Extra列中。
当查询仅使用属于单个索引的列时,MySQL 可以使用此连接类型。
- 如果索引是查询的覆盖索引,可以满足表中所有需要的数据,则只扫描索引树。在这种情况下,该
-
对先前表中的每个行组合进行全表扫描。如果该表是第一个未标记的表
const,这通常不好,并且在所有其他情况下通常 非常糟糕。通常,您可以ALL通过添加索引来避免 基于常量值或早期表中的列值从表中检索行
possible_keys(JSON名:possible_keys)该
possible_keys列指示 MySQL 可以选择从中查找该表中行的索引。请注意,此列完全独立于EXPLAIN. 这意味着某些键possible_keys在实际中可能无法与生成的表顺序一起使用。如果此列是
NULL(或在 JSON 格式的输出中未定义),则没有相关索引。在这种情况下,您可以通过检查WHERE子句来检查它是否引用了适合编制索引的某些列或多列,从而提高查询的性能。如果是这样,请创建适当的索引并EXPLAIN再次检查查询 。key(JSON名:key)该
key列表示 MySQL 实际决定使用的键(索引)。如果 MySQL 决定使用其中一个possible_keys索引来查找行,则该索引将作为键值列出。可能
key会命名值中不存在的索引possible_keys。如果没有任何possible_keys索引适合查找行,但查询选择的所有列都是某个其他索引的列,就会发生这种情况。也就是说,命名索引覆盖了选定的列,因此虽然它不用于确定要检索哪些行,但索引扫描比数据行扫描更有效。对于
InnoDB,二级索引可能会覆盖选定的列,即使查询也选择了主键,因为InnoDB将主键值与每个二级索引一起存储。如果key是NULL,则 MySQL 找不到可用于更有效地执行查询的索引。要强制MySQL使用或忽略列出的索引
possible_keys列,使用FORCE INDEX,USE INDEX或IGNORE INDEX在您的查询。对于
MyISAM表,运行ANALYZE TABLE有助于优化器选择更好的索引。对于MyISAM表,myisamchk –analyze执行相同的操作。key_len(JSON名:key_length)该
key_len列表示 MySQL 决定使用的键的长度。的值key_len使您能够确定 MySQL 实际使用的多部分键的多少部分。如果key列说NULL,key_len列也说NULL。由于密钥存储格式的原因,列的密钥长度
NULL比列的长度NOT NULL大一。ref(JSON名:ref)该
ref列显示哪些列或常量与列中指定的索引进行比较以key从表中选择行。如果值为
func,则使用的值是某个函数的结果。要查看哪个功能,请使用SHOW WARNINGS以下内容EXPLAIN查看扩展EXPLAIN输出。该函数实际上可能是一个运算符,例如算术运算符。rows(JSON名:rows)该
rows列表示 MySQL 认为它必须检查以执行查询的行数。对于
InnoDB表格,这个数字是一个估计值,可能并不总是准确的。filtered(JSON名:filtered)该
filtered列指示按表条件过滤的表行的估计百分比。最大值为 100,这意味着没有发生行过滤。从 100 开始减小的值表示过滤量增加。rows显示估计的检查行数,rows×filtered显示与下表连接的行数。例如,如果rows是 1000 和filtered50.00 (50%),则与下表连接的行数为 1000 × 50% = 500。Extra(JSON 名称:无)此列包含有关 MySQL 如何解析查询的附加信息。有关不同值的说明,请参阅
EXPLAIN额外信息。没有与
Extra列对应的单个 JSON 属性 ;但是,此列中可能出现的值会作为 JSON 属性或作为属性的文本公开message。
解释额外信息
该Extra列 EXPLAIN输出包含MySQL解决查询的额外信息。以下列表说明了可以出现在此列中的值。每个项目还为 JSON 格式的输出指示哪个属性显示Extra值。对于其中一些,有一个特定的属性。其他显示为message 属性的文本。
Extra:查询中每一步实现的额外细节信息,主要会是以下内容。
Distinct:查找distinct 值,当mysql找到了第一条匹配的结果时,将停止该值的查询,转为后面其他值查询。
Full scan on NULL key:子查询中的一种优化方式,主要在遇到无法通过索引访问null值的使用。
Range checked for each record (index map: N):通过 MySQL 官方手册的描述,当 MySQL Query Optimizer 没有发现好的可以使用的索引时,如果发现前面表的列值已知,部分索引可以使用。对前面表的每个行组合,MySQL检查是否可以使用range或 index_merge访问方法来索取行。
SELECT tables optimized away:当我们使用某些聚合函数来访问存在索引的某个字段时,MySQL Query Optimizer 会通过索引直接一次定位到所需的数据行完成整个查询。当然,前提是在 Query 中不能有 GROUP BY 操作。如使用MIN()或MAX()的时候。
Using filesort:当Query 中包含 ORDER BY 操作,而且无法利用索引完成排序操作的时候,MySQL Query Optimizer 不得不选择相应的排序算法来实现。
Using index:所需数据只需在 Index 即可全部获得,不须要再到表中取数据。
Using index for group-by:数据访问和 Using index 一样,所需数据只须要读取索引,当Query 中使用GROUP BY或DISTINCT 子句时,如果分组字段也在索引中,Extra中的信息就会是 Using index for group-by。
Using temporary:当 MySQL 在某些操作中必须使用临时表时,在 Extra 信息中就会出现Using temporary 。主要常见于 GROUP BY 和 ORDER BY 等操作中。
Using where:如果不读取表的所有数据,或不是仅仅通过索引就可以获取所有需要的数据,则会出现 Using where 信息。
Using where with pushed condition:这是一个仅仅在 NDBCluster存储引擎中才会出现的信息,而且还须要通过打开 Condition Pushdown 优化功能才可能被使用。控制参数为 engine_condition_pushdown 。
Impossible WHERE noticed after reading const tables:MySQL Query Optimizer 通过收集到的统计信息判断出不可能存在结果。
No tables:Query 语句中使用 FROM DUAL或不包含任何 FROM子句。
Not exists:在某些左连接中,MySQL Query Optimizer通过改变原有 Query 的组成而使用的优化方法,可以部分减少数据访问次数。
EXPLAIN 输出解释(例子)
通过取输出rows 列中的值的乘积,您可以很好地表明连接的好坏EXPLAIN。这应该告诉您 MySQL 必须检查多少行才能执行查询。如果您使用max_join_size系统变量限制查询,则 此行积还用于确定SELECT 要执行哪些多表语句以及要中止哪些多表语句。请参见 第 5.1.1 节 “配置服务器”。
以下示例显示了如何根据 提供的信息逐步优化多表连接 EXPLAIN。
假设您有SELECT此处显示的 语句,并且您计划使用EXPLAIN以下命令检查它 :
1 | EXPLAIN SELECT tt.TicketNumber, tt.TimeIn, |
对于此示例,请做出以下假设:
被比较的列已声明如下。
表 专栏 数据类型 ttActualPCCHAR(10)ttAssignedPCCHAR(10)ttClientIDCHAR(10)etEMPLOYIDCHAR(15)doCUSTNMBRCHAR(15)这些表具有以下索引。
表 索引 ttActualPCttAssignedPCttClientIDetEMPLOYID(主键)doCUSTNMBR(主键)
- 这些
tt.ActualPC值不是均匀分布的。
最初,在执行任何优化之前,该 EXPLAIN语句会生成以下信息:
1 | table type possible_keys key key_len ref rows Extra |
因为type是 ALL为每个表,这个输出表明MySQL正在生成的所有表的笛卡儿积; 也就是说,行的每个组合。这需要相当长的时间,因为必须检查每个表中行数的乘积。对于手头的情况,此乘积为 74 × 2135 × 74 × 3872 = 45,268,558,720 行。如果桌子更大,你只能想象需要多长时间。
这里的一个问题是,如果将列声明为相同的类型和大小,MySQL 可以更有效地使用列上的索引。在这种情况下,VARCHAR与 CHAR被认为是相同的,如果它们被声明为相同的大小。 tt.ActualPC被声明为 CHAR(10)and et.EMPLOYID is CHAR(15),因此存在长度不匹配。
要修复列长度之间的这种差异,请使用 ALTER TABLE将ActualPC10 个字符延长 到 15 个字符:
1 | mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15); |
现在tt.ActualPC和 et.EMPLOYID都是 VARCHAR(15)。EXPLAIN再次执行该 语句会产生以下结果:
1 | table type possible_keys key key_len ref rows Extra |
这并不完美,但要好得多:rows值的乘积 少了 74 倍。此版本在几秒钟内执行。
可以进行第二次更改以消除tt.AssignedPC = et_1.EMPLOYID和tt.ClientID = do.CUSTNMBR比较的列长度不匹配:
1 | mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15), |
修改后, EXPLAIN生成此处显示的输出:
1 | table type possible_keys key key_len ref rows Extra |
在这一点上,查询几乎被优化了。剩下的问题是,默认情况下,MySQL 假定tt.ActualPC 列中的值是均匀分布的,而tt表并非如此。幸运的是,很容易告诉 MySQL 分析密钥分布:
1 | mysql> ANALYZE TABLE tt; |
使用额外的索引信息,连接是完美的并 EXPLAIN产生以下结果:
1 | table type possible_keys key key_len ref rows Extra |
rows输出中 的列 EXPLAIN是来自 MySQL 连接优化器的有根据的猜测。通过将rows乘积与查询返回的实际行数进行比较,检查这些数字是否更接近真实 情况。如果数字完全不同,您可能会通过STRAIGHT_JOIN在 SELECT语句中使用并尝试在FROM子句中以不同顺序列出表来 获得更好的性能 。(但是, STRAIGHT_JOIN可能会阻止使用索引,因为它禁用了半连接转换。请参阅第 8.2.2.1 节,“使用半连接转换 优化 IN 和 EXISTS 子查询谓词”.)
在某些情况下,可以在EXPLAIN SELECT与子查询一起使用时执行修改数据的语句。
详细可以参考官网:https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
MySQL 性能优化神器 Explain 使用分析
简介
MySQL 提供了一个 EXPLAIN 命令, 它可以对 SELECT 语句进行分析, 并输出 SELECT 执行的详细信息, 以供开发人员针对性优化.
EXPLAIN 命令用法十分简单, 在 SELECT 语句前加上 Explain 就可以了, 例如:
1 | EXPLAIN SELECT * from user_info WHERE id < 300; |
准备
为了接下来方便演示 EXPLAIN 的使用, 首先我们需要建立两个测试用的表, 并添加相应的数据:
1 | CREATE TABLE `user_info` ( |
EXPLAIN 输出格式
EXPLAIN 命令的输出内容大致如下:
1 | mysql> explain select * from user_info where id = 2\G |
各列的含义如下:
- id: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符.
- select_type: SELECT 查询的类型.
- table: 查询的是哪个表
- partitions: 匹配的分区
- type: join 类型
- possible_keys: 此次查询中可能选用的索引
- key: 此次查询中确切使用到的索引.
- ref: 哪个字段或常数与 key 一起被使用
- rows: 显示此查询一共扫描了多少行. 这个是一个估计值.
- filtered: 表示此查询条件所过滤的数据的百分比
- extra: 额外的信息
接下来我们来重点看一下比较重要的几个字段.
select_type
select_type 表示了查询的类型, 它的常用取值有:
- SIMPLE, 表示此查询不包含 UNION 查询或子查询
- PRIMARY, 表示此查询是最外层的查询
- UNION, 表示此查询是 UNION 的第二或随后的查询
- DEPENDENT UNION, UNION 中的第二个或后面的查询语句, 取决于外面的查询
- UNION RESULT, UNION 的结果
- SUBQUERY, 子查询中的第一个 SELECT
- DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.
最常见的查询类别应该是 SIMPLE 了, 比如当我们的查询没有子查询, 也没有 UNION 查询时, 那么通常就是 SIMPLE 类型, 例如:
1 | mysql> explain select * from user_info where id = 2\G |
如果我们使用了 UNION 查询, 那么 EXPLAIN 输出 的结果类似如下:
1 | mysql> EXPLAIN (SELECT * FROM user_info WHERE id IN (1, 2, 3)) |
table
表示查询涉及的表或衍生表
type
type 字段比较重要, 它提供了判断查询是否高效的重要依据依据. 通过 type 字段, 我们判断此次查询是 全表扫描 还是 索引扫描 等.
type 常用类型
type 常用的取值有:
system: 表中只有一条数据. 这个类型是特殊的const类型.const: 针对主键或唯一索引的等值查询扫描, 最多只返回一行数据. const 查询速度非常快, 因为它仅仅读取一次即可. 例如下面的这个查询, 它使用了主键索引, 因此type就是const类型的.
1 | mysql> explain select * from user_info where id = 2\G |
eq_ref: 此类型通常出现在多表的 join 查询, 表示对于前表的每一个结果, 都只能匹配到后表的一行结果. 并且查询的比较操作通常是=, 查询效率较高. 例如:
1 | mysql> EXPLAIN SELECT * FROM user_info, order_info WHERE user_info.id = order_info.user_id\G |
ref: 此类型通常出现在多表的 join 查询, 针对于非唯一或非主键索引, 或者是使用了最左前缀规则索引的查询. 例如下面这个例子中, 就使用到了ref类型的查询:
1 | mysql> EXPLAIN SELECT * FROM user_info, order_info WHERE user_info.id = order_info.user_id AND order_info.user_id = 5\G |
range: 表示使用索引范围查询, 通过索引字段范围获取表中部分数据记录. 这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中. 当type是range时, 那么 EXPLAIN 输出的ref字段为 NULL, 并且key_len字段是此次查询中使用到的索引的最长的那个.
例如下面的例子就是一个范围查询:
1 | mysql> EXPLAIN SELECT * |
index: 表示全索引扫描(full index scan), 和 ALL 类型类似, 只不过 ALL 类型是全表扫描, 而 index 类型则仅仅扫描所有的索引, 而不扫描数据.index类型通常出现在: 所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据. 当是这种情况时, Extra 字段 会显示Using index.
例如:
1 | mysql> EXPLAIN SELECT name FROM user_info \G |
上面的例子中, 我们查询的 name 字段恰好是一个索引, 因此我们直接从索引中获取数据就可以满足查询的需求了, 而不需要查询表中的数据. 因此这样的情况下, type 的值是 index, 并且 Extra 的值是 Using index.
- ALL: 表示全表扫描, 这个类型的查询是性能最差的查询之一. 通常来说, 我们的查询不应该出现 ALL 类型的查询, 因为这样的查询在数据量大的情况下, 对数据库的性能是巨大的灾难. 如一个查询是 ALL 类型查询, 那么一般来说可以对相应的字段添加索引来避免. 下面是一个全表扫描的例子, 可以看到, 在全表扫描时, possible_keys 和 key 字段都是 NULL, 表示没有使用到索引, 并且 rows 十分巨大, 因此整个查询效率是十分低下的.
1 | mysql> EXPLAIN SELECT age FROM user_info WHERE age = 20 \G |
type 类型的性能比较
通常来说, 不同的 type 类型的性能关系如下:
ALL < index < range ~ index_merge < ref < eq_ref < const < system
ALL 类型因为是全表扫描, 因此在相同的查询条件下, 它是速度最慢的.
而 index 类型的查询虽然不是全表扫描, 但是它扫描了所有的索引, 因此比 ALL 类型的稍快.
后面的几种类型都是利用了索引来查询数据, 因此可以过滤部分或大部分数据, 因此查询效率就比较高了.
possible_keys
possible_keys 表示 MySQL 在查询时, 能够使用到的索引. 注意, 即使有些索引在 possible_keys 中出现, 但是并不表示此索引会真正地被 MySQL 使用到. MySQL 在查询时具体使用了哪些索引, 由 key 字段决定.
key
此字段是 MySQL 在当前查询时所真正使用到的索引.
key_len
表示查询优化器使用了索引的字节数. 这个字段可以评估组合索引是否完全被使用, 或只有最左部分字段被使用到. key_len 的计算规则如下:
- 字符串
- char(n): n 字节长度
- varchar(n): 如果是 utf8 编码, 则是 3 n + 2字节; 如果是 utf8mb4 编码, 则是 4 n + 2 字节.
- 数值类型:
- TINYINT: 1字节
- SMALLINT: 2字节
- MEDIUMINT: 3字节
- INT: 4字节
- BIGINT: 8字节
- 时间类型
- DATE: 3字节
- TIMESTAMP: 4字节
- DATETIME: 8字节
- 字段属性: NULL 属性 占用一个字节. 如果一个字段是 NOT NULL 的, 则没有此属性.
我们来举两个简单的栗子:
1 | mysql> EXPLAIN SELECT * FROM order_info WHERE user_id < 3 AND product_name = 'p1' AND productor = 'WHH' \G |
上面的例子是从表 order_info 中查询指定的内容, 而我们从此表的建表语句中可以知道, 表 order_info 有一个联合索引:
1 | KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`) |
不过此查询语句 WHERE user_id < 3 AND product_name = 'p1' AND productor = 'WHH' 中, 因为先进行 user_id 的范围查询, 而根据 最左前缀匹配 原则, 当遇到范围查询时, 就停止索引的匹配, 因此实际上我们使用到的索引的字段只有 user_id, 因此在 EXPLAIN 中, 显示的 key_len 为 9. 因为 user_id 字段是 BIGINT, 占用 8 字节, 而 NULL 属性占用一个字节, 因此总共是 9 个字节. 若我们将user_id 字段改为 BIGINT(20) NOT NULL DEFAULT '0', 则 key_length 应该是8.
上面因为 最左前缀匹配 原则, 我们的查询仅仅使用到了联合索引的 user_id 字段, 因此效率不算高.
接下来我们来看一下下一个例子:
1 | mysql> EXPLAIN SELECT * FROM order_info WHERE user_id = 1 AND product_name = 'p1' \G; |
这次的查询中, 我们没有使用到范围查询, key_len 的值为 161. 为什么呢? 因为我们的查询条件 WHERE user_id = 1 AND product_name = 'p1' 中, 仅仅使用到了联合索引中的前两个字段, 因此 keyLen(user_id) + keyLen(product_name) = 9 + 50 * 3 + 2 = 161
1 | 9 是 bigint 为 8 字节,默认 null 为 1 字节;50*3+2是因为 product_name 长度为 50 ,存储内容为 utf8,所以就是 50*3+2 |
rows
rows 也是一个重要的字段. MySQL 查询优化器根据统计信息, 估算 SQL 要查找到结果集需要扫描读取的数据行数. 这个值非常直观显示 SQL 的效率好坏, 原则上 rows 越少越好.
Extra
EXplain 中的很多额外的信息会在 Extra 字段显示, 常见的有以下几种内容:
- Using filesort
当 Extra 中有
Using filesort时, 表示 MySQL 需额外的排序操作, 不能通过索引顺序达到排序效果. 一般有Using filesort, 都建议优化去掉, 因为这样的查询 CPU 资源消耗大.
例如下面的例子:
1 | mysql> EXPLAIN SELECT * FROM order_info ORDER BY product_name \G |
我们的索引是
1 | KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`) |
但是上面的查询中根据 product_name 来排序, 因此不能使用索引进行优化, 进而会产生 Using filesort.
如果我们将排序依据改为 ORDER BY user_id, product_name, 那么就不会出现 Using filesort 了. 例如:
1 | mysql> EXPLAIN SELECT * FROM order_info ORDER BY user_id, product_name \G |
- Using index “覆盖索引扫描”, 表示查询在索引树中就可查找所需数据, 不用扫描表数据文件, 往往说明性能不错
- Using temporary 查询有使用临时表, 一般出现于排序, 分组和多表 join 的情况, 查询效率不高, 建议优化.
转载至:https://segmentfault.com/a/1190000008131735
获取命名连接的执行计划信息
要获取在命名连接中执行的可解释语句的执行计划,请使用以下语句:
1 | EXPLAIN [options] FOR CONNECTION connection_id; |
EXPLAIN FOR CONNECTION返回EXPLAIN当前用于在给定连接中执行查询的信息。由于数据(和支持统计)的更改,它可能会产生与EXPLAIN在等效查询文本上运行不同的结果 。这种行为差异可用于诊断更多瞬态性能问题。例如,如果您在一个会话中运行需要很长时间才能完成的语句,则EXPLAIN FOR CONNECTION在另一个会话中使用可能会产生有关延迟原因的有用信息。
*connection_id*是从INFORMATION_SCHEMA PROCESSLIST表或 SHOW PROCESSLIST语句中获得的连接标识符 。如果您有PROCESS权限,您可以为任何连接指定标识符。否则,您只能为自己的连接指定标识符。在所有情况下,您都必须有足够的权限来解释对指定连接的查询。
如果命名连接没有执行语句,则结果为空。否则,EXPLAIN FOR CONNECTION 仅当在命名连接中执行的语句是可解释的时才适用。这包括 SELECT, DELETE, INSERT, REPLACE,和 UPDATE。(但是, EXPLAIN FOR CONNECTION不适用于准备好的语句,即使是这些类型的准备好的语句。)
如果命名连接正在执行可解释语句,则输出是您将通过EXPLAIN在语句本身上使用获得的内容 。
如果命名连接正在执行无法解释的语句,则会发生错误。例如,您不能为当前会话命名连接标识符,因为 EXPLAIN无法解释:
1 | mysql> SELECT CONNECTION_ID(); |
该Com_explain_other状态变量表示的数 EXPLAIN FOR CONNECTION执行的语句。
估计查询性能
在大多数情况下,您可以通过计算磁盘寻道来估计查询性能。对于小表,通常可以在一次磁盘查找中找到一行(因为索引可能已缓存)。对于较大的表,您可以估计,使用 B 树索引,您需要多次查找才能找到一行: 。 log(*row_count*) / log(*index_block_length* / 3 * 2 / (*index_length* + *data_pointer_length*)) + 1
在 MySQL 中,一个索引块通常为 1,024 字节,数据指针通常为 4 个字节。对于键值长度为三个字节(大小为MEDIUMINT)的 500,000 行表 ,公式表示 log(500,000)/log(1024/3*2/(3+4)) + 1= 搜索 4。
该索引需要大约 500,000 * 7 * 3/2 = 5.2MB 的存储空间(假设典型的索引缓冲区填充率为 2/3),因此您可能在内存中拥有大量索引,因此只需要一两次调用读取数据以查找行。
但是,对于写入,您需要四个搜索请求来查找放置新索引值的位置,通常需要两个搜索请求来更新索引和写入行。
前面的讨论并不意味着您的应用程序性能会随着 log 缓慢退化 *N。只要所有内容都被操作系统或 MySQL 服务器缓存,随着表变大,事情只会变得稍微慢一点。在数据变得太大而无法缓存后,事情开始变慢,直到您的应用程序仅受磁盘搜索(随着 log 增加N*)的约束 。为避免这种情况,请随着数据的增长增加密钥缓存大小。对于MyISAM 表,键缓存大小由key_buffer_size系统变量控制 。
2. 控制查询计划评估
查询优化器的任务是找到执行 SQL 查询的最佳计划。因为“好”和“坏”之间的性能差异计划可以是数量级的(即,几秒与几小时甚至几天),大多数查询优化器,包括 MySQL 的优化器,在所有可能的查询评估计划中执行或多或少的穷举搜索最佳计划。对于连接查询,MySQL 优化器调查的可能计划的数量随着查询中引用的表的数量呈指数增长。对于少量表(通常少于 7 到 10 个),这不是问题。但是,当提交较大的查询时,查询优化所花费的时间很容易成为服务器性能的主要瓶颈。
一种更灵活的查询优化方法使用户能够控制优化器在搜索最佳查询评估计划时的详尽程度。一般的想法是优化器调查的计划越少,编译查询所花费的时间就越少。另一方面,由于优化器跳过了一些计划,它可能会错过找到最佳计划。
优化器相对于它评估的计划数量的行为可以使用两个系统变量来控制:
- 该
optimizer_prune_level变量告诉优化器根据对每个表访问的行数的估计跳过某些计划。我们的经验表明,这种“有根据的猜测”很少会错过最佳计划,并且可能会大大减少查询编译时间。这就是optimizer_prune_level=1默认情况下此选项启用 ( ) 的原因。但是,如果您认为优化器错过了更好的查询计划,则可以关闭此选项(optimizer_prune_level=0) 的风险是查询编译可能需要更长的时间。请注意,即使使用这种启发式方法,优化器仍会探索大致呈指数级数量的计划。 - 该
optimizer_search_depth变量告诉多远到“未来”的优化应该是每个不完整的计划,以评估是否应进一步扩大。的较小值optimizer_search_depth可能会导致查询编译时间缩短几个数量级。例如,具有 12、13 或更多表optimizer_search_depth的查询如果接近查询中的表数,则可能很容易需要数小时甚至数天的时间来编译 。同时,如果编译optimizer_search_depth等于 3 或 4,优化器可以在不到一分钟的时间内为同一查询编译。如果您不确定 的合理值是多少optimizer_search_depth,可以将此变量设置为 0 以告诉优化器自动确定该值。
3. 可切换优化
该optimizer_switch系统变量能够在优化行为的控制。它的值是一组标志,每个标志的值为on 或off以指示相应的优化器行为是启用还是禁用。此变量具有全局和会话值,可以在运行时更改。可以在服务器启动时设置全局默认值。
要查看当前的优化器标志集,请选择变量值:
1 | mysql> SELECT @@optimizer_switch\G |
要更改 的值 optimizer_switch,请分配一个值,该值由一个或多个命令的逗号分隔列表组成:
1 | SET [GLOBAL|SESSION] optimizer_switch='command[,command]...'; |
每个*command*值都应具有下表中显示的形式之一。
| 命令语法 | 意义 |
|---|---|
default |
将每个优化重置为其默认值 |
*opt_name*=default |
将命名优化设置为其默认值 |
*opt_name*=off |
禁用命名优化 |
*opt_name*=on |
启用命名优化 |
详细命令介绍可以参考官方文档:https://dev.mysql.com/doc/refman/8.0/en/switchable-optimizations.html
4. 优化器提示
控制优化器策略的一种方法是设置 optimizer_switch系统变量(请参阅第 8.9.2 节,“可切换优化”)。对此变量的更改会影响所有后续查询的执行;为了以不同的方式影响一个查询,有必要optimizer_switch在每个查询之前进行更改 。
控制优化器的另一种方法是使用优化器提示,可以在单个语句中指定。由于优化器提示适用于每个语句,因此它们对语句执行计划的控制比使用 optimizer_switch. 例如,您可以对语句中的一个表启用优化并禁用对不同表的优化。
优化器提示概述
优化器提示适用于不同的范围级别:
- 全局:提示影响整个语句
- 查询块:提示影响语句中的特定查询块
- 表级:提示影响查询块中的特定表
- 索引级别:提示影响表中的特定索引
下表总结了可用的优化器提示、它们影响的优化器策略以及它们适用的范围。更多细节将在后面给出。
可用的优化器提示
| 提示名称 | 说明 | 适用范围 |
|---|---|---|
BKA, NO_BKA |
影响批处理密钥访问加入处理 | 查询块、表 |
BNL, NO_BNL |
MySQL 8.0.20之前:影响Block Nested-Loop join处理;MySQL 8.0.18 及更高版本:也会影响散列连接优化;MySQL 8.0.20 及更高版本:仅影响散列连接优化 | 查询块、表 |
DERIVED_CONDITION_PUSHDOWN, NO_DERIVED_CONDITION_PUSHDOWN |
使用或忽略物化派生表的派生条件下推优化(MySQL 8.0.22 新增) | 查询块、表 |
GROUP_INDEX, NO_GROUP_INDEX |
在GROUP BY操作中使用或忽略指定的索引或索引进行索引扫描 (MySQL 8.0.20 中添加) |
索引 |
HASH_JOIN, NO_HASH_JOIN |
影响 Hash Join 优化(仅限 MySQL 8.0.18 | 查询块、表 |
INDEX, NO_INDEX |
充当JOIN_INDEX, GROUP_INDEX, and ORDER_INDEX的组合,或充当NO_JOIN_INDEX, NO_GROUP_INDEX, and 的组合 NO_ORDER_INDEX(MySQL 8.0.20 新增) |
索引 |
INDEX_MERGE, NO_INDEX_MERGE |
影响索引合并优化 | 表、索引 |
JOIN_FIXED_ORDER |
将FROM子句中指定的表顺序用于连接顺序 |
查询块 |
JOIN_INDEX, NO_JOIN_INDEX |
对任何访问方法使用或忽略指定的一个或多个索引(在 MySQL 8.0.20 中添加) | 索引 |
JOIN_ORDER |
使用提示中指定的表顺序进行连接顺序 | 查询块 |
JOIN_PREFIX |
对连接顺序的第一个表使用提示中指定的表顺序 | 查询块 |
JOIN_SUFFIX |
将提示中指定的表顺序用于连接顺序的最后一个表 | 查询块 |
MAX_EXECUTION_TIME |
限制语句执行时间 | 全球 |
MERGE, NO_MERGE |
影响合并到外部查询块的派生表/视图 | 桌子 |
MRR, NO_MRR |
影响多范围读取优化 | 表、索引 |
NO_ICP |
影响索引条件下推优化 | 表、索引 |
NO_RANGE_OPTIMIZATION |
影响范围优化 | 表、索引 |
ORDER_INDEX, NO_ORDER_INDEX |
使用或忽略指定的索引或索引对行进行排序(MySQL 8.0.20 中添加) | 索引 |
QB_NAME |
为查询块分配名称 | 查询块 |
RESOURCE_GROUP |
在语句执行期间设置资源组 | 全球 |
SEMIJOIN, NO_SEMIJOIN |
影响半连接策略;从 MySQL 8.0.17 开始,这也适用于反连接 | 查询块 |
SKIP_SCAN, NO_SKIP_SCAN |
影响跳过扫描优化 | 表、索引 |
SET_VAR |
在语句执行期间设置变量 | 全球 |
SUBQUERY |
影响物化, IN到EXISTS 子查询的策略 |
查询块 |
优化器提示语法
MySQL 支持 SQL 语句中的注释,如 第 9.7 节“注释”中所述。优化器提示必须在/*+ ... */注释中指定。也就是说,优化器提示使用/* ... */ C 风格注释语法的变体,+在/*注释开始序列后面有一个字符。例子:
1 | /*+ BKA(t1) */ |
+ 字符 后允许有空格。
解析器承认优化的初始关键字后暗示的意见SELECT, UPDATE, INSERT, REPLACE,和 DELETE语句。在这些上下文中允许使用提示:
在查询和数据更改语句的开头:
1
2
3
4
5SELECT /*+ ... */ ...
INSERT /*+ ... */ ...
REPLACE /*+ ... */ ...
UPDATE /*+ ... */ ...
DELETE /*+ ... */ ...在查询块的开头:
1
2
3
4
5(SELECT /*+ ... */ ... )
(SELECT ... ) UNION (SELECT /*+ ... */ ... )
(SELECT /*+ ... */ ... ) UNION (SELECT /*+ ... */ ... )
UPDATE ... WHERE x IN (SELECT /*+ ... */ ...)
INSERT ... SELECT /*+ ... */ ...在以
EXPLAIN. 例如:1
2EXPLAIN SELECT /*+ ... */ ...
EXPLAIN UPDATE ... WHERE x IN (SELECT /*+ ... */ ...)
这意味着您可以使用它 EXPLAIN来查看优化器提示如何影响执行计划。使用 SHOW WARNINGS后立即EXPLAIN查看如何使用提示。EXPLAIN 以下SHOW WARNINGS显示的扩展输出指示使用了哪些提示。不显示被忽略的提示。
一个提示注释可以包含多个提示,但一个查询块不能包含多个提示注释。这是有效的:
1 | SELECT /*+ BNL(t1) BKA(t2) */ ... |
但这是无效的:
1 | SELECT /*+ BNL(t1) */ /* BKA(t2) */ ... |
当一个提示注释包含多个提示时,存在重复和冲突的可能性。以下一般准则适用。对于特定的提示类型,可能会应用其他规则,如提示说明中所示。
- 重复提示:对于诸如 的提示
/*+ MRR(idx1) MRR(idx1) */,MySQL 使用第一个提示并发出有关重复提示的警告。 - 冲突提示:对于诸如 的提示
/*+ MRR(idx1) NO_MRR(idx1) */,MySQL 使用第一个提示并发出有关第二个冲突提示的警告。
查询块名称是标识符,并遵循有关哪些名称有效以及如何引用它们的通常规则。
提示名称、查询块名称和策略名称不区分大小写。
表级优化器提示
表级提示影响:
- 使用块嵌套循环 (BNL) 和批量密钥访问 (BKA) 连接处理算法。
- 派生表、视图引用或公用表表达式应该合并到外部查询块中,还是使用内部临时表具体化。
- 使用派生表条件下推优化(在 MySQL 8.0.22 中添加)。
这些提示类型适用于特定表或查询块中的所有表。
查询优化器的提示补充
如果对优化器选择的执行计划不满意,可以使用优化器提供的几个提示来控制最终的执行计划,关于每个提示的具体用法,建议直接阅读官方手册,一些提示和版本有直接关系,可以使用的一些提示如下:
high_priority和low_priority:
这个提示告诉mysql,当多个语句同时访问某一个表的时候,哪些语句的优先级相对高一些,哪些语句的优先级相对低一些。
high_priority用于select语句的时候,mysql会将其放到表的队列的最前面,而不是按照常规顺序等待,high_priority还可以用于insert语句,其效果只是简单地抵消了全局low_priority设置对该语句的影响。
low_priority则更好相反,它会让该语句一直处于等待状态,只要队列中还有需要访问同一个表的语句,即使是后到的请求也会插到该语句的前面去。很明显,容易把自己给饿死,low_priority提示在select,insert,update和delete语句中都可以使用。
注意:这两个提示只对使用表锁的存储引擎有效,千万不要在innodb或者其他有细粒度锁机制和并发控制的引擎中使用,即使是在myisam中使用也要注意,因为这两个提示会导致并发插入被禁用,可能会严重降低性能。这两个提示只是简单地控制了mysql访问某个数据表的队列顺序,仅此而已。
delayed:
这个提示对insert和replace有效,mysql会将使用该提示的语句立即返回给客户端,并将插入的行数据当如到缓冲区,然后在表空闲时批量将数据写入,日志系统使用这样的提示非常有效,或者是其他需要写入大量数据但是客户端却不需要等待单挑语句完成的IO应用。但这个用法有一些喜爱你知,并不是所有的存储引擎都支持,并且该提示会导致函数last_insert_id()无法正常工作。
straight_join:
这个提示可放置在select关键字之后,也可以放置在任何两个关联表的表名之前,第一个用法是让查询中所有的表按照在语句中出现的顺序进行关联,第二个用法则是固定其前后两个表的关联顺序。当mysql没正确选择关联顺序的时候,或者由于可能的顺序太多导致mysql无法评估所有的关联顺序的时候,straight_join都会很有用,如果关联表可能的顺序太多,可能导致mysql花费大量时间在statistics状态。可以使用explain语句来查看关联顺序,然后加上这个提示再用explain查看有没有变化。
sql_small_result和sql_big_result:
这两个提示只对select有效,他们告诉优化器对group by或者distinct查询如何使用临时表及其排序,sql_small_result告诉优化器结果集会很小,可以将结果集放在内存的索引临时表,以避免排序操作,如果是sql_big_result,则告诉优化器结果集可能非常大,建议使用磁盘临时表做排序操作。
sql_cache和sql_no_cache:
这个提示告诉mysql这个结果集是否应该缓存在查询缓存中,如:select sql_cache|sql_no_cache * from tb_name,紧跟在select关键字后面。
sql_calc_found_rows:
严格来说,这个并不是一个优化器提示,它不会告诉优化器任何关于执行计划的东西,它会让mysql返回的结果集包含更多的相关信息,查询中加上该提示mysql会计算除去limit子句后这个查询要返回的结果集的总数。而实际上只返回limit要求的结果集,结果集总数可以通过found_row()获得这个值。一般不要使用这个提示。
for update和lock in share mode:
这也不是真正的优化器提示,这两个提示主要控制select语句的锁机制,但只对实现了行级锁的引擎有效,使用该提示对符合查询条件的数据行加锁,对于insert .. select语句是不需要这两个提示的,因为对于mysql5.0和更新的版本会默认给这些记录加上读锁。内置的支持这两个提示的引擎就是innodb,另外需要记住的是,这两个提示会让某些优化无法正常使用,如:索引覆盖扫描,innodb不能在不访问主键的情况下排他地锁定行,因为行的版本信息保存在主键中。这两个提示经常被开发滥用,很容易造成服务器的锁争用问题,应该尽可能地避免使用这两个提示。通常可以使用其他更好的方式来实现同样的目的。
use index,ignore index和force index:
这几个提示告诉优化器使用或者不使用或者强制使用哪些所以来查询记录。在mysql5.0和更早的版本中,这些提示并不会影响到优化器选择那个索引进行排序和分组,在5.1和之后
的版本可以通过新增选项for order by和for group by来指定是否对排序和分组有效。force index和use index基本相同,除了一点,force index会告诉优化器全表扫描的成本远远高于索引扫描。哪怕实际上该索引用处不大。优化器也会使用force index指定的索引。
在mysql5.0和更新的版本中,新增了一些参数来控制优化器的行为:
optimizer_search_depth:
这个参数控制优化器在穷举执行计划时的限度,如果查询长时间处于statistics状态,那么可以考虑调低此参数的值,默认为62,即,这个也控制了最大关联表数量。
optimizer_prune_level:
该参数默认是打开的,为1,这让优化器会根据需要扫描的行数来决定是否跳过某些执行计划
optimizer_switch:
这个变量包含了一些开启/关闭优化器特性的标志位,如:mysql5.1中可以通过这个参数来控制禁用索引合并的特性。
5.5默认为:
index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on
5.6默认为:
index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,subquery_materialization_cost_based=on,use_index_extensions=on
这三个参数,前两个参数是用来控制优化器可以走一些捷径的,这些捷径可以让优化器在处理非常复杂的SQL语句时,仍然可以很高效,但这也可能让优化器错过一些真正最优的执行计划,所以应该根据实际需要来修改这些参数。
要注意:在优化器面前耍小聪明是不好的,因为这样做不但收效甚微,而且给后期维护带来了很多额外的工作量,在mysql版本升级的时候,这个问题就很突出了,你设置的优化器提示很可能会让新版的优化器的优化策略失效。除非特殊需要,否则不要使用这些提示来改变默认的执行计划。mysql5.5和5.6在各方面都有非常大的改进,一般来说升级都很顺利,但仍然建议检查各个细节,可以使用percona toolkit中的pt-upgrade工具来检查在新版本中运行的SQL是否与老版本一样,返回相同的结果。
版权声明:本文为CSDN博主「weixin_40002009」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_40002009/article/details/114331551
索引提示
索引提示向优化器提供有关如何在查询处理期间选择索引的信息。索引和优化器提示可以单独使用或一起使用。
该提示告诉 MySQL 仅使用命名索引之一来查找表中的行。替代语法告诉 MySQL 不要使用某些特定的索引或索引。如果显示 MySQL 使用了可能索引列表中的错误索引,这些提示很有用。 USE INDEX (*index_list*)``IGNORE INDEX (*index_list*)EXPLAIN
该FORCE INDEX提示的作用就像,增加表扫描被认为是 非常昂贵的。换句话说,仅当无法使用命名索引之一来查找表中的行时才使用表扫描。 USE INDEX (*index_list*)
每个提示都需要索引名称,而不是列名称。要引用主键,请使用 name PRIMARY。要查看表的索引名称,请使用SHOW INDEX语句或 INFORMATION_SCHEMA.STATISTICS 表。
的*index_name*价值不一定是完整的索引名。它可以是索引名称的明确前缀。如果前缀不明确,则会发生错误。
例子:
1 | SELECT * FROM table1 USE INDEX (col1_index,col2_index) |
MySql共有三种索引提示,分别是:USE INDEX、IGNORE INDEX和FORCE INDEX,他们之间的区别是:
use index:use index告诉MySql用列表中的其中一个索引去做本次查询,例子:
1 | SELECT * FROM table1 USE INDEX (col1_index,col2_index) |
ignore index:ignore index告诉mysql不要使用某些索引去做本次查询,例子:
1 | SELECT * FROM table1 IGNORE INDEX (col3_index) |
force index:force index和use index功能类似,都是告诉mySQL去使用某些索引。force index和use index的区别是,如果使用force index,那么全表扫描就会被假定为需要很高代价,除非不能使用索引,否则不会考虑全表扫描;而使用use index的话,如果MySql觉得全表扫描代价更低的话,仍然会使用全表扫描。例子:
1 | SELECT * FROM table1 FORCE INDEX (col3_index) |
索引提示的用途 可以在索引提示的后边使用FOR语句指定提示的范围,索引提示共有三种适用范围,分别是FOR JOIN、FOR ORDER BY、FOR GROUP BY:
FOR JOIN:索引提示用于查找行或者用于表的连接。 FOR ORDER BY:索引提示用于排序。 FOR GROUP BY:索引提示用于分组。 此外,需要注意的是,如果在mySQL 5.0版本及以下,如果不指定FOR语句,那么mySQL只会用它来查找行;而在新版本的mySQL,如果不指定FOR语句,那么mySQL会把索引用于所有用途。 几个例子:
1 | SELECT * FROM t1 USE INDEX (i1) IGNORE INDEX FOR ORDER BY (i2) |
原文链接:https://blog.csdn.net/zhang3361999/article/details/104252518
5. 优化器成本模型
为了生成执行计划,优化器使用成本模型,该模型基于对查询执行期间发生的各种操作的成本的估计。优化器有一组内置的默认“成本常量”,可用于制定有关执行计划的决策。
优化器还有一个成本估算数据库,可在执行计划构建期间使用。这些估计值存储在系统数据库的server_cost和 engine_cost表中, mysql并且可以随时进行配置。这些表的目的是可以轻松调整优化器在尝试获得查询执行计划时使用的成本估计。
可配置优化器成本模型的工作方式如下:
- 服务器在启动时将成本模型表读入内存,并在运行时使用内存中的值。
NULL表中指定的任何非成本估算优先于相应的编入默认成本常数。任何NULL估计都向优化器指示使用编译的默认值。 - 在运行时,服务器可能会重新读取成本表。这发生在动态加载存储引擎或
FLUSH OPTIMIZER_COSTS执行语句时。 - 成本表使服务器管理员能够通过更改表中的条目轻松调整成本估算。通过将条目的成本设置为 ,也可以轻松恢复为默认值
NULL。优化器使用内存中的成本值,因此对表的更改应紧随其后FLUSH OPTIMIZER_COSTS才能生效。 - 客户端会话开始时当前的内存中成本估算适用于整个会话,直到结束。特别是,如果服务器重新读取成本表,任何更改的估计仅适用于随后启动的会话。现有会话不受影响。
- 成本表特定于给定的服务器实例。服务器不会将成本表更改复制到副本。
成本模型数据库
优化器成本模型数据库由mysql系统数据库中的两个表组成,其中包含查询执行期间发生的操作的成本估算信息:
server_cost:一般服务器操作的优化器成本估算engine_cost:特定存储引擎特定操作的优化器成本估算
该server_cost表包含以下列:
cost_name成本模型中使用的成本估算的名称。该名称不区分大小写。如果服务器在读取此表时无法识别成本名称,则会将警告写入错误日志。
cost_value成本估算值。如果该值为 non-
NULL,则服务器将其用作成本。否则,它使用默认估计值(编译值)。DBA 可以通过更新此列来更改成本估算。如果服务器在读取此表时发现成本值无效(非正),则会在错误日志中写入警告。要覆盖默认成本估算(对于指定 的条目
NULL),请将成本设置为非NULL值。要恢复为默认值,请将值设置为NULL。然后执行FLUSH OPTIMIZER_COSTS告诉服务器重新读取成本表。last_update最后一行更新的时间。
comment与成本估算相关的描述性注释。DBA 可以使用此列提供有关成本估算行为何存储特定值的信息。
default_value成本估算的默认(编译)值。此列是只读生成的列,即使关联的成本估算发生更改,它也会保留其值。对于在运行时添加到表中的行,此列的值为
NULL。
server_cost表 的主键是cost_name列,因此不可能为任何成本估算创建多个条目。
服务器识别表的这些cost_name 值server_cost:
disk_temptable_create_cost,disk_temptable_row_cost存储在基于磁盘的存储引擎(
InnoDB或MyISAM)中的内部创建的临时表的成本估算 。增加这些值会增加使用内部临时表的成本估计,并使优化器更喜欢使用较少的查询计划。有关此类表的信息,请参阅 第 8.4.4 节,“MySQL 中的内部临时表使用”。与相应内存参数 (
memory_temptable_create_cost,memory_temptable_row_cost)的默认值相比,这些磁盘参数的默认值越大,处理基于磁盘的表的成本越高。key_compare_cost比较记录键的成本。增加此值会导致比较多个键的查询计划变得更加昂贵。例如,
filesort与避免使用索引进行排序的查询计划相比,执行 a 的查询计划 变得相对昂贵。memory_temptable_create_cost,memory_temptable_row_costMEMORY存储引擎中 存储的内部创建的临时表的成本估算。增加这些值会增加使用内部临时表的成本估计,并使优化器更喜欢使用较少的查询计划。有关此类表的信息,请参阅 第 8.4.4 节,“MySQL 中的内部临时表使用”。与相应磁盘参数 (
disk_temptable_create_cost,disk_temptable_row_cost)的默认值相比,这些内存参数的默认值较小反映了处理基于内存的表的成本较低。row_evaluate_cost评估记录条件的成本。与检查较少行的查询计划相比,增加此值会导致检查多行的查询计划成本更高。例如,与读取较少行的范围扫描相比,表扫描变得相对昂贵。
该engine_cost表包含以下列:
engine_name此成本估算适用的存储引擎的名称。该名称不区分大小写。如果值为
default,则它适用于所有没有自己命名条目的存储引擎。如果服务器在读取此表时无法识别引擎名称,则会将警告写入错误日志。device_type此成本估算适用的设备类型。该列旨在为不同的存储设备类型指定不同的成本估算,例如硬盘驱动器与固态驱动器。目前,不使用此信息,0 是唯一允许的值。
cost_name与
server_cost表中相同。cost_value与
server_cost表中相同。last_update与
server_cost表中相同。comment与
server_cost表中相同。default_value成本估算的默认(编译)值。此列是只读生成的列,即使关联的成本估算发生更改,它也会保留其值。对于在运行时添加到表中的行,此列的值为
NULL,但如果该行的cost_name值与原始行之一相同,则该default_value列的值与该行的值相同。
engine_cost表 的主键是一个包含 ( cost_name, engine_name, device_type) 列的元组,因此不可能为这些列中的任何值组合创建多个条目。
服务器识别表的这些cost_name 值engine_cost:
io_block_read_cost从磁盘读取索引或数据块的成本。与读取较少磁盘块的查询计划相比,增加此值会导致读取许多磁盘块的查询计划成本更高。例如,与读取较少块的范围扫描相比,表扫描变得相对昂贵。
memory_block_read_cost类似于
io_block_read_cost,但表示从内存数据库缓冲区读取索引或数据块的成本。
如果io_block_read_cost和 memory_block_read_cost值不同,则执行计划可能会在同一查询的两次运行之间发生变化。假设内存访问的成本小于磁盘访问的成本。在这种情况下,在数据被读入缓冲池之前的服务器启动时,您可能会得到与查询运行后不同的计划,因为数据在内存中。
更改成本模型数据库
对于希望从默认值更改成本模型参数的 DBA,尝试将值加倍或减半并测量效果。
对io_block_read_cost和 memory_block_read_cost参数的更改最有可能产生有价值的结果。这些参数值使据访问方法的成本模型能够考虑从不同来源读取信息的成本;也就是说,从磁盘读取信息与读取内存缓冲区中已有信息的成本。例如,在所有其他条件相同的情况下,设置 io_block_read_cost为大于的值 memory_block_read_cost会导致优化器更喜欢读取已保存在内存中的信息的查询计划,而不是必须从磁盘读取的计划。
此示例显示如何更改 的默认值 io_block_read_cost:
1 | UPDATE mysql.engine_cost |
此示例显示如何更改io_block_read_cost仅用于 InnoDB存储引擎的值 :
1 | INSERT INTO mysql.engine_cost |
6. 优化器统计
该column_statistics数据字典表存储直方图统计有关列值,以供在构建查询执行计划的优化。要执行直方图管理,请使用该ANALYZE TABLE语句。
该column_statistics表具有以下特征:
- 该表包含除几何类型(空间数据)和
JSON. - 该表是持久的,因此不需要在每次服务器启动时创建列统计信息。
- 服务器对表执行更新;用户没有。
column_statistics用户不能直接访问 该表,因为它是数据字典的一部分。直方图信息可使用 获得 INFORMATION_SCHEMA.COLUMN_STATISTICS,它是作为数据字典表上的视图实现的。 COLUMN_STATISTICS有这些列:
SCHEMA_NAME,TABLE_NAME,COLUMN_NAME:统计信息适用的架构、表和列的名称。HISTOGRAM:JSON描述列统计信息的值,存储为直方图。
列直方图包含存储在列中的值范围的一部分的桶。直方图是 JSON允许灵活地表示列统计信息的对象。这是一个示例直方图对象:
1 | { |
一文读懂MySQL 8.0直方图
0. 什么是直方图
直方图(Histogram)是数据库提供的一种(索引之外的)基础统计信息,用于描述列上的数据分布情况。它最典型的场景是估算查询谓词的选择率,以便选择合适的执行计划。
也就是说,某个列可以不创建索引但创建直方图,也可以帮助提升查询效率。 MySQL 8.0开始支持直方图,这是个很大的进步。
直方图可以针对某个列记录其数据分布统计信息,例如有个列的值是从1到1万,那么可以利用直方图分成100个桶(bucket),每个桶中统计这1万个值是怎么分布的,以及每个桶中的最大值、最小值、占比等信息。
虽然可以利用索引优化SQL效率,但索引维护的代价更高,索引要保持更新,而直方图可以按需手动更新。
索引统计信息也有不可靠的时候,例如存在数据倾斜,或者统计延迟等问题。
另外,在有需要的时候,可以在每个有需要的列上创建直方图,但却不太可能同时创建多个单列索引,那样代价太高了。
例如下面这个执行计划:
1 | [root@yejr.run]> explain select * from t1 where seq = 1234; |
在还没创建直方图之前,seq列上同时也没有索引,这时是全表扫描,注意到 filtered 列的值是10%。
创建完直方图之后,再看这个执行计划:
1 | [root@yejr.run]> explain select * from t1 where seq = 1234; |
注意到 filtered 列值变成了 0%,并且实际耗时是原来的81%,虽然绝对值也不算小,但相对于原来的全表扫描也还是要节省了将近20%耗时。
所以说,直方图还是很有意义的,当然了,直方图还是无法代替索引,只在一些特定的场景里比较有用。
1. 直方图怎么工作
MySQL支持两种直方图模式:等宽、等高。等宽直方图是每个桶保存一个值以及这个值累积频率,等高直方图每个桶需要保存不同值的个数,上下限以及累积频率等。MySQL会自动选用哪种类型的直方图,无需也不能指定。一般来说,数据数据分布范围比较大的话就采用等高,反之,如果数据分布比较小就采用等宽。
直方图的统计信息物理表 column_statistics 存储在mysql表空间中,无法直接读写,但可以访问 information_schema.COLUMN_STATISTICS 视图来查看统计结果。
1 | [root@yejr.run]> show create view column_statistics\G |
每条记录对应一个直方图统计结果,用JSON格式保存。
此外,还有个参数 histogram_generation_max_mem_size 用来控制创建直方图时可用的内存,该参数很重要,后面会讲到。
截止MySQL 8.0.19版本,直方图支持多种数据类型和场景,甚至包括虚拟列。但不支持以下几种情况:
- 加密表、临时表。
- JSON数据类型、空间(spatial)数据类型。
- 已创建唯一索引的单列。
来个一个创建失败的例子:
1 | [root@yejr.run]> analyze table t2 update histogram on nu; |
MySQL干脆利落地拒绝了这种反智行为。
当然了,如果是一个列创建了非唯一辅助索引,就可以创建直方图,不会冲突。
来创建个正式直方图:
1 | +---------+-----------+----------+------------------------------------------------+ |
再看下 COLUMN_STATISTICS 中存储的统计信息:
1 | [root@yejr.run]> select SCHEMA_NAME, TABLE_NAME, COLUMN_NAME, JSON_PRETTY(HISTOGRAM) from COLUMN_STATISTICS\G |
上面这个等高直方图,共100个桶,每个桶的数据量从2571 ~ 2620不等,总数据量259550,占比99.9667%。此外,还有大约0.033%为NULL的记录。
再来个等宽的直方图
1 | "buckets": [ |
等宽直方图里,每个桶里记录是各个值的分布信息。
2. 同时有索引和直方图会怎样
某天,群里有同学在问,假如有个列同时创建了直方图和索引,优化器会怎么选择呢?
在回答之前,可以先开通脑筋想几秒钟…
事实上,真是这样的话,优化器会选择索引而非直方图。因为索引的统计信息相对”更及时”,也可能”更精确”,因为直方图是需要手动更新的,没办法保证”及时性”。当然了,我没去看源码,仅是我猜的,并通过试验确认的。
假设上面创建直方图的列 seq,同时也创建了索引,在开启 optimizer_trace 之后,可以看到两个执行计划之间的区别(我只选取了部分内容)
- 无索引时,走直方图
1 | "considered_execution_plans": [ |
虽然看起来是要走全表扫描,但因为有了直方图,实际上还是很快就能返回结果的。
- 有索引时,优先走索引
1 | "considered_execution_plans": [ |
如果有源码大佬,也请帮忙确认下是不是这样吧。
3. 如何提高直方图的统计精确度
前文我们提到过参数 histogram_generation_max_mem_size,其作用是控制在创建/更新直方图时所需的内存大小。
1 | The maximum amount of memory available for generating histogram statistics. |
该参数默认值是 20000000(不到20MB),最小值 1000000(约976KB),这是个会话级(session)分配的内存,而且是每次创建/更新直方图都需要分配,执行结束后就释放。
介绍完前置信息,该说重点了,在直方图里如何提高统计精确度。
在扫描InnoDB data page进行直方图数据统计时,大致是这样的步骤:
- 估算要统计的列数据类型长度,记为 row_size_bytes。
- 可用内存除以每条记录长度,得到预计可以采样的数据量 rows_in_memory = histogram_generation_max_mem_size / row_size_bytes。
- 计算得到采样比例 sample_percentage = rows_in_memory / rows_in_table。其中 rows_in_table 是表预估总记录数。
- 依照采样比例,扫描data page,得到采样结果。例如,采样比例是10%,那么就是扫描1个page后,跳过9个page,然后继续采样。 这几个步骤是以我的三脚猫源码阅读水平得到的结果,若有出入,还请留言指正。 上述步骤所对应的代码是
sql/histograms/histogram.cc,约868行附近的update_histogram函数。
MySQL目前对数据长度处理的非常粗粒度,只区分了下面几种情况,这就导致了直方图列实际所需要的内存可能要比它定义的类型长度要更大,也可以看下源码中的定义:
1 | vim sql/histograms/histogram.cc +113 |
可以看到,把TINYINT(1字节)、SMALLINT(2字节)等都统统按 Value_map_type::INT 来处理了,而这个类型实际上是 longlong 的,也就是 16字节。
另外,每条记录还需要约42字节的额外开销(比大多数据类型长度还要大,尴尬)。
1 | vim sql/histograms/value_map.h +262 |
其他的几个数据类型也是很粗犷的处理了,在以后的版本应该会改进吧。
如果 histogram_generation_max_mem_size 不够大,则采样比例比较低,就会影响准确度,那么应该设置多少合理呢,可以按照下面这个方法:
- 先设置最小值。
- 执行一次采样。
- 查看采样比例。
- 反推出要全部采样所需的内存。 当然了,如果表数据量特别大,也没必要全部采样,毕竟消耗的内存比较多,而且也需要更多的物理读。 来个实例演示。
1 | # 设置 histogram_generation_max_mem_size 为最小值 |
与此同时,我在另一个session中,分别在之前和之后查询上面创建直方图的线程内存消耗情况。
1 | # 第一次查询 |
两次查询 total_allocated 的差值 1235412(1.17MB) 主要就是由于创建直方图所需的内存。
现在,可以推算所需要的内存大约是
1 | [root@yejr.run]> select 1235412 / 0.059548422871929935; |
现在加大内存设置后,再做一次看看
1 | [root@yejr.run]> set session histogram_generation_max_mem_size = 1000000; |
再次提醒,并不是非得所有page都要被采集到,否则代价可能无法承受。。。
全文完。
转载至:https://cloud.tencent.com/developer/article/1628479
7. MySQL · 内核特性 · 统计信息的现状和发展
简介
我们知道查询优化问题其实是一个搜索问题。基于代价的优化器 ( CBO ) 由三个模块构成:计划空间、搜索算法和代价估计 [1] ,分别负责“看到”最优执行计划和“看准”最优执行计划。如果不能“看准”最优执行计划,那么优化器基本上就是瞎忙活,甚至会产生严重的影响,出现运算量特别大的 SQL ,造成在线业务的抖动甚至崩溃。

在上图中,代价估计用一个多项式表示,其系数 c 反应了硬件环境和算子特性,而数值 n 则由查询条件基于统计信息计算而得到。
现在主流的评估模型仍可溯源于 selinger 97 代价模型 [2] 。虽然各种机器学习模型从未停止过探索,但其效果上往往还不如极其简单的代价模型和比较精确的行数估计 ( cardinality estimation ) [3] 。统计信息的质量直接影响基数估算的准确性,其重要性是显而易见的。
需要注意的是,基于统计数据和即时采样都可以获得行数估计。事实上 MySQL 的 range optimizer 和 ref optimizer 就是重度依赖于索引采样 (index dive) ,而 join optimizer 则用索引统计信息 (index stats ,又称 record per key 或者 density vector ) 。索引采样需要计算谓词范围内的 page 数和 page 平均密度,对于小范围评估非常准确,对于大范围评估误差就比较大,此外,需要读索引数据, I/O 路径比较长,开销有时也是不可忽视的。
谈到统计信息,就会涉及管理框架和统计数据两个部分。令人遗憾的是,在 MySQL 里这两部分都是非常原始的。本文主要讨论管理框架缺陷,同时会涉及数据质量问题。
统计信息管理
我们知道 MySQL 遵循的是计算 ( SQL ,又称 Server ) 和存储 ( Storage Engine ) 分层的设计,在两层之间有一个 handler 接口层。每个存储引擎都需要提供自己的 handler 实现。MySQL 主流存储引擎仍然是 InnoDB 。本文所讨论的统计信息问题正是与 InnoDB 密切相关的。
由于分层设计,统计信息就会存在两种组织方式: 1) Storage Engine 提供采样接口,而在 Server 层基于样本完成各种指标计算,也可以是 2) Storage Engine 提供统计信息,只在 handler 层中提供一些简单的格式适配。除了 8.0 引入的直方图是在 Server 层基于 handler 采样接口实现的,其他统计信息,都是直接从 Storage Engine 读出并在 hander 层适配的。
需要说明的是,商业数据库里广泛应用的直方图,在 MySQL 内核里还只是个配角,究其原因大概有:基于 index dive 的 range optimizer 在 MySQL 主要业务 TP 业务场景中表现还行 ,而 InnoDB 的采样算法 ( row-based random sampling ) 性能问题也限制了应用场景,直到到比较新的 8.0.19 版本 [4] 才发布了重点改进 ( block-based random sampling ) [5] 。
那么, InnoDB 的统计信息支持,有什么问题和影响呢?
总图
下面这个图中绘制了三层的不同对象和模块,从上到下依次是 Server 、handler 和 InnoDB 。图中包含了 Server 和 InnoDB 的统计信息表示,以及适配函数和更新机制。

信息表示
在 SQL 层中,统计信息存于 TABLE 对象和 TABLE_SHARE 对象中。 TABLE 是会话级的(在 MySQL 中,一个会话即一个客户连接),TABLE_SHARE 是全局共享的,语义上 TABLE 和 TABLE_SHARE 是多对一的关系。此外, TABLE 和 TABLE_SHARE 都有相应的缓存,分别称为 table_open_cache 和 table_definition_cache 。 为了优化锁竞争,TABLE 缓存做了哈希分区 ( 每个分区称为一个 instance ) 。
统计信息的表示和 open table 逻辑是密切相关的。open table 简单地讲,如果有 TABLE 对象就复用,否则根据 TABLE_SHARE 构造 ( open_table_from_share ) ,如果 TABLE_SHARE 都没有,那就先从数据字典 ( data dictionary ) 构造 TABLE_SHARE ,再构造 TABLE 。在构造 TABLE 时会将文件大小、page 大小和表行数等统计信息放到handler::stats 中,但索引统计信息和单列直方图仍然是放在 TABLE_SHARE 中为所有会话 TABLE 所共享的。
在 InnoDB 中,统计信息缓存在 dict_table_t 和 dict_index_t 中,前者包含表级统计信息 ( 行数、主索引字节数和二级索引总字节数 ) ,后者包含索引级统计信息(密度向量、B+树总页数和叶子页数)。而 InnoDB 采用了聚簇主键索引,所以,行数其实也是从主索引获得的。这些信息会持久化在 mysql.innodb_table_stats 和 mysql.innodb_index_stats 两个系统表中。
更新机制
统计信息收集是通过 dict_stats_update_persistent() 函数来完成的,具体收集算法这里不展开,其统计指标更新流程是:
1 | 1. 持写锁 |
显然,这里持写锁时间是会比较长的,这也可能是 HA_STATUS_NO_LOCK 需求的来源。在 handler::info() 同步信息时通常会带上 HA_STATUS_NO_LOCK 标记,表示读 Storage Engine 统计信息时不持读锁。
在 Server 和 Storage Engine 两层之间的信息同步是 handler::info() 接口负责的。这个接口函数通过一个参数来标记操作内容:
1 | HA_STATUS_VARIABLE 需要同步表级统计信息到 handler::stats (ha_statistics) |
具体标记由调用方根据场景来决定。由于重新收集统计信息时需要更新 dict_table_t 和 dict_index_t 相关字段,而同步统计信息时会读这些字段,这把锁可以保证读写版本是一致。统计信息一般是 8 字节数值,在 64-bit 机器上,这些数值本身的读写可以认为是原子的,统计信息对版本一致性也有一定的容忍度,直观上理解,读的时候不持锁也是可以的。
从总图也可以看到,DML 会将表级统计信息从 InnoDB 同步到 handler::stats ,重新构造 TABLE 时会同步表和索引统计信息,而 ANALYZE 命令除了同步表和索引统计信息之外,还要求重新收集。
Information Schema 和 SHOW INDEX
当发生执行计划回退时,我们通常会试图求证于当前的统计信息,一种办法是使用 SHOW INDEX 命令,另一种是直接读 mysql 库中的两个统计表,或者 information schema 中的相关视图。但这两个命令是绕过 SQL 层的缓存,直接读 InnoDB 中缓存的统计信息,此外,还有专用的内部缓存表,即 mysql.table_stats 和 mysql.index_stats ,其缓存时间由系统变量 information_schema_stats_expiry 控制,默认有效期是一天。但优化器使用的是 SQL 层的缓存,也就是说,如果同步机制本身出了问题,那么,这两个命令其实产生欺骗的。事实上,这个同步机制确实也有点问题。目前并没有办法直接查看 SQL层缓存的统计信息,所以,唯一可信的是 optimizer trace 中的数值,虽然并非原始统计信息,但基本上也可以支持一定程度的还原。
更新机制
重新收集统计信息,有多种触发情况:用户可以主动发起 ANALYZE 命令来重新收集,重建表结束时也会重新收集,此外, InnoDB 的后台统计线程 ( dict0stats_bg.cc ) 还会在修改行数累积到一定数量时 ( persistent 10% 或 transient 1/16 , 见 row_update_statistics_if_needed() ) 重新收集,样本大小分别由 innodb_stats_persistent_sample_pages 和 innodb_stats_transient_sample_pages 控制。考虑到采样开销,这两个参数的默认值是 20 和 8 ,也就是说,不管用户表数据量多大,InnoDB 都只采集 20 个 page 。
重新收集的入口函数是 dict_stats_update_persistent() 。顺便说一句, InnoDB 持久化统计信息是从 5.6.6 开始成为默认配置的 [7] [8] ,而非持久化统计信息继续应用于一些系统表,这两套逻辑还有一定的重叠度。
但是,对于 SQL 层维护的统计信息 ( 如直方图 ) ,由于没有更新计数的支持,所以,只能通过内部定时任务 ( events ) 或者外部定时任务来驱动更新。
问题和影响
数据质量
InnoDB 统计信息对于无论多大的表,默认都只随机采样 20 个 page 。显然,对于这么小的 block-based sampling 样本,算法上很难产生可靠的统计 ,除非数据是趋向于均匀分布的。直方图虽然可以比较好地拟合数据分布,但也需要足够大的随机样本 [6] 。事实上,生产环境查询性能问题,很多是数据倾斜导致的。
时效性
由于有 TABLE 缓存以及 TABLE_SHARE 缓存,什么时候构造 TABLE 对象,其实是不可预知的,换句话说,密度向量什么时候能够更新是没有保证的。理论上,只要会话缓存足够大,若不主动 ANALYZE ,密度向量可能长期没有更新!而新连接由于无可复用的 TABLE 对象,调用了 open_table_from_share() ,其他会话中该表相关的执行计划可能就莫名其妙变了。
顺便说一句, ANALYZE 命令不太常见,一方面,可能是因为大家误以为后台统计任务会合理地更新信息,另一方面,可能是因为确实是不知道什么时候需要更新,毕竟除了在批量更新或数据导入场景下可能是比较清晰的,其他时机都无从知晓。而且,它还有阻塞查询的概率风险 [9] 。
一致性
从统计流程可以看到,在收集前有一个外部可见的缓存清零操作。也就是说,同步信息时不持读锁的话,除了版本不一致外,还可能读到零。当然 info() 读到零值时会进行一些处理,比如说,对于密度向量,它会认为表中所有记录都是相同的,对于元组数,它会认为是空表。显然,不管是那种处理,对于正常统计规律来讲,都是一个突变。一般来说,统计信息可以容忍一定范围的误差,甚至只要保持统计性质不变,长期不更新都可以,但突变就完全打破了这个基础,业务上就可能有莫名其妙的全表扫描,或者有更好的索引却不选。
在 20 个 page 的默认采样配置下,大概 20~30 ms 就完成了统计更新。但低耗时也掩盖了更多的管理逻辑问题:由于缺乏对统计收集任务的合理协调,实际情况是会有多次毫无意义的重复收集操作。
按说重建表时是要暂停收集统计信息的,但实际上新的统计任务仍然会由修改行数累积触发,当主索引处于 OnlineDDL 状态时,统计指标更新流程清零操作后会跳过搜集,读到零的时间窗口会被急剧放大,直到重建表结束后再恢复正常。随着 OnlineDDL [10] 越来越多的使用,生产环境全表扫描问题越来越多。好消息是,这个问题已经有修复方案了 [11] 。
解决办法
显然,现有更新机制的同步问题和一致性问题,属于程序缺陷,需要修复。 作为短期规避措施, ANALYZE 命令可以加到定时任务,但要修复潜在的阻塞风险 [9] 。
从优化器角度来看,InnoDB 统计信息不论在指标丰富程度还是管理框架方面,基本上无法满足各种优化场景的需要。统计质量导致“看不准” 最优执行计划,属于方案缺陷,可以从两个方面来着手:1) 增强估计能力和统计数据支持,2) 限定执行计划搜索空间。虽然都可以有一些人工干预的机制作为短期的过渡方案,但是,在比较大的部署规模下,为产出高效而稳定的执行计划,建立系统化的统计信息管理机制 [12] ,其重要性就是显而易见的了。

转载至:http://mysql.taobao.org/monthly/2020/12/05/
索引采样统计信息
区别度:一个索引的不同值的个数, 我们建立索引的时候有一个原则就是区别度越大性能优化越大,索引一般类似性别这样的字段不适合建索引,只能排除概率一半左右的数据
基数:统计样本的条数
采样率:InnoDB一般不会使用所有表的行数作为基数进行分析,否则对性能影响很大。则会通过配置项 N 个数据页上统计区别度的平均值,再乘以所有数据页数,得到基数,所以基数只是一个近似值。数据会不断地更新,那么统计信息也会不准却,触发重新统计的条件就是 当数据行数超过 1/M时,而其中的 M和N值是根据 innerdb_stats_persistent配置项觉定的,当设置为 ON时表示统计信息持久化,M = 20,N = 10; 当设置为 OFF时,表示不持久化统计信息(内存中), M = 8 N = 16。
我们也可以执行命令 analyze table 表名 手动触发重新采用统计,如下:

此时可以执行命令 show index from 表名 查看新的索引统计信息,其中 Cardinality就是采样计算的统计基数,是一个近似值,如下图:

7.3 缓冲和缓存
1. InnoDB 缓冲池优化
InnoDB维护一个称为缓冲池的存储区域, 用于在内存中缓存数据和索引。了解 InnoDB缓冲池的工作原理,并利用它将经常访问的数据保存在内存中,是 MySQL 调优的一个重要方面。
有关InnoDB缓冲池内部工作原理的说明、 其 LRU 替换算法的概述以及一般配置信息,请参阅第 15.5.1 节,“缓冲池”。
有关其他InnoDB缓冲池配置和调整信息,请参阅以下部分:
- 第 15.8.3.4 节,“配置 InnoDB 缓冲池预取(预读)”
- 第 15.8.3.5 节,“配置缓冲池刷新”
- 第 15.8.3.3 节,“使缓冲池扫描抵抗”
- 第 15.8.3.2 节,“配置多个缓冲池实例”
- 第 15.8.3.6 节,“保存和恢复缓冲池状态”
- 第 15.8.3.1 节,“配置 InnoDB 缓冲池大小”
2. MyISAM 密钥缓存
为了最小化磁盘 I/O,MyISAM存储引擎采用了许多数据库管理系统使用的策略。它采用缓存机制将最常访问的表块保存在内存中:
- 对于索引块,维护一个称为键缓存(或 键缓冲区)的特殊结构 。该结构包含许多块缓冲区,其中放置了最常用的索引块。
- 对于数据块,MySQL 没有使用特殊的缓存。相反,它依赖于本机操作系统文件系统缓存。
本节首先介绍MyISAM密钥缓存的基本操作 。然后讨论可提高密钥缓存性能并使您能够更好地控制缓存操作的功能:
- 多个会话可以同时访问缓存。
- 您可以设置多个键缓存并将表索引分配给特定的缓存。
要控制密钥缓存的大小,请使用 key_buffer_size系统变量。如果此变量设置为零,则不使用密钥缓存。如果该key_buffer_size值太小而无法分配最少数量的块缓冲区 (8),则也不会使用密钥缓存 。
当密钥高速缓存不可操作时,仅使用操作系统提供的本机文件系统缓冲来访问索引文件。(换句话说,使用与表数据块相同的策略访问表索引块。)
索引块是访问MyISAM索引文件的连续单元 。通常一个索引块的大小等于索引B树的节点大小。(索引在磁盘上使用 B 树数据结构表示。树底部的节点是叶节点。叶节点上方的节点是非叶节点。)
键缓存结构中的所有块缓冲区大小相同。此大小可以等于、大于或小于表索引块的大小。通常这两个值之一是另一个的倍数。
当必须访问来自任何表索引块的数据时,服务器首先检查它是否在键缓存的某个块缓冲区中可用。如果是,则服务器访问密钥缓存中的数据而不是磁盘上的数据。也就是说,它从缓存读取或写入缓存,而不是从磁盘读取或写入。否则,服务器选择包含不同表索引块(或多个块)的缓存块缓冲区,并用所需表索引块的副本替换其中的数据。只要新的索引块在缓存中,就可以访问索引数据。
如果碰巧选择用于替换的块已被修改,则该块被认为是“脏的”。”在这种情况下,之前被取代时,其内容被刷新到它所来自的表索引。
通常,服务器遵循LRU(最近使用最少使用)策略:在选择替换块时,它会选择最近最近使用的索引块。为了使这个选择更容易,密钥缓存模块将所有使用过的块维护在一个按使用时间排序的特殊列表(LRU 链)中。当一个块被访问时,它是最近使用的并被放置在列表的末尾。当需要替换块时,列表开头的块是最近最少使用的,并成为最先驱逐的候选者。
该InnoDB存储引擎还采用LRU算法来管理它的缓冲池。
补充
MyISAM引擎中,为了提高io效率以及读取效率,将对磁盘频繁读取的索引数据加载至内存中操作。
MyISAM设计了一个在存放在内存中的索引缓冲池Key Cache。Key Cache只缓存索引数据,通过LRU算法将读取频繁的索引加载到Key Cache中来。
通过系统变量 key_buffer_size 来控制Key Cache的大小,这个变量关乎到缓存的性能。
InnoDB引擎中,同样设置了缓存池buffer pool,与MyISAM有所区别的是,buffer pool不仅仅缓存了索引数据,同时还缓存了表数据。
这样的缓冲池同时也带来了很多问题:缓存中的数据如何与磁盘上的数据保持一致,缓存中的数据支不支持修改更新操作,以及与日志记录模块的同步等等问题。
要解决这些问题带来的操作时繁琐的,但是相比于整体性能的提升,也是值得的:硬盘的存取速度与内存的速度更本不是一个数量级的,通过内存来读取数据,可以大大的提高数据库的整体性能。
MySQL官方文档这样建议,除了用于系统运行的内存外,剩余的内存建议尽可能大的设置buffer pool。这样一来有着大容量的buffer pool,在实际应用上的表现更像与一个in-memory database,相比于对磁盘的读写速度,读写性能简直就是巨大的提升。
这一切当然基于读取数据在buffer pool的命中率上面,修改更新等操作会改变buffer pool的一些数据,通过LRU算法更新,将buffer pool的命中率维持在一个比较高的水平。
还有一个问题就是怎么将buffer pool中的数据同步到磁盘。想想如果更新一次buffer pool就写一次磁盘,那这样子的效率和直接读写磁盘并没有提高多少,这里就需要设计出同步策略来解决这个问题。
InnoDB是事务安全的,修改buffer pool的数据后,同时还要将此操作记录在事务日志中去。这里对buffer pool的修改操作后,并没有直接将数据同步到磁盘,而是将此操作记录到事务日志文件中去。这里又有一个疑问,为什么不将数据写到磁盘的表数据文件里去,而是写到磁盘的事务日志文件去呢,同样是磁盘写操作,有何不同?
这里涉及到磁盘寻道读写问题,学过计算机组成原理的就知道了,磁盘读写可以分为两种:顺序读写以及随机读写,如果为随机读写,将要花一定的时间用于磁头寻址上,如果为顺序读写,则是连续的将数据写入磁面,磁头寻址操作很少。这两种读写方式的效率也可见区别甚大
事务日志文件是InnoDB引擎申请连续物理空间的固定大小的一个文件,对日志文件的读写基本上是顺序读写,寻址操作甚少。
而buffer pool中的表数据多而复杂:多个表的数据文件在磁盘中的存储空间是不同的,具有随机性,若每次更新buffer pool中的数据到磁盘,每次操作的表空间表现出随机性,对磁盘的读写也是随机的,这样以来频繁的寻址读写操作,将使磁盘处于一个繁忙随机读写状态。
所以buffer pool的策略也使得整体io性能得到了提升。
原文链接:https://blog.csdn.net/weixin_35825868/article/details/113213592
shared-key-cache访问
线程可以同时访问shared-key-cache区,但须符合以下条件:
- 多个会话可以访问未更新的缓冲区。
- 正在更新的缓冲区会导致需要使用它的会话等待更新完成。
- 多个会话可以发起导致缓存块替换的请求,只要它们不相互干扰(即只要它们需要不同的索引块,从而导致不同的缓存块被替换)。
对shared-key-cache缓存的共享访问使服务器能够显着提高吞吐量。
对密钥缓存的共享访问可提高性能,但并不能完全消除会话之间的争用。它们仍然争夺管理对密钥缓存缓冲区的访问的控制结构。为了进一步减少键缓存访问争用,MySQL 还提供了多个键缓存。此功能使您可以将不同的表索引分配给不同的键缓存。
如果有多个键缓存,服务器必须知道在处理给定MyISAM表的查询时使用哪个缓存 。默认情况下,所有 MyISAM表索引都缓存在默认键缓存中。要将表索引分配给特定的键缓存,请使用该CACHE INDEX 语句
中点插入策略
默认情况下,键缓存管理系统使用简单的 LRU 策略来选择要驱逐的键缓存块,但它也支持更复杂的方法,称为 中点插入策略。
使用中点插入策略时,LRU链分为两部分:热子表和温子表。两个部分之间的划分点不是固定的,但是密钥缓存管理系统会注意温暖的部分不要 “太短”,始终包含至少 key_cache_division_limit 百分比的关键缓存块。 key_cache_division_limit是结构化键缓存变量的一个组成部分,所以它的值是一个可以为每个缓存设置的参数。
当索引块从表中读取到键缓存中时,它被放置在热子列表的末尾。在一定数量的命中(块的访问)之后,它被提升到热子列表。目前,对于所有索引块,提升一个块(3)所需的命中数是相同的。
提升到热子列表的块放置在列表的末尾。然后块在这个子列表中循环。如果块在子列表的开头停留足够长的时间,则将其降级到暖子列表。这个时间是由key_cache_age_threshold key缓存的组件的值决定的 。
阈值规定,对于包含*N*块的密钥缓存,在最后一次*N* * key_cache_age_threshold / 100命中中未访问的热子列表开头的块 将被移动到热子列表的开头。然后它成为驱逐的第一个候选者,因为替换块总是从热子列表的开头获取。
中点插入策略使您能够将更有价值的块始终保留在缓存中。如果您更喜欢使用普通 LRU 策略,请将 key_cache_division_limit 值设置为其默认值 100。
当执行需要索引扫描的查询有效地将与有价值的高级 B 树节点对应的所有索引块从缓存中推出时,中点插入策略有助于提高性能。为避免这种情况,您必须使用中点插入策略, key_cache_division_limit设置远小于 100。然后,在索引扫描操作期间,宝贵的频繁命中节点也会保留在热子列表中。
索引预加载
如果键缓存中有足够的块来保存整个索引的块,或者至少是与其非叶节点对应的块,那么在开始使用它之前用索引块预加载键缓存是有意义的。预加载使您能够以最有效的方式将表索引块放入键缓存缓冲区:通过从磁盘顺序读取索引块。
在没有预加载的情况下,块仍会根据查询的需要放入密钥缓存中。尽管块保留在缓存中,但由于有足够的缓冲区供所有块使用,因此它们是按随机顺序从磁盘中提取的,而不是按顺序提取的。
要将索引预加载到缓存中,请使用该 LOAD INDEX INTO CACHE语句。例如,以下语句预加载表t1和索引的节点(索引块)t2:
1 | mysql> LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES; |
该IGNORE LEAVES修饰符仅导致预加载索引的非叶节点的块。因此,所示语句预加载来自 的所有索引块t1,但仅预加载来自 的非叶节点的块t2。
如果已使用CACHE INDEX语句将索引分配给键缓存 ,则预加载会将索引块放入该缓存中。否则,索引将加载到默认密钥缓存中。
Key Cache Block Size
可以使用该key_cache_block_size 变量为单个key缓存指定块缓冲区的大小 。这允许调整索引文件的 I/O 操作的性能。
当读取缓冲区的大小等于本机操作系统 I/O 缓冲区的大小时,可实现 I/O 操作的最佳性能。但是将关键节点的大小设置为等于 I/O 缓冲区的大小并不总是能确保最佳的整体性能。服务器在读取大叶子节点时,会拉入大量不必要的数据,有效防止读取其他叶子节点。
要控制表.MYI 索引文件中块的大小,请在服务器启动时MyISAM使用该 --myisam-block-size选项。
重构Key缓存
可以随时通过更新其参数值来重构密钥缓存。例如:
1 | mysql> SET GLOBAL cold_cache.key_buffer_size=4*1024*1024; |
如果您为缓存组件key_buffer_size或 key_cache_block_size键缓存组件分配了 一个不同于组件当前值的值,服务器会破坏缓存的旧结构并根据新值创建一个新结构。如果缓存包含任何脏块,服务器会在销毁和重新创建缓存之前将它们保存到磁盘。如果您更改其他密钥缓存参数,则不会发生重组。
重构密钥缓存时,服务器首先将任何脏缓冲区的内容刷新到磁盘。之后,缓存内容变得不可用。但是,重组不会阻止需要使用分配给缓存的索引的查询。相反,服务器使用本机文件系统缓存直接访问表索引。文件系统缓存不如使用键缓存那么高效,因此尽管执行查询,但可以预期速度会减慢。缓存被重构后,它再次可用于缓存分配给它的索引,并且停止使用文件系统缓存来缓存索引。
预处理语句和存储程序的缓存
对于客户端可能在会话期间多次执行的某些语句,服务器将语句转换为内部结构并缓存该结构以在执行期间使用。缓存使服务器能够更有效地执行,因为它避免了在会话期间再次需要时重新转换语句的开销。这些语句会发生转换和缓存:
- 准备好的语句,包括在 SQL 级别处理的
PREPARE语句(使用语句)和使用二进制客户端/服务器协议处理的语句(使用mysql_stmt_prepare()C API 函数)。所述max_prepared_stmt_count系统变量控制语句的服务器高速缓存的总数量。(所有会话中准备好的语句数量的总和。) - 存储程序(存储过程和函数、触发器和事件)。在这种情况下,服务器会转换并缓存整个程序主体。该
stored_program_cache系统变量指示存储的程序每个会话的服务器缓存的大致数量。
服务器在每个会话的基础上为准备好的语句和存储的程序维护缓存。为一个会话缓存的语句不能被其他会话访问。当会话结束时,服务器会丢弃为其缓存的所有语句。
当服务器使用缓存的内部语句结构时,必须注意该结构不会过时。语句使用的对象可能会发生元数据更改,从而导致当前对象定义与内部语句结构中表示的定义不匹配。DDL 语句会发生元数据更改,例如创建、删除、更改、重命名或截断表或分析、优化或修复表的语句。表内容更改(例如,使用INSERT或 UPDATE)不会更改元数据,也不会更改SELECT语句。
这是问题的说明。假设客户准备了以下语句:
1 | PREPARE s1 FROM 'SELECT * FROM t1'; |
将SELECT *内部结构扩展为表中的列列表。如果使用 修改表中的列集ALTER TABLE,则准备好的语句将过时。如果服务器在下一次客户端执行时没有检测到这个变化s1,准备好的语句会返回错误的结果。
为避免由准备好的语句引用的表或视图的元数据更改引起的问题,服务器检测这些更改并在下次执行语句时自动重新准备该语句。即服务器重新解析语句并重建内部结构。在从表定义缓存中刷新引用的表或视图之后,也会发生重新解析,要么隐式地为缓存中的新条目腾出空间,要么显式地由于FLUSH TABLES.
类似地,如果存储程序使用的对象发生更改,服务器会重新分析程序中受影响的语句。
服务器还检测表达式中对象的元数据更改。这些可能会在陈述具体可用于存储程序,如DECLARE CURSOR或流量控制语句,如 IF, CASE和 RETURN。
为避免重新解析整个存储的程序,服务器仅在需要时重新解析程序中受影响的语句或表达式。例子:
假设表或视图的元数据已更改。对
SELECT *访问表或视图的程序内的a进行重新解析,但对SELECT *不访问表或视图的a 不进行 重新解析。当语句受到影响时,如果可能,服务器只会部分重新解析它。考虑这个
CASE声明:1
2
3
4
5
6CASE case_expr
WHEN when_expr1 ...
WHEN when_expr2 ...
WHEN when_expr3 ...
...
END CASE如果元数据更改仅影响,则重新解析该表达式。 并且不会重新解析其他表达式。
WHEN *when_expr3*case_exprWHEN
重新解析使用对原始转换为内部形式有效的默认数据库和 SQL 模式。
服务器最多尝试重新解析 3 次。如果所有尝试都失败,则会发生错误。
重新解析是自动的,但在一定程度上它会降低准备好的语句和存储程序的性能。
对于准备好的语句,Com_stmt_reprepare 状态变量跟踪重新准备 的次数。
补充

一、MyISAM Key Cache详解:
为了最小化磁盘I/O,MyISAM将最频繁访问的索引块(“indexblock”)都放在内存中,这样的内存缓冲区我们称之为Key Cache,它的大小可以通过参数key_buffer_size来控制。在MyISAM的索引文件中(MYI),连续的单元(contiguous unit)组成一个Block,Index block的大小等于该BTree索引节点的大小。Key Cache就是以Block为单位的。
- MyISAM如何使用Key Cache
当MySQL请求(读或写)MyISAM索引文件中某个IndexBlock时,首先会看Key Cache队列中是否已经缓存了对应block。如果有,就直接在Key Cache队列中进行读写了,不再需要请求磁盘。如果是写请求,那么Key Cache中的对应Block就会被标记为Dirty(和磁盘不一致)。在MyISAM在Key Cache成功请求(读写)某个Block后,会将该Block放到Key Cache队列的头部。
如果Key Cache中没有待请求(读或写)的Block,MyISAM会向磁盘请求对应的Block,并将其放到KeyCache的队列头部。队列如果满了,会将队列尾部的Block删除,该Block如果是Dirty的,会将其Flush到磁盘上。我们看到MyISAM维护了一个LRU(Least Recently Used)的Key Cache队列。队列中的Dirty Block会在Block被踢出队列时Flush到磁盘上。
2.并发访问
Key Cache中的index Block是可以被并发访问的(Shared access ),下面是一些规则:
a.多个没有更新操作的session可以并发同一个block buffer
b.多个session同时访问某一个block buffer,如果某个session是update操作,则优先访问
c.多个session如果都需要进行block replacement,是可以并发操作。(从index file中读取block更新到key cache,但是key cache已满,需要删除一些block buffer的操作叫做block replacement)
原文链接:https://blog.csdn.net/weixin_31060209/article/details/113496000
7.4 优化加锁操作
MySQL 使用锁定管理表内容的争用 :
- 内部锁定在 MySQL 服务器本身内执行,以管理多个线程对表内容的争用。这种类型的锁定是内部锁定,因为它完全由服务器执行并且不涉及其他程序。
- 当服务器和其他程序锁定
MyISAM表文件以在它们之间协调哪个程序可以在哪个时间访问表时,就会发生外部锁定。
1. 内部锁定方法
本节讨论内部锁定;也就是说,在 MySQL 服务器本身内执行锁定以管理多个会话对表内容的争用。这种类型的锁定是内部锁定,因为它完全由服务器执行并且不涉及其他程序
行级锁定
MySQL对表使用行级锁定InnoDB来支持多个会话的同时写访问,使其适用于多用户、高并发和 OLTP 应用程序。
为避免在单个表上执行多个并发写入操作时出现 死锁InnoDB,请在事务开始时通过SELECT ... FOR UPDATE为预期要修改的每组行发出一条语句来获取必要的锁,即使数据更改语句在事务中稍后出现。如果事务修改或锁定多个表,则在每个事务中以相同的顺序发出适用的语句。死锁会影响性能而不是表示严重错误,因为 默认情况下会InnoDB自动 检测死锁条件并回滚受影响的事务之一。
在高并发系统上,当大量线程等待同一个锁时,死锁检测会导致速度减慢。有时,禁用死锁检测并在innodb_lock_wait_timeout 发生死锁时依赖事务回滚设置可能更有效 。可以使用innodb_deadlock_detect 配置选项禁用死锁检测 。
行级锁的优点:
- 当不同的会话访问不同的行时,锁冲突更少。
- 回滚的更改较少。
- 可以长时间锁定单行。
表级锁定
MySQL对、和 表使用表级锁定MyISAM, 一次只允许一个会话更新这些表。这种锁定级别使这些存储引擎更适合只读、多读或单用户应用程序。 MEMORY``MERGE
这些存储引擎总是在查询开始时立即请求所有需要的锁,并始终以相同的顺序锁定表,从而避免 死锁。权衡是这种策略降低了并发性;其他想要修改表的会话必须等到当前数据更改语句完成。
表级锁的优点:
- 需要的内存相对较少(行锁定需要每行或一组行锁定的内存)
- 在表的大部分上使用时速度很快,因为只涉及一个锁。
- 如果您经常
GROUP BY对大部分数据进行操作或必须经常扫描整个表,则速度很快。
MySQL 授予表写锁如下:
- 如果表上没有锁,则在其上放置写锁。
- 否则,将锁请求放入写锁队列。
MySQL 授予表读锁如下:
- 如果表上没有写锁,则在其上放置读锁。
- 否则,将锁请求放入读锁队列。
表更新的优先级高于表检索。因此,当一个锁被释放时,该锁对写锁队列中的请求可用,然后对读锁队列中的请求可用。这确保了即使在表有大量活动时,对表的更新也不会“饿死”SELECT。但是,如果一个表有很多更新, SELECT语句会等待直到没有更多更新。
该MyISAM存储引擎支持并发插入,减少读者和作者之间的竞争给定表:如果一个MyISAM 表有数据文件的中间没有空闲块,行总是在数据文件的末尾插入。在这种情况下,您可以为一个没有锁的表自由混合并发 INSERT和 SELECT语句 MyISAM。也就是说,您可以将行插入到MyISAM表同时其他客户端正在读取它。空洞可能是由于表中间的行被删除或更新造成的。如果有空洞,并发插入将被禁用,但在所有空洞都被新数据填充后会自动再次启用。要控制此行为,请使用 concurrent_insert系统变量。
补充
MyISAM在某些条件下允许并发插入下读取,并且它让你可以“高度”某些操作,以尽可能少地阻止工作。
MyISAM如何删除和插入行??
删除操作不会重新安排整个表,它们只是把行标记为已经删除,并且在表中留下了一些“洞”。MyISAM在可能的情况下会优先使用这些“洞”,为插入复用空间。如果表是完整的,它就会把新的行拼接在表的最后。
即使MyISAM有表级别的锁,它也能在读取的同时把行拼接到表尾。它通过禁止读取最后一行做到了这一点。这避免了不连续的读取。
但是,当表中间的数据改变的时候,要提供连续读取就困难得多。MVCC是最通用的解决这个问题的方法,它在创建新版本数据的同时提供老版本数据读取。
MyISAM不支持MVCC,所以它只有到达表尾的时候才允许并发插入。
可以使用concurrent_insert变量配置MyISAM的并发插入行为,它有下面的值:
0 MyISAM不允许并发插入,每一次插入都会把表锁住
1 默认值。只要表中没有空缺,MyISAM就允许并发插入
2 该值在MyISAM5.0及更高的版本可用。它强制并发插入到表尾,即使表在空缺也不例外。如果没有线程从表中读取数据,MysQL就会把新数据插入到空缺中。使用了该设置,表的碎片会增多,所以就需要更经常地对表进行优化。
可以配置MySQL把一些操作延迟,然后合并到一起执行。例如,可以使用delay_key_write延迟写入索引。这会带来一些明显的矛盾,立即写入索引(安全但是代价很高),或者等待写入并希望在写入前不要断电(更快,但是如果断电的话就会导致大规模的索引的损坏,因为索引文件已经明显过期了)。也可以使用low_priorite_updates让insert,replace,delete用update比select的优先级更低。这等同于全局地给update使用low_priority修饰符。
MySQL提供了几个语句调节符,允许你修改它的调度策略:
· LOW_PRIORITY关键字应用于DELETE、INSERT、LOAD DATA、REPLACE和UPDATE。
· HIGH_PRIORITY关键字应用于SELECT和INSERT语句。
· DELAYED关键字应用于INSERT和REPLACE语句。
LOW_PRIORITY和HIGH_PRIORITY调节符影响那些使用数据表锁的存储引擎(例如MyISAM和MEMORY)。DELAYED调节符作用于MyISAM和MEMORY数据表。
改变语句调度的优先级
LOW_PRIORITY关键字影响DELETE、INSERT、LOAD DATA、REPLACE和UPDATE语句的执行调度。通常情况下,某张数据表正在被读取的时候,如果有写入操作到达,那么写入者一直等待读取者完成操作(查询开始之后就不能中断,因此允许读取者完成操作)。如果写入者正在等待的时候,另一个读取操作到达了,该读取操作也会被阻塞(block),因为默认的调度策略是写入者优先于读取者。当第一个读取者完成操作的时候,写入者开始操作,并且直到该写入者完成操作,第二个读取者才开始操作。
如果写入操作是一个LOW_PRIORITY(低优先级)请求,那么系统就不会认为它的优先级高于读取操作。在这种情况下,如果写入者在等待的时候,第二个读取者到达了,那么就允许第二个读取者插到写入者之前。只有在没有其它的读取者的时候,才允许写入者开始操作。理论上,这种调度修改暗示着,可能存在LOW_PRIORITY写入操作永远被阻塞的情况。如果前面的读取操作在进行的过程中一直有其它的读取操作到达,那么新的请求都会插入到LOW_PRIORITY写入操作之前。
SELECT查询的HIGH_PRIORITY(高优先级)关键字也类似。它允许SELECT插入正在等待的写入操作之前,即使在正常情况下写入操作的优先级更高。另外一种影响是,高优先级的SELECT在正常的SELECT语句之前执行,因为这些语句会被写入操作阻塞。
如果你希望所有支持LOW_PRIORITY选项的语句都默认地按照低优先级来处理,那么请使用–low-priority-updates选项来启动服务器。通过使用INSERT HIGH_PRIORITY来把INSERT语句提高到正常的写入优先级,可以消除该选项对单个INSERT语句的影响。
DELAYED调节符应用于INSERT和REPLACE语句。当DELAYED插入操作到达的时候,服务器把数据行放入一个队列中,并立即给客户端返回一个状态信息,这样客户端就可以在数据表被真正地插入记录之前继续进行操作了。如果读取者从该数据表中读取数据,队列中的数据就会被保持着,直到没有读取者为止。接着服务器开始插入延迟数据行(delayed-row)队列中的数据行。在插入操作的同时,服务器还要检查是否有新的读取请求到达和等待。如果有,延迟数据行队列就被挂起,允许读取者继续操作。当没有读取者的时候,服务器再次开始插入延迟的数据行。这个过程一直进行,直到队列空了为止。
几点要注意事项:· INSERT DELAYED应该仅用于指定值清单的INSERT语句。服务器忽略用于INSERT DELAYED…SELECT语句的DELAYED。· 服务器忽略用于INSERT DELAYED…ON DUPLICATE UPDATE语句的DELAYED。· 因为在行被插入前,语句立刻返回,所以您不能使用LAST_INSERT_ID()来获取AUTO_INCREMENT值。AUTO_INCREMENT值可能由语句生成。· 对于SELECT语句,DELAYED行不可见,直到这些行确实被插入了为止。· DELAYED在从属复制服务器中被忽略了,因为DELAYED不会在从属服务器中产生与主服务器不一样的数据。
注意,目前在队列中的各行只保存在存储器中,直到它们被插入到表中为止。这意味着,如果您强行中止了mysqld(例如,使用kill -9)或者如果mysqld意外停止,则所有没有被写入磁盘的行都会丢失。
IGNORE是MySQL相对于标准SQL的扩展。如果在新表中有重复关键字,或者当STRICT模式启动后出现警告,则使用IGNORE控制ALTER TABLE的运行。如果没有指定IGNORE,当重复关键字错误发生时,复制操作被放弃,返回前一步骤。如果指定了IGNORE,则对于有重复关键字的行,只使用第一行,其它有冲突的行被删除。并且,对错误值进行修正,使之尽量接近正确值。
insert ignore into tb(…) value(…)
这样不用校验是否存在了,有则忽略,无则添加
原文链接:https://blog.csdn.net/weixin_32541907/article/details/113432386
选择锁定类型
通常,在以下情况下,表锁优于行级锁:
该表的大多数语句都是读取。
表的语句是读取和写入的混合,其中写入是对单行的更新或删除,可以通过一个键读取来获取:
1
2UPDATE tbl_name SET column=value WHERE unique_key_col=key_value;
DELETE FROM tbl_name WHERE unique_key_col=key_value;GROUP BY在没有任何写入器的情况下对整个表进行 多次扫描或操作。
使用更高级别的锁,您可以通过支持不同类型的锁来更轻松地调整应用程序,因为锁开销低于行级锁。
除行级锁定之外的选项:
- 版本控制(例如在 MySQL 中用于并发插入的版本),其中一个写入器可以与多个读取器同时使用。这意味着数据库或表支持不同的数据视图,具体取决于访问开始的时间。这个其他常见的术语是 “时间旅行, ” “上写副本, ” 或“按需复制。”
- 在许多情况下,按需复制优于行级锁定。然而,在最坏的情况下,它可以使用比使用普通锁更多的内存。
- 除了使用行级锁,您还可以使用应用程序级锁,例如MySQL提供的
GET_LOCK()和RELEASE_LOCK()在 MySQL 中提供的锁 。这些是咨询锁,因此它们仅适用于相互协作的应用程序。
2. 表锁定问题
InnoDB表使用行级锁定,以便多个会话和应用程序可以同时读取和写入同一个表,而不会相互等待或产生不一致的结果。对于这个存储引擎,避免使用该LOCK TABLES语句,因为它没有提供任何额外的保护,而是降低了并发性。自动行级锁定使这些表适合包含最重要数据的最繁忙的数据库,同时还简化了应用程序逻辑,因为您不需要锁定和解锁表。因此, InnoDB存储引擎是 MySQL 中的默认值。
MySQL 对除 InnoDB. 锁定操作本身没有太多开销。但是因为在任何时候只有一个会话可以写入一个表,为了获得这些其他存储引擎的最佳性能,主要将它们用于经常查询但很少插入或更新的表。
在选择使用InnoDB或不同的存储引擎创建表时 ,请记住表锁定的以下缺点:
- 表锁定允许多个会话同时从一个表中读取,但是如果一个会话想要写入一个表,它必须首先获得独占访问权限,这意味着它可能必须等待其他会话先完成该表。在更新期间,想要访问此特定表的所有其他会话必须等到更新完成。
- 当会话正在等待时,表锁定会导致问题,因为磁盘已满并且在会话继续之前需要有可用空间。在这种情况下,所有想要访问问题表的会话也将处于等待状态,直到有更多的磁盘空间可用。
- 一个
SELECT是需要很长的时间,从更新表的同时,使其他场次出现缓慢或无响应运行防止其他会话声明。当一个会话正在等待获得对表的独占访问权以进行更新时,其他发出SELECT语句的会话 在它后面排队,即使是只读会话也减少了并发性。
3. 并发插入
该MyISAM存储引擎支持并发插入,减少读者和作者之间的竞争给定表:如果一个MyISAM表已经在数据文件中没有孔(中删除的行),一个 INSERT语句可以执行行添加到表的末尾同时该 SELECT语句正在从表中读取行。如果有多个 INSERT语句,它们将与SELECT语句同时排队并按顺序执行 。并发的结果INSERT可能不会立即可见。
所述concurrent_insert系统变量可以被设置为修改并发插入处理。默认情况下,变量设置为AUTO(或 1),并发插入按刚才描述的方式处理。如果 concurrent_insert设置为 NEVER(或 0),则禁用并发插入。如果变量设置为ALWAYS (或 2),则即使对于已删除行的表,也允许在表末尾进行并发插入。另请参阅concurrent_insert系统变量的说明。
如果您使用的是二进制日志,并发插入将转换为CREATE ... SELECTor INSERT ... SELECT语句的正常插入。这样做是为了确保您可以通过在备份操作期间应用日志来重新创建表的精确副本。见第 5.4.4 节,“二进制日志”。此外,对于这些语句,读取锁定被放置在 selected-from 表上,从而阻止插入到该表中。结果是该表的并发插入也必须等待。
使用LOAD DATA,如果您指定 CONCURRENT一个MyISAM 满足并发插入条件的表(即中间不包含空闲块),则其他会话可以在LOAD DATA执行时从该表中检索数据。使用该 CONCURRENT选项会LOAD DATA稍微影响性能 ,即使没有其他会话同时使用该表。
如果指定HIGH_PRIORITY,--low-priority-updates如果服务器是用该选项启动的,它会覆盖该选项的效果 。它还导致不使用并发插入。
为LOCK TABLE,之间的差READ LOCAL并且READ是 READ LOCAL允许不冲突的 INSERT语句(并发插入),而锁被保持来执行。但是,如果您要在持有锁时使用服务器外部的进程来操作数据库,则不能使用此方法。
4. 元数据锁定
MySQL 使用元数据锁定来管理对数据库对象的并发访问并确保数据一致性。元数据锁定不仅适用于表,还适用于模式、存储程序(过程、函数、触发器、计划事件)、表空间、使用GET_LOCK()函数获取的用户锁 。
Performance Schema metadata_locks表公开元数据锁信息,这对于查看哪些会话持有锁、被阻塞等待锁等很有用。
元数据锁定确实涉及一些开销,随着查询量的增加而增加。多个查询尝试访问相同对象的次数越多,元数据争用就会增加。
元数据锁定不是表定义缓存的替代,它的互斥锁和锁不同于 LOCK_open互斥锁。
元数据锁获取
如果给定的锁有多个等待者,则首先满足最高优先级的锁请求,但会出现与max_write_lock_count系统变量相关的异常 。写锁请求的优先级高于读锁请求。但是,如果 max_write_lock_count设置为某个较低的值(比如 10),如果读锁请求已经被传递给 10 个写锁请求,那么读锁请求可能比挂起的写锁请求更受欢迎。通常不会发生此行为,因为 max_write_lock_count默认情况下具有非常大的值。
语句一个一个而不是同时获取元数据锁,并在进程中进行死锁检测。
DML 语句通常按照语句中提到的表的顺序获取锁。
DDL 语句、LOCK TABLES和其他类似语句试图通过按名称顺序获取显式命名表上的锁来减少并发 DDL 语句之间可能的死锁数量。对于隐式使用的表(例如也必须锁定的外键关系中的表),可能会以不同的顺序获取锁。
元数据锁定释放
为确保事务可串行化,服务器不得允许一个会话在另一个会话中未完成的显式或隐式启动事务中使用的表上执行数据定义语言 (DDL) 语句。服务器通过获取事务中使用的表的元数据锁并将这些锁的释放推迟到事务结束来实现这一点。表上的元数据锁可防止更改表的结构。这种锁定方法意味着一个会话中的事务正在使用的表不能在其他会话的 DDL 语句中使用,直到事务结束。
5. 外部锁定
外部锁定是使用文件系统锁定来管理MyISAM多个进程对数据库表的争用。外部锁定用于不能假设单个进程(例如 MySQL 服务器)是唯一需要访问表的进程的情况。这里有些例子:
如果您运行多个使用相同数据库目录的服务器(不推荐),则每个服务器都必须启用外部锁定。
如果您使用myisamchk对表执行表维护操作
MyISAM,则必须确保服务器没有运行,或者服务器启用了外部锁定,以便它根据需要锁定表文件,以便与myisamchk协调以 访问表。使用myisampack打包MyISAM表也是如此 。如果服务器在启用外部锁定的情况下运行,您可以随时使用myisamchk进行读取操作,例如检查表。在这种情况下,如果服务器尝试更新myisamchk正在使用的表 ,则服务器在继续之前等待 myisamchk完成。
如果您使用myisamchk进行修复或优化表等写操作,或者如果您使用 myisampack打包表,则 必须始终确保 mysqld服务器没有使用该表。如果你不停止mysqld,至少在你运行myisamchk之前 做一个 mysqladmin flush-tables。如果服务器和 myisamchk同时访问表,您的表可能会损坏。
在外部锁定生效的情况下,需要访问表的每个进程在继续访问表之前获取表文件的文件系统锁。如果无法获取所有必需的锁,则该进程将被阻止访问该表,直到可以获得锁(在当前持有锁的进程释放它们之后)。
外部锁定会影响服务器性能,因为服务器有时必须等待其他进程才能访问表。
7.5 优化 MySQL 服务器
本节讨论数据库服务器的优化技术,主要处理系统配置而不是调整 SQL 语句。本节中的信息适用于希望确保跨其管理的服务器的性能和可伸缩性的 DBA;用于构建包括设置数据库的安装脚本的开发人员;以及为了开发、测试等而自己运行 MySQL 的人,他们希望最大限度地提高自己的生产力。
1. 优化磁盘I/O
本节介绍了当您可以将更多更快的存储硬件用于数据库服务器时配置存储设备的方法。
磁盘寻道是一个巨大的性能瓶颈。当数据量开始增长到无法进行有效缓存时,这个问题变得更加明显。对于您或多或少随机访问数据的大型数据库,您可以确定至少需要一个磁盘寻道来读取和几个磁盘寻道来写入内容。要尽量减少此问题,请使用寻道时间较短的磁盘。
通过将文件符号链接到不同磁盘或对磁盘进行条带化来增加可用磁盘轴的数量(从而减少搜索开销):
使用符号链接
这意味着,对于
MyISAM表,您将索引文件和数据文件从它们在数据目录中的通常位置符号链接到另一个磁盘(也可能是条带化的)。这使得搜索和读取时间都更好,假设磁盘也没有用于其他目的。不支持将符号链接用于
InnoDB表。但是,可以将InnoDB数据和日志文件放在不同的物理磁盘上。条纹
条带化意味着您有许多磁盘,并将第一个块放在第一个磁盘上,将第二个块放在第二个磁盘上,将*
N*第 -th 个块放在 ( ) 磁盘上,依此类推。这意味着如果您的正常数据大小小于条带大小(或完全对齐),您将获得更好的性能。条带化非常依赖于操作系统和条带大小,因此请使用不同的条带大小对您的应用程序进行基准测试。*N* MOD *number_of_disks*条带化的速度差异 非常依赖于参数。根据您设置条带化参数和磁盘数量的方式,您可能会得到数量级的差异。您必须选择针对随机或顺序访问进行优化。
为了可靠性,您可能希望使用 RAID 0+1(条带化加镜像),但在这种情况下,您需要 2 个 *
N*驱动器来保存 *N*数据驱动器。如果你有钱,这可能是最好的选择。但是,您可能还需要投资一些音量管理软件才能有效地处理它。一个不错的选择是根据数据类型的关键程度来改变 RAID 级别。例如,可以在 RAID 0 磁盘上存储可以重新生成的半重要数据,但将主机信息和日志等真正重要的数据存储在 RAID 0+1 或 RAID *
N磁盘上。N*由于更新奇偶校验位需要时间,如果您有很多写入,RAID 可能会成为问题。您还可以为数据库使用的文件系统设置参数:
如果您不需要知道上次访问文件的时间(这在数据库服务器上不是很有用),您可以使用该
-o noatime选项挂载您的文件系统。这会跳过对文件系统上 inode 中最后访问时间的更新,从而避免了一些磁盘搜索。在许多操作系统上,您可以通过使用
-o async选项挂载文件系统来将其设置为异步更新。如果您的计算机相当稳定,这应该可以为您提供更好的性能而不会牺牲太多的可靠性。(这个标志在 Linux 上默认是打开的。)
在 MySQL 中使用 NFS
在考虑是否将 NFS 与 MySQL 一起使用时,您应该谨慎。潜在问题因操作系统和 NFS 版本而异,包括以下内容:
- 放置在 NFS 卷上的 MySQL 数据和日志文件被锁定且无法使用。例如,在多个 MySQL 实例访问同一数据目录或 MySQL 因断电而异常关闭的情况下,可能会出现锁定问题。NFS 版本 4 通过引入咨询和基于租用的锁定解决了底层锁定问题。但是,不建议在 MySQL 实例之间共享数据目录。
- 由于收到的消息无序或网络流量丢失而导致数据不一致。为避免此问题,请使用 TCP with
hard和intrmount 选项。 - 最大文件大小限制。NFS 版本 2 客户端只能访问文件的最低 2GB(有符号的 32 位偏移)。NFS 版本 3 客户端支持更大的文件(最多 64 位偏移)。支持的最大文件大小还取决于 NFS 服务器的本地文件系统。
在专业 SAN 环境或其他存储系统中使用 NFS 往往比在此类环境之外使用 NFS 提供更高的可靠性。但是,SAN 环境中的 NFS 可能比直接连接或总线连接的非旋转存储慢。
如果您选择使用 NFS,建议使用 NFS 版本 4 或更高版本,因为在部署到生产环境之前彻底测试您的 NFS 设置。
优化服务器硬件
MySQL 中,可以通过两个方面来优化服务器,即硬件和配置参数的优化。通过这些优化方式,可以提高 MySQL 的运行速度。
本节内容需要较全面的知识,可能很难理解,一般只有专业的数据库管理员才能进行这一类的优化。下面为读者介绍优化 MySQL 服务器的方法。
服务器的硬件直接决定着 MySQL 数据库的性能。例如,增加内存和提高硬盘的读写速度,可以提高 MySQL 数据库的查询、更新的速度。
优化服务器硬件的方法主要有以下几种:
- 配置较大的内存
- 配置高速磁盘系统,以减少读盘的等待时间,提高响应速度
- 合理分布磁盘 I/O,把磁盘 I/O 分散在多个设备上,以减少资源竞争,提高并行操作能力
- 配置多处理器,MySQL 是多线程的数据库,多处理器可同时执行多个线程
随着硬件技术的成熟,硬件的价格也随之降低。现在普通的个人电脑都已经配置了 8GB 内存,甚至一些个人电脑配置 16GB 内存。因为内存的读写速度比硬盘的读写速度快。可以在内存中为 MySQL 设置更多的缓冲区,这样可以提高 MySQL 的访问的速度。如果将查询频率很高的记录存储在内存中,那么查询速度就会很快。
如果条件允许,可以将内存提高到 16GB。并且选择 my-innodb-heavy-4G.ini 作为 MySQL 数据库的配置文件。但是,这个配置文件主要支持 InnoDB 存储引擎的表。如果使用 8GB 内存,可以选择 my-huge.ini 作为配置文件。MySQL 所在的计算机最好是专用数据库服务器,这样数据库就可以完全利用该机器的资源。
服务器类型分为 Developer Machine、Server Machine 和 Dedicate MySQL Server Machine。其中 Developer Machine 用来做软件开发的时候使用,数据库占用的资源比较少。后面两者占用的资源比较多,尤其是 Dedicate MySQL Server Machine,其几乎要占用所有的资源。
还可以使用多块磁盘来存储数据。这样可以从多个磁盘上并行读取数据,提高数据库读取数据的速度。通过镜像机制可以将不同计算机上的 MySQL 服务器进行同步,这些 MySQL 服务器中的数据都是一样的。通过不同的 MySQL 服务器来提供数据库服务,这样可以降低单个 MySQL 服务器的压力,从而提高 MySQL 的性能。
优化MySQL参数
- 查看性能参数的方法
MySQL 服务器启动之后,可以使用 SHOW VARIABLES;命令查看系统参数,也可称为静态参数。这些参数是系统默认或者 DBA 调整优化后的参数,可以通过 SET 命令或在配置文件中修改。
- 设置优化性能参数
在 MySQL 中,有些参数直接影响到系统的性能。我们可以通过优化 MySQL 的参数提高资源利用率,从而达到提高 MySQL 服务器性能的目的。以下配置参数都在 my.cnf 或者 my.ini 文件的 [mysqld] 组中。
下面对几个重要参数进行详细介绍。
1)key_buffer_size(针对MyISAM存储引擎)
表示索引缓存的大小,这个参数是对 MyISAM 表性能影响最大的一个参数。值越大,索引进行查询的速度越快。
通过检查状态值 key_read_requests 和 key_reads,可以知道 key_buffer_size 的值是否合理。正常情况下,key_reads / key_read_requests 的比例值需小于 0.01。
2)table_cache(针对MyISAM存储引擎)
表示数据库用户同时打开的表的个数。值越大,能够同时打开的表的个数越多。需要注意的是,这个值不是越大越好,因为同时打开的表太多会影响操作系统的性能。
在设置该参数的时候,可以通过 open_tables 和 opened_tables 变量的值来确定该参数的值。open_tables 参数表示当前打开的表缓存数,opened_tables 参数表示曾经打开的表缓存数。
如果 open_tables 的值已经接近 table_cache 的值,且 opened_tables 还在不断变大,则说明 MySQL 正在将缓存的表释放以容纳新的表,此时可能需要加大table_cache 的值。对于大多数情况,比较适合的值如下:
open_tables / opened_tables >= 0.85 open_tables / table_cache <= 0.95
执行 FLUSH TABLE 操作后,系统会关闭一些当前没有使用的表缓存,因此 FLUSH TABLE 后,open_tables 参数的值会变小,opened_tables 参数的值不会变。
3)query_cache_size
表示查询缓存区的大小。使用查询缓存区可以提高查询的速度。
内存中会为 MySQL 保留部分的缓存区,这些缓存区可以提高 MySQL 的处理速度。
可以从以下几个方面考虑如何设置该参数的大小:
- 查询缓存对 DDL 和 DML 语句的性能的影响
- 查询缓存的内部维护成本
- 查询缓存的命中率以及内存使用率等因素
4)query_cache_type
表示查询缓冲区的开启状态,用于控制查询结果是否放到查询缓存中。这种方式只适用于修改操作少且经常执行相同的查询操作的情况,其默认值为 0。
- 值为 0 表示关闭;
- 值为 1 表示开启;
- 值为 2 表示按要求使用查询缓存区,只有 SELECT 语句中使用了 SQL_CACHE 关键字,查询缓存区才会使用。例如,SELECT SQL_CACHE * FROM student。
5)max_connections
表示数据库的最大连接数,默认值为 100。参数最大值不能超过 16384,即使超过也以 16384 为准。该参数设置过小的最明显特征是出现“Too many connections”错误。当然连接数也不是越大越好,因为这些连接会浪费内存的资源。
6)sort_buffer_size
表示排序缓存区的大小。值越大,排序的速度越快。
7)read_buffer_size
表示为每个线程保留的缓冲区的大小。当线程需要从表中连续读取记录时需要用到这个缓冲区。
8)read_rnd_buffer_size
表示为每个线程保留的缓冲区的大小,与 read_buffer_size 相似。但主要用于存储按特定顺序读取出来的记录。
9)innodb_buffer_pool_size
表示 InnoDB 类型的表和索引的最大缓存。值越大,查询的速度越快。但是这个值太大了也会影响操作系统的性能。
调优参考计算方法:
- val = Innodb_buffer_pool_pages_data / Innodb_buffer_pool_pages_total * 100%
- val > 95% 则考虑增大 innodb_buffer_pool_size, 建议使用物理内存的 75%
- val < 95% 则考虑减小 innodb_buffer_pool_size, 建议设置为:Innodb_buffer_pool_pages_data * Innodb_page_size * 1.05 / (102410241024)
10)innodb_log_file_size
该参数的作用是设置日志组中每个日志文件的大小。该参数在高写入负载尤其是大数据集的情况下很重要,这个值越大则性能相对较高。最好不要超过 innodb_log_files_in_group * innodb_log_file_size 的 0.75。
11)innodb_log_files_in_group
该参数用于指定数据库中有几个日志组,默认为2个,因为有可能出现跨日志的大事务,所以一般来讲,建议使用 3~4 个日志组。
12)innodb_log_buffer_size
该参数的作用是设置日志缓存的大小,一旦提交事务,则将该缓存池中的内容写到磁盘的日志文件上。该参数的设置在中等强度写入负载以及较短事务情况下,一般都可以满足服务器的性能要求。如果服务器负载较大,可以考虑加大该参数的值。一般缓存池中的内存每秒钟写到磁盘一次,所以设置较大会浪费内存空间,一般设置为 8MB~16MB 就足够了。
可以参考 Innodb_os_log_written 的值,如果该值增加过快,可以适当的增加该参数的值。
13)innodb_flush_log_at_trx_commit
表示何时将缓冲区的数据写入日志文件,并且将日志文件写入磁盘中。该参数有 3 个值,分别为 0、1 和 2。
- 值为 0 时,表示每隔 1 秒将数据写入日志文件并将日志文件写入磁盘;
- 值为 1 时,表示每次提交事务时将数据写入日志文件并将日志文件写入磁盘;
- 值为 2 时,表示每次提交事务时将数据写入日志文件,每隔 1 秒将日志文件写入磁盘。
该参数的默认值为 1,是最安全最合理的值。为了保证事务的持久性和一致性,建议将该参数设置为 1。
参数设置的值要根据自己的实际情况来设置,并不是值越大越好,可能设置的数值太大体现不出优化效果,反而造成系统空间被占用,导致操作系统变慢。合理的配置参数可以提高 MySQL 服务器的性能。需要注意的是,配置完参数以后,需要重新启动 MySQL 服务配置才会生效。
2. 使用符号链接
您可以将数据库或表从数据库目录移动到其他位置,并用指向新位置的符号链接替换它们。您可能希望这样做,例如,将数据库移动到具有更多可用空间的文件系统,或通过将表分散到不同磁盘来提高系统速度。
对于InnoDB表,使用语句的DATA DIRECTORY子句CREATE TABLE而不是符号链接,如第 15.6.1.2 节,“在外部创建表”中所述。此新功能是一种受支持的跨平台技术。
推荐的方法是将整个数据库目录符号链接到不同的磁盘。符号链接 MyISAM表仅作为最后的手段。
要确定数据目录的位置,请使用以下语句:
1 | SHOW VARIABLES LIKE 'datadir'; |
在 Unix 上,使用以下过程符号链接数据库:
使用
CREATE DATABASE以下命令创建数据库:1
mysql> CREATE DATABASE mydb1;
Using
CREATE DATABASE在 MySQL 数据目录中创建数据库,并允许服务器使用有关数据库目录的信息更新数据字典。停止服务器以确保在移动新数据库时不会在新数据库中发生任何活动。
将数据库目录移动到有可用空间的某个磁盘。例如,使用tar或 mv。如果您使用的是复制而不是移动数据库目录的方法,请在复制后删除原始数据库目录。
在数据目录中创建一个指向移动的数据库目录的软链接:
1
shell> ln -s /path/to/mydb1 /path/to/datadir
该命令创建一个
mydb1在数据目录中命名的符号链接 。重新启动服务器。
3. 优化内存使用
MySQL 如何使用内存
MySQL 分配缓冲区和缓存以提高数据库操作的性能。默认配置旨在允许 MySQL 服务器在具有大约 512MB RAM 的虚拟机上启动。您可以通过增加某些缓存和缓冲区相关系统变量的值来提高 MySQL 性能。您还可以修改默认配置以在内存有限的系统上运行 MySQL。
下面的列表描述了 MySQL 使用内存的一些方式。在适用的情况下,引用了相关的系统变量。有些项目是存储引擎或特定于功能的。
所述
InnoDB缓冲器池是保持高速缓存的存储区域InnoDB表,索引,及其它辅助缓冲器中的数据。为了提高大量读取操作的效率,缓冲池被划分为 可能包含多行的页面。为了缓存管理的效率,缓冲池被实现为页面的链表;使用LRU算法的变体,很少使用的数据从缓存中老化 。存储引擎接口使优化器能够提供有关记录缓冲区大小的信息,用于优化器估计可能读取多行的扫描。缓冲区大小可以根据估计的大小而变化。
InnoDB使用这种可变大小的缓冲能力来利用行预取,并减少锁存和 B 树导航的开销。所有线程共享
MyISAM密钥缓冲区。的key_buffer_size系统变量决定其大小。所述
myisam_use_mmap系统变量可以被设置为1,使能对所有内存映射MyISAM表。如果内部内存临时表变得太大(使用
tmp_table_size和max_heap_table_size系统变量确定 ),MySQL 会自动将表从内存转换为磁盘格式。从 MySQL 8.0.16 开始,磁盘临时表始终使用 InnoDB 存储引擎。在MySQL性能模式是在低级别监控MySQL服务器执行的功能。Performance Schema 以增量方式动态分配内存,将其内存使用扩展到实际服务器负载,而不是在服务器启动期间分配所需的内存。一旦分配了内存,在服务器重新启动之前它不会被释放。
服务器用于管理客户端连接的每个线程都需要一些特定于线程的空间。以下列表指出了这些以及哪些系统变量控制它们的大小:
- 一个栈 (
thread_stack) - 连接缓冲区 (
net_buffer_length) - 结果缓冲区 (
net_buffer_length)
- 一个栈 (
所有线程共享相同的基本内存。
当不再需要某个线程时,分配给它的内存会被释放并返回给系统,除非该线程返回到线程缓存中。在这种情况下,内存保持分配状态。
每个对表执行顺序扫描的请求都会分配一个读取缓冲区。的
read_buffer_size系统变量决定缓冲器大小。当以任意顺序读取行(例如,按照排序)时, 可以分配随机读取缓冲区以避免磁盘寻道。的
read_rnd_buffer_size系统变量决定缓冲器大小。所有连接都在一次传递中执行,大多数连接甚至可以在不使用临时表的情况下完成。大多数临时表是基于内存的哈希表。具有大行长度(计算为所有列长度的总和)或包含
BLOB列的临时表 存储在磁盘上。大多数执行排序的请求会根据结果集大小分配一个排序缓冲区和零到两个临时文件。
几乎所有的解析和计算都是在线程本地和可重用的内存池中完成的。小项目不需要内存开销,从而避免了正常的缓慢内存分配和释放。仅为意外大的字符串分配内存。
对于每个具有
BLOB列的表,缓冲区会动态扩大以读取更大的BLOB值。如果您扫描一个表,缓冲区将增长到BLOB最大值。MySQL 需要用于表缓存的内存和描述符。所有在用表的处理程序结构都保存在表缓存中,并以“先进先出” (FIFO) 方式进行管理。
一个
FLUSH TABLES语句或 中mysqladmin冲水表命令关闭不在使用一次,并标记所有在用的表被关闭当前正在执行的线程结束时,所有表。这有效地释放了大部分使用中的内存。FLUSH TABLES直到所有表都关闭后才返回。服务器在内存中缓存信息的结果
GRANT,CREATE USER,CREATE SERVER,和INSTALL PLUGIN语句。相应的REVOKE、DROP USER、DROP SERVER和UNINSTALL PLUGIN语句不会释放此内存 ,因此对于执行导致缓存的语句的许多实例的服务器,除非使用 释放,否则缓存内存使用量会增加FLUSH PRIVILEGES。在复制拓扑中,以下设置会影响内存使用,并且可以根据需要进行调整:
max_allowed_packet复制源上 的 系统变量限制了源发送到其副本进行处理的最大消息大小。此设置默认为 64M。- 多线程副本上 的系统变量
replica_pending_jobs_size_max(来自 MySQL 8.0.26)或slave_pending_jobs_size_max(在 MySQL 8.0.26 之前)设置可用于保存等待处理的消息的最大内存量。此设置默认为 128M。内存仅在需要时分配,但如果您的复制拓扑有时处理大型事务,则可能会使用它。这是一个软限制,可以处理更大的交易。 rpl_read_size复制源或副本上 的系统变量控制从二进制日志文件和中继日志文件读取的最小数据量(以字节为单位)。默认值为 8192 字节。为从二进制日志和中继日志文件读取的每个线程分配一个大小为该值的缓冲区,包括源上的转储线程和副本上的协调器线程。- 所述
binlog_transaction_dependency_history_size系统变量限制保持为一个内存历史行哈希的数量。 - 所述
max_binlog_cache_size系统变量指定由单个事务存储器使用的上限。 - 该
max_binlog_stmt_cache_size系统变量指定由语句缓存内存使用的上限。
启用大页面支持
某些硬件/操作系统架构支持大于默认值(通常为 4KB)的内存页。这种支持的实际实现取决于底层硬件和操作系统。由于减少了转换后备缓冲区 (TLB) 未命中,执行大量内存访问的应用程序可以通过使用大页面来获得性能改进。
在 MySQL 中,InnoDB 可以使用大页面为其缓冲池和附加内存池分配内存。
7.6 优化 SQL 语句
数据库应用的核心逻辑是通过 SQL 语句来执行的,无论是直接通过解释器发出,还是通过 API 后台提交。本节中的调优指南有助于加速各种 MySQL 应用程序。该指南涵盖了读取和写入数据的 SQL 操作、一般 SQL 操作的幕后开销以及在特定场景(例如数据库监控)中使用的操作。
1. 优化 SELECT 语句
查询以SELECT 语句的形式执行数据库中的所有查找操作。调整这些语句是重中之重,无论是实现动态网页的亚秒响应时间,还是缩短数小时生成大量夜间报告的时间。
此外SELECT语句,进行查询调谐技术也适用于结构,如 CREATE TABLE...AS SELECT, INSERT INTO...SELECT和WHERE在条款 DELETE的语句。这些语句有额外的性能考虑,因为它们将写操作与面向读的查询操作结合起来。
NDB Cluster 支持 join pushdown 优化,其中合格的 join 被完整发送到 NDB Cluster 数据节点,在那里它可以分布在它们之间并并行执行。
优化查询的主要考虑因素是:
要使慢
SELECT ... WHERE查询更快,首先要检查是否可以添加 索引。在WHERE子句中使用的列上设置索引,以加快评估、过滤和结果的最终检索。为避免浪费磁盘空间,请构建一小组索引以加速应用程序中使用的许多相关查询。索引对于使用连接和 外键等功能引用不同表的查询尤其重要 。您可以使用该
EXPLAIN语句来确定哪些索引用于SELECT. 请参阅 第 8.3.1 节,“MySQL 如何使用索引”和 第 8.8.1 节,“使用 EXPLAIN 优化查询”。隔离和调整查询的任何部分,例如函数调用,这需要过多的时间。根据查询的结构方式,可以为结果集中的每一行调用一次函数,甚至可以为表中的每一行调用一次函数,这大大放大了任何低效率。
尽量减少 查询中的全表扫描次数 ,尤其是对于大表。
通过
ANALYZE TABLE定期使用该语句使表统计信息保持最新 ,以便优化器拥有构建高效执行计划所需的信息。了解特定于每个表的存储引擎的调优技术、索引技术和配置参数。双方
InnoDB并MyISAM有两套准则的实现和维持查询高性能。有关详细信息,请参阅第 8.5.6 节,“优化 InnoDB 查询”和 第 8.6.1 节,“优化 MyISAM 查询”。您可以
InnoDB使用第 8.5.3 节“优化 InnoDB 只读事务”中的技术优化表的 单查询事务 。避免以难以理解的方式转换查询,尤其是在优化器自动执行某些相同转换的情况下。
如果某个基本准则无法轻松解决性能问题,请通过阅读
EXPLAIN计划并调整索引、WHERE子句、连接子句等来调查特定查询的内部细节 。(当您达到一定的专业水平时,阅读EXPLAIN计划可能是您每次查询的第一步。)调整 MySQL 用于缓存的内存区域的大小和属性。通过有效使用
InnoDB缓冲池、MyISAM键缓存和 MySQL 查询缓存,重复查询运行得更快,因为结果是从内存中检索的第二次和后续时间。即使对于使用高速缓存区域快速运行的查询,您仍然可以进一步优化,以便它们需要更少的高速缓存,从而使您的应用程序更具可扩展性。可扩展性意味着您的应用程序可以处理更多并发用户、更大的请求等,而不会导致性能大幅下降。
处理锁定问题,其中查询速度可能会受到同时访问表的其他会话的影响。
WHERE 子句优化
本节讨论可以对处理WHERE子句进行的优化。这些示例使用 SELECT语句,但相同的优化适用WHERE于DELETEand UPDATE语句中的子句 。
您可能想重写查询以加快算术运算,同时牺牲可读性。由于 MySQL 会自动进行类似的优化,因此您通常可以避免这项工作,并将查询保留为更易于理解和可维护的形式。MySQL 执行的一些优化如下:
去除不必要的括号:
1
2((a AND b) AND c OR (((a AND b) AND (c AND d))))
-> (a AND b AND c) OR (a AND b AND c AND d)恒定折叠:
1
2(a<b AND b=c) AND a=5
-> b>5 AND b=c AND a=5
恒定条件去除:
1 | (b>=5 AND b=5) OR (b=6 AND 5=5) OR (b=7 AND 5=6) |
在 MySQL 8.0.14 及更高版本中,这发生在准备阶段而不是优化阶段,这有助于简化连接。
索引使用的常量表达式只计算一次。
从 MySQL 8.0.16 开始,将检查数值类型列与常量值的比较,检查并折叠或删除无效或过时的值:
1
2
3# CREATE TABLE t (c TINYINT UNSIGNED NOT NULL);
SELECT * FROM t WHERE c ≪ 256;
-≫ SELECT * FROM t WHERE 1;COUNT(*)在没有 a 的单个表上WHERE直接从表信息中检索MyISAM和MEMORY表。这也适用于任何NOT NULL仅与一张表一起使用的表达式。早期检测无效的常量表达式。MySQL 很快检测到某些
SELECT语句是不可能的并且不返回任何行。对于连接中的每个表,
WHERE构建一个更简单WHERE的表以获得对该表的快速 评估并尽快跳过行。在查询中的任何其他表之前,首先读取所有常量表。常量表是以下任何一项:
- 空表或只有一行的表。
- 与 a或 索引
WHERE上的子句一起使用的表,其中所有索引部分都与常量表达式进行比较并定义为。PRIMARY KEY``UNIQUE``NOT NULL
以下所有表都用作常量表:
1
2
3SELECT * FROM t WHERE primary_key=1;
SELECT * FROM t1,t2
WHERE t1.primary_key=1 AND t2.primary_key=t1.id;通过尝试所有可能性,可以找到连接表的最佳连接组合。如果
ORDER BYandGROUP BY子句中的所有列 都来自同一个表,则在加入时优先选择该表。如果有一个
ORDER BY子句和一个不同的GROUP BY子句,或者如果ORDER BY或GROUP BY包含来自连接队列中第一个表以外的表的列,则会创建一个临时表。如果使用
SQL_SMALL_RESULT修饰符,MySQL 将使用内存中的临时表。查询每个表索引,并使用最佳索引,除非优化器认为使用表扫描更有效。有一次,根据最佳索引是否跨越表的 30% 来使用扫描,但固定百分比不再决定使用索引还是扫描之间的选择。优化器现在更加复杂,它的估计基于其他因素,例如表大小、行数和 I/O 块大小。
在某些情况下,MySQL 可以从索引中读取行,甚至无需查阅数据文件。如果索引中使用的所有列都是数字,则仅使用索引树来解析查询。
在输出每一行之前,那些不匹配
HAVING子句的将被跳过。
一些非常快的查询示例:
1 | SELECT COUNT(*) FROM tbl_name; |
MySQL 仅使用索引树解析以下查询,假设索引列是数字:
1 | SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val; |
以下查询使用索引以排序顺序检索行,而无需单独的排序传递:
1 | SELECT ... FROM tbl_name |
范围优化
range访问方法使用单个索引来检索包含一个或若干个索引值的时间间隔内表行的子集。它可用于单部分或多部分索引。以下部分描述了优化器使用范围访问的条件。
单个索引
对于单部分索引,索引值区间可以方便地用WHERE子句中的相应条件表示,表示为范围条件而不是区间。
定义如下:
- 对于btree和hash索引,当使用=、<=>、in()、is null或is not null运算符时,索引键与常量值进行比较是一个范围条件。
- 此外,对于B-tree索引,当使用>、<、>=、<=、between、!=或<>这些操作符或者参数是常量字符串且不以通配符开头的like关键字时,索引键与常量的比较是一个范围条件。
- 对于所有索引类型,多个范围条件与或组合在一起,形成一个范围条件。
上述说明中的”常量值”是指下列值之一:
- 查询字符串中的常量
- 来自同一个Join连接的常量或系统表的列
- 不关联子查询的结果
- 完全由上述类型的子表达式组成的任何表达式
举个栗子:
1 | SELECT * FROM t1 WHERE |
假设key1是索引列,nonkey没有索引。
合并的过程如下:
- 提取where条件:
1 | (key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR |
- 移除掉没有索引的列nonkey并且,like查询以%开头的(因为用不上索引,通配符开头定位不到索引page页),然后用True代替它,这样可以保证数据查询不会出错。(因为提取后还得把这些去掉的条件加上再进行过滤,只是为了合并范围,最大化的利用索引)
1 | (key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR |
- 有些条件的结果不是真就是假这种的就可以移除掉用True和False代替
(key1 LIKE 'abcde%' OR TRUE)is always true(key1 < 'uux' AND key1 > 'z')is always false
1 | (key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE) |
- 去掉没必要的True和False
1 | (key1 < 'abc') OR (key1 < 'bar') |
- 将重叠的区间合并
1 | (key1 < 'bar') |
一般而言(如前例所示),用于范围扫描的条件比WHERE子句的限制要小。MySQL执行额外的检查以筛选出满足范围条件而不是完整的WHERE子句的行。
范围条件提取算法可以处理任意深度的嵌套和/或构造,其输出不取决于条件在WHERE子句中出现的顺序。
MySQL不支持为空间索引的范围访问方法合并多个范围。要解决这个限制,可以使用带有相同select语句的union,除非将每个空间谓词放在不同的select中。
复合索引
复合索引的范围条件是单列索引范围条件的扩展。复合索引上的范围条件限制索引行位于一个或多个索引键元组区间。索引键元组区间是在一组索引键元组上定义的,使用索引中的顺序。
举个栗子:
定义为key1(key_part1、key_part2、key_part3)的复合索引,以及以下按键顺序列出的键元组集:
1 | key_part1 key_part2 key_part3 |
*key_part1* = 1 这个条件定义了如下区间:(inf表示∞这个符号)
1 | (1,-inf,-inf) <= (key_part1,key_part2,key_part3) < (1,+inf,+inf) |
该区间覆盖了前面数据集中的第4、第5和第6个元组,并且可以被范围访问方法使用。
相比之下,条件*key_part3* = 'abc'义单个区间,并且不能被范围访问方法使用。
以下描述更详细地说明了复合索引的范围条件如何工作。
- 对于哈希索引,可以使用包含相同值的每个区间。这意味着只能在以下形式的条件下生成区间:
1 | key_part1 cmp const1 |
这里,const1、const2,…是常量,cmp是=、<=>或是空比较运算符之一,条件涵盖了所有索引部分。(也就是说,有n个条件,n部分索引的每个部分对应一个条件。)例如,下面是由三部分组成的哈希索引的范围条件:
1 | key_part1 = 1 AND key_part2 IS NULL AND key_part3 = 'foo' |
对于btree索引,区间可能适用于与和组合的条件,其中每个条件使用=、<=>、为空、>、<、>=、<=、<=、!=、<>、between或like”pattern”(其中”pattern”不以通配符开头)。只要能够确定包含所有符合条件的行的单个索引key元组,就可以使用区间(如果<>或/!=,则为两个区间)。
只要是使用了=,<=>,或者is null操作符,MySQL优化器就尝试使用额外的索引键来确定区间。如果操作符是>,<,>=,<=,!=,<>,between或like,优化器也会使用它,但不会再考虑其他的索引键。对于下面的表达式,优化器使用来自第一次比较的=。它也使用了来自第二次比较的>=,但没有考虑进一步的索引键,也没有使用第三次比较进行区间构造:
1 | key_part1 = 'foo' AND key_part2 >= 10 AND key_part3 > 10 |
单个区间是:
1 | ('foo',10,-inf) < (key_part1,key_part2,key_part3) < ('foo',+inf,+inf) |
创造的区间包含原始条件没有的行是可能的。例如,前面的区间就包含了值(‘foo’,11,0),这并不符合原始条件。
- 如果覆盖区间内包含行集的条件与or相结合,则它们形成一个覆盖区间并集内包含行集的条件。如果条件与and相结合,它们也形成了一个新的条件,这个条件覆盖了它们的区间交际中包含的行集。例如,对于两部分索引的这种情况。
1 | (key_part1 = 1 AND key_part2 < 2) OR (key_part1 > 5) |
那么区间是:
1 | (1,-inf) < (key_part1,key_part2) < (1,2) |
在这个栗子中,第一行的区间使用复合索引中的单列作为左边边界,使用复合索引中的两列作为右边边界。第二行的区间只使用了复合索引中的一列。expalin输出中的key_len列表示所使用的索引键前缀的最大长度。
在一些情况下,key_len列表明了有一个索引键被使用,但这可能不是你期望的。假设key_part1和key_part2可以为空。然后key_len列在以下条件下显示这两个索引键的长度:
1 | key_part1 >= 1 AND key_part2 < 2 |
但是,实际上,条件被转化成如下所示:
1 | key_part1 >= 1 AND key_part2 IS NOT NULL |
有关如何执行优化以组合或消除单个索引上范围条件的间隔的说明,和单个索引一样,对多个列复合索引的范围条件执行类似的步骤。
多值比较的等范围优化
考虑下面的表达式,其中col_name是一个索引列:
1 | col_name IN(val1, ..., valN) |
如果col_name等于多个值中的任何一个,则每个表达式都为true。这些比较是相等范围比较(其中”范围”是单个值)。优化器估计为相等范围比较读取限定行的成本,如下所示:
如果列名称上有唯一索引,则每个范围的行估计值为1,因为最多只能有一行具有给定值。
否则,col_name上的任何索引都是非唯一的,优化器可以使用dive进入索引或index statistics信息来估计每个范围的行数。
对于索引下潜dive,优化器在范围的每一端进行数据挖掘,并使用范围中的行数作为估计值。例如,表达式col_name in(10,20,30)有三个相等的范围,优化器对每个范围进行两次dive以生成行估计。每对数据挖掘都会生成具有给定值的行数的估计值。
两者区别如下:
索引数据dive提供了准确的行估计,但是随着表达式中比较值的数量增加,优化器生成行估计需要更长的时间。
索引index statistics的使用不如索引数据挖掘准确,但允许对大值列表进行更快的行估计。
区分这2种方式主要是因为:
- 查询优化器使用代价估算模型计算每个计划的代价,选择其中代价最小的
- 单表扫描时,需要计算代价,所以单表的索引扫描也需要计算代价
- 单表的计算公式通常是:代价=元组数*IO平均值
- 所以不管是哪种扫描方式,都需要计算元组数
eq_range_index_dive_limit 系统变量使您能够配置优化程序从一个行估计策略切换到另一个行估计策略的值数量。若要允许使用指数下潜来比较n个相等范围,请将eq-range-index-dive-limit设置为n+1。若要禁用统计数据的使用并始终使用索引dive(就是潜入到索引中,利用索引完成元组数的估算),而不考虑n,请将eq-range-index-dive-limit设置为0。
要更新表索引统计信息以获得最佳估计,请使用分析表。
即使在使用索引dive的情况下,也会跳过满足所有这些条件的查询:
- 存在单个索引强制索引提示。其想法是,如果强制使用指数,那么在指数中进行跳水的额外开销就没有什么好处。
- 索引不唯一,不是全文索引。
- 不存在子查询。
- 不存在distinct、group by或order by子句。
这些dive-skipping只适用于单表查询。对于多个表查询(联接),不会跳过索引dive。可以使用optimizer_trace打印出来优化器执行的过程,查看具体的选择。
跳过扫描范围访问方法
考虑以下场景:
1 | CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2)); |
为了执行这个查询,MySQL 可以选择索引扫描来获取所有行(索引包括所有要选择的列),然后应用子句中的f2 > 40 条件WHERE来生成最终结果集。
范围扫描比全索引扫描效率更高,但在这种情况下不能使用,因为f1在第一个索引列上没有条件 。但是,从 MySQL 8.0.13 开始,优化器可以f1使用一种类似于松散索引扫描的称为跳过扫描的方法执行多次范围扫描,对每个值进行一次扫描(请参阅第 8.2.1.17 节,“GROUP BY 优化”):
- 在第一个索引部分的不同值之间跳过,
f1(索引前缀)。 - 针对
f2 > 40剩余索引部分的条件,对每个不同的前缀值执行子范围扫描。
对于前面显示的数据集,算法的运行方式如下:
- 获取第一个关键部分 (
f1 = 1)的第一个不同值。 - 根据第一个和第二个关键部分 (
f1 = 1 AND f2 > 40)构建范围。 - 执行范围扫描。
- 获取第一个键部分 (
f1 = 2)的下一个不同值。 - 根据第一个和第二个关键部分 (
f1 = 2 AND f2 > 40)构建范围。 - 执行范围扫描。
使用此策略会减少访问的行数,因为 MySQL 会跳过不符合每个构造范围的行。此跳过扫描访问方法适用于以下条件:
- 表 T 至少有一个复合索引,其关键部分的形式为 ([A_1, …, A_
k,] B_1, …, B_m, C [, D_1, …, D_n])。关键部分 A 和 D 可以为空,但 B 和 C 必须为非空。 - 查询仅引用一张表。
- 查询不使用
GROUP BY或DISTINCT。 - 查询仅引用索引中的列。
- A_1, …, A_ 上的谓词*
k*必须是等式谓词并且它们必须是常量。这包括IN()操作员。 - 查询必须是连接查询;即,
AND的OR条件:(*cond1*(*key_part1*) OR *cond2*(*key_part1*)) AND (*cond1*(*key_part2*) OR ...) AND ... - C 上必须有一个范围条件。
- D 列上的条件是允许的。D 上的条件必须与 C 上的范围条件一起使用。
EXPLAIN 输出中 指示使用跳过扫描,如下所示:
Using index for skip scan在Extra列表示所使用的松散索引跳跃扫描访问方法。- 如果索引可用于跳过扫描,则该索引应在
possible_keys列中可见。
跳过扫描的使用在优化器跟踪输出中由"skip scan"以下形式的元素指示 :
1 | "skip_scan_range": { |
您可能还会看到一个 "best_skip_scan_summary"元素。如果选择跳过扫描作为最佳范围访问变体, "chosen_range_access_summary"则写入 a。如果选择跳过扫描作为总体最佳访问方法, "best_access_path"则存在元素。
跳过扫描的使用取决于系统变量的 skip_scan标志 optimizer_switch值。请参见第 8.9.2 节,“可切换优化”。默认情况下,此标志为on。要禁用它,请设置skip_scan为 off。
除了使用 optimizer_switch系统变量来控制优化器在会话范围内使用跳过扫描之外,MySQL 还支持优化器提示以在每个语句的基础上影响优化器。
行构造器表达式的范围优化
优化器能够将范围扫描访问方法应用到如下所示查询:
1 | SELECT ... FROM t1 WHERE ( col_1, col_2 ) IN (( 'a', 'b' ), ( 'c', 'd' )); |
以前,要使用范围扫描,必须将查询编写为:
1 | SELECT ... FROM t1 WHERE ( col_1 = 'a' AND col_2 = 'b' ) |
为了让优化器使用范围扫描,查询必须满足以下条件:
- 只使用In()谓词,而不使用In()。
- 在in()谓词的左侧,行构造函数只包含列引用。
- 在in()谓词的右侧,行构造函数只包含运行时常量,这些常量是在执行期间绑定到常量的文本或本地列引用。
- 在in()谓词的右侧,有多个行构造函数。
有关优化器和行构造函数的详细信息参考 Section 8.2.1.19, “Row Constructor Expression Optimization”
限制内存用于范围优化
要控制范围优化器可用的内存,请使用range_optimizer_max_mem_size系统变量:
- 值为0表示”no limit”没有限制。
- 如果值大于0,优化器将跟踪考虑范围访问方法时消耗的内存。如果即将超过指定的限制,则放弃范围访问方法,而考虑其他方法,包括全表扫描。这可能不太理想。如果发生这种情况,将出现以下警告(其中n是当前范围”
range_optimizer_max_mem_size“值):
1 | Warning 3170 Memory capacity of N bytes for |
- 对于UPDATE和DELETE语句,如果优化器返回到完整的表扫描,并且启用了
sql_safe_updates系统变量,则会发生错误而不是警告,因为实际上没有使用键来确定要修改的行。参考Using Safe-Updates Mode (–safe-updates)
4 . 对于超出可用范围优化内存且优化器返回到非最佳计划的单个查询,增加 range_optimizer_max_mem_size可能会提高性能。要估计处理范围表达式所需的内存量,请使用以下准则:
对于如下简单查询,如果range access方法有一个候选键,则每个谓词与大约230个字节组合或使用大约230个字节:
1 | SELECT COUNT(*) FROM t |
类似地,对于如下查询,每个谓词与大约125个字节结合使用:
1 | SELECT COUNT(*) FROM t |
至于使用in()谓词的查询:
1 | SELECT COUNT(*) FROM t |
in()列表中的每个文本值都计为与or组合的谓词。如果有两个in()列表,则谓词的数目与每个列表中的文字值的数目相结合,或是两者的乘积。因此,与前一种情况结合的谓词的数目是m×n。
索引合并优化
该指数合并与多址接入方式检索行 range扫描并合并他们的结果为一体。此访问方法仅合并来自单个表的索引扫描,而不是跨多个表的扫描。合并可以产生其底层扫描的联合、交集或交集。
示例:
1 | SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20; |
Index Merge的已知缺陷
- 如果在
WHERE语句中,存在多层嵌套的AND/OR,MySQL可能不会选择最优的方案,可以尝试通过拆分WHERE子句的条件来进行转换:
1 | (x AND y) OR z => (x OR z) AND (y OR z) |
Index Merger不能应用于全文索引(fulltext index)
Index Merge的EXPLAIN输出
type列的值显示为index_mergekey列显示使用的索引列表key_len列显示这些索引的最大长度(列表)。``` Extra
1
2
3
列显示Index Merge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
的算法:
- `Using intersect(...)`
- `Using union(...)`
- `Using sort_union(...)`
```ruby
mysql> explain select * from test_merge where (col1<10 and col2>50) or col3=50;
+----+-------------+------------+------------+-------------+---------------+---------------+---------+------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------------+---------------+---------------+---------+------+------+----------+----------------------------------------------+
| 1 | SIMPLE | test_merge | NULL | index_merge | idx_1_2,idx_3 | idx_1_2,idx_3 | 5,5 | NULL | 214 | 100.00 | Using sort_union(idx_1_2,idx_3); Using where |
+----+-------------+------------+------------+-------------+---------------+---------------+---------+------+------+----------+----------------------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from test_merge where (col1=10 and col2=50) or col3=50;
+----+-------------+------------+------------+-------------+---------------+---------------+---------+------+------+----------+-----------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------------+---------------+---------------+---------+------+------+----------+-----------------------------------------+
| 1 | SIMPLE | test_merge | NULL | index_merge | idx_1_2,idx_3 | idx_1_2,idx_3 | 10,5 | NULL | 22 | 100.00 | Using union(idx_1_2,idx_3); Using where |
+----+-------------+------------+------------+-------------+---------------+---------------+---------+------+------+----------+-----------------------------------------+
1 row in set, 1 warning (0.00 sec)
Index Merge 算法
Index Merge Intersection 索引合并交集
这种方法适用于WHERE子句中的条件是通过AND结合的不同索引的范围条件时,其中的每个条件都需要满足下列条件之一:
- 如果其中的索引是多列索引,条件中需要包括索引的所有列
key_part1 = const1 AND key_part2 = const2 ... AND key_partN = constN - 在
Innodb表的主键上的范围条件
示例:
1 | SELECT * FROM innodb_table |
索引合并交集算法在所有使用的索引上同时进行扫描,并从扫描结果中生成行的交集
如果查询中的所有列都被使用的索引覆盖,不需要检索所有表行(EXPLAIN输出中的Extra列中包括Using index)。例如这个语句:
SELECT COUNT(*) FROM t1 WHERE key1 = 1 AND key2 = 1;
如果使用的索引没有覆盖查询中所有的行,只有当所有使用的索引的范围条件满足时才检索整个行。
如果合并条件中包括Innodb表主键索引条件,主键并不用来检索数据,而是用来筛选使用其他条件检索出的行。 # 就是先通过其他的范围条件筛选出一部分数据,在从这部分数据中,通过主键来筛选出最终的结果
Index Merge Union 索引合并并集*
这种方法适用于WHERE子句中的条件是通过OR结合的不同索引的范围条件时,其中的每个条件都需要满足下列条件之一:
- 如果其中的索引是多列索引,条件中需要包括索引的所有列
key_part1 = const1 AND key_part2 = const2 ... AND key_partN = constN - 在
Innodb表的主键上的范围条件 - 适用于
Index Merger intersection算法的条件
示例:
1 | SELECT * FROM t1 |
Index Merge Sort_Union
这种方法适用于WHERE子句中的条件是通过OR结合的不同索引的范围条件,但是不能使用Index Merge Union算法的情景
示例:
1 | SELECT * FROM tbl_name |
sort_union和union算法的区别是,sort_union必须在返回行数据前先获取行ID并对行ID进行排序。
禁用Index Merge
在optimizer_swith中有4个关于Index Merge的变量:
index_merge,index_merge_intersection,index_merge_union,index_merge_sort_union
默认情况下都是启用的。要单独启用某个算法,设置index_merge=off,并将相应的标志设置为on
问题答案
- 什么是
Index Merge?Index Merge的限制有哪些? 如果查询中使用到了不同的索引,可以对不同索引的条件分别进行范围扫描,然后将扫描结果合并得到最终的结果,这就是Index Merge。 限制:只能合并同一个表的索引扫描结果,不能跨表合并。此外,无法对fulltext索引进行合并 - 如何查看语句是否使用了
Index Merge?EXPLAIN中type列的值为index_merge表示使用了索引合并。根据索引合并算法的不同,会在Extra列中显示Using intersect/union/sort_union Index Merge有哪几种?分别适用于那些情景? 3种:Intersection,Union,Sort_unionIntersection:使用AND结合的关于不同索引的条件(普通索引的等值表达式或者主键索引的范围表达式)Union和Sort Union:使用OR结合的关于不同索引的范围条件 区别:当条件为普通索引的等值表达式或者主键索引的范围表达式时,可以使用Union。其他不符合条件的只能使用Sort Union如果包括多列索引,在范围条件中需要包括索引中的所有列。- 如何控制优化器是否使用
Index Merge在optimizer_swith中有4个关于Index Merge的变量:index_merge,index_merge_intersection,index_merge_union,index_merge_sort_union默认情况下都是启用的。要单独启用某个算法,设置index_merge=off,并将相应的标志设置为on
作者:月饮沙 链接:https://www.jianshu.com/p/34bd66629355
Hash Join 优化
从 MySQL 8.0.18 开始,MySQL 对每个连接具有等连接条件的任何查询使用哈希连接,并且其中没有可应用于任何连接条件的索引,例如:
1 | SELECT * |
当有一个或多个索引可用于单表谓词时,也可以使用散列连接。
散列连接通常比以前版本的 MySQL 中使用的块嵌套循环算法(请参阅块嵌套循环连接算法)更快,并且旨在用于这种情况 。从 MySQL 8.0.20 开始,删除了对块嵌套循环的支持,并且服务器在以前使用块嵌套循环的任何地方使用哈希连接。
散列连接也用于涉及多个连接的查询,只要每对表的至少一个连接条件是等连接,就像这里显示的查询:
1 | SELECT * FROM t1 |
在刚刚显示的使用内部联接的情况下,任何不是等联接的额外条件都将在联接执行后作为过滤器应用。(对于外部联接,例如左联接、半联接和反联接,它们作为联接的一部分打印。)这可以在以下输出中看到EXPLAIN:
1 | mysql> EXPLAIN FORMAT=TREE |
从刚刚显示的输出中也可以看出,多个散列连接可以(并且已经)用于具有多个等连接条件的连接。
在 MySQL 8.0.20 之前,如果任何一对连接表没有至少一个等连接条件,则无法使用散列连接,并且使用较慢的块嵌套循环算法。在 MySQL 8.0.20 及更高版本中,在这种情况下使用散列连接,如下所示:
1 | mysql> EXPLAIN FORMAT=TREE |
散列连接也适用于笛卡尔积——也就是说,当没有指定连接条件时,如下所示:
1 | mysql> EXPLAIN FORMAT=TREE |
在 MySQL 8.0.20 及更高版本中,连接不再需要包含至少一个等连接条件才能使用散列连接。这意味着可以使用散列连接优化的查询类型包括以下列表(带有示例):
内部非等连接:
1
2
3
4
5
6
7mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 JOIN t2 ON t1.c1 < t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t1.c1 < t2.c1) (cost=4.70 rows=12)
-> Inner hash join (no condition) (cost=4.70 rows=12)
-> Table scan on t2 (cost=0.08 rows=6)
-> Hash
-> Table scan on t1 (cost=0.85 rows=6)半连接:
1
2
3
4
5
6
7
8
9
10mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1
-> WHERE t1.c1 IN (SELECT t2.c2 FROM t2)\G
*************************** 1. row ***************************
EXPLAIN: -> Nested loop inner join
-> Filter: (t1.c1 is not null) (cost=0.85 rows=6)
-> Table scan on t1 (cost=0.85 rows=6)
-> Single-row index lookup on <subquery2> using <auto_distinct_key> (c2=t1.c1)
-> Materialize with deduplication
-> Filter: (t2.c2 is not null) (cost=0.85 rows=6)
-> Table scan on t2 (cost=0.85 rows=6)反连接:
1
2
3
4
5
6
7
8
9mysql> EXPLAIN FORMAT=TREE SELECT * FROM t2
-> WHERE NOT EXISTS (SELECT * FROM t1 WHERE t1.col1 = t2.col1)\G
*************************** 1. row ***************************
EXPLAIN: -> Nested loop antijoin
-> Table scan on t2 (cost=0.85 rows=6)
-> Single-row index lookup on <subquery2> using <auto_distinct_key> (c1=t2.c1)
-> Materialize with deduplication
-> Filter: (t1.c1 is not null) (cost=0.85 rows=6)
-> Table scan on t1 (cost=0.85 rows=6)左外连接:
1
2
3
4
5
6mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> Left hash join (t2.c1 = t1.c1) (cost=3.99 rows=36)
-> Table scan on t1 (cost=0.85 rows=6)
-> Hash
-> Table scan on t2 (cost=0.14 rows=6)右外连接(注意 MySQL 将所有右外连接重写为左外连接):
1
2
3
4
5
6mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 RIGHT JOIN t2 ON t1.c1 = t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> Left hash join (t1.c1 = t2.c1) (cost=3.99 rows=36)
-> Table scan on t2 (cost=0.85 rows=6)
-> Hash
-> Table scan on t1 (cost=0.14 rows=6)
默认情况下,MySQL 8.0.18 及更高版本尽可能使用散列连接。可以使用BNL和 NO_BNL优化器提示之一来控制是否使用散列连接 。
MySQL 8.0.18 支持 hash_join=on或 hash_join=off作为optimizer_switch服务器系统变量设置的一部分 以及优化器提示 HASH_JOIN或 NO_HASH_JOIN。在 MySQL 8.0.19 及更高版本中,这些不再有任何影响。)
可以使用join_buffer_size系统变量控制散列连接的内存使用情况 ;散列连接不能使用比这个数量更多的内存。当散列连接所需的内存超过可用量时,MySQL 会使用磁盘上的文件来处理此问题。如果发生这种情况,您应该意识到如果散列连接无法放入内存并且它创建的文件多于设置的文件,则连接可能不会成功 open_files_limit。为避免此类问题,请进行以下任一更改:
- 增加
join_buffer_size以使散列连接不会溢出到磁盘。 - 增加
open_files_limit。
从 MySQL 8.0.18 开始,哈希连接的连接缓冲区是增量分配的;因此,您可以在join_buffer_size不分配大量 RAM 的小查询的情况下设置 更高,但外连接分配整个缓冲区。在 MySQL 8.0.20 及更高版本中,散列连接也用于外连接(包括反连接和半连接),因此这不再是问题。
引擎条件下推优化
这种优化提高了非索引列和常量之间直接比较的效率。在这种情况下,条件被“下推”到存储引擎进行评估。此优化只能由NDB存储引擎使用。
对于 NDB Cluster,这种优化可以消除在集群的数据节点和发出查询的 MySQL 服务器之间通过网络发送不匹配行的需要,并且可以将查询速度提高 5 到 10 倍。其中条件下推可能但未使用。
假设一个 NDB Cluster 表定义如下:
1 | CREATE TABLE t1 ( |
引擎条件下推可用于查询,例如此处显示的查询,其中包括非索引列和常量之间的比较:
1 | SELECT a, b FROM t1 WHERE b = 10; |
在输出中可以看到引擎条件下推的使用EXPLAIN:
1 | mysql> EXPLAIN SELECT a, b FROM t1 WHERE b = 10\G |
但是,引擎条件下推不能 用于以下查询:
1 | SELECT a,b FROM t1 WHERE a = 10; |
引擎条件下推在此处不适用,因为列上存在索引a。(索引访问方法会更有效,因此会优先选择条件下推。)
当使用>or<运算符将索引列与常量进行比较时,也可以使用引擎条件下推 :
1 | mysql> EXPLAIN SELECT a, b FROM t1 WHERE a < 2\G |
其他支持的发动机条件下推比较包括以下内容:
*column* [NOT] LIKE *pattern**
pattern*必须是包含要匹配的模式的字符串文字;有关语法,请参阅第 12.8.1 节,“字符串比较函数和运算符”。*column* IS [NOT] NULL*column* IN (*value_list*)中的每一项都*
value_list* 必须是一个常量、字面值。*column* BETWEEN *constant1* AND *constant2**
constant1*并且 *constant2*每个都必须是常量、文字值。
限制。 发动机状态下推受到以下限制:
- 引擎条件下推仅由
NDB存储引擎支持 。 - 在 NDB 8.0.18 之前,列可以与仅计算为常量值的常量或表达式进行比较。在 NDB 8.0.18 及更高版本中,只要列的类型完全相同,包括相同的符号、长度、字符集、精度和比例,就可以相互比较列。
- 比较中使用的列不能是
BLOB或TEXT类型中的任何 一种。此排除也扩展到JSON、BIT和ENUM列。 - 要与列进行比较的字符串值必须使用与列相同的排序规则。
- 不直接支持联接;涉及多个表的条件在可能的情况下单独推送。使用扩展
EXPLAIN输出来确定实际下推的条件。
以前,引擎条件下推仅限于引用条件被推送到的同一表中的列值的术语。从 NDB 8.0.16 开始,查询计划中较早的表中的列值也可以从推送条件中引用。这减少了在连接处理期间必须由 SQL 节点处理的行数。过滤也可以在 LDM 线程中并行执行,而不是在单个mysqld 进程中执行。这有可能显着提高查询性能。
索引条件下推优化
索引条件下推 (ICP) 是针对 MySQL 使用索引从表中检索行的情况的优化。在没有 ICP 的情况下,存储引擎遍历索引以定位基表中的行并将它们返回给 MySQL 服务器,该服务器评估WHERE行的条件。启用 ICP,并且如果WHERE可以仅使用索引中的列来评估部分 条件,则 MySQL 服务器会推送这部分WHERE条件下降到存储引擎。然后,存储引擎使用索引条目评估推送的索引条件,并且仅当满足该条件时才从表中读取行。ICP可以减少存储引擎访问基表的次数和MySQL服务器访问存储引擎的次数。
索引条件下推优化的适用性取决于以下条件:
- 当需要访问全表行时
range, ICP 用于ref、eq_ref、 和ref_or_null访问方法。 - ICP可以用于
InnoDB和MyISAM表,包括分区InnoDB和MyISAM表。 - 对于
InnoDB表,ICP 仅用于二级索引。ICP 的目标是减少全行读取的次数,从而减少 I/O 操作。对于InnoDB聚集索引,完整的记录已经读入InnoDB缓冲区。在这种情况下使用 ICP 不会减少 I/O。 - 在虚拟生成的列上创建的二级索引不支持 ICP。
InnoDB支持虚拟生成列上的二级索引。 - 不能下推引用子查询的条件。
- 不能下推引用存储函数的条件。存储引擎不能调用存储的函数。
- 无法按下触发条件。(有关触发条件的信息,请参阅 第 8.2.2.3 节,“使用 EXISTS 策略优化子查询”。)
要了解此优化的工作原理,请首先考虑未使用索引条件下推时索引扫描的进行方式:
- 获取下一行,首先通过读取索引元组,然后通过使用索引元组定位并读取整个表行。
- 测试
WHERE适用于该表的条件部分。根据测试结果接受或拒绝该行。
使用索引条件下推,扫描是这样进行的:
- 获取下一行的索引元组(但不是完整的表行)。
- 测试
WHERE适用于该表的条件部分,并且可以仅使用索引列进行检查。如果不满足条件,则处理下一行的索引元组。 - 如果满足条件,则使用索引元组定位并读取整个表行。
- 测试
WHERE适用于该表的条件的其余部分。根据测试结果接受或拒绝该行。
EXPLAIN使用索引条件下推时,输出显示 Using index condition在 Extra列中。它不显示,Using index 因为当必须读取完整的表行时这不适用。
假设一个表包含有关人员及其地址的信息,并且该表的索引定义为 INDEX (zipcode, lastname, firstname)。如果我们知道一个人的zipcode价值但不确定姓氏,我们可以这样搜索:
1 | SELECT * FROM people |
MySQL 可以使用索引来扫描带有 zipcode='95054'. 第二部分 ( lastname LIKE '%etrunia%') 不能用于限制必须扫描的行数,因此如果没有索引条件下推,此查询必须检索所有具有 zipcode='95054'.
使用索引条件下推,MySQLlastname LIKE '%etrunia%'在读取完整表行之前检查该 部分。这避免读取与匹配zipcode条件但不匹配条件的 索引元组对应的完整行 lastname。
默认情况下启用索引条件下推。可以optimizer_switch通过设置index_condition_pushdown 标志来使用系统变量 来控制它 :
1 | SET optimizer_switch = 'index_condition_pushdown=off'; |
嵌套循环连接算法
MySQL 使用嵌套循环算法或它的变体来执行表之间的连接。
嵌套循环连接算法
一个简单的嵌套循环连接 (NLJ) 算法一次一个地从循环中的第一个表中读取行,将每一行传递给处理连接中下一个表的嵌套循环。这个过程重复多少次,只要有剩余的表要连接。
假设要使用以下连接类型执行三个表t1、t2和 之间 t3的连接:
1 | Table Join Type |
如果使用简单的 NLJ 算法,则连接的处理方式如下:
1 | for each row in t1 matching range { |
由于 NLJ 算法一次将一行从外循环传递到内循环,因此它通常会多次读取在内循环中处理的表。
块嵌套循环连接算法
块嵌套循环 (BNL) 连接算法使用缓冲外部循环中读取的行来减少必须读取内部循环中的表的次数。例如,如果将 10 行读入缓冲区并将缓冲区传递到下一个内部循环,则可以将内部循环中读取的每一行与缓冲区中的所有 10 行进行比较。这将必须读取内表的次数减少了一个数量级。
在 MySQL 8.0.18 之前,该算法适用于无法使用索引的 equi-joins;在 MySQL 8.0.18 及更高版本中,在这种情况下采用散列连接优化。从 MySQL 8.0.20 开始,MySQL 不再使用块嵌套循环,并且在以前使用块嵌套循环的所有情况下都使用哈希连接。
MySQL 连接缓冲具有以下特点:
- 当连接类型为
ALLor 时index(换句话说,当无法使用可能的键,并且分别对数据或索引行进行了完整扫描) 或 时,可以使用连接缓冲range。缓冲的使用也适用于外部联接,如第 8.2.1.12 节,“阻止嵌套循环和批处理键访问联接”中所述。 - 永远不会为第一个非常量表分配连接缓冲区,即使它是
ALL或 类型index。 - 只有对连接感兴趣的列才存储在其连接缓冲区中,而不是整行。
- 的
join_buffer_size系统变量来确定每个的大小联接缓冲液用于处理查询。 - 为每个可以缓冲的连接分配一个缓冲区,因此可以使用多个连接缓冲区处理给定的查询。
- 连接缓冲区在执行连接之前分配并在查询完成后释放。
对于之前为 NLJ 算法(无缓冲)描述的示例连接,使用连接缓冲按如下方式完成连接:
1 | for each row in t1 matching range { |
如果*S是每个所存储的大小 t1,t2在组合联接缓冲液和C*在缓冲器中的组合的数量,次表的数量t3被扫描的是:
1 | (S * C)/join_buffer_size + 1 |
数t3扫描降低为价值join_buffer_size 时增加,最高可达点 join_buffer_size是大到足以容纳所有上一行组合。在这一点上,将其变大并不能提高速度。
嵌套连接优化
表达连接的语法允许嵌套连接。以下讨论参考第 13.2.10.2 节,“JOIN 子句”中描述的连接语法 。
*table_factor与 SQL 标准相比, 的 语法得到了扩展。后者只接受table_reference,而不是一对括号内的列表。如果我们将table_reference*项目列表中的每个逗号视为等效于内部连接,那么这是一个保守的扩展 。例如:
1 | SELECT * FROM t1 LEFT JOIN (t2, t3, t4) |
相当于:
1 | SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) |
在 MySQL 中,CROSS JOIN在语法上等价于INNER JOIN; 它们可以相互替换。在标准 SQL 中,它们不是等价的。 INNER JOIN与ON子句一起使用 ;CROSS JOIN否则使用。
通常,在仅包含内部连接操作的连接表达式中可以忽略括号。考虑这个连接表达式:
1 | t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL) |
删除括号和左侧的分组操作后,该连接表达式转换为以下表达式:
1 | (t1 LEFT JOIN t2 ON t1.a=t2.a) LEFT JOIN t3 |
然而,这两个表达式并不等价。看到这一点,假设表t1, t2以及t3具有以下状态:
- 表
t1包含行(1),(2) - 表
t2包含行(1,101) - 表
t3包含行(101)
在这种情况下,第一个表达式返回包含行(1,1,101,101), 的结果集(2,NULL,NULL,NULL),而第二个表达式返回行(1,1,101,101), (2,NULL,NULL,101):
1 | mysql> SELECT * |
在以下示例中,外连接操作与内连接操作一起使用:
1 | t1 LEFT JOIN (t2, t3) ON t1.a=t2.a |
该表达式不能转换为以下表达式:
1 | t1 LEFT JOIN t2 ON t1.a=t2.a, t3 |
对于给定的表状态,这两个表达式返回不同的行集:
1 | mysql> SELECT * |
因此,如果我们在带有外连接运算符的连接表达式中省略括号,我们可能会更改原始表达式的结果集。
更准确地说,我们不能忽略左外连接操作的右操作数和右连接操作的左操作数中的括号。换句话说,我们不能忽略外连接操作的内表表达式的括号。可以忽略其他操作数(外部表的操作数)的括号。
以下表达式:
1 | (t1,t2) LEFT JOIN t3 ON P(t2.b,t3.b) |
对于任何表t1,t2,t3和P属性上的任何条件都 等效于此表达式 t2.b 和t3.b:
1 | t1, t2 LEFT JOIN t3 ON P(t2.b,t3.b) |
每当连接表达式 ( joined_table)中连接操作的执行顺序不是从左到右时,我们就谈论嵌套连接。考虑以下查询:
1 | SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b) ON t1.a=t2.a |
这些查询被认为包含这些嵌套连接:
1 | t2 LEFT JOIN t3 ON t2.b=t3.b |
在第一个查询中,嵌套连接由左连接操作构成。在第二个查询中,它由内连接操作构成。
在第一个查询中,括号可以省略:连接表达式的语法结构规定了连接操作的相同执行顺序。对于第二个查询,不能省略括号,尽管没有它们可以明确解释此处的连接表达式。在我们的扩展语法中,(t2, t3)第二个查询中的括号是必需的,尽管理论上可以在没有它们的情况下解析查询:我们仍然会为查询提供明确的句法结构,因为LEFT JOIN和ON 扮演表达式的左右分隔符的角色(t2,t3)。
前面的示例演示了以下几点:
- 对于仅涉及内连接(而不是外连接)的连接表达式,可以删除括号并从左到右计算连接。事实上,可以按任何顺序评估表。
- 通常,对于外连接或外连接与内连接的混合,情况并非如此。去掉括号可能会改变结果。
带有嵌套外连接的查询以与带有内连接的查询相同的管道方式执行。更确切地说,利用了嵌套循环连接算法的一种变体。回忆嵌套循环连接执行查询的算法(参见第 8.2.1.7 节,“嵌套循环连接算法”)。假设对 3 个表的连接查询T1,T2,T3具有以下形式:
1 | SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2) |
在这里,P1(T1,T2)和 P2(T3,T3)是一些连接条件(在表达式上),而P(T1,T2,T3)是表列上的条件T1,T2,T3。
嵌套循环连接算法将以下列方式执行此查询:
1 | FOR each row t1 in T1 { |
符号t1||t2||t3表示通过连接的行的列构成的行 t1,t2和 t3。在下面的一些示例中, NULL表名出现的地方表示NULL用于该表的每一列的行。例如,t1||t2||NULL 表示通过连接行t1和的列构造的行t2,并且 NULL对于 的每一列 t3。据说这样的行是 NULL补充的。
现在考虑一个带有嵌套外连接的查询:
1 | SELECT * FROM T1 LEFT JOIN |
对于此查询,修改嵌套循环模式以获得:
1 | FOR each row t1 in T1 { |
通常,对于外连接操作中第一个内表的任何嵌套循环,都会引入一个标志,该标志在循环之前关闭并在循环之后检查。当从外部表中的当前行找到表示内部操作数的表中的匹配项时,该标志打开。如果在循环周期结束时标志仍然关闭,则没有找到外部表的当前行的匹配项。在这种情况下,行由NULL内部表的列的值补充 。结果行被传递到输出的最终检查或下一个嵌套循环,但前提是该行满足所有嵌入的外连接的连接条件。
在示例中,嵌入了由以下表达式表示的外连接表:
1 | (T2 LEFT JOIN T3 ON P2(T2,T3)) |
对于具有内连接的查询,优化器可以选择不同顺序的嵌套循环,例如:
1 | FOR each row t3 in T3 { |
对于具有外连接的查询,优化器只能选择这样一种顺序,即外表的循环先于内表的循环。因此,对于我们使用外连接的查询,只有一种嵌套顺序是可能的。对于以下查询,优化器评估两个不同的嵌套。在两个嵌套中, T1必须在外循环中处理,因为它用于外连接。T2和 T3在内连接中使用,因此必须在内循环中处理连接。然而,由于该连接是内部联接,T2并 T3可以以任何顺序进行处理。
1 | SELECT * T1 LEFT JOIN (T2,T3) ON P1(T1,T2) AND P2(T1,T3) |
一个嵌套评估T2,然后 T3:
1 | FOR each row t1 in T1 { |
另一个嵌套评估T3,然后 T2:
1 | FOR each row t1 in T1 { |
在讨论内连接的嵌套循环算法时,我们省略了一些对查询执行性能影响可能很大的细节。我们没有提到所谓的 “下推”条件。假设我们的 WHERE条件 P(T1,T2,T3)可以用一个联合公式表示:
1 | P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3). |
在这种情况下,MySQL 实际上使用以下嵌套循环算法来执行带有内连接的查询:
1 | FOR each row t1 in T1 such that C1(t1) { |
你看,每个合取的C1(T1), C2(T2),C3(T3)是最内环的推到最外环的地方进行评估。如果C1(T1)是一个非常严格的条件,这个条件下推可能会大大减少从表T1 传递到内部循环的行数。因此,查询的执行时间可能会大大缩短。
对于带有外部连接的查询,WHERE 只有在发现外部表中的当前行与内部表中的匹配项后才检查条件。因此,将条件推出内部嵌套循环的优化不能直接应用于具有外部连接的查询。在这里,我们必须引入条件下推谓词,这些谓词由遇到匹配时打开的标志保护。
回想一下这个带有外连接的例子:
1 | P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3) |
对于该示例,使用受保护下推条件的嵌套循环算法如下所示:
1 | FOR each row t1 in T1 such that C1(t1) { |
通常,可以从连接条件(例如P1(T1,T2)和 )中 提取下推谓词P(T2,T3)。在这种情况下,下推谓词也由一个标志保护,该标志防止检查谓词以查找NULL由相应外连接操作生成的-complemented 行。
如果由WHERE条件中的谓词引发,则禁止通过键从一个内部表访问同一嵌套连接中的另一个内部表。
外连接优化
外连接包括LEFT JOIN和 RIGHT JOIN。
MySQL 实现如下: *A* LEFT JOIN *B* *join_specification*
- 表*
B设置为依赖表A*以及依赖的所有表 *A*。 - 表*
A被设置为依赖于条件B*中使用的所有表(除了)LEFT JOIN。 - 该
LEFT JOIN条件用于决定如何从表中的行 *B*。(换句话说,WHERE不使用子句中的任何条件。) - 所有标准的连接优化都会被执行,除了一个表总是在它依赖的所有表之后被读取。如果存在循环依赖,则会发生错误。
WHERE执行 所有标准优化。- 如果有*
A与WHERE子句匹配的行,但没有B*与ON条件匹配的 *B*行,则会生成一个额外的 行,所有列都设置为NULL。 - 如果您使用
LEFT JOIN查找不以某种存在表中的行,你有下面的测试:*col_name* IS NULL在WHERE部分地方 *col_name*是声明为一列NOT NULL,MySQL的停止搜索更多的行(特定组合键),它已发现后符合LEFT JOIN条件的一行。
该RIGHT JOIN实施类似于的LEFT JOIN并反转表的作用。
对于 一个 LEFT JOIN,如果WHERE生成的NULL行的 条件始终为 false ,则将LEFT JOIN更改为内部联接。例如, WHERE如果条款是在下面的查询错误的t2.column1是 NULL:
1 | SELECT * FROM t1 LEFT JOIN t2 ON (column1) WHERE t2.column2=5; |
因此,将查询转换为内部联接是安全的:
1 | SELECT * FROM t1, t2 WHERE t2.column2=5 AND t1.column1=t2.column1; |
在 MySQL 8.0.14 及更高版本中,WHERE 由常量文字表达式产生的琐碎条件在准备期间被删除,而不是在优化的后期阶段,此时连接已经被简化。早期去除琐碎条件允许优化器将外连接转换为内连接;这可能会导致对具有在WHERE 子句中包含琐碎条件的外连接的查询的改进计划,例如:
1 | SELECT * FROM t1 LEFT JOIN t2 ON condition_1 WHERE condition_2 OR 0 = 1 |
优化器现在在准备过程中看到 0 = 1 总是假的,使OR 0 = 1冗余,并删除它,留下这个:
1 | SELECT * FROM t1 LEFT JOIN t2 ON condition_1 where condition_2 |
现在优化器可以将查询重写为内部联接,如下所示:
1 | SELECT * FROM t1 JOIN t2 WHERE condition_1 AND condition_2 |
现在优化器可以在表t2之前使用表,t1如果这样做会产生更好的查询计划。要提供有关表连接顺序的提示,请使用优化器提示
外连接简化
FROM在许多情况下,查询子句中的 表表达式被简化。
在解析器阶段,具有右外连接操作的查询被转换为仅包含左连接操作的等效查询。在一般情况下,执行转换使得此右连接:
1 | (T1, ...) RIGHT JOIN (T2, ...) ON P(T1, ..., T2, ...) |
变成这个等价的左连接:
1 | (T2, ...) LEFT JOIN (T1, ...) ON P(T1, ..., T2, ...) |
该表单的所有内部连接表达式T1 INNER JOIN T2 ON P(T1,T2)都被替换为 list T1,T2,P(T1,T2)作为WHERE条件连接(或嵌入连接的连接条件,如果有的话)。
当优化器评估外连接操作的计划时,它只考虑对于每个这样的操作,在访问内表之前访问外表的计划。优化器的选择是有限的,因为只有这样的计划才能使用嵌套循环算法执行外连接。
考虑这种形式的查询,其中R(T2) 大大缩小了 table 中匹配行的数量 T2:
1 | SELECT * T1 LEFT JOIN T2 ON P1(T1,T2) |
如果查询按写入的方式执行,优化器别无选择,只能先访问限制较少的表,T1然后再访问限制 较多的表 T2,这可能会产生非常低效的执行计划。
相反,如果WHERE条件为空拒绝,MySQL 会将查询转换为没有外连接操作的查询。(也就是说,它将外连接转换为内连接。)如果条件计算为FALSE或UNKNOWN为NULL操作生成的任何 补充行,则称该条件为外连接操作拒绝空值 。
因此,对于这个外连接:
1 | T1 LEFT JOIN T2 ON T1.A=T2.A |
诸如此类的条件是拒绝空值的,因为它们对于任何NULL补充行( T2列设置为NULL)都不成立:
1 | T2.B IS NOT NULL |
诸如此类的条件不会被 null 拒绝,因为它们可能适用于NULL-complemented 行:
1 | T2.B IS NULL |
检查外部联接操作的条件是否为空拒绝的一般规则很简单:
- 它的形式为
A IS NOT NULL,其中A是任何内部表的属性 - 它是一个包含对内部表的引用的谓词,
UNKNOWN当其参数之一为NULL - 它是一个包含拒绝空的条件作为合词的合词
- 它是空拒绝条件的分离
对于查询中的一个外部联接操作,条件可以是空拒绝的,而对于另一个则不是空拒绝的。在这个查询中,WHERE条件对于第二个外连接操作是空拒绝的,但对于第一个不是空拒绝的:
1 | SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A |
如果WHERE查询中的外连接操作的条件为空拒绝,则外连接操作将替换为内连接操作。
例如,在前面的查询中,第二个外连接是拒绝空的,可以用内连接替换:
1 | SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A |
对于原始查询,优化器仅评估与单个表访问顺序兼容的计划 T1,T2,T3。对于重写的查询,它另外考虑了访问顺序 T3,T1,T2。
一个外连接操作的转换可能会触发另一个外连接操作的转换。因此,查询:
1 | SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A |
首先转换为查询:
1 | SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A |
这相当于查询:
1 | SELECT * FROM (T1 LEFT JOIN T2 ON T2.A=T1.A), T3 |
剩余的外连接操作也可以被内连接替换,因为条件T3.B=T2.B 是空拒绝的。这会导致查询完全没有外部联接:
1 | SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3 |
有时优化器会成功替换嵌入的外连接操作,但无法转换嵌入的外连接。以下查询:
1 | SELECT * FROM T1 LEFT JOIN |
转换为:
1 | SELECT * FROM T1 LEFT JOIN |
这只能重写为仍然包含嵌入外连接操作的表单:
1 | SELECT * FROM T1 LEFT JOIN |
任何在查询中转换嵌入外连接操作的尝试都必须考虑嵌入外连接的连接条件和 WHERE条件。在这个查询中,WHERE嵌入外连接的 条件不是空拒绝,但是嵌入外连接的连接条件T2.A=T1.A AND T3.C=T1.C是空拒绝的:
1 | SELECT * FROM T1 LEFT JOIN |
因此,查询可以转换为:
1 | SELECT * FROM T1 LEFT JOIN |
多范围读取优化
当表很大且未存储在存储引擎的缓存中时,使用二级索引上的范围扫描读取行可能会导致对基表的许多随机磁盘访问。通过磁盘扫描多范围读取 (MRR) 优化,MySQL 通过首先仅扫描索引并收集相关行的键来尝试减少范围扫描的随机磁盘访问次数。然后对键进行排序,最后使用主键的顺序从基表中检索行。Disk-sweep MRR 的动机是减少随机磁盘访问的次数,而是实现对基表数据的更顺序扫描。
多范围读取优化提供了以下好处:
- MRR 允许基于索引元组按顺序访问数据行,而不是按随机顺序访问。服务器获取一组满足查询条件的索引元组,按照数据行ID的顺序进行排序,并使用排序后的元组依次检索数据行。这使得数据访问更加高效且成本更低。
- MRR 支持对需要通过索引元组访问数据行的操作的键访问请求进行批处理,例如范围索引扫描和对连接属性使用索引的等连接。MRR 迭代一系列索引范围以获得合格的索引元组。随着这些结果的积累,它们被用于访问相应的数据行。在开始读取数据行之前不需要获取所有索引元组。
在虚拟生成列上创建的二级索引不支持 MRR 优化。 InnoDB支持虚拟生成列上的二级索引。
以下场景说明了 MRR 优化何时具有优势:
方案A:MRR可用于InnoDB和 MyISAM索引范围扫描和表相等联接的操作。
- 索引元组的一部分累积在缓冲区中。
- 缓冲区中的元组按其数据行 ID 排序。
- 根据排序的索引元组序列访问数据行。
场景 B:MRR 可 NDB用于多范围索引扫描的表或按属性执行等连接时。
- 一部分范围(可能是单键范围)在提交查询的中央节点上的缓冲区中累积。
- 范围被发送到访问数据行的执行节点。
- 访问的行被打包成包并发送回中央节点。
- 接收到的带有数据行的包被放置在一个缓冲区中。
- 从缓冲区读取数据行。
使用 MRR 时Extra,EXPLAIN输出中的列 显示 Using MRR。
InnoDB``MyISAM如果不需要访问全表行来生成查询结果,则不要使用 MRR。如果结果可以完全基于索引元组中的信息(通过覆盖索引)产生,就是这种情况;MRR 没有任何好处。
两个optimizer_switch系统变量标志提供了使用 MRR 优化的接口。该mrr标志控制是否启用 MRR。如果 mrr启用 ( on),该 mrr_cost_based标志控制优化器是尝试在使用和不使用 MRR ( on) 或尽可能使用 MRR ( )之间做出基于成本的选择off。默认情况下,mrr是 on和 mrr_cost_based是 on。请参见 第 8.9.2 节,“可切换优化”。
对于 MRR,存储引擎使用read_rnd_buffer_size系统变量的值 作为可以为其缓冲区分配多少内存的指南。引擎最多使用 read_rnd_buffer_size字节并确定要在单次传递中处理的范围数。
阻止嵌套循环和批量密钥访问联接
在 MySQL 中,可以使用批处理密钥访问 (BKA) 连接算法,该算法使用对连接表的索引访问和连接缓冲区。BKA 算法支持内连接、外连接和半连接操作,包括嵌套外连接。BKA 的好处包括由于更高效的表扫描而提高了连接性能。此外,以前仅用于内连接的块嵌套循环 (BNL) 连接算法得到了扩展,可用于外连接和半连接操作,包括嵌套外连接。
Batched Key Access 对于多表join语句,当MySQL使用索引访问第二个join表的时候,使用一个join buffer来收集第一个操作对象生成的相关列值。BKA构建好key后,批量传给引擎层做索引查找。key是通过MRR接口提交给引擎的. 这样,MRR使得查询更有效率。 大致的过程如下:
- BKA使用join buffer保存由join的第一个操作产生的符合条件的数据。
- 然后BKA算法构建key来访问被连接的表,并批量使用MRR接口提交keys到数据库存储引擎去查找。
- 提交keys之后,MRR使用最佳的方式来获取行并反馈给BKA .
BKA使用join buffer size来确定buffer的大小,buffer越大,访问被join的表/内部表就越顺序。 MRR接口有2个应用场景: 场景1:应用于传统的基于磁盘的存储引擎(innodb,myisam),对于这些引擎join buffer中keys是一次性提交到MRR,MRR通过key找到rowid,通过rowid来获取数据 场景2:应用于远程存储引擎(NDB),来自join buffer上的部分key,从SQL NODE发送到DATA NODE,然后SQL NODE会收到通过相关关系匹配的行组合。然后使用这些行组合匹配出新行。然后在发送新key,直到发完为止。
- BNL和BKA,MRR的关系
BNL和BKA都是批量的提交一部分结果集给下一个被join的表(标记为T),从而减少访问表T的次数,那么它们有什么区别呢?BNL和BKA的思想是类似的,详情见:《nest-loop-join官方手册》 第一 BNL比BKA出现的早,BKA直到5.6才出现,而BNL至少在5.1里面就存在。 第二 BNL主要用于当被join的表上无索引,Join buffering can be used when the join is of type ALL or index (in other words, when no possible keys can be used, and a full scan is done, of either the data or index rows, respectively) 第三 BKA主要是指在被join表上有索引可以利用,那么就在行提交给被join的表之前,对这些行按照索引字段进行排序,因此减少了随机IO,排序这才是两者最大的区别,但是如果被join的表没用索引呢?那就使用BNL了。 BKA实现的过程中就是通过传递keys给MRR接口,本质上还是在MRR里面实现,下面这幅图则展示了它们之间的关系:

转载至:
https://www.cnblogs.com/vadim/p/7403847.html
批量密钥访问联接
MySQL 实现了一种连接表的方法,称为批处理密钥访问 (BKA) 连接算法。当对第二个连接操作数生成的表进行索引访问时,可以应用 BKA。与 BNL 连接算法一样,BKA 连接算法使用连接缓冲区来累积由连接操作的第一个操作数产生的行的感兴趣的列。然后 BKA 算法为缓冲区中的所有行构建访问要联接的表的键,并将这些键批量提交给数据库引擎进行索引查找。密钥通过多范围读取 (MRR) 接口提交给引擎(请参阅 第 8.2.1.11 节,“多范围读取优化”)。提交键后,MRR 引擎函数以最佳方式在索引中执行查找,获取这些键找到的连接表的行,并开始向 BKA 连接算法提供匹配的行。每个匹配的行都与对连接缓冲区中一行的引用耦合。
使用 BKA 时,值 join_buffer_size定义了每次向存储引擎请求的密钥批次有多大。缓冲区越大,对连接操作的右手表进行的顺序访问就越多,这可以显着提高性能。
要使用 BKA,系统变量的 batched_key_access标志optimizer_switch必须设置为on。BKA 使用 MRR,因此mrr标志也必须是on. 目前,对 MRR 的成本估算过于悲观。因此,也有必要对 mrr_cost_based要 off用于要使用的BKA。以下设置启用 BKA:
1 | mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on'; |
MRR 函数有两种执行情况:
- 第一个场景用于传统的基于磁盘的存储引擎,例如
InnoDB和MyISAM. 对于这些引擎,通常将连接缓冲区中所有行的键一次提交到 MRR 接口。引擎特定的 MRR 函数对提交的键执行索引查找,从中获取行 ID(或主键),然后根据 BKA 算法的请求为所有这些选定的行 ID 一一获取行。每一行都返回一个关联引用,允许访问连接缓冲区中匹配的行。MRR 函数以最佳方式获取行:按行 ID(主键)顺序获取行。这提高了性能,因为读取是按磁盘顺序而不是随机顺序进行的。 - 第二种场景用于远程存储引擎,如
NDB. 连接缓冲区中一部分行的键包及其关联由 MySQL 服务器(SQL 节点)发送到 MySQL Cluster 数据节点。作为回报,SQL 节点接收匹配行的一个包(或多个包)以及对应的关联。BKA 连接算法采用这些行并构建新的连接行。然后一组新的键被发送到数据节点,并且来自返回包的行用于构建新的连接行。该过程一直持续到联接缓冲区中的最后一个键被发送到数据节点,并且 SQL 节点已接收并联接与这些键匹配的所有行。
在第一种情况下,连接缓冲区的一部分被保留来存储索引查找选择的行 ID(主键)并作为参数传递给 MRR 函数。
没有特殊的缓冲区来存储为连接缓冲区中的行构建的键。相反,为缓冲区中的下一行构建键的函数作为参数传递给 MRR 函数。
在EXPLAIN输出中,当Extra 值包含Using join buffer (Batched Key Access)且type值为 refor 时,表示对表使用 BKA eq_ref。
多表连接分析
不使用Join buffer
a) 简单的嵌套循环
对用户表的每一行,扫描完整表,根据组成人员的行去判断是否满足条件,并返回满足条件的结果给客户端。

b) 索引嵌套循环

使用加入缓冲区
a) 块嵌套循环

b) 批量密钥访问

c) 批量密钥访问(唯一)hash join
与批量键访问不同的是,r中的列是唯一的索引,在r记录写入加入缓存的时候,会记录一个键的哈希表,针对不同的键去表中查询。(疑问,为什么只有唯一的时候才能用这种方式?不是唯一的话,s表可能会扫描出多条数据,也可以用这种方式去处理,减少表的重复扫描)。
mysql 散列,数据库散列连接的详细解释(新的MySQL特性)
长期以来,MySQL执行连接的唯一算法是嵌套循环算法的变体,但是嵌套循环算法在某些情况下效率很低,这也是MySQL一直受到批评的一个问题。
随着MySQL 8.0.18的发布,MySQL服务器可以使用散列连接。本文将简要介绍如何实现散列连接,并看看它在MySQL中是如何工作的,何时使用它,以及有哪些限制。
推荐研究:MySQL教程
哈希连接简介
什么是散列连接?
哈希连接是关系数据库中使用的一种连接算法,它只能在具有相等连接条件的连接中使用(a.b = c.b)。它通常比嵌套循环算法更有效(除了探测端非常非常小),尤其是当索引未命中时。
简而言之,哈希连接算法是先将一个小表加载到内存哈希表中,然后遍历大表的数据,逐行查找哈希表以匹配合格的数据,并将其返回给客户端。
(哈希表只是一个例子。可以理解,实际散列的关键字是连接的值,该值是数据行的链表。)
哈希连接通常分为两个阶段,即构建阶段和探测阶段。在构造阶段,选择适当的表作为“构造输入”来构造哈希表,然后依次遍历另一个“探测输入”表的记录来探测哈希表,以找到满足连接条件的记录。
以上图为例,查询城市对应的省份。我们假设城市是建设投入。在构建阶段,服务器构建一个城市散列表,遍历城市表,然后将行依次放入散列表中,键散列(省_id)和值对应于城市行。“”
在探测阶段,服务器开始从探测省读取行。对于每一行,哈希(省.省_id)值被用作查找键来探测哈希表以匹配该行。
也就是说,当所有构建输入都可以加载到内存中时,每条探测线都被扫描一次,并且两个输入之间的匹配线可以通过使用恒定时间搜索来找到。
如果内存中有太多的数据,该怎么办?
将所有构建输入加载到内存中无疑是最有效的,但是在某些情况下,内存不足以将整个表加载到内存中,因此需要批量处理。
有两种常见的做法:
批量加载到内存处理
1.读取可容纳在最大内存中的记录,创建哈希表,并构建输入以生成哈希表;
2.遍历探测输入,对哈希表的这一部分进行完全探测;
3.清理哈希表并重复该过程,直到完成所有处理。
此方法将导致探头输入表被扫描多次。
写入文件处理
1.当内存在哈希表构建阶段用完时,服务器会将剩余的构建输入写到磁盘上的许多小文件中,所有的小文件块都可以在计算后读入内存中,创建一个哈希表(避免文件块太大,以后无法加载到内存中,需要再次分离);
2.在探测阶段,因为探测行可能与写入磁盘的构建输入的某一行匹配,所以也有必要将探测输入写入磁盘;
3.检测阶段完成后,从磁盘读取块文件并将其加载到内存哈希表中,然后从检测输入中读取响应块文件并检测匹配项;
4.处理后,移动到下一对文件,直到所有处理完成。
MySQL中的哈希连接实现
MySQL将选择两个输入中较小的一个作为构建输入(以字节计算),当内存足够时将构建输入加载到内存中,当内存不足时将其写入文件。
您可以使用join_buffer_size系统变量来控制哈希连接的内存使用。哈希连接的内存使用不能超过这个数量。当超过这个数量时,MySQL将使用文件进行处理。
如果内存超过join_buffer_size,文件超过open_files_limit,执行可能会失败。
有以下两种解决方案可供选择:
●增加连接缓冲区大小,以避免哈希连接溢出到磁盘
●增加打开文件限制
MySQL什么时候会使用哈希连接?
在MySQL版本8.0.18中,如果使用一个或多个相等的连接条件将表连接在一起,并且没有可用于连接条件的索引,将使用哈希连接。如果索引可用,MySQL倾向于使用索引查找来支持嵌套循环。
默认情况下,MySQL尽可能使用哈希连接,这可以通过两种方式打开或关闭:
低设置全局或会话变量(哈希_连接=开或哈希_连接=关);
设置优化器_开关= & quothash_join=off;●使用提示(散列_连接或无散列_连接)。
转载至:https://blog.csdn.net/weixin_39639550/article/details/115883626
散列连接,索引,嵌套循环
深入理解嵌套循环执行计划 为什么要引入散列连接呢?假设两张表t1(c1 int,c2 int),t2(d1 int,d2 int),查询语句为select c1,d1 from t1 inner join t2 on c1=d1。如果数据库没有实现散列连接、合并连接的话,只能选择使用嵌套循环。从上篇文章中我们可以得到,对于t1的每一条记录,都需要遍历t2的每一条记录。因此,当t1的记录数数为m,t2的记录数为n,那么该查询语句访问的记录次数为mn。当m=10000、n=10000时,那么mn=100000000(1亿)。这是比较夸张的浪费时间。如果m是100万,n是100万,那么mn就是1万亿次,读一万亿次记录,这是不能忍受的。 这里需要提到的一点是:我们不以读取记录的多少作为评价标准,在实际代价评估中,采用数据页(也可称为数据块,I/O的基本单位)。但是两者之间又是有联系的,假设每个页存放100个数据,那么t1的数据页为100页(10000/100),t2的数据页为100页,那么对于t1中的每一条记录,需要遍历t2的100页,加上该记录在t1中也属于一个数据页。因此,对于t1中的每一个记录,需要访问101个数据页。那么该查询的I/O量为:10000(100+1)=1010000页。如果考虑到数据页的缓冲,情况会更加复杂。代价评估是个很复杂的课题,可能需要单独写个系列来阐述数据库查询优化系统的代价评估模型。这里我们不考虑数据页缓冲,也就相当于假设数据库缓冲区的大小仅仅为1个页。 好了,继续前面的话题。 如果t1(c1)上建立有唯一索引iut1c1,那么可以将t2作为外表,对于t2的每一条记录,使用d1的值去命中索引iut1c1对应的B树。假设该B树的高度为3层,那么对于t2的每一条记录,需要访问t1表索引iut1c1中三个页(B树的高度),加上本身在t2中属于一个页。所以,在这种情况下,查询代价为:10000*(3+1)=40000页。 我们来对比一下,没有索引与有索引,两者之间的代价对比约等于25:1(比值1010000:40000)。也可以这么认为,假设没有索引的时候执行需要25s,那么有索引的情况下只需要1s。 这里我们把话题再延展下,如果m,n都为1000000,占用的块都为10000页(1000000/100)。没有索引的情况的I/O量为:1000000*(10000+1)=10001000000页。在t1(c1)有索引,该索引的高度对应的高度为4的情况下,假设I/O量为:100000*(4+1)=5000000。对比一下,没有索引与有索引,两者之间的代价比约等于2000:1。相等于,假设没有索引的情况下执行需要2000s,那么有索引的情况下只需要1s。 从上面的对比当中,我们可以发现索引的重要性,在实际应用当中,80%的查询性能问题来源于没有创建索引或者没有创建合适的索引。
2 数据库内核使用建立临时索引的方法 大家可能听到过一个这样的概念:“在sqlserver系统中,如果用户没有创建索引,执行查询时,sqlserver会自动创建该索引。” 这里我们先撇开sqlserver到底是使用临时索引还是散列连接,我们只是对这句话加以理解。 对于上文提到的查询语句,执行过程描述如下: 1)create index itemp on t1(c1); 2)执行查询语句select c1,d1 from t1 inner join t2 on c1=d1; 3)drop index itemp; 我们来评估下代价。如上文锁描述,假设m,n都为1000000,占用的块都为10000页。 首先是计算构造索引的代价:对t1的数据进行全扫描,对于每一条记录要插入到B树中,假设插入操作平均需要使用3个页。(因为起始时,B树只有一层,插入只需要访问1页,B树两层使需要访问2页,等等)。该步骤的代价为:1000000(3+1)=4000000页。 然后计算查询的代价,前面已经计算过:100000(4+1)=5000000页。 所以,整个代价为4000000+5000000=9000000页。 进行对比:10000:9:5(比值10001000000:9000000:5000000)。不使用索引的代价为10000,使用临时索引的代价为9,使用用户创建的索引代价为5。 所以,我们发现使用临时索引还是个不错的选择。
3 数据库内核使用散列连接的方法 首先我们讲下散列连接的原理: 1)对t1表(称为构建表)进行全扫描,对于每一个记录,对c1值进行使用内部散列函数,然后将该数据存放到相应的散列桶。 2)开始读t2表(称为探查散列表),对于t2的每一个记录,对d1值使用同样的散列函数,得到相应的散列值,查看该桶中是否有行。 如果相应的桶中没有行,则会丢失t2中这一行记录。如果散列桶中如果有一些行呢,则会精通的检查散列连接判断是否存在合适的匹配。因为不同的值可以产生同样的散列值。找到精确匹配的值,组合成记录放入结果集中。 我们来评估下代价。 1)首先我们先看构建散列的代价,对于t1的每一个记录,一般只需要访问一个散列桶。所以该步骤的代价为:1000000*(1+1)=2000000页。 2)对于t2的每一个记录,一般只需要访问一个散列桶。所以该步骤的代价为:1000000*(1+1)=2000000页。 所以,整个代价为2000000+2000000=4000000页。 进行对比:10000:4:5(比值10001000000:4000000:5000000),不使用索引的代价为10000,使用散列连接的代价为4,使用用户创建的索引代价为5。 是不是觉得不可思议?散列连接的代价竟然比使用索引的连接还小。我们通过一个例子来验证一下: SQL> create table t1(c1 int,c2 int); Table created.
总结:
1 循环嵌套
t1表的记录都要在t2全部查询。
2 索引
t1表的记录在t2种合适前几页查询。
3 散列 首先利用散列函数建立散列桶,t1散列桶每条的记录在t2散列桶查询
转载至:https://www.huaweicloud.com/articles/12642743.html
条件过滤
在连接处理中,前缀行是从连接中的一个表传递到下一个表的那些行。通常,优化器会尝试在连接顺序的早期放置具有低前缀计数的表,以防止行组合数快速增加。如果优化器可以使用有关从一个表中选择并传递到下一个表的行的条件信息,它可以更准确地计算行估计并选择最佳执行计划。
如果没有条件过滤,表的前缀行计数基于WHERE子句根据优化器选择的任何访问方法选择的估计行数 。条件过滤使优化器可以使用WHERE访问方法未考虑的子句中的其他相关条件 ,从而改进其前缀行计数估计。例如,即使可能有一种基于索引的访问方法可用于从连接中的当前表中选择行,但也可能有其他条件用于连接中的表。WHERE 可以过滤(进一步限制)传递到下一个表的合格行的估计值的子句。
只有在以下情况下,条件才会对过滤估计起作用:
- 它指的是当前表。
- 它取决于连接序列中早期表中的一个或多个常量值。
- 访问方法尚未考虑到它。
在EXPLAIN输出中,该 rows列表示所选访问方法的行估计值,该filtered 列反映条件过滤的效果。 filtered值以百分比表示。最大值为 100,这意味着没有发生行过滤。从 100 开始减小的值表示过滤量增加。
前缀行计数(估计从连接中的当前表传递到下一个表的行数)是rows和 filtered值的乘积。也就是说,前缀行数是估计的行数,减去估计的过滤效果。例如,如果rows是 1000 并且filtered是 20%,条件过滤会将估计的行数 1000 减少到前缀行数 1000 × 20% = 1000 × .2 = 200。
考虑以下查询:
1 | SELECT * |
假设数据集具有以下特征:
该
employee表有 1024 行。该
department表有 12 行。两个表在 上都有一个索引
dept_no。该
employee表在 上有一个索引first_name。8 行满足以下条件
employee.first_name:1
employee.first_name = 'John'
150 行满足以下条件
employee.hire_date:1
employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01'
1 行满足两个条件:
1
2employee.first_name = 'John'
AND employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01'
没有条件过滤, EXPLAIN产生这样的输出:
1 | +----+------------+--------+------------------+---------+---------+------+----------+ |
对于employee,name索引上的访问方法 选取与 名称匹配的 8 行'John'。没有进行过滤(filtered是 100%),所以所有行都是下一个表的前缀行:前缀行数是 rows× filtered= 8 × 100% = 8。
通过条件过滤,优化器会额外考虑WHERE 访问方法未考虑的子句中的条件。在这种情况下,优化器使用启发式方法来估计 16.31%BETWEEN 条件的过滤效果employee.hire_date。结果,EXPLAIN产生如下输出:
1 | +----+------------+--------+------------------+---------+---------+------+----------+ |
现在前缀行数为rows× filtered= 8 × 16.31% = 1.3,更能反映实际数据集。
通常,优化器不会计算最后一个连接表的条件过滤效果(前缀行计数减少),因为没有下一个表要传递行。发生异常 EXPLAIN:为了提供更多信息,对所有连接的表计算过滤效果,包括最后一个。
要控制优化器是否考虑其他过滤条件,请使用 系统变量的 condition_fanout_filter标志optimizer_switch(请参阅 第 8.9.2 节,“可切换优化”)。此标志默认启用,但可以禁用以抑制条件过滤(例如,如果发现特定查询在没有它的情况下会产生更好的性能)。
如果优化器高估了条件过滤的效果,则性能可能比不使用条件过滤时更差。在这种情况下,这些技术可能会有所帮助:
如果某列未编入索引,请将其编入索引,以便优化器了解有关列值分布的一些信息,并可以改进其行估计值。
同样,如果没有可用的列直方图信息,则生成直方图(请参阅 第 8.9.6 节,“优化器统计信息”)。
更改连接顺序。完成此操作的方法包括连接顺序优化器提示(参见 第 8.9.3 节,“优化器提示”),
STRAIGHT_JOIN紧跟在SELECT, 和STRAIGHT_JOIN连接运算符之后。禁用会话的条件过滤:
1
SET optimizer_switch = 'condition_fanout_filter=off';
或者,对于给定的查询,使用优化器提示:
1
SELECT /*+ SET_VAR(optimizer_switch = 'condition_fanout_filter=off') */ ...
恒定折叠优化
常量和列值之间的比较,其中常量值超出范围或相对于列类型的类型错误,现在在查询优化期间处理一次,而不是在执行期间逐行处理。可以以这种方式处理的比较是 >,>=, <,<=, <>/ !=, =和<=>。
考虑由以下语句创建的表:
1 | CREATE TABLE t (c TINYINT UNSIGNED NOT NULL); |
所述WHERE查询条件 SELECT * FROM t WHERE c < 256包含积分常数256,其超出范围为 TINYINT UNSIGNED柱。以前,这是通过将两个操作数都视为较大类型来处理的,但是现在,由于 for 的任何允许值c小于常量,WHERE表达式可以改为折叠为WHERE 1,以便将查询重写为SELECT * FROM t WHERE 1。
这使得优化器可以WHERE完全删除 表达式。如果该列可以 c为空(即仅定义为 TINYINT UNSIGNED),则查询将被重写为:
1 | SELECT * FROM t WHERE ti IS NOT NULL |
与支持的 MySQL 列类型相比,对常量执行折叠如下:
整数列类型。 整数类型与以下类型的常量进行比较,如下所述:
整数值。 如果常量超出列类型的范围,则比较将折叠为
1或IS NOT NULL,如已显示。如果常量是范围边界,则比较折叠为
=。例如(使用与已定义相同的表):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22mysql> EXPLAIN SELECT * FROM t WHERE c >= 255;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 5
filtered: 20.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS;
*************************** 1. row ***************************
Level: Note
Code: 1003
Message: /* select#1 */ select `test`.`t`.`ti` AS `ti` from `test`.`t` where (`test`.`t`.`ti` = 255)
1 row in set (0.00 sec)浮点值或定点值。 如果常量是十进制类型之一(例如
DECIMAL、REAL、DOUBLE、 或FLOAT)并且具有非零小数部分,则不能相等;相应地折叠。对于其他比较,根据符号向上或向下舍入为整数值,然后执行范围检查和处理,如已针对整数-整数比较所述的那样。甲
REAL太小,以表示值DECIMAL被舍入到0.01或-.01取决于标志,那么作为一个处理DECIMAL。字符串类型。 尝试将字符串值解释为整数类型,然后将比较处理为整数值之间的比较。如果失败,请尝试将该值作为
REAL.
DECIMAL 或 REAL 列。 十进制类型与以下类型的常量进行比较,如下所述:
整数值。 对列值的整数部分执行范围检查。如果没有折叠结果,将常量转换
DECIMAL为与列值具有相同小数位数,然后将其检查为 aDECIMAL(见下文)。DECIMAL 或 REAL 值。 检查溢出(即,常量的整数部分是否比列的十进制类型允许的位数多)。如果是这样,折叠。
如果常量的有效小数位数比列的类型多,则截断常量。如果比较运算符是
=or<>,则折叠。如果运算符是>=或<=,则由于截断而调整运算符。例如,如果列的类型为DECIMAL(3,1),则SELECT * FROM t WHERE f >= 10.13变为SELECT * FROM t WHERE f > 10.1。如果常量的小数位数少于列的类型,则将其转换为具有相同位数的常量。对于
REAL值的下溢 (即小数位数太少无法表示),请将常量转换为十进制 0。字符串值。 如果该值可以解释为整数类型,则按原样处理。否则,尝试将其作为
REAL.
FLOAT 或 DOUBLE 列。 或 与常量比较的值按如下方式处理:
FLOAT(*m*,*n*)``DOUBLE(*m*,*n*)如果值溢出列的范围,则折叠。
如果值多于*
n* 小数,则截断,在折叠期间进行补偿。对于=和<>比较,折叠到TRUE、FALSE、 或IS [NOT] NULL如前所述;对于其他运算符,请调整运算符。如果该值超过
m整数位,则折叠。
限制。 此优化不能用于以下情况:
- 使用
BETWEEN或 进行比较IN。 - 随着
BIT使用日期或时间类型的列或列。 - 在准备语句的准备阶段,虽然它可以在准备语句实际执行的优化阶段应用。这是因为在语句准备期间,常量的值尚不清楚。
IS NULL 优化
MySQL 可以执行相同的优化 ,它可以用于 . 例如,MySQL能使用索引和范围来搜索 与。 col_name IS NULLcol_name = constant_valueNULLIS NULL
例子:
1 | SELECT * FROM tbl_name WHERE key_col IS NULL; |
如果WHERE子句包含声明为 的列的 条件,则 该表达式将被优化掉。如果该列可能会产生任何结果(例如,如果它来自 a 右侧的表),则 不会发生这种优化。 col_name IS NULLNOT NULL``NULL``LEFT JOIN
MySQL 还可以优化组合 ,这是解析子查询中常见的一种形式。 显示 何时使用此优化。 *col_name* = *expr* OR *col_name* IS NULLEXPLAINref_or_null
这种优化可以处理IS NULL任何关键部分。
一些优化的查询示例,假设在列a和 b表上有一个索引t2:
1 | SELECT * FROM t1 WHERE t1.a=expr OR t1.a IS NULL; |
ref_or_null首先读取引用键,然后单独搜索具有NULL键值的行。
优化只能处理一个IS NULL级别。在以下查询中,MySQL 仅在表达式上使用键查找(t1.a=t2.a AND t2.a IS NULL),而不能在 上使用键部分 b:
1 | SELECT * FROM t1, t2 |
ORDER BY 优化
本节介绍 MySQL 何时可以使用索引来满足ORDER BY子句,无法使用索引时使用的 filesort操作,以及从优化器获得的关于 的执行计划信息ORDER BY。
一个ORDER BY有和没有 LIMIT可能以不同的顺序返回行。
在某些情况下,MySQL 可能会使用索引来满足 ORDER BY子句并避免执行filesort 操作所涉及的额外排序。
即使ORDER BY索引与索引不完全匹配,也可以使用索引,只要索引的所有未使用部分和所有额外 ORDER BY列都是WHERE子句中的常量 。如果索引不包含查询访问的所有列,则仅当索引访问比其他访问方法便宜时才使用索引。
假设在 上有索引 ,以下查询可能会使用该索引来解析该 部分。如果还必须读取不在索引中的列,优化器是否真的这样做取决于读取索引是否比表扫描更有效。 (*key_part1*, *key_part2*)``ORDER BY
在此查询中,索引 on 使优化器能够避免排序:
(*key_part1*, *key_part2*)1
2SELECT * FROM t1
ORDER BY key_part1, key_part2;但是,查询使用
SELECT *,它可以选择比*key_part1*和 多的列 *key_part2*。在这种情况下,扫描整个索引并查找表行以查找不在索引中的列可能比扫描表并对结果进行排序更昂贵。如果是这样,优化器可能不使用索引。如果SELECT *仅选择索引列,则使用索引并避免排序。如果
t1是InnoDB表,则表主键是索引的隐式部分,并且可以使用索引来解析ORDER BY此查询:1
2SELECT pk, key_part1, key_part2 FROM t1
ORDER BY key_part1, key_part2;在这个查询中,*
key_part1*是常量,所以通过索引访问的所有行都是 *key_part2*有序的,如果子句有足够的选择性使得索引范围扫描比表扫描便宜,则索引可以避免排序:(*key_part1*, *key_part2*)``WHERE1
2
3SELECT * FROM t1
WHERE key_part1 = constant
ORDER BY key_part2;在接下来的两个查询中,是否使用索引与
DESC之前没有显示的相同查询类似 :1
2
3
4
5
6SELECT * FROM t1
ORDER BY key_part1 DESC, key_part2 DESC;
SELECT * FROM t1
WHERE key_part1 = constant
ORDER BY key_part2 DESC;an 中的两列
ORDER BY可以按相同方向(bothASC或 bothDESC)或相反方向(oneASC、 oneDESC)排序。索引使用的一个条件是索引必须具有相同的同质性,但不必具有相同的实际方向。如果查询混合使用
ASCandDESC,如果索引也使用相应的混合升序和降序列,则优化器可以在列上使用索引:1
2SELECT * FROM t1
ORDER BY key_part1 DESC, key_part2 ASC;如果是降序和 升序*
key_part1*, 优化器可以使用 ( ,key_part2) 上的索引。如果是升序和降序,它还可以在这些列上使用索引(使用向后扫描)。见第 8.3.13 节,“降序索引”。key_part1key_part2key_part1**key_part2在接下来的两个查询中, *
key_part1*将与一个常量进行比较。如果WHERE子句的选择性足以使索引范围扫描比表扫描便宜,则使用索引:1
2
3
4
5
6
7SELECT * FROM t1
WHERE key_part1 > constant
ORDER BY key_part1 ASC;
SELECT * FROM t1
WHERE key_part1 < constant
ORDER BY key_part1 DESC;在接下来的查询中,
ORDER BY没有 name *key_part1*,但所有选定的行都有一个常 *key_part1*量值,因此仍然可以使用索引:1
2
3SELECT * FROM t1
WHERE key_part1 = constant1 AND key_part2 > constant2
ORDER BY key_part2;
在某些情况下,MySQL无法使用索引来解析ORDER BY,尽管它仍然可以使用索引来查找与WHERE子句匹配的行 。例子:
查询
ORDER BY在不同的索引上使用:1
SELECT * FROM t1 ORDER BY key1, key2;
该查询用于
ORDER BY索引的非连续部分:1
SELECT * FROM t1 WHERE key2=constant ORDER BY key1_part1, key1_part3;
用于获取行的索引与 中使用的索引不同
ORDER BY:1
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
查询
ORDER BY与包含索引列名称以外的术语的表达式一起使用:1
2SELECT * FROM t1 ORDER BY ABS(key);
SELECT * FROM t1 ORDER BY -key;该查询连接了许多表,并且其中的列
ORDER BY并非全部来自用于检索行的第一个非常量表。(这是EXPLAIN输出中第一个 没有const连接类型的表。)查询有不同的
ORDER BY和GROUP BY表达式。仅在
ORDER BY子句中命名的列的前缀上有索引。在这种情况下,索引不能用于完全解析排序顺序。例如,如果仅CHAR(20)索引列的前 10 个字节,则索引无法区分第 10 个字节之后的 值,filesort因此需要 a。索引不按顺序存储行。例如,对于表中的
HASH索引 就是如此MEMORY。
用于排序的索引的可用性可能会受到列别名的使用的影响。假设该列 t1.a已编入索引。在此语句中,选择列表中列的名称是 a。它指的是t1.a,如同在参考a在 ORDER BY,所以上的索引 t1.a可用于:
1 | SELECT a FROM t1 ORDER BY a; |
在该语句中,选择列表中列的名称也是a,但它是别名。它指的是ABS(a),如同在参考a在ORDER BY,所以上的索引t1.a不能使用:
1 | SELECT ABS(a) AS a FROM t1 ORDER BY a; |
在下面的语句中,theORDER BY 引用的名称不是选择列表中列的名称。但是t1 named 中有一个列a,因此可以使用ORDER BY引用t1.a和索引t1.a。(当然,生成的排序顺序可能与 的顺序完全不同 ABS(a)。)
1 | SELECT ABS(a) AS b FROM t1 ORDER BY a; |
以前(MySQL 5.7 及更低版本), GROUP BY在某些条件下隐式排序。在 MySQL 8.0 中,这种情况不再发生,因此ORDER BY NULL不再需要在末尾指定抑制隐式排序(如之前所做的那样)。但是,查询结果可能与以前的 MySQL 版本不同。要生成给定的排序顺序,请提供一个ORDER BY子句。
使用文件排序来满足 ORDER BY
如果索引不能用于满足ORDER BY子句,MySQL 将执行filesort读取表行并对它们进行排序的 操作。Afilesort构成查询执行中的额外排序阶段。
为了获得用于filesort操作的内存,从 MySQL 8.0.12 开始,优化器根据需要增量分配内存缓冲区,直到sort_buffer_size系统变量指示的大小 ,而不是sort_buffer_size像 MySQL 8.0 之前那样预先分配固定数量的 字节.12. 这使用户能够设置sort_buffer_size更大的值来加速更大的排序,而不必担心小排序使用过多的内存。(对于多线程弱的 Windows 上的多个并发排序,可能不会出现这种好处malloc。)
一个filesort操作使用临时磁盘文件作为必要的,如果结果集是太大,无法在内存中。某些类型的查询特别适合完全在内存中的filesort操作。例如,优化器可以 filesort在内存中有效地处理ORDER BY 以下形式的查询(和子查询)操作,无需临时文件:
1 | SELECT ... FROM single_table ... ORDER BY non_index_column [DESC] LIMIT [M,]N; |
此类查询在仅显示较大结果集中的几行的 Web 应用程序中很常见。例子:
1 | SELECT col1, ... FROM t1 ... ORDER BY name LIMIT 10; |
影响 ORDER BY 优化
对于未使用的慢ORDER BY查询 filesort,请尝试将max_length_for_sort_data 系统变量降低 到适合触发 filesort. (将此变量的值设置得太高的一个症状是高磁盘活动和低 CPU 活动的组合。)此技术仅适用于 MySQL 8.0.20 之前。从 8.0.20 开始,max_length_for_sort_data由于优化器更改使其过时且无效, 因此已弃用。
要提高ORDER BY速度,请检查是否可以让 MySQL 使用索引而不是额外的排序阶段。如果这是不可能的,请尝试以下策略:
增加
sort_buffer_size变量值。理想情况下,该值应该足够大以使整个结果集适合排序缓冲区(以避免写入磁盘和合并传递)。考虑到存储在排序缓冲区中的列值的大小受
max_sort_length系统变量值的影响。例如,如果元组存储长字符串列的值并且您增加 的值max_sort_length,则排序缓冲区元组的大小也会增加并且可能需要您增加sort_buffer_size。要监视合并传递的次数(以合并临时文件),请检查
Sort_merge_passes状态变量。增加
read_rnd_buffer_size变量值以便一次读取更多行。将
tmpdir系统变量更改为指向具有大量可用空间的专用文件系统。变量值可以列出几个以循环方式使用的路径;您可以使用此功能将负载分散到多个目录中。:在 Unix 上用冒号字符 ( ) 和;在 Windows 上用分号字符 ( )分隔路径。路径应该命名位于不同物理磁盘上的文件系统中的目录 ,而不是同一磁盘上的不同分区。
ORDER BY 执行计划信息可用
使用 EXPLAIN (参见第 8.8.1 节,“使用 EXPLAIN 优化查询”),您可以检查 MySQL 是否可以使用索引来解析ORDER BY子句:
- 如果输出
Extra列EXPLAIN不包含Using filesort,则使用索引并且filesort不执行 a 。 - 如果输出
Extra列EXPLAIN包含Using filesort,则不使用索引并filesort执行 a。
此外,如果filesort执行了 a ,优化器跟踪输出将包含一个 filesort_summary块。例如:
1 | "filesort_summary": { |
peak_memory_used表示排序过程中任一时刻使用的最大内存。这是一个值,但不一定与sort_buffer_size系统变量的值一样大 。在 MySQL 8.0.12 之前,输出sort_buffer_size改为显示 ,指示sort_buffer_size. (在 MySQL 8.0.12 之前,优化器总是sort_buffer_size为排序缓冲区分配 字节。从 8.0.12 开始,优化器以增量方式分配排序缓冲区内存,从少量开始,并根据需要添加更多,最多为 sort_buffer_size字节。)
该sort_mode值提供有关排序缓冲区中元组内容的信息:
<sort_key, rowid>:这表示排序缓冲区元组是包含原始表行的排序键值和行 ID 的对。元组按排序键值排序,行 ID 用于从表中读取行。<sort_key, additional_fields>:这表示排序缓冲区元组包含查询引用的排序键值和列。元组按排序键值排序,列值直接从元组中读取。<sort_key, packed_additional_fields>:与之前的变体一样,但附加列紧密地打包在一起,而不是使用固定长度的编码。
EXPLAIN不区分优化器是否filesort在内存中执行 。filesort在优化器跟踪输出中可以看到内存 中的使用。
GROUP BY 优化
满足一个GROUP BY 子句的最通用的方法是扫描整个表并创建一个新的临时表,其中每个组的所有行都是连续的,然后使用这个临时表来发现组并应用聚合函数(如果有)。在某些情况下,MySQL 能够做得比这更好,并通过使用索引访问避免创建临时表。
使用索引的最重要的前提条件 GROUP BY是所有GROUP BY列都引用来自同一索引的属性,并且索引按顺序存储其键(例如,对于BTREE索引是这样,但对于HASH索引则不然 )。临时表的使用是否可以由索引访问代替还取决于查询中使用索引的哪些部分、为这些部分指定的条件以及选择的聚合函数。
有两种方法可以GROUP BY 通过索引访问来执行查询,详见以下部分。第一种方法将分组操作与所有范围谓词(如果有)一起应用。第二种方法首先执行范围扫描,然后对生成的元组进行分组。
松散索引扫描也可以GROUP BY在某些条件下不存在的 情况下使用。请参阅 跳过扫描范围访问方法。
松散索引扫描
最有效的处理方式GROUP BY是使用索引直接检索分组列。通过这种访问方法,MySQL 使用了一些索引类型的属性,键是有序的(例如,BTREE)。此属性允许在索引中使用查找组,而不必考虑索引中满足所有WHERE条件的所有键 。这种访问方法只考虑索引中的一小部分键,因此称为松散索引扫描。当没有WHERE 子句时,松散索引扫描读取与组数一样多的键,这可能比所有键的数量小得多。如果WHERE子句包含范围谓词(请参阅第 8.8.1 节“使用 EXPLAIN 优化查询”中的range连接类型 的讨论 ),松散索引扫描查找满足范围条件的每个组的第一个键,并再次读取尽可能小的键键数。这在以下条件下是可能的:
- 查询在单个表上。
- 该
GROUP BY唯一名称是构成该指数并没有其他列的最左边的前缀列。(如果GROUP BY查询有一个DISTINCT子句,而不是,则所有不同的属性都引用构成索引最左边前缀的列。)例如,如果一个表t1的索引为(c1,c2,c3),则如果查询具有 ,则松散索引扫描适用GROUP BY c1, c2。如果查询具有GROUP BY c2, c3(列不是最左边的前缀)或GROUP BY c1, c2, c4(c4不在索引中),则不适用 。 - 选择列表中使用的唯一聚合函数(如果有)是
MIN()andMAX(),并且它们都引用同一列。该列必须在索引中,并且必须紧跟在GROUP BY. - 索引的任何其他部分而不是
GROUP BY查询中引用的部分必须是常量(即,它们必须以与常量相等的方式被引用),除了参数 ofMIN()或MAX()函数。 - 对于索引中的列,必须对完整的列值进行索引,而不仅仅是前缀。例如,对于
c1 VARCHAR(20), INDEX (c1(10)),索引仅使用c1值的前缀,不能用于松散索引扫描。
如果松散索引扫描适用于查询,则 EXPLAIN输出显示 Using index for group-by在 Extra列中。
假设idx(c1,c2,c3)table 上 有一个索引 t1(c1,c2,c3,c4)。松散索引扫描访问方法可用于以下查询:
1 | SELECT c1, c2 FROM t1 GROUP BY c1, c2; |
由于给出的原因,无法使用此快速选择方法执行以下查询:
-
1
SELECT c1, SUM(c2) FROM t1 GROUP BY c1;
GROUP BY子句中 的列不构成索引最左边的前缀:1
SELECT c1, c2 FROM t1 GROUP BY c2, c3;
该查询引用了该部分之后的键的
GROUP BY一部分,并且该 部分与常量不相等:1
SELECT c1, c3 FROM t1 GROUP BY c1, c2;
如果要包含查询,则可以使用松散索引扫描。
WHERE c3 = *const*
除了已经支持的MIN()和 MAX()引用之外,松散索引扫描访问方法还可以应用于选择列表中其他形式的聚合函数引用:
AVG(DISTINCT)、SUM(DISTINCT)、 和COUNT(DISTINCT)支持。AVG(DISTINCT)并SUM(DISTINCT)采取一个论点。COUNT(DISTINCT)可以有多个列参数。- 查询中 不能有
GROUP BYorDISTINCT子句。 - 之前描述的松散索引扫描限制仍然适用。
假设idx(c1,c2,c3)table 上 有一个索引 t1(c1,c2,c3,c4)。松散索引扫描访问方法可用于以下查询:
1 | SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1; |
紧密索引扫描
紧密索引扫描可以是完整索引扫描或范围索引扫描,具体取决于查询条件。
当不满足松散索引扫描的条件时,仍然可以避免为GROUP BY查询创建临时表。如果WHERE子句中有范围条件,该方法只读取满足这些条件的键。否则,它执行索引扫描。由于此方法读取WHERE子句定义的每个范围内的所有键 ,或者如果没有范围条件则扫描整个索引,因此称为 紧密索引扫描。使用紧密索引扫描,只有在找到所有满足范围条件的键后才执行分组操作。
要使此方法起作用,查询中的所有列都有一个恒定的相等条件就足够了,这些列涉及键的各部分之前或之间的部分GROUP BY。来自等式条件的常量填充 了搜索键中的任何“空白”,以便可以形成索引的完整前缀。这些索引前缀然后可用于索引查找。如果GROUP BY result 需要排序,并且可以形成作为索引前缀的搜索键,MySQL 也避免了额外的排序操作,因为在有序索引中搜索前缀已经按顺序检索了所有键。
假设idx(c1,c2,c3)table 上 有一个索引 t1(c1,c2,c3,c4)。以下查询不适用于前面描述的松散索引扫描访问方法,但仍适用于紧密索引扫描访问方法。
中有一个缺口
GROUP BY,但它被条件覆盖c2 = 'a':1
SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
在
GROUP BY不与钥匙的第一部分开始,但它提供了该部分恒定的条件:1
SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;
DISTINCT 优化
DISTINCT结合ORDER BY在很多情况下需要一个临时表。
因为DISTINCT可以使用GROUP BY,了解 MySQL 如何处理 不属于所选列的部分ORDER BY或HAVING子句中的列。请参阅 第 12.20.3 节,“MySQL 处理 GROUP BY”。
在大多数情况下,DISTINCT可以将子句视为 的特例GROUP BY。例如,以下两个查询是等效的:
1 | SELECT DISTINCT c1, c2, c3 FROM t1 |
由于这种等效性,适用于GROUP BY查询的优化 也可以应用于带有DISTINCT子句的查询。因此,有关DISTINCT查询优化可能性的更多详细信息 ,请参阅 第 8.2.1.17 节,“GROUP BY 优化”。
与 结合使用时 ,MySQL 会在找到唯一行后立即停止 。 LIMIT *row_count*``DISTINCTrow_count
如果您不使用查询中命名的所有表中的列,MySQL 会在找到第一个匹配项后立即停止扫描任何未使用的表。在以下情况下,假设 t1之前使用过t2 (您可以使用 进行检查 EXPLAIN),MySQL 会t2在t1找到 中的第一行时停止读取(对于 中的任何特定行 ) t2:
1 | SELECT DISTINCT t1.a FROM t1, t2 where t1.a=t2.a; |
LIMIT 查询优化
如果您只需要结果集中指定数量的行,请LIMIT在查询中使用子句,而不是获取整个结果集并丢弃额外的数据。
MySQL 有时会优化有子句和没有 子句的查询: LIMIT *row_count*``HAVING
如果你只用 选择几行
LIMIT,MySQL 在某些情况下会使用索引,而通常它更喜欢进行全表扫描。如果与ORDER BY结合使用 ,MySQL 会在找到排序结果的第一行后立即停止排序 ,而不是对整个结果进行排序。如果使用索引完成排序,则速度非常快。如果必须进行文件排序,则在找到第一个之前,将选择与查询不带子句的查询匹配的所有行,并且对它们中的大部分或全部进行排序 。找到初始行后,MySQL 不会对结果集的任何剩余部分进行排序。
LIMIT *row_count*``ORDER BYrow_countLIMITrow_count这种行为的一种表现是,
ORDER BY有和没有的 查询LIMIT可能以不同的顺序返回行,如本节后面所述。如果与DISTINCT结合使用 ,MySQL 会在找到唯一行后立即停止。
LIMIT *row_count*``DISTINCTrow_count在某些情况下,
GROUP BY可以通过按顺序读取索引(或对索引进行排序),然后计算摘要直到索引值发生变化来解决 a。在这种情况下,不计算任何不必要的 值。LIMIT *row_count*``GROUP BY一旦 MySQL 向客户端发送了所需数量的行,它就会中止查询,除非您使用
SQL_CALC_FOUND_ROWS. 在这种情况下,可以使用 检索行数SELECT FOUND_ROWS()。见 第 12.16 节,“信息函数”。LIMIT 0快速返回一个空集。这对于检查查询的有效性很有用。它还可以用于在使用 MySQL API 的应用程序中获取结果列的类型,该 API 使结果集元数据可用。使用 mysql客户端程序,您可以使用--column-type-info选项来显示结果列类型。如果服务器使用临时表来解析查询,它会使用子句来计算需要多少空间。
LIMIT *row_count*如果未使用索引
ORDER BY但LIMIT也存在子句,则优化器可能能够避免使用合并文件并使用内存中filesort操作对内存中的行进行排序 。
如果多行在列中具有相同的值ORDER BY,服务器可以自由地以任何顺序返回这些行,并且可能会根据整体执行计划以不同的方式返回。换句话说,这些行的排序顺序对于无序列是不确定的。
影响执行计划的一个因素是 LIMIT,因此ORDER BY 有和没有的查询LIMIT可能会以不同的顺序返回行。考虑这个查询,它按category列排序,但对于id和 rating列是不确定的:
1 | mysql> SELECT * FROM ratings ORDER BY category; |
包括LIMIT可能会影响每个category值中的行顺序。例如,这是一个有效的查询结果:
1 | mysql> SELECT * FROM ratings ORDER BY category LIMIT 5; |
在每种情况下,行都按ORDER BY列排序,这是 SQL 标准所要求的全部内容。
如果使用和不使用 确保相同的行顺序很重要,请LIMIT在ORDER BY子句中包含额外的列以使顺序具有确定性。例如,如果id值是唯一的,您可以通过如下排序使给定category值的行 按id顺序显示 :
1 | mysql> SELECT * FROM ratings ORDER BY category, id; |
对于带有ORDER BYor GROUP BY和LIMIT 子句的查询,优化器会在默认情况下尝试选择有序索引,因为这样做会加快查询执行速度。在 MySQL 8.0.21 之前,无法覆盖此行为,即使在使用其他优化可能更快的情况下也是如此。从 MySQL 8.0.21 开始,可以通过将optimizer_switch系统变量的 prefer_ordering_index标志设置为 来关闭此优化 off。
示例:首先我们创建并填充一个表t,如下所示:
1 | # Create and populate a table t: |
验证该 prefer_ordering_index标志是否已启用:
1 | mysql> SELECT @@optimizer_switch LIKE '%prefer_ordering_index=on%'; |
由于以下查询有一个LIMIT 子句,我们希望它尽可能使用有序索引。在这种情况下,正如我们从EXPLAIN输出中看到的那样 ,它使用表的主键。
1 | mysql> EXPLAIN SELECT c2 FROM t |
现在我们禁用 prefer_ordering_index标志,并重新运行相同的查询;这次它使用索引 i(包括子句中id2使用的 列 WHERE)和文件排序:
1 | mysql> SET optimizer_switch = "prefer_ordering_index=off"; |
函数调用优化
MySQL 函数在内部被标记为确定性或非确定性。一个函数是不确定的,如果给定参数的固定值,它可以为不同的调用返回不同的结果。非确定性函数的例子: RAND(), UUID().
如果一个函数被标记为非确定性的,WHERE则对每一行(从一个表中选择时)或行组合(从多表连接中选择时)评估子句中对它的引用。
MySQL 还根据参数类型确定何时评估函数,参数是表列还是常量值。每当该列更改值时,都必须评估以表列作为参数的确定性函数。
非确定性函数可能会影响查询性能。例如,某些优化可能不可用,或者可能需要更多锁定。以下讨论使用 RAND()但也适用于其他非确定性函数。
假设一个表t具有以下定义:
1 | CREATE TABLE t (id INT NOT NULL PRIMARY KEY, col_a VARCHAR(100)); |
考虑这两个查询:
1 | SELECT * FROM t WHERE id = POW(1,2); |
由于与主键的相等比较,这两个查询似乎都使用主键查找,但这仅适用于其中的第一个:
- 第一个查询总是最多生成一行,因为
POW()常量参数是一个常量值,用于索引查找。 - 第二个查询包含一个使用非确定性函数的表达式,该函数
RAND()在查询中不是常量,但实际上对于表的每一行都有一个新值t。因此,查询读取表的每一行,评估每一行的谓词,并输出主键与随机值匹配的所有行。这可能是零、一或多行,具体取决于id列值和RAND()序列中的值 。
非确定性的影响不仅限于 SELECT陈述。此 UPDATE语句使用非确定性函数来选择要修改的行:
1 | UPDATE t SET col_a = some_expr WHERE id = FLOOR(1 + RAND() * 49); |
据推测,目的是最多更新主键与表达式匹配的一行。但是,它可能会更新零行、一行或多行,具体取决于 id列值和RAND()序列中的值 。
刚刚描述的行为对性能和复制有影响:
- 由于非确定性函数不会产生常量值,因此优化器无法使用可能适用的策略,例如索引查找。结果可能是表扫描。
InnoDB可能会升级为范围键锁,而不是为一个匹配的行使用单行锁。- 不能确定性执行的更新对于复制是不安全的。
困难源于 RAND()函数对表的每一行进行一次评估。为避免多个函数评估,请使用以下技术之一:
将包含非确定性函数的表达式移到单独的语句中,将值保存在变量中。在原始语句中,将表达式替换为对变量的引用,优化器可以将其视为常量值:
1
2SET @keyval = FLOOR(1 + RAND() * 49);
UPDATE t SET col_a = some_expr WHERE id = @keyval;将随机值分配给派生表中的变量。这种技术使变量在用于
WHERE子句中的比较之前被赋值一次 :1
2UPDATE /*+ NO_MERGE(dt) */ t, (SELECT FLOOR(1 + RAND() * 49) AS r) AS dt
SET col_a = some_expr WHERE id = dt.r;
如前所述,WHERE子句中的不确定表达式 可能会阻止优化并导致表扫描。但是,WHERE如果其他表达式是确定性的,则可以部分优化该子句。例如:
1 | SELECT * FROM t WHERE partial_key=5 AND some_column=RAND(); |
如果优化器可以使用partial_key来减少所选行的集合, RAND()则执行次数会更少,这会减少非确定性对优化的影响。
窗函数优化
窗口函数影响优化器考虑的策略:
- 如果子查询具有窗口函数,则禁用子查询的派生表合并。子查询总是被物化。
- 半连接不适用于窗口函数优化,因为半连接适用于
WHERE和 中的子查询JOIN ... ON,其中不能包含窗口函数。 - 优化器按顺序处理多个具有相同排序要求的窗口,因此对于第一个窗口之后的窗口可以跳过排序。
- 优化器不会尝试合并可以在单个步骤中评估的窗口(例如,当多个
OVER子句包含相同的窗口定义时)。解决方法是在WINDOW子句中定义窗口并在 子句中引用窗口名称OVER。
不用作窗口函数的聚合函数在最外层可能的查询中聚合。例如,在这个查询中,MySQL 看到它COUNT(t1.b)在外部查询中不存在,因为它位于WHERE子句中:
1 | SELECT * FROM t1 WHERE t1.a = (SELECT COUNT(t1.b) FROM t2); |
因此,MySQL 在子查询内聚合,将其 t1.b视为常量并返回t2.
替换WHERE为 HAVING导致错误:
1 | mysql> SELECT * FROM t1 HAVING t1.a = (SELECT COUNT(t1.b) FROM t2); |
发生错误是因为COUNT(t1.b)可以存在于 中HAVING,因此使外部查询聚合。
窗口函数(包括用作窗口函数的聚合函数)没有前面的复杂性。它们总是在编写它们的子查询中聚合,而不是在外部查询中。
窗口函数评估可能会受到windowing_use_high_precision 系统变量值的影响, 系统变量决定是否在不损失精度的情况下计算窗口操作。默认情况下, windowing_use_high_precision 已启用。
对于某些移动框架聚合,可以应用逆聚合函数从聚合中删除值。这可以提高性能,但可能会损失精度。例如,将一个非常小的浮点值添加到一个非常大的值会导致非常小的值被 大值“隐藏”。稍后反转大值时,小值的效果就消失了。
由于反向聚合导致的精度损失仅是对浮点(近似值)数据类型的操作的一个因素。对于其他类型,逆向聚合是安全的;这包括DECIMAL,它允许小数部分但它是一个精确值类型。
为了更快的执行,MySQL 总是在安全的情况下使用反向聚合:
- 对于浮点值,逆向聚合并不总是安全的,可能会导致精度损失。默认是避免逆向聚合,这种聚合速度较慢但保留精度。如果允许为了速度而牺牲安全性,
windowing_use_high_precision则可以禁用以允许逆聚合。 - 对于非浮点数据类型,逆向聚合始终是安全的,无论
windowing_use_high_precision值如何都可以使用 。 windowing_use_high_precision对MIN()和 没有影响MAX(),在任何情况下都不使用逆聚合。
对于方差函数 STDDEV_POP()、 STDDEV_SAMP()、 VAR_POP()、 VAR_SAMP()及其同义词的评估,评估可以在优化模式或默认模式下进行。优化模式可能会在最后一个有效数字中产生略有不同的结果。如果允许这种差异, windowing_use_high_precision 则可以禁用以允许优化模式。
对于EXPLAIN,窗口执行计划信息过于广泛,无法以传统输出格式显示。要查看窗口信息,请使用 EXPLAIN FORMAT=JSON并查找该 windowing元素。
窗口函数
什么是窗口函数
含义:窗口函数也叫OLAP函数(Online Anallytical Processing,联机分析处理),可以对数据进行实时分析处理。
作用:
- 解决排名问题,e.g.每个班级按成绩排名
- 解决TOPN问题,e.g.每个班级前两名的学生
语法:
select 窗口函数 over (partition by 用于分组的列名, order by 用于排序的列名
分类:
- 专用窗口函数:rank(),dense_rank(),row_number()
- 汇总函数:max(),min(),count(),sum(),avg()
注意:*窗口函数是对where后者group by子句处理后的结果进行操作,因此按照SQL语句的运行顺序,窗口函数一般放在select子句中。*
窗口函数的用法
- 专用窗口函数
rank()函数
1 | #按班级分类,将成绩降序排序 |
说明
- rank()是排序函数,括号中不需要有参数;
- 通过partition by将班级分类,相当于之前用过的group by子句功能,但是group by子句分类汇总会改变原数据的行数,而用窗口函数自救保持原行数;
- 通过order by将成绩降序排列,与之前学的order by子句用法一样,后边可以升序asc或者降序desc;
总结:
- 窗口函数这里的“窗口”表示范围,可以理解为将原数据划分范围,即分组,然后用函数实现某些目的
- 窗口函数有分组和排序的功能
- 不减少原表的行数
2. 其他专用窗口函数:dense_rank/row_number
- 用法与rank()函数相同
1 | SELECT*, |
- 当成绩相同时,会存在并列的情况,主要区别是三个函数如何处理并列情况:
在rank()函数,如果有并列情况,会占用下一个名次的位置,比如,成绩为100的学生有三个并列第一,那么99分的学生是第二名,通过rank()函数,名次是:1,1,1,4;
在dense()函数中,如果有并列的情况,不会占用下一个名词,同用上个例子,名次是:1,1,1,2;
在row_number()函数中,会忽略并列的情况,同用上述例子,名次是:1,2,3,4;

- 案例
1 | #要求按成绩排名,如果出现并列,需要出现类似1,1,1,2的形式 |

经典面试问题-topN问题
- 相关业务问题:
- 每个类别下用户最喜欢的产品是哪个?
- 每个类别下用户点击最多的5个商品是什么?
*这类问题就需要分组取最大值,最小值,每组最大的n条记录*
2. 解决方法
- 分组取最大值(用关联子查询)
1 | #查询每个学号成绩是最大的所有信息 |

注意:因为0003号选择的三个课程成绩一样,所以最大成绩有三个
- 分组取最小值
1 | #查询每个学号成绩是最大的所有信息 |

注意:因为0003号选择的三个课程成绩一样,所以最小成绩有三个
- 分组取最大N条记录
1 | #查询每个学生成绩最高的两个科目 |
说明
- 为了不受并列的影响,该题用row_number()
- 注意在子查询后边加别名
- 易错的写法:select*,row_number() over(partition by 姓名 order by 成绩 desc) as ranking from test where ranking<=2;按照sql运行顺序,where后边不能加别名,因为select子句在where子句之后运行
- 涉及到既要分组又要排序的情况,要想到用窗口函数
topN问题模板
1 | SELECT * |
聚合函数作为窗口函数

- 与平均值比较
注意这里不能用where 成绩>分组平均值,因为where子句在select子句之前执行
正确语句是套用子查询:

法二关联子查询
思路
- 单科成绩:需要对每门科目进行分组
- 平均成绩:avg()求每组的平均值
- 学生名单:输出信息中需要有学生姓名
步骤1:求分组平均值
1 | SELECT AVG(成绩) AS 平均值,科目 |
步骤2:比较
1 | SELECT* |
因为是按照科目分组,所以应该将科目进行关联
窗口函数的移动平均(以平均值为例)
作用:通过preceding,following,current row等调整作用范围,基本语法为
ROWS BETWEEN 一个时间点 AND 一个时间点
时间点可以表示为:
- n PRECEDING : 前n行
- n FOLLOWING:后n行
- CURRENT ROW : 当前行
- UNBOUNDED PRECEDING:窗口第一行
- UNBOUNDED FOLLOWING:窗口的最后一行
用法:
1 | #查询前两行到当前行的平均成绩 |
解释:
rows N proceding—N表示在当前行的前N行,比如,N=2,当前行在第4行,那么该平均值是第2行,第3行,第4行,这三行数据的平均值
注意事项
- 窗口函数中的如果省略partition by,则结果不进行分组,则以整个表为范围,也就是窗口为整个表;
- 如果省略order by 则不进行排序;
总结
1.窗口函数的语法:
窗口函数 over (partition by 用于分列的列名 order by 用于排序的列名);
2.功能:既能分组又可以排序,且不改变行数
3.分类:
- 专用窗口函数—rank(),dense_rank(),row_number()(括号中没有参数,注意区分三者区别)
- 聚合函数—max(),min(),sum(),avg(),count() (括号中有参数)
4.注意事项
原则上一般写在select子句中
5.应用场景
- 经典TOPN问题:找出每个部门工资排名前N的员工
模板:
1 | select* |
注意:不要忘记起别名,子查询的别名以及排序结果的别名
- 经典排名问题
业务需求“在每个组内排名”,比如,每个部门按业绩来排名
- 在每个组内比较问题
可以用关联子查询,也可以用窗口函数实现
补充
窗口函数基于查询结果的行数据进行计算,窗口函数运行在HAVING子句之后、 ORDER BY子句之前。窗口函数需要特殊的关键字OVER子句来指定窗口即触发一个窗口函数。
分析型数据库MySQL版支持三种类型的窗口函数:聚合函数、排序函数和值函数。
语法
1 | function over (partition by a order by b RANGE|ROWS BETWEEN start AND end) |
窗口函数包含以下三个部分。
分区规范:用于将输入行分散到不同的分区中,过程和
GROUP BY子句的分散过程相似。排序规范:决定输入数据行在窗口函数中执行的顺序。
窗口区间:指定计算数据的窗口边界。
窗口区间支持
RANGE、ROWS两种模式:RANGE按照计算列值的范围进行定义。ROWS按照计算列的行数进行范围定义。RANGE、ROWS中可以使用BETWEEN start AND end指定边界可取值。BETWEEN start AND end
取值为:
CURRENT ROW,当前行。N PRECEDING,前n行。UNBOUNDED PRECEDING,直到第1行。N FOLLOWING,后n行。UNBOUNDED FOLLOWING,直到最后1行。
例如,以下查询根据当前窗口的每行数据计算profit的部分总和。
1 | select year,country,profit,sum(profit) over (partition by country order by year ROWS BETWEEN UNBOUNDED PRECEDING and CURRENT ROW) as slidewindow from testwindow; |
而以下查询只能计算出profit的总和。
1 | select country,sum(profit) over (partition by country) from testwindow; |
注意事项
边界值的取值有如下要求:
start不能为UNBOUNDED FOLLOWING,否则提示Window frame start cannot be UNBOUNDED FOLLOWING错误。end不能为UNBOUNDED PRECEDING,否则提示Window frame end cannot be UNBOUNDED PRECEDING错误。start为CURRENT ROW并且end为N PRECEDING时,将提示Window frame starting from CURRENT ROW cannot end with PRECEDING错误。start为N FOLLOWING并且end为N PRECEDING时,将提示Window frame starting from FOLLOWING cannot end with PRECEDING错误。start为N FOLLOWING并且end为CURRENT ROW,将提示Window frame starting from FOLLOWING cannot end with CURRENT ROW错误。
当模式为RANGE时:
start或者end为N PRECEDING时,将提示Window frame RANGE PRECEDING is only supported with UNBOUNDED错误。start或者end为N FOLLOWING时,将提示Window frame RANGE FOLLOWING is only supported with UNBOUNDED错误。
转载至:https://help.aliyun.com/document_detail/120397.html
行构造函数表达式优化
行构造函数允许同时比较多个值。例如,这两个语句在语义上是等价的:
1 | SELECT * FROM t1 WHERE (column1,column2) = (1,1); |
此外,优化器以相同的方式处理这两个表达式。
如果行构造函数列不覆盖索引的前缀,优化器不太可能使用可用索引。考虑下表,它有一个主键 (c1, c2, c3):
1 | CREATE TABLE t1 ( |
在此查询中,该WHERE子句使用索引中的所有列。但是,行构造函数本身不包含索引前缀,因此优化器仅使用c1( key_len=4, 的大小c1):
1 | mysql> EXPLAIN SELECT * FROM t1 |
在这种情况下,使用等效的非构造函数表达式重写行构造函数表达式可能会导致更完整的索引使用。对于给定的查询,行构造函数和等效的非构造函数表达式是:
1 | (c2,c3) > (1,1) |
重写查询以使用非构造函数表达式会导致优化器使用索引 ( key_len=12)中的所有三列:
1 | mysql> EXPLAIN SELECT * FROM t1 |
因此,为了获得更好的结果,请避免将行构造函数与AND/OR 表达式混合使用 。使用其中之一。
在某些条件下,优化器可以将范围访问方法应用于IN()具有行构造函数参数的表达式。
避免全表扫描
当 MySQL 使用全表扫描来解析查询时, 列中 EXPLAIN显示 的输出。这通常发生在以下条件下: ALLtype
- 该表非常小,执行表扫描比费心查找键要快。这对于行数少于 10 行且行长较短的表很常见。
- 索引列 的
ONorWHERE子句中没有可用的限制 。 - 您正在将索引列与常量值进行比较,并且 MySQL 已经计算出(基于索引树)常量覆盖了表的太大部分,并且表扫描会更快。请参阅 第 8.2.1.1 节,“WHERE 子句优化”。
- 您正在通过另一列使用基数较低的键(许多行与键值匹配)。在这种情况下,MySQL 假定通过使用键可能需要多次键查找,并且表扫描会更快。
对于小表,表扫描通常是合适的,性能影响可以忽略不计。对于大表,请尝试以下技术以避免优化器错误地选择表扫描:
使用更新的扫描表的键分布。请参阅 第 13.7.3.1 节,“分析表语句”。
ANALYZE TABLE *tbl_name*使用
FORCE INDEX的扫描表告诉MySQL该表扫描是非常昂贵相比,使用给定的指标:1
2SELECT * FROM t1, t2 FORCE INDEX (index_for_column)
WHERE t1.col_name=t2.col_name;
2. 优化子查询、派生表、视图引用和公用表表达式
MySQL 查询优化器有不同的策略可用于评估子查询:
- 对于具有使用子查询
IN,= ANY或EXISTS谓词,优化器具有以下选择:- 半连接
- 物化
EXISTS策略
- 对于与
NOT IN,<> ALL或NOT EXISTS谓词一起使用的子查询,优化器有以下选择:- 物化
EXISTS策略
对于派生表,优化器有以下选择(也适用于视图引用和公用表表达式):
- 将派生表合并到外部查询块中
- 将派生表具体化为内部临时表
1 | 注意 |
使用半连接转换优化 IN 和 EXISTS 子查询谓词
半连接是一种准备时转换,它支持多种执行策略,例如表拉出、重复剔除、首次匹配、松散扫描和物化。优化器使用半连接策略来改进子查询执行,如本节所述。
Semi-join(半连接) 半连接主要场景:检查一个结果集(外表)的记录是否在另外一个结果集(字表)中存在匹配记录,半连接仅关注”子表是否存在匹配记录”,而并不考虑”子表存在多少条匹配记录”,半连接的返回结果集仅使用外表的数据集,查询语句中IN或EXISTS语句常使用半连接来处理。
MySQL支持5中Semi-join策略:
1 | 1、DuplicateWeedout |
=========================================
DuplicateWeedout
DuplicateWeedout: 使用临时表对semi-join产生的结果集去重。
Duplicate Weedout:将半联接当作联接运行,并使用临时表删除重复记录。

=========================================
FirstMatch FirstMatch: 只选用内部表的第1条与外表匹配的记录。 FirstMatch:在扫描内部表中的行组合时,如果给定值组有多个实例,请选择一个而不是全部返回。这种“快捷方式”扫描并消除了不必要行的产生。

LooseScan LooseScan: 把inner-table数据基于索引进行分组,取每组第一条数据进行匹配。
LooseScan:使用索引扫描子查询表,该索引允许从每个子查询的值组中选择单个值。

Materializelookup Materializelookup: 将inner-table去重固化成临时表,遍历outer-table,然后在固化表上去寻找匹配。
将子查询具体化为具有索引的临时表,并使用临时表执行联接。索引用于删除重复项。在将临时表与外部表连接时,该索引也可能稍后用于查找;如果没有,则扫描该表。
=========================================
MaterializeScan MaterializeScan: 将inner-table去重固化成临时表,遍历固化表,然后在outer-table上寻找匹配。

=========================================
在MySQL中优化器开关optimizer_switch中,以下参数影响Semi-join的选择:
1 | semijoin={on|off} |
=========================================
总结
在SemiJoin中5中优化策略中,影响策略的最关键的因素:
1、inner-table和outer-table上的数据量。
2、inner-table和outer-table上是否有能快速定位数据的索引。
转载至:https://www.cnblogs.com/gaogao67/p/10555306.html
在关系数据库中,连接查询(JOIN)可以从两个或多个表中获取相关的数据。我们熟悉的连接查询包括内连接、左/右/全外连接、交叉连接等。
除此之外,还有两种特殊的连接查询:半连接(Semi Join)和反连接(Anti Join)。由于 SQL 标准没有定义这两种连接查询语法,而是通过子查询的方式实现相同的效果;因此,本文就来介绍一下它们的概念和作用。
1 | 📝本文内容适用于各种数据库,包括 Oracle、MySQL、Microsoft SQL Server、PostgreSQL 以及 SQLite 等。 |
半连接
半连接返回左表中与右表至少匹配一次的数据行,通常体现为 EXISTS 或者 IN 子查询。半连接的示意图如下:

table1 中的 id = 2 在 table2 中没有对应的数据,所以连接的结果不包含该记录。
半连接只会返回左表中的数据,右表只用于条件判断。另外,即使右表中存在多个匹配的数据,左边中的数据只返回一次。半连接通常用于存在性判断,例如哪些顾客购买了产品,而不需要知道他们购买的具体产品和数量。 以下语句用于查找拥有员工的部门(示例数据):
1 | SELECT * |
以上语句也可以使用 IN 或者 =ANY 操作符加上子查询来实现:
1 | SELECT * |
虽然 SQL 没有定义 SEMI JOIN 关键字,但是我们可以通过数据库的执行计划查看相关的信息。以下是 MySQL 数据库中的执行计划:
1 | EXPLAIN ANALYZE |
其中,Nested loop semijoin 表示这是一个嵌套循环的半连接查询。
1 | 📝关于各种数据库执行计划的查看方式和结果解释,可以参考这篇文章。 |
半连接也可以使用内连接实现,例如:
1 | SELECT DISTINCT d.* |
首先通过内连接获取所有满足条件的数据,然后执行 DISTINCT 操作去除重复值;显然这种方式不如半连接效率高,不过大多数数据库可以实现这两者的等价转换。
反连接
反连接返回左表中与右表不匹配的数据行,通常体现为 NOT EXISTS 或者 NOT IN 子查询。反连接的逻辑与半连接正好相反,示意图如下:

table1 中只有 id = 2 在 table2 中没有对应的数据,所以连接的结果返回了该记录。
反连接只会返回左表中的数据,右表只用于条件判断。反查询常见的应用包括:查找没有员工的部门信息,或者没有购买任何产品的顾客信息等。
例如,以下语句返回了没有员工的部门:
1 | SELECT * |
以上语句也可以使用 NOT IN 或者 !=ALL 操作符加上子查询来实现:
1 | SELECT * |
使用 NOT IN 或者 !=ALL 操作符时需要注意子查询中可能出现的 NULL 值。例如:
1 | SELECT * |
以上查询不会返回任何结果,因为它的查询条件实际上等价于:
1 | SELECT * |
其中,dept_id != NULL 导致所有数据都不会满足条件;因为任何数值和 NULL 进行比较的结果都是未知(Unknown),也就是不为真(True)。
虽然 SQL 没有定义 ANTI JOIN 关键字,但是我们可以通过数据库的执行计划查看相关的信息。以下是 MySQL 数据库中的执行计划:
1 | EXPLAIN ANALYZE |
其中,Nested loop antijoin 表示这是一个嵌套循环的反连接查询。
反连接也可以使用外连接实现,例如:
1 | SELECT DISTINCT d.* |
首先通过左外连接获取所有满足条件的数据,然后使用 WHERE 条件找出右表中不存在的数据,最后执行 DISTINCT 操作去除重复值;显然这种方式不如反连接效率高,不过大多数数据库可以实现这两者的等价转换。
转载至:https://blog.csdn.net/horses/article/details/108152329
在 MySQL 8.0.16 及更高版本中,任何带有 EXISTS子查询谓词的语句都受到与带有等效IN子查询谓词的语句相同的半连接转换 。
从 MySQL 8.0.17 开始,以下子查询被转换为反联接:
NOT IN (SELECT ... FROM ...)NOT EXISTS (SELECT ... FROM ...).IN (SELECT ... FROM ...) IS NOT TRUEEXISTS (SELECT ... FROM ...) IS NOT TRUE.IN (SELECT ... FROM ...) IS FALSEEXISTS (SELECT ... FROM ...) IS FALSE.
简而言之,任何形式的子查询的否定IN (SELECT ... FROM ...)或EXISTS (SELECT ... FROM ...)转换为反连接。
在 MySQL 中,子查询必须满足以下条件才能作为半连接处理(或者,在 MySQL 8.0.17 及更高版本中,如果NOT修改子查询,则为反连接 ):
它必须是出现在 or子句顶层的
IN,= ANY, orEXISTS谓词的一部分,可能作为表达式中的一个术语 。例如:WHERE``ON``AND1
2
3
4SELECT ...
FROM ot1, ...
WHERE (oe1, ...) IN
(SELECT ie1, ... FROM it1, ... WHERE ...);它不得包含
HAVING子句。它不得包含任何聚合函数(无论是显式还是隐式分组)。
它不能有
LIMIT子句。该语句不得
STRAIGHT_JOIN在外部查询中使用 连接类型。该
STRAIGHT_JOIN修改必须不存在。外部表和内部表的总数必须小于连接中允许的最大表数。
子查询可以是相关的或不相关的。在 MySQL 8.0.16 及更高版本中,去相关查看
WHERE用作 的参数的子查询子句中的微相关谓词EXISTS,并使其可以像在 中使用一样 对其进行优化IN (SELECT b FROM ...)。术语平凡相关意味着谓词是一个相等谓词,它是WHERE子句中的唯一谓词 (或与 组合AND),一个操作数来自子查询中引用的表,另一个操作数来自外部查询块。该
DISTINCT关键字是允许的,但忽略。半连接策略自动处理重复删除。一个
GROUP BY子句允许的,但忽略,除非子查询还包含一个或多个聚合函数。一个
ORDER BY条款是允许的,但忽略,因为排序是无关的半连接策略的评价。
使用物化优化子查询
优化器使用物化来实现更高效的子查询处理。物化通过将子查询结果生成为临时表(通常在内存中)来加速查询执行。MySQL第一次需要子查询结果时,它会将该结果具体化到一个临时表中。任何后续需要结果时,MySQL 都会再次引用临时表。优化器可以使用散列索引对表进行索引,以使查找快速且成本低廉。索引包含唯一值以消除重复项并使表更小。
子查询实现尽可能使用内存中的临时表,如果表变得太大,则回退到磁盘存储。
如果不使用具体化,优化器有时会将非相关子查询重写为相关子查询。例如,以下IN子查询是不相关的(*where_condition* 仅涉及 fromt2和 not 列t1):
1 | SELECT * FROM t1 |
优化器可能会将其重写为 EXISTS相关子查询:
1 | SELECT * FROM t1 |
MySQL引入了Materialization(物化)这一关键特性用于子查询(比如在IN/NOT IN子查询以及 FROM 子查询)优化。 具体实现方式是:在SQL执行过程中,第一次需要子查询结果时执行子查询并将子查询的结果保存为临时表 ,后续对子查询结果集的访问将直接通过临时表获得。 与此同时,优化器还具有延迟物化子查询的能力,先通过其它条件判断子查询是否真的需要执行。物化子查询优化SQL执行的关键点在于对子查询只需要执行一次。 与之相对的执行方式是对外表的每一行都对子查询进行调用,其执行计划中的查询类型为“DEPENDENT SUBQUERY”。
在使用Materialization(物化)能提高SQL性能的同时,也有必要留意相关SQL是否存在进一步优化空间的可能性。比如下面描述的场景:
1 | mysql>explain extended Select * from (select * from score where score >= 60) derived1 where class_id = 10; |
从执行计划可看出,MySQL首先物化了子查询(select_type=DERIVED,或者以format=json格式查看执行计划),然后再通过class_id字段对结果集进行过滤。这个SQL从语义上,也可以写成如下形式,若索引合理执行效率会更高。
1 | select * from score where score >= 60 and class_id=10 |
从这个例子可以看出子查询物化时的一个潜在问题:当子查询本身比较耗费资源或结果集较大时,往往存在较高的优化空间,特别是在外层条件可作用于子查询的情况下。通过条件下推,在执行过程中尽早减少数据访问量,能显著提高性能。本文重点描述将条件下推到物化子查询的场景。
分析
事实上前面提到的查询在5.7版本可以自动重写。打开优化器选项 derived_merge=on 后,查看重写后的语句如下:
1 | select `remall`.`score`.`class_id` AS `class_id`,`remall`.`score`.`student_id` AS `student_id`,`remall`.`score`.`score` AS `score` |
另一方面,并不是所有子查询可以做到自动条件下推。比如下面这个语句:
1 | select * from (select class_id, avg(score) from score group by class_id) derived1 where class_id = 10; |
出现这种现象的原因是MySQL优化器目前只能对Mergable的视图或子查询进行重写。理解这一概念可以先从视图的两种算法入手:merge 和 temptable。
一般较为复杂的视图或子查询会使用temptable算法类型,包括:
- 聚合子查询;
- 含有LIMIT的子查询;
- UNION 或UNION ALL子查询;
- 输出字段中的子查询;
我们也可以显示的通过创建视图来判断子查询是否使用了merge算法。 比如:
1 | mysql>create algorithm=merge view v as select class_id, avg(score) from score group by class_id; |
我们创建视图时指定使用merge,但是数据库判定该算法不适合因此使用默认的undefined(实际执行过程中使用temptable算法)。
1 | /** |
使用merge算法的视图或子查询能够将查询条件下推到视图或子查询内部;而temptable算法子查询或视图不能将条件下推,只能在结果集上做进一步过滤。优化器对对这一判断标准为:
1 | bool merge_derived(THD *thd, TABLE_LIST *derived_table) |
条件下推原则
不是所有数据库引擎都完美实现条件下推下推到子查询的功能。对MySQL中使用聚合查询的视图或者from子查询,建议的条件下推原则是:
查询中只依赖于视图或者from子查询输出字段的where 条件能够安全的下推。
同时需要注意条件下推到视图或derived table子查询后所存放的恰当位置:
- 从语义上看,下推到聚合子查询的条件可以放在 HAVING 子句里。下推后的 HAVING字句可以是: HAVING xxx and NEW_CONDITION operation VALUE;
- 若条件是子查询的group 字段,且该条件上有索引,那么将该条件放在子查询的where字句中,性能会更好(HAVING条件中不含聚合函数时,将该条件下推到where字句中过滤整个group)。
对于其他类型的视图或from子查询,也可以通过语义检查的方式进行人工条件下推。
总结
任何数据库的优化器都不是万能的。 了解优化器的特性后并规避其短处,才能写出最优SQL语句。
使用 EXISTS 策略优化子查询
某些优化适用于使用IN(or =ANY) 运算符测试子查询结果的比较。本节讨论这些优化,特别是关于NULL价值观带来的挑战。讨论的最后一部分建议您如何帮助优化器。
某些优化适用于使用IN(or =ANY) 运算符测试子查询结果的比较。本节讨论这些优化,特别是关于NULL价值观带来的挑战。讨论的最后一部分建议您如何帮助优化器。
考虑以下子查询比较:
1 | outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where) |
MySQL的评估查询“从外到内。” 也就是说,它首先获取外部表达式的值 *outer_expr*,然后运行子查询并捕获它产生的行。
一个非常有用的优化是“通知”子查询,唯一感兴趣的行是那些内部表达式*inner_expr等于 的行outer_expr*。这是通过将适当的相等性下推到子查询的WHERE子句中以使其更具限制性来完成的。转换后的比较如下所示:
1 | EXISTS (SELECT 1 FROM ... WHERE subquery_where AND outer_expr=inner_expr) |
转换后,MySQL 可以使用下推等式来限制它必须检查以评估子查询的行数。
更一般地,*N* 值与返回*N-value 行的子查询 的比较受到相同的转换。如果oe_i*和 *ie_i*表示对应的外层和内层表达式值,这个子查询比较:
1 | (oe_1, ..., oe_N) IN |
变成:
1 | EXISTS (SELECT 1 FROM ... WHERE subquery_where |
使用合并或物化优化派生表、视图引用和公用表表达式
优化器可以使用两种策略处理派生表引用(也适用于视图引用和公用表表达式):
- 将派生表合并到外部查询块中
- 将派生表具体化为内部临时表
示例 1:
1 | SELECT * FROM (SELECT * FROM t1) AS derived_t1; |
通过合并派生表 derived_t1,该查询的执行类似于:
1 | SELECT * FROM t1; |
示例 2:
1 | SELECT * |
通过合并派生表 derived_t2,该查询的执行类似于:
1 | SELECT t1.*, t2.f1 |
如果优化器选择物化策略而不是为派生表合并,它会按如下方式处理查询:
- 优化器推迟派生表实现,直到在查询执行期间需要其内容。这提高了性能,因为延迟实现可能会导致根本不必这样做。考虑一个将派生表的结果连接到另一个表的查询:如果优化器首先处理另一个表并发现它没有返回任何行,则不需要进一步执行连接,优化器可以完全跳过具体化派生表。
- 在查询执行期间,优化器可能会向派生表添加索引以加快从中检索行的速度。
EXPLAIN 对于SELECT包含派生表的查询, 请考虑以下语句:
1 | EXPLAIN SELECT * FROM (SELECT * FROM t1) AS derived_t1; |
优化器通过延迟派生表直到在SELECT执行期间需要结果来避免具体化派生表 。在这种情况下,不会执行查询(因为它发生在 EXPLAIN语句中),因此永远不需要结果。
即使对于已执行的查询,派生表物化的延迟也可能使优化器能够完全避免物化。发生这种情况时,查询执行速度会比执行具体化所需的时间更快。考虑以下查询,它将派生表的结果连接到另一个表:
1 | SELECT * |
如果t1首先优化处理并且WHERE子句产生空结果,则连接必须为空并且派生表不需要具体化。
对于派生表需要物化的情况,优化器可以向物化表添加索引以加快对它的访问。如果这样的索引能够 ref访问表,则可以大大减少查询执行期间读取的数据量。考虑以下查询:
1 | SELECT * |
优化器会在列上构造一个索引 f1,derived_t2如果这样做会启用ref对最低成本执行计划的 访问。添加索引后,优化器可以将物化派生表与带索引的常规表一样对待,并从生成的索引中获得类似的好处。与没有索引的查询执行成本相比,索引创建的开销可以忽略不计。如果 ref访问将导致比其他访问方法更高的成本,优化器不会创建索引并且不会丢失任何内容。
对于派生表的具体化,对于公用表表达式 (CTE) 也是如此。此外,以下注意事项特别适用于 CTE。
如果 CTE 由查询具体化,即使查询多次引用它,它也会为查询具体化一次。
递归 CTE 总是物化的。
如果 CTE 被具体化,如果优化器估计索引可以加速顶级语句对 CTE 的访问,则它会自动添加相关索引。这类似于派生表的自动索引,不同之处在于如果 CTE 被多次引用,优化器可能会创建多个索引,以最合适的方式加速每个引用的访问。
派生条件下推优化
MySQL 8.0.22 及更高版本支持符合条件的子查询的派生条件下推。对于诸如 的查询,在许多情况下可以将外部 条件下推到派生表,在这种情况下会导致 。当派生表无法合并到外层查询中时(例如,如果派生表使用聚合),将外层条件下推到派生表应该减少需要处理的行数,从而加快执行查询。 SELECT * FROM (SELECT i, j FROM t1) AS dt WHERE i > *constant*``WHERE``SELECT * FROM (SELECT i, j FROM t1 WHERE i > *constant*) AS dt``WHERE
1 | 注意 |
WHERE在以下情况下,可以将 外部条件下推到派生物化表:
当派生表不使用聚合或窗口函数时,
WHERE可以直接将外部条件下推给它。这包括WHERE具有接合多个谓词条件AND,OR或两者。例如,查询
SELECT * FROM (SELECT f1, f2 FROM t1) AS dt WHERE f1 < 3 AND f2 > 11被重写为SELECT f1, f2 FROM (SELECT f1, f2 FROM t1 WHERE f1 < 3 AND f2 > 11) AS dt。当派生表有 a
GROUP BY并且不使用窗口函数时,WHERE引用一个或多个不属于 的列的外部 条件GROUP BY可以作为HAVING条件下推到派生表 。例如,
SELECT * FROM (SELECT i, j, SUM(k) AS sum FROM t1 GROUP BY i, j) AS dt WHERE sum > 100在导出条件下推后重写为SELECT * FROM (SELECT i, j, SUM(k) AS sum FROM t1 GROUP BY i, j HAVING sum > 100) AS dt。当派生表使用 a
GROUP BY并且外部WHERE条件中的GROUP BY列是列时,WHERE引用这些列的 条件可以直接下推到派生表。例如,查询
SELECT * FROM (SELECT i,j, SUM(k) AS sum FROM t1 GROUP BY i,j) AS dt WHERE i > 10被重写为SELECT * FROM (SELECT i,j, SUM(k) AS sum FROM t1 WHERE i > 10 GROUP BY i,j) AS dt。
以下限制和限制适用于派生表条件下推优化:
- 如果派生表包含
UNION. - 派生表不能使用
LIMIT子句。 - 不能下推包含子查询的条件。
- 如果派生表是外连接的内表,则无法使用优化。
- 如果一个物化派生表是一个公用表表达式,如果它被多次引用,条件不会下推到它。
- 如果条件为 形式,则可以下推使用参数的条件
*derived_column* > ?。如果外部WHERE条件中的派生列 是?在基础派生表中具有 a 的表达式,则无法下推此条件。
3. 优化 INFORMATION_SCHEMA 查询
监视数据库的应用程序可能会频繁使用 INFORMATION_SCHEMA表。要最有效地为这些表编写查询,请使用以下一般准则:
- 尝试仅查询
INFORMATION_SCHEMA作为数据字典表视图的表。 - 尝试仅查询静态元数据。为动态元数据选择列或使用检索条件以及静态元数据会增加处理动态元数据的开销。
1 | 笔记 |
这些列代表动态表元数据;也就是说,随着表格内容的变化而变化的信息。
默认情况下,MySQL 会在查询列时从mysql.index_stats和 mysql.table_stats字典表中检索这些列的缓存值,这比直接从存储引擎中检索统计信息更有效。如果缓存的统计信息不可用或已过期,MySQL 会从存储引擎检索最新的统计信息并将它们缓存在mysql.index_stats和 mysql.table_stats字典表中。后续查询会检索缓存的统计信息,直到缓存的统计信息过期。
该 information_schema_stats_expiry 会话变量定义缓存统计到期之前的一段时间。默认为 86400 秒(24 小时),但时间段可以延长至一年。
要随时更新给定表的缓存值,请使用 ANALYZE TABLE.
对于INFORMATION_SCHEMA作为数据字典表视图实现的表,底层数据字典表上的索引允许优化器构建高效的查询执行计划。要查看优化器所做的选择,请使用EXPLAIN. 要同时查看服务器用于执行 INFORMATION_SCHEMA查询的查询,请使用 SHOW WARNINGS紧随其后的EXPLAIN。
服务器如何处理该语句?要找出答案,请使用 EXPLAIN:
1 | mysql> EXPLAIN SELECT COLLATION_NAME |
要查看用于统计该语句的查询,请使用 SHOW WARNINGS:
1 | mysql> SHOW WARNINGS\G |
如 所示SHOW WARNINGS,服务器将查询COLLATION_CHARACTER_SET_APPLICABILITY 作为对系统数据库中的character_sets和 collations数据字典表 的查询来处理 mysql。##
4. 优化性能模式查询
监视数据库的应用程序可能会频繁使用 Performance Schema 表。要最有效地为这些表编写查询,请利用它们的索引。例如,包含一个WHERE子句,该子句根据与索引列中的特定值的比较来限制检索的行。
大多数 Performance Schema 表都有索引。不包含的表是那些通常包含很少行或不太可能被频繁查询的表。Performance Schema 索引使优化器可以访问除全表扫描之外的执行计划。这些索引还提高了相关对象的性能,例如sys使用这些表的架构视图。
要查看给定的 Performance Schema 表是否具有索引以及它们是什么,请使用SHOW INDEX或 SHOW CREATE TABLE:
1 | mysql> SHOW INDEX FROM performance_schema.accounts\G |
要查看 Performance Schema 查询的执行计划以及它是否使用任何索引,请使用 EXPLAIN:
1 | mysql> EXPLAIN SELECT * FROM performance_schema.accounts |
该EXPLAIN输出表明优化器使用accounts 表ACCOUNT包括索引 USER和HOST列。
Performance Schema 索引是虚拟的:它们是 Performance Schema 存储引擎的构造,不使用内存或磁盘存储。Performance Schema 向优化器报告索引信息,以便它可以构建高效的执行计划。Performance Schema 反过来使用有关要查找的内容(例如,特定键值)的优化器信息,以便它可以执行有效的查找,而无需构建实际的索引结构。这种实现提供了两个重要的好处:
- 它完全避免了频繁更新的表通常会产生的维护成本。
- 它在查询执行的早期阶段减少了检索到的数据量。对于索引列上的条件,性能模式有效地仅返回满足查询条件的表行。如果没有索引,Performance Schema 将返回表中的所有行,要求优化器稍后针对每一行评估条件以产生最终结果。
Performance Schema 索引是预定义的,不能删除、添加或更改。
Performance Schema 索引类似于哈希索引。例如:
- 它们仅用于使用
=or<=>运算符的相等比较 。 - 它们是无序的。如果查询结果必须具有特定的行排序特征,请包含
ORDER BY子句。
5. 优化数据变更语句
优化 INSERT 语句
要优化插入速度,请将许多小操作合并为一个大操作。理想情况下,您建立一个连接,一次发送许多新行的数据,并将所有索引更新和一致性检查延迟到最后。
插入一行所需的时间由以下因素决定,其中数字表示大致比例:
- 连接: (3)
- 向服务器发送查询:(2)
- 解析查询:(2)
- 插入行:(1 × 行大小)
- 插入索引:(1 × 索引数)
- 闭幕式:(1)
这没有考虑打开表的初始开销,每个并发运行的查询都会执行一次。
*N*假设 B 树索引, 表的大小会减慢 log 插入索引的速度。
您可以使用以下方法来加速插入:
- 如果您同时插入来自同一客户端的多行,请使用
INSERT包含多个VALUES列表的语句一次插入多行。这比使用单独的单行INSERT语句快得多(在某些情况下快很多倍)。如果要将数据添加到非空表,则可以调整bulk_insert_buffer_size变量以加快数据插入速度。请参阅 第 5.1.8 节,“服务器系统变量”。 - 从文本文件加载表格时,请使用
LOAD DATA. 这通常比使用INSERT语句快 20 倍 。见 第 13.2.7 节,“LOAD DATA 语句”。 - 利用列具有默认值的事实。仅当要插入的值与默认值不同时才显式插入值。这减少了 MySQL 必须做的解析并提高了插入速度。
- 有关 特定于表的提示,请参阅第 8.5.5 节,“InnoDB 表的批量数据加载”
InnoDB。 - 有关 特定于表的提示,请参阅第 8.6.2 节“MyISAM 表的批量数据加载”
MyISAM。
优化 UPDATE 语句
更新语句像SELECT查询一样进行优化 ,但具有额外的写入开销。写入速度取决于更新的数据量和更新的索引数量。未更改的索引不会更新。
获得快速更新的另一种方法是延迟更新,然后在以后连续进行多次更新。如果锁定表,一起执行多个更新比一次执行一个要快得多。
对于MyISAM使用动态行格式的表,将行更新为更长的总长度可能会拆分该行。如果你经常这样做,OPTIMIZE TABLE偶尔使用是非常重要的 。
mysql 批量更新共有以下四种办法
1、 replace into 批量更新
1 | replace into 表名l (id,字段1) values (1,'2'),(2,'3'),...(x,'y'); |
2、insert into …on duplicate key update批量更新
1 | insert into 表名l (id,字段1) values (1,'2'),(2,'3'),...(x,'y') on duplicate key update 字段1=values(字段1); |
3.创建临时表,先更新临时表,然后从临时表中update
1 | create temporary table tmp(id int(4) primary key,dr varchar(50)); |
注意:这种方法需要用户有temporary 表的create 权限。
4、使用mysql 自带的语句构建批量更新
mysql 实现批量 可以用点小技巧来实现:
1 | UPDATE yoiurtable |
这句sql 的意思是,更新dingdan 字段,如果id=1 则dingdan 的值为3,如果id=2 则dingdan 的值为4…… where部分不影响代码的执行,但是会提高sql执行的效率。确保sql语句仅执行需要修改的行数,这里只有3条数据进行更新,而where子句确保只有3行数据执行。
例子:
1 | UPDATE book |
如果更新多个值的话,只需要稍加修改:
1 | UPDATE categories |
到这里,已经完成一条mysql语句更新多条记录了。
转载至:https://zhuanlan.zhihu.com/p/99707106
导读
我们在向数据库里批量插入数据的时候,会遇到要将原有主键或者unique索引所在记录更新的情况,而如果没有主键或者unique索引冲突的时候,直接执行插入操作。
这种情况下,有三种方式执行:
直接
直接每条select, 判断, 然后insert,毫无疑问,这是最笨的方法了,不断的查询判断,有主键或索引冲突,执行update,否则执行insert. 数据量稍微大一点这种方式就不行了。
稍微高级一些的方式。
replace
这是mysql自身的一个语法,使用 replace 的时候。其语法为:
1 | replace into tablename (f1, f2, f3) values(vf1, vf2, vf3),(vvf1, vvf2, vvf3) |
这中语法会自动查询主键或索引冲突,如有冲突,他会先删除原有的数据记录,然后执行插入新的数据。
insert on duplicate key.
这也是一种方式,mysql的insert操作中也给了一种方式,语法如下:
1 | INSERT INTO table (a,b,c) VALUES (1,2,3) |
在insert时判断是否已有主键或索引重复,如果有,一句update后面的表达式执行更新,否则,执行插入。
第一种方式不说了,replace和insert on duplicate key这两种方式,哪中效率更高一些呢,毕竟,我们的执行sql,追求的就是高效。
分析
在最终实践结果中,得到接过如下: 在数据库数据量很少的时候, 这两种方式都很快,无论是直接的插入还是有冲突时的更新,都不错,但在数据库表的内容数量比较大(如百万级)的时候,两种方式就不太一样了,
首先是直接的插入操作,两种的插入效率都略低, 比如直接向表里插入1000条数据(百万级的表(innodb引擎)),二者都差不多需要5,6甚至十几秒。究其原因,我的主机性能是一方面,但在向大数据表批量插入数据的时候,每次的插入都要维护索引的, 索引固然可以提高查询的效率,但在更新表尤其是大表的时候,索引就成了一个不得不考虑的问题了。
其次是更新表,这里的更新的时候是带主键值的(因为我是从另一个表获取数据再插入,要求主键不能变) 同样直接更新1000条数据, replace的操作要比insert on duplicate的操作低太多太多, 当insert瞬间完成(感觉)的时候,replace要7,8s, replace慢的原因我是知道的,在更新数据的时候,要先删除旧的,然后插入新的,在这个过程中,还要重新维护索引,所以速度慢,但为何insert on duplicate的更新却那么快呢。 在向老大请教后,终于知道,insert on duplicate 的更新操作虽然也会更新数据,但其对主键的索引却不会有改变,也就是说,insert on duplicate 更新对主键索引没有影响.因此对索引的维护成本就低了一些(如果更新的字段不包括主键,那就要另说了)。
题外话:
在向数据量大的表里批量插入更新数据的时候,随着插入的数量越来越多,会导致越来越慢,这种情况下,因为我们用的innodb表,可以开启事务, 每次批量执行一批数据更新后提交, 再重新开事务处理下批数据,这样会有效增加效率
还有说明一下: 当我们执行数据库的插入和更新操作很慢的时候,不仅仅是语句,主机性能也很重要, 比如内存和cpu, 如果是虚拟机要相应适当调整, 如果在各种优化了之后效率还是很低, 但cpu和内存的占用却不高,那么就很可能是磁盘的IO性能了,这也会导致数据的更新速度慢。
转载至:https://segmentfault.com/a/1190000002527333
描述¶
INSERT … ON DUPLICATE KEY UPDATE 是对INSERT语句的 MariaDB/MySQL 扩展,如果它找到重复的唯一键或主键,将改为执行UPDATE。
如果行被插入,则行/秒受影响的值报告为 1,如果行被更新,则报告为 2,除非设置了 API 的CLIENT_FOUND_ROWS标志。
如果匹配多个唯一索引,则只更新第一个。不建议在具有多个唯一索引的表上使用此语句。
如果表有AUTO_INCREMENT主键并且语句插入或更新一行,则LAST_INSERT_ID()函数返回其 AUTO_INCREMENT 值。
该VALUES()函数只能在ON DUPLICATE KEY UPDATE子句中使用,在任何其他上下文中都没有意义。它从INSERT语句的部分返回列值。此功能对于多行插入特别有用。
使用 时IGNORE和DELAYED选项将被忽略ON DUPLICATE KEY UPDATE。
例子¶
1 | CREATE TABLE ins_duplicate (id INT PRIMARY KEY, animal VARCHAR(30)); |
如果没有现有的键,则该语句作为常规 INSERT 运行:
1 | INSERT INTO ins_duplicate VALUES (4,'Gorilla') ON DUPLICATE KEY UPDATE animal='Gorilla'; |
由于现有键,主键值为 1 的常规 INSERT 将失败:
1 | INSERT INTO ins_duplicate VALUES (1,'Antelope'); |
但是,我们可以使用 INSERT ON DUPLICATE KEY UPDATE 代替:
1 | INSERT INTO ins_duplicate VALUES (1,'Antelope') ON DUPLICATE KEY UPDATE animal='Antelope'; |
请注意,有两行报告为受影响,但这仅指 UPDATE。
1 | SELECT * FROM ins_duplicate; |
添加第二个唯一列:
1 | ALTER TABLE ins_duplicate ADD id2 INT; |
如果两行匹配唯一键匹配,则只更新第一行。这可能不安全,除非您确定自己在做什么,否则不建议这样做。请注意,下面显示的警告出现在MariaDB 5.5及更早版本中,但已在MariaDB 10.0 中删除,因为 MariaDB 现在假定键已按顺序检查,如SHOW CREATE TABLE所示。
1 | INSERT INTO ins_duplicate VALUES (2,'Lion',13) ON DUPLICATE KEY UPDATE animal='Lion'; |
尽管id为3的第三行的id2为13,也匹配,但没有更新。
将 id 更改为 auto_increment 字段。如果添加了新行,则 auto_increment 向前移动。如果该行被更新,它保持不变。
1 | ALTER TABLE `ins_duplicate` CHANGE `id` `id` INT( 11 ) NOT NULL AUTO_INCREMENT; |
从语句的 INSERT 部分引用列值:
1 | INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6) |
转载至:https://mariadb.com/kb/en/insert-on-duplicate-key-update/
MySQL中insert ignore,insert on duplicate和replace into,你可能没想过区别
读完需要10分钟
速读仅需5分钟
在数据流转中或者日常的数据操作中,势必会有数据写入的过程,如果把一些数据写入一张数据库表中,如果写入量有100万,而重复的数据有90万,那么如何让这10%的数据能够更高更高效的写入。
在MySQL方向提供了Insert ignore into,insert into on duplicate,replace into这几种写入的方式,看起来好像都差不多,但是实际上在一些场景下的差异还比较大,如果使用不当,恰恰是性能的瓶颈。
整体上我分为两个大的部分,会分别测试这三种数据写入场景。
第一部分基于id,name的数据列,其中id为主键,自增
第二部分基于id,xid,name的数据列,其中id为主键,自增,xid为唯一性索引
至于为什么要这么分,我们可以先看结果再做讨论。
*1*
基于id,name的数据列,其中id为主键,自增
为了三种测试场景的基准对等,数据初始化会按照如下的三种方式来进行。
数据初始化
| create table test_data(id int primary key auto_increment,name varchar(30)) engine=innodb; |
|---|
| insert into test_data values(1,’aa’),(2,’bb’),(3,’cc’); |
show create table test_data\G Table: test_dataCreate Table: CREATE TABLE test_data ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(30) DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
insert ignore
| insert ignore into test_data values(1,’aa’);Query OK, 0 rows affected, 1 warning (0.00 sec)>>show warnings;+———+——+—————————————+| Level | Code | Message |+———+——+—————————————+| Warning | 1062 | Duplicate entry ‘1’ for key ‘PRIMARY’ |+———+——+—————————————+1 row in set (0.00 sec) |
|---|
show create table test_data\G Table: test_dataCreate Table: CREATE TABLE test_data ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(30) DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
| insert ignore into test_data values(1,’aaa’);Query OK, 0 rows affected, 1 warning (0.01 sec)>>show warnings;+———+——+—————————————+| Level | Code | Message |+———+——+—————————————+| Warning | 1062 | Duplicate entry ‘1’ for key ‘PRIMARY’ |+———+——+—————————————+1 row in set (0.00 sec) |
| insert ignore into test_data values(4,’cc’);Query OK, 1 row affected (0.01 sec) |
| select * from test_data;+—-+——+| id | name |+—-+——+| 1 | aa || 2 | bb || 3 | cc || 4 | cc |+—-+——+4 rows in set (0.00 sec) |
replace into场景
| >>replace into test_data values(1,’aa’);Query OK, 1 row affected (0.01 sec) |
|---|
show create table test_data\G Table: test_dataCreate Table: CREATE TABLE test_data ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(30) DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
| replace into test_data values(1,’aaa’);Query OK, 2 rows affected (0.00 sec) |
| replace into test_data values(4,’cc’);Query OK, 1 row affected (0.00 sec) |
| select *from test_data;+—-+——+| id | name |+—-+——+| 1 | aaa || 2 | bb || 3 | cc || 4 | cc |+—-+——+4 rows in set (0.00 sec) |
insert into on duplicate场景
| insert into test_data values(1,’aa’) on duplicate key update id=id;Query OK, 0 rows affected (0.00 sec)insert into test_data values(1,’aa’) on duplicate key update id=id, name=name;Query OK, 0 rows affected (0.00 sec) |
|---|
show create table test_data\G Table: test_dataCreate Table: CREATE TABLE test_data ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(30) DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
| insert into test_data values(1,’aaa’) on duplicate key update id=id;Query OK, 0 rows affected (0.00 sec)insert into test_data values(1,’aaa’) on duplicate key update id=id,name=name;Query OK, 0 rows affected (0.00 sec) |
| insert into test_data values(4,’cc’) on duplicate key update id=id;Query OK, 1 row affected (0.01 sec)insert into test_data values(4,’ccc’) on duplicate key update id=id, name=name;Query OK, 0 rows affected (0.00 sec) |
| select * from test_data;+—-+——+| id | name |+—-+——+| 1 | aa || 2 | bb || 3 | cc || 4 | cc |+—-+——+4 rows in set (0.00 sec) |
小结:这三种场景的结果从自增列的处理方式来看是完全对等的,但是对于重复数据的处理方式还是存在差异。
相比而言,replace into和insert into on duplicate存在本质的区别,replace into是覆盖写,即删除原来的,写入新的。不光是主键列,其他列也会保持一致
insert into on duplicate则可以根据自己的需求来定制重复数据的处理策略,不会主动改变数据。
insert ignore into 在这种场景下最为通用,而且对于数据的侵入性最小。
所以如果要保证源端的数据基于主键完全一致,不管非主键列的数据是否一致,都需要完全覆盖,选择replace into是一种好的方法。
否则采用insert into on duplcate或者insert ignore into
*2*
基于id,xid,name的数据列,其中id为主键,自增,xid为唯一性索引
为了三种测试场景的基准对等,数据初始化会按照如下的三种方式来进行。
数据初始化
| create table test_data(id int primary key auto_increment,xid int unique key,name varchar(30)) engine=innodb; |
|---|
| insert into test_data(xid,name) values(1,’aa’),(2,’bb’),(3,’cc’);Query OK, 3 rows affected (0.01 sec)Records: 3 Duplicates: 0 Warnings: 0 |
| select *from test_data;+—-+——+——+| id | xid | name |+—-+——+——+| 1 | 1 | aa || 2 | 2 | bb || 3 | 3 | cc |+—-+——+——+3 rows in set (0.00 sec) |
insert ignore into
| insert ignore into test_data(xid,name) values(1,’aa’);Query OK, 0 rows affected, 1 warning |
|---|
CREATE TABLE test_data ( id int(11) NOT NULL AUTO_INCREMENT, xid int(11) DEFAULT NULL, name varchar(30) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY xid (xid)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 |
| insert ignore into test_data(xid,name) values(1,’aaa’);Query OK, 0 rows affected, 1 warning (0.01 sec)mysql--root@localhost:test 18:58:13>>show warnings;+———+——+———————————–+| Level | Code | Message |+———+——+———————————–+| Warning | 1062 | Duplicate entry ‘1’ for key ‘xid’ |+———+——+———————————–+ |
| insert ignore into test_data(xid,name) values(4,’dd’);Query OK, 1 row affected (0.00 sec) |
Create Table: CREATE TABLE test_data ( id int(11) NOT NULL AUTO_INCREMENT, xid int(11) DEFAULT NULL, name varchar(30) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY xid (xid)) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 |
| >select * from test_data;+—-+——+——+| id | xid | name |+—-+——+——+| 1 | 1 | aa || 2 | 2 | bb || 3 | 3 | cc || 6 | 4 | dd |+—-+——+——+4 rows in set (0.00 sec) |
replace into
| replace into test_data(xid,name) values(1,’aa’);Query OK, 2 rows affected (0.00 sec) |
|---|
| +—-+——+——+| id | xid | name |+—-+——+——+| 2 | 2 | bb || 3 | 3 | cc || 4 | 1 | aa |+—-+——+——+3 rows in set (0.00 sec) |
| replace into test_data(xid,name) values(1,’aaa’);Query OK, 2 rows affected (0.01 sec) |
| select *from test_data;+—-+——+——+| id | xid | name |+—-+——+——+| 2 | 2 | bb || 3 | 3 | cc || 5 | 1 | aaa |+—-+——+——+ |
| replace into test_data(xid,name) values(4,’cc’);Query OK, 1 row affected (0.00 sec) |
| select *from test_data;+—-+——+——+| id | xid | name |+—-+——+——+| 2 | 2 | bb || 3 | 3 | cc || 5 | 1 | aaa || 6 | 4 | dd |+—-+——+——+4 rows in set (0.00 sec) |
insert into on duplicate
| insert into test_data(xid,name) values(1,’aa’) on duplicate key update xid=xid;Query OK, 0 rows affected (0.00 sec)insert into test_data(xid,name) values(1,’aa’) on duplicate key update xid=xid, name=name;Query OK, 0 rows affected (0.01 sec) |
|---|
| +—-+——+——+| id | xid | name |+—-+——+——+| 1 | 1 | aa || 2 | 2 | bb || 3 | 3 | cc |+—-+——+——+3 rows in set (0.00 sec) |
| insert into test_data(xid,name) values(1,’aaa’) on duplicate key update xid=xid;Query OK, 0 rows affected (0.01 sec)insert into test_data(xid,name) values(1,’aaa’) on duplicate key update xid=xid,name=name;Query OK, 0 rows affected (0.00 sec) |
| insert into test_data(xid,name) values(4,’cc’) on duplicate key update xid=xid;Query OK, 1 row affected (0.01 sec)insert into test_data(xid,name) values(4,’ccc’) on duplicate key update xid=xid, name=name;Query OK, 0 rows affected (0.00 sec) |
| select * from test_data;+—-+——+——+| id | xid | name |+—-+——+——+| 1 | 1 | aa || 2 | 2 | bb || 3 | 3 | cc || 8 | 4 | cc |+—-+——+——+4 rows in set (0.00 sec) |
小结:在这个场景里面,可以看到三种场景的变化真是很大,而且区别也很明显。
insert ignore into如果不指定自增列,尽管没有写入数据,但是自增列依然会自增
replace into如果不指定自增列,会看到数据重新写入的效果已经非常明显,而且自增列始终会自动维护。
insert into on duplicate对于重复数据依然会消耗自增列值,实现相对更加灵活。
转载至:https://cloud.tencent.com/developer/article/1582295
优化 DELETE 语句
删除MyISAM表中单个行所需的时间 与索引数完全成正比。要更快地删除行,您可以通过增加key_buffer_size系统变量来增加键缓存的大小 。
从MyISAM表中删除所有行, 比 . 截断操作不是事务安全的;在活动事务或活动表锁的过程中尝试一个错误时会发生错误。
6. 优化数据库权限
权限设置越复杂,适用于所有 SQL 语句的开销就越大。简化GRANT语句建立的权限 使 MySQL 能够减少客户端执行语句时的权限检查开销。例如,如果您不授予任何表级或列级权限,则服务器永远不需要检查tables_priv和 columns_priv表的内容。同样,如果您不对任何帐户设置资源限制,则服务器不必执行资源计数。如果您的语句处理负载非常高,请考虑使用简化的授权结构来减少权限检查开销。
7. 其他优化技巧
本节列出了一些提高查询处理速度的杂项提示:
如果您的应用程序发出多个数据库请求来执行相关更新,将这些语句组合到一个存储例程中可以帮助提高性能。同样,如果您的应用程序基于多个列值或大量数据计算单个结果,则将计算组合到可加载函数中有助于提高性能。然后,由此产生的快速数据库操作可供其他查询、应用程序甚至用不同编程语言编写的代码重用。有关更多信息,请参阅 第 25.2 节,“使用存储的例程”和 向 MySQL 添加函数。
要修复
ARCHIVE表中出现的任何压缩问题 ,请使用OPTIMIZE TABLE. 请参阅 第 16.5 节,“存档存储引擎”。如果可能,将报告归类为“实时”或 “统计”,其中统计报告所需的数据仅从实时数据定期生成的汇总表中创建。
如果您的数据不符合行和列表结构,您可以将数据打包并存储到
BLOB列中。在这种情况下,您必须在应用程序中提供代码来打包和解包信息,但这可能会节省读取和写入相关值集的 I/O 操作。使用 Web 服务器,将图像和其他二进制资产存储为文件,路径名存储在数据库中而不是文件本身。大多数 Web 服务器比数据库内容更擅长缓存文件,因此使用文件通常更快。(尽管在这种情况下您必须自己处理备份和存储问题。)
如果您需要非常高的速度,请查看低级 MySQL 接口。例如,通过直接访问 MySQL
InnoDB或MyISAM存储引擎,与使用 SQL 接口相比,您可以获得显着的速度提升。同样,对于使用
NDBCLUSTER存储引擎的数据库 ,您可能希望调查 NDB API 的可能用途(请参阅 MySQL NDB Cluster API 开发人员指南)。复制可以为某些操作提供性能优势。您可以在副本之间分配客户端检索以拆分负载。为避免在进行备份时减慢源速度,您可以使用副本进行备份。请参阅第 17 章,复制。
7.7 优化和索引
提高操作性能的最佳方法 SELECT是在查询中测试的一个或多个列上创建索引。索引条目就像指向表行的指针一样,允许查询快速确定哪些行与WHERE子句中的条件匹配,并检索这些行的其他列值。所有 MySQL 数据类型都可以建立索引。
尽管为查询中使用的每个可能的列创建索引可能很诱人,但不必要的索引浪费了 MySQL 确定使用哪些索引的空间和时间。索引还会增加插入、更新和删除的成本,因为每个索引都必须更新。您必须找到正确的平衡点,才能使用最佳索引集实现快速查询。
1. MySQL 如何使用索引
索引用于快速查找具有特定列值的行。如果没有索引,MySQL 必须从第一行开始,然后通读整个表以查找相关行。桌子越大,这个成本就越高。如果该表具有相关列的索引,MySQL 可以快速确定要在数据文件中间查找的位置,而无需查看所有数据。这比顺序读取每一行要快得多。
大多数 MySQL 索引(PRIMARY KEY、 UNIQUE、INDEX和 FULLTEXT)都存储在 B 树中。例外:空间数据类型的索引使用 R 树;MEMORY 表也支持哈希索引;InnoDB对FULLTEXT索引使用倒排列表。
MySQL 对这些操作使用索引:
WHERE快速 查找与子句匹配的行。从考虑中排除行。如果有多个索引之间的选择,MySQL通常使用找到最少行数的索引(最具 选择性的索引)。
如果表具有多列索引,则优化器可以使用索引的任何最左边的前缀来查找行。举例来说,如果你有一个三列的索引
(col1, col2, col3),你有索引的搜索功能(col1),(col1, col2)以及(col1, col2, col3)。有关更多信息,请参阅 第 8.3.6 节,“多列索引”。在执行连接时从其他表中检索行。如果将列声明为相同的类型和大小,MySQL 可以更有效地使用列上的索引。在这种情况下,
VARCHAR与CHAR被认为是相同的,如果它们被声明为相同的大小。例如,VARCHAR(10)和CHAR(10)是相同的大小,但VARCHAR(10)和CHAR(15)不是。对于非二进制字符串列之间的比较,两列应使用相同的字符集。例如,将一
utf8列与一latin1列进行比较排除了索引的使用。如果不进行转换就无法直接比较值,则不同列的比较(例如,将字符串列与临时或数字列进行比较)可能会阻止使用索引。对于给定的值,如
1在数值列,它可能比较等于在字符串列,例如任何数量的值'1',' 1','00001',或'01.e1'。这排除了对字符串列使用任何索引的可能性。查找特定索引列的
MIN()或MAX()值*key_col。这是由预处理器优化的,该预处理器检查您是否 在索引中之前出现的所有关键部分上使用。在这种情况下,MySQL 对每个or 表达式执行单个键查找,并将其替换为常量。如果所有表达式都替换为常量,则查询立即返回。例如:WHERE *key_part_N* = *constant*key_col*MIN()MAX()1
2SELECT MIN(key_part2),MAX(key_part2)
FROM tbl_name WHERE key_part1=10;如果排序或分组是在可用索引的最左前缀(例如,)上完成的,则对表进行排序或分组 。如果所有关键部分后跟,则以相反的顺序读取密钥。(或者,如果索引是降序索引,则按前向顺序读取键。)请参阅 第 8.2.1.16 节“ORDER BY 优化”、 第 8.2.1.17 节“GROUP BY 优化”和 第 8.3.13 节“降序索引”。
ORDER BY *key_part1*, *key_part2*``DESC在某些情况下,可以优化查询以在不咨询数据行的情况下检索值。(为查询提供所有必要结果的索引称为 覆盖索引。)如果查询仅使用某个索引中包含的表中的列,则可以从索引树中检索所选值以提高速度:
1
2SELECT key_part3 FROM tbl_name
WHERE key_part1=1
索引对于小表或大表的查询不太重要,其中报告查询处理大部分或所有行。当查询需要访问大部分行时,顺序读取比通过索引读取要快。顺序读取最小化磁盘搜索,即使查询不需要所有行。
2. 主键优化
表的主键表示您在最重要的查询中使用的一列或一组列。它有一个关联的索引,用于快速查询性能。查询性能受益于NOT NULL优化,因为它不能包含任何NULL值。使用InnoDB存储引擎,表数据在物理上进行组织,以根据主键列或列进行超快速查找和排序。
如果您的表很大而且很重要,但没有明显的列或一组列用作主键,您可以创建一个单独的列,并使用自动递增值作为主键。当您使用外键连接表时,这些唯一 ID 可以用作指向其他表中相应行的指针。
3. 空间索引优化
MySQL 允许SPATIAL在NOT NULL几何值列上创建索引 (请参阅 第 11.4.10 节,“创建空间索引”)。优化器检查SRID索引列的属性以确定用于比较的空间参考系统 (SRS),并使用适合 SRS 的计算。(在 MySQL 8.0 之前,优化器SPATIAL使用笛卡尔计算执行索引值的比较;如果列包含具有非笛卡尔 SRID 的值,则此类操作的结果是不确定的。)
为了使比较正常工作,SPATIAL索引中的每一列都 必须受 SRID 限制。也就是说,列定义必须包含显式 SRID属性,并且所有列值必须具有相同的 SRID。
优化器SPATIAL只考虑SRID 限制列的索引:
- 限制为笛卡尔 SRID 的列上的索引启用笛卡尔边界框计算。
- 仅限于地理 SRID 的列上的索引支持地理边界框计算。
优化器会忽略SPATIAL没有SRID属性(因此不受 SRID 限制)的列上的索引。MySQL仍然维护着这样的索引,如下:
它们会针对表修改(
INSERT、UPDATE、DELETE等)进行更新。即使该列可能包含笛卡尔值和地理值的混合,也会发生更新,就像索引是笛卡尔索引一样。它们的存在只是为了向后兼容(例如,能够在 MySQL 5.7 中执行转储并在 MySQL 8.0 中恢复)。因为
SPATIAL不受 SRID 限制的列上的索引对优化器没有用,所以应该修改每个这样的列:验证列中的所有值是否具有相同的 SRID。要确定几何列中包含的 SRID *
col_name*,请使用以下查询:1
SELECT DISTINCT ST_SRID(col_name) FROM tbl_name;
如果查询返回多于一行,则该列包含混合的 SRID。在这种情况下,修改其内容,使所有值都具有相同的 SRID。
重新定义列以具有显式
SRID属性。重新创建
SPATIAL索引。
4. 外键优化
如果一个表有很多列,并且您查询了许多不同的列组合,那么将不经常使用的数据拆分成单独的表,每个表都有几列,并通过复制数字 ID 将它们关联回主表可能会很有效主表中的列。这样,每个小表都可以有一个主键,用于快速查找其数据,并且您可以使用连接操作仅查询所需的列集。根据数据的分布方式,查询可能执行更少的 I/O 并占用更少的缓存内存,因为相关列在磁盘上打包在一起。(为了最大化性能,查询尝试从磁盘读取尽可能少的数据块;
5. 列索引
最常见的索引类型涉及单个列,将来自该列的值的副本存储在数据结构中,从而可以快速查找具有相应列值的行。B 树数据结构让索引可以快速找到特定值、一组值或值范围,对应于子句中的=、 >、≤、 BETWEEN、IN、 等运算符WHERE。
每个表的最大索引数和最大索引长度是每个存储引擎定义的。请参阅 第 15 章InnoDB 存储引擎和 第 16 章替代存储引擎。所有存储引擎都支持每个表至少 16 个索引,总索引长度至少为 256 字节。大多数存储引擎都有更高的限制。
索引前缀
使用 字符串列的索引规范中的语法,您可以创建仅使用该列的第一个字符的索引 。以这种方式仅索引列值的前缀可以使索引文件更小。索引 a 或 列时, 必须为索引指定前缀长度。例如: *col_name*(*N*)NBLOBTEXT
1 | CREATE TABLE test (blob_col BLOB, INDEX(blob_col(10))); |
对于InnoDB使用REDUNDANT 或 COMPACT 行格式的表, 前缀最长可达 767 字节 。对于InnoDB使用DYNAMIC 或 COMPRESSED 行格式的表, 前缀长度限制为 3072 字节 。对于 MyISAM 表,前缀长度限制为 1000 字节。
笔记
前缀限制以字节为单位,而在前缀长度CREATE TABLE, ALTER TABLE和 CREATE INDEX语句被解释为非二进制串类型的字符数(CHAR, VARCHAR, TEXT对于二进制串类型),并且字节数(BINARY, VARBINARY, BLOB)。在为使用多字节字符集的非二进制字符串列指定前缀长度时,请考虑这一点。
如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,并检查剩余的行以寻找可能的匹配项。
有关索引前缀的其他信息,请参阅 第 13.1.15 节,“CREATE INDEX 语句”。
全文索引
FULLTEXT索引用于全文搜索。只有InnoDB和 MyISAM存储引擎支持 FULLTEXT索引和仅适用于 CHAR, VARCHAR和 TEXT列。索引总是在整个列上进行,并且不支持列前缀索引。有关详细信息,请参阅 第 12.10 节,“全文搜索功能”。
优化应用于FULLTEXT针对单个InnoDB表的某些类型的 查询 。具有这些特征的查询特别有效:
FULLTEXT仅返回文档 ID 或文档 ID 和搜索排名的查询。FULLTEXT按分数降序对匹配行进行排序并应用LIMIT子句以获取前 N 个匹配行的查询。要应用此优化,必须没有WHERE子句,只有一个ORDER BY按降序排列的子句。FULLTEXT仅检索COUNT(*)与搜索词匹配的行的 值的查询,没有其他WHERE子句。将WHERE子句编码为 ,不使用任何比较运算符。WHERE MATCH(*text*) AGAINST ('*other_text*')``> 0
对于包含全文表达式的查询,MySQL 在查询执行的优化阶段评估这些表达式。优化器不只是查看全文表达式并进行估计,它实际上是在制定执行计划的过程中对它们进行评估。
此行为的一个含义是, EXPLAIN对于全文查询,通常比在优化阶段不进行表达式评估的非全文查询慢。
EXPLAIN全文查询可能会出现Select tables optimized away在Extra列中,因为优化过程中发生了匹配;在这种情况下,在以后的执行过程中不需要进行表访问。
空间索引
您可以为空间数据类型创建索引。 MyISAM并InnoDB 支持空间类型的 R 树索引。其他存储引擎使用 B 树来索引空间类型( ARCHIVE不支持空间类型索引的 除外)。
MEMORY 存储引擎中的索引
该MEMORY存储引擎使用 HASH默认的索引,而且还支持 BTREE索引。
6. 多列索引
MySQL 可以创建复合索引(即多列上的索引)。一个索引最多可以包含 16 列。对于某些数据类型,您可以索引列的前缀(请参阅 第 8.3.5 节,“列索引”)。
MySQL 可以将多列索引用于测试索引中所有列的查询,或仅测试第一列、前两列、前三列等的查询。如果在索引定义中以正确的顺序指定列,单个复合索引可以加快对同一个表的多种查询。
多列索引可以被认为是一个排序数组,其中的行包含通过连接索引列的值创建的值。
笔记
作为复合索引的替代方案,您可以引入一个基于其他列信息“散列”的列。如果此列很短、相当独特且有索引,则它可能比许多列上的“宽”索引更快。在 MySQL 中,很容易使用这个额外的列:
1 | SELECT * FROM tbl_name |
假设一个表具有以下规范:
1 | CREATE TABLE test ( |
该name指数是在一个索引 last_name和first_name 列。该索引可用于查询中的查找,这些查询为last_name和first_name 值的组合指定了已知范围内的 值。它还可以用于仅指定last_name值的查询, 因为该列是索引的最左侧前缀(如本节后面所述)。因此,name索引用于以下查询中的查找:
1 | SELECT * FROM test WHERE last_name='Jones'; |
但是,name索引 不用于以下查询中的查找:
1 | SELECT * FROM test WHERE first_name='John'; |
假设您发出以下 SELECT语句:
1 | SELECT * FROM tbl_name |
如果col1和 上 存在多列索引col2,则可以直接获取相应的行。如果col1和 上存在单独的单列索引 col2,优化器会尝试使用索引合并优化(请参阅 第 8.2.1.3 节,“索引合并优化”),或者通过决定哪个索引排除更多行并使用该索引来获取行。
如果表具有多列索引,则优化器可以使用索引的任何最左边的前缀来查找行。举例来说,如果你有一个三列的索引(col1, col2, col3),你有索引的搜索功能 (col1),(col1, col2)以及 (col1, col2, col3)。
如果列不构成索引的最左边前缀,MySQL 不能使用索引来执行查找。假设您有SELECT此处显示的语句:
1 | SELECT * FROM tbl_name WHERE col1=val1; |
如果 上存在索引(col1, col2, col3),则只有前两个查询使用该索引。第三个和第四个查询确实涉及索引列,但不使用索引来执行查找,因为(col2)和 (col2, col3)不是最左边的前缀 (col1, col2, col3)。
7. InnoDB 和 MyISAM 索引统计收集
存储引擎收集有关表的统计信息以供优化器使用。表统计基于值组,其中值组是一组具有相同键前缀值的行。出于优化器的目的,一个重要的统计数据是平均值组大小。
MySQL通过以下方式使用平均值组大小:
估计每次
ref访问 必须读取多少行估计部分连接产生的行数,即形式的操作产生的行数
1
(...) JOIN tbl_name ON tbl_name.key = expr
随着索引的平均值组大小的增加,索引对这两个目的的用处不大,因为每次查找的平均行数增加: 为了使索引有利于优化目的,最好每个索引值都针对一个较小的表中的行数。当给定的索引值产生大量行时,索引的用处不大,MySQL 也不太可能使用它。
平均值组大小与表基数有关,即值组的数量。该 SHOW INDEX语句显示基于 的基数值*N/S*,其中 *N是表中的行数,S*是平均值组大小。该比率产生表中值组的近似数量。
对于基于<=>比较运算符的连接,NULL与任何其他值没有区别对待:NULL <=> NULL,就像任何其他 . *N* <=> *N*N
但是,对于基于=运算符的连接, NULL与非NULL值不同: 当或 (或两者) 为时,则不为真 。这会影响 以下形式的比较访问:如果当前值为is ,则 MySQL 不访问表 ,因为比较不可能为真。 *expr1* = *expr2*expr1**expr2NULLref*tbl_name.key* = *expr*exprNULL
对于=比较,NULL表中有多少个值并不重要。出于优化目的,相关值是非NULL值组的平均大小。但是,MySQL 当前无法收集或使用该平均大小。
对于InnoDB和MyISAM 表,您可以分别通过innodb_stats_method和 myisam_stats_method系统变量对表统计信息的收集进行一些控制 。这些变量具有三个可能的值,它们的区别如下:
当变量设置为 时
nulls_equal,所有NULL值都被视为相同(即,它们都形成一个值组)。如果
NULL值组大小远高于平均非NULL值组大小,则此方法将平均值组大小向上倾斜。这使得索引对优化器来说似乎不如它对于寻找非NULL值的连接有用。因此,该nulls_equal方法可能会导致优化器ref在它应该使用的时候不使用索引进行 访问。当变量设置为 时
nulls_unequal,NULL值不被视为相同。相反,每个NULL值形成一个大小为 1 的单独值组。如果您有许多
NULL值,此方法会向下倾斜平均值组大小。如果平均非NULL值组大小很大,将NULL每个值作为一组大小为 1 进行计数会导致优化器高估查找非NULL值的连接的索引值。因此,当其他方法可能更好时,该nulls_unequal方法可能会导致优化器使用此索引进行ref查找。当变量设置为 时
nulls_ignored,NULL值将被忽略。
如果您倾向于使用许多使用<=>而不是 的连接 =,则 NULL值在比较中并不特殊,并且一个NULL等于另一个。在这种情况下,nulls_equal是合适的统计方法。
该innodb_stats_method系统变量具有全局值; 该 myisam_stats_method系统变量有全局和会话值。设置全局值会影响来自相应存储引擎的表的统计信息收集。设置会话值仅影响当前客户端连接的统计信息收集。这意味着您可以通过设置会话值来强制使用给定方法重新生成表的统计信息,而不会影响其他客户端 myisam_stats_method。
要重新生成MyISAM表统计信息,您可以使用以下任何一种方法:
- 执行myisamchk –stats_method= *
method_name* –analyze - 更改表使其统计信息过期(例如插入一行然后删除),然后设置
myisam_stats_method并发出ANALYZE TABLE语句
关于使用innodb_stats_methodand 的 一些注意事项 myisam_stats_method:
- 如上所述,您可以强制显式收集表统计信息。但是,MySQL 也可以自动收集统计信息。例如,如果在对表执行语句的过程中,其中一些语句修改了表,MySQL 可能会收集统计信息。(例如,对于批量插入或删除,或某些
ALTER TABLE语句,可能会发生 这种情况。)如果发生这种情况,将使用任何值innodb_stats_method或myisam_stats_method当时有。因此,如果您使用一种方法收集统计信息,但在稍后自动收集表的统计信息时将系统变量设置为另一种方法,则使用另一种方法。 - 无法确定使用哪种方法为给定表生成统计信息。
- 这些变量仅适用于
InnoDB和MyISAM表。其他存储引擎只有一种收集表统计信息的方法。通常它更接近nulls_equal方法。
MySQL为Null会导致5个问题,个个致命!
正式开始之前,我们先来看下 MySQL 服务器的配置和版本号信息,如下图所示:

“兵马未动粮草先行”,看完了相关的配置之后,我们先来创建一张测试表和一些测试数据。
1 | -- 如果存在 person 表先删除 |
构建的测试数据,如下图所示:

有了数据之后,我们就来看当列中存在 NULL 值时,究竟会导致哪些问题?
1.count 数据丢失
当某列存在 NULL 值时,再使用 count 查询该列,就会出现数据“丢失”问题,如下 SQL 所示:
1 | select count(*),count(name) from person; |
查询执行结果如下:

从上述结果可以看出,当使用的是 count(name) 查询时,就丢失了两条值为 NULL 的数据丢失。
解决方案
如果某列存在 NULL 值时,就是用 count(*) 进行数据统计。
扩展知识:不要使用 count(常量)
阿里巴巴《Java开发手册》强制规定:不要使用 count(列名) 或 count(常量) 来替代 count(),count() 是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。 说明:count(*) 会统计值为 NULL 的行,而 count(列名) 不会统计此列为 NULL 值的行。
2.distinct 数据丢失
当使用 count(distinct col1, col2) 查询时,如果其中一列为 NULL,那么即使另一列有不同的值,那么查询的结果也会将数据丢失,如下 SQL 所示:
1 | select count(distinct name,mobile) from person; |
查询执行结果如下:

数据库的原始数据如下:

从上述结果可以看出手机号一列的 10 条数据都是不同的,但查询的结果却为 8。
3.select 数据丢失
如果某列存在 NULL 值时,如果执行非等于查询(<>/!=)会导致为 NULL 值的结果丢失。比如以下这个数据:

我需要查询除 name 等于“Java”以外的所有数据,预期返回的结果是 id 从 2 到 10 的数据,但当执行以下查询时:
1 | select * from person where name<>'Java' order by id; |
查询结果均为以下内容:

可以看出为 NULL 的两条数据凭空消失了,这个结果并不符合我们的正常预期。
解决方案
要解决以上的问题,只需要在查询结果中拼加上为 NULL 值的结果即可,执行 SQL 如下:
1 | select * from person where name<>'Java' or isnull(name) order by id; |
最终的执行结果如下:

4.导致空指针异常
如果某列存在 NULL 值时,可能会导致 sum(column) 的返回结果为 NULL 而非 0,如果 sum 查询的结果为 NULL 就可以能会导致程序执行时空指针异常(NPE),我们来演示一下这个问题。首先,我们先构建一张表和一些测试数据:
1 | -- 如果存在 goods 表先删除 |
表中原始数据如下:

接下来我们使用 sum 查询,执行以下 SQL:
1 | select sum(num) from goods where id>=4; |
查询执行结果如下:

当查询的结果为 NULL 而非 0 时,就可以能导致空指针异常。
解决空指针异常
可以使用以下方式来避免空指针异常:
1 | select ifnull(sum(num), 0) from goods where id>=4; |
查询执行结果如下:

5.增加了查询难度
当某列值中有 NULL 值时,在进行 NULL 值或者非 NULL 值的查询难度就增加了。
所谓的查询难度增加指的是当进行 NULL 值查询时,必须使用NULL 值匹配的查询方法,比如 IS NULL 或者 IS NOT NULL 又或者是 IFNULL(cloumn) 这样的表达式进行查询,而传统的 =、!=、<>... 等这些表达式就不能使用了,这就增加了查询的难度,尤其是对小白程序员来说,接下来我们来演示一下这些问题。还是以 person 表为例,它的原始数据如下:

错误用法 1:
1 | select * from person where name<>null; |
执行结果为空,并没有查询到任何数据,如下图所示:

错误用法 2:
1 | select * from person where name!=null; |
执行结果也为空,没有查询到任何数据,如下图所示:
.jpg)
正确用法 1:
1 | select * from person where name is not null; |
执行结果如下:

正确用法 2:
1 | select * from person where !isnull(name); |
执行结果如下:
.jpg)
推荐用法
阿里巴巴《Java开发手册》推荐我们使用 ISNULL(cloumn) 来判断NULL 值,原因是在 SQL 语句中,如果在 null 前换行,影响可读性;而 ISNULL(column) 是一个整体,简洁易懂。从性能数据上分析 ISNULL(column) 执行效率也更快一些。
扩展知识:NULL 不会影响索引
细心的朋友可能发现了,我在创建 person 表的 name 字段时,为其创建了一个普通索引,如下图所示:

然后我们用 explain 来分析查询计划,看当 name 中有 NULL 值时是否会影响索引的选择。explain 的执行结果如下图所示:

从上述结果可以看出,即使 name 中有 NULL 值也不会影响 MySQL 使用索引进行查询。
总结
本文我们讲了当某列为 NULL 时可能会导致的 5 种问题:丢失查询结果、导致空指针异常和增加了查询的难度。因此在最后提倡大家在创建表的时候尽量设置 is not null 的约束,如果某列确实没有值,可以设置空值(’’)或 0 作为其默认值。
转载至:https://www.zhihu.com/tardis/sogou/art/347002120
MySQL中NULL值引起的小锅
这一系列文章主要说明了一个道理:MySQL查询优化器决策是否使用某个索引执行查询时的依据是使用该索引的成本是否足够低,而成本很大程度上取决于需要扫描的二级索引记录数量占表中所有记录数量的比例。
innodb_stats_method的作用
我们知道索引列不重复的值的数量这个统计数据对于MySQL查询优化器十分重要,因为通过它可以计算出在索引列中平均一个值重复多少行,它的应用场景主要有两个:
- 单表查询中单点区间太多,比方说这样:
1 | SELECT * FROM tbl_name WHERE key IN ('xx1', 'xx2', ..., 'xxn'); |
当IN里的参数数量过多时,采用index dive的方式直接访问B+树索引去同步统计每个单点区间对应的记录的数量就太耗费性能了,所以直接依赖统计数据中的平均一个值重复多少行来计算单点区间对应的记录数量。
- 连接查询时,如果有涉及两个表的等值匹配连接条件,该连接条件对应的被驱动表中的列又拥有索引时,则可以使用
ref访问方法来对被驱动表进行查询,比方说这样:
1 | SELECT * FROM t1 JOIN t2 ON t1.column = t2.key WHERE ...; |
在真正执行对t2表的查询前,t1.comumn的值是不确定的,所以我们也不能通过index dive的方式直接访问B+树索引去同步统计每个单点区间对应的记录的数量,所以也只能依赖统计数据中的平均一个值重复多少行来计算单点区间对应的记录数量。
在统计索引列不重复的值的数量时,有一个比较烦的问题就是索引列中出现NULL值怎么办,比方说某个索引列的内容是这样:
1 | +------+ |
此时计算这个col列中不重复的值的数量就有下边的分歧:
- 有的人认为
NULL值代表一个未确定的值,所以设计MySQL的大叔才认为任何和NULL值做比较的表达式的值都为NULL,就是这样:
1 | mysql> SELECT 1 = NULL; |
所以每一个NULL值都是独一无二的,也就是说统计索引列不重复的值的数量时,应该把NULL值当作一个独立的值,所以col列的不重复的值的数量就是:4(分别是1、2、NULL、NULL这四个值)。
- 有的人认为其实
NULL值在业务上就是代表没有,所有的NULL值代表的意义是一样的,所以col列不重复的值的数量就是:3(分别是1、2、NULL这三个值)。 - 有的人认为这
NULL完全没有意义嘛,所以在统计索引列不重复的值的数量时压根儿不能把它们算进来,所以col列不重复的值的数量就是:2(分别是1、2这两个值)。
设计MySQL的大叔蛮贴心的,他们提供了一个名为innodb_stats_method的系统变量,相当于在计算某个索引列不重复值的数量时如何对待NULL值这个锅甩给了用户,这个系统变量有三个候选值:
nulls_equal:认为所有NULL值都是相等的。这个值也是innodb_stats_method的默认值。 如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别多,所以倾向于不使用索引进行访问。nulls_unequal:认为所有NULL值都是不相等的。 如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别少,所以倾向于使用索引进行访问。nulls_ignored:直接把NULL值忽略掉。
反正这个锅是甩给用户了,当你选定了innodb_stats_method值之后,优化器即使选择了不是最优的执行计划,那也跟设计MySQL的大叔们没关系了哈~ 当然对于用户的我们来说,最好不在索引列中存放NULL值才是正解。
两种不同的统计数据存储方式
InnoDB提供了两种存储统计数据的方式:
- 永久性的统计数据 这种统计数据存储在磁盘上,也就是服务器重启之后这些统计数据还在。
- 非永久性的统计数据 这种统计数据存储在内存中,当服务器关闭时这些这些统计数据就都被清除掉了,等到服务器重启之后,在某些适当的场景下才会重新收集这些统计数据。
设计MySQL的大叔们给我们提供了系统变量innodb_stats_persistent来控制到底采用哪种方式去存储统计数据。在MySQL 5.6.6之前,innodb_stats_persistent的值默认是OFF,也就是说InnoDB的统计数据默认是存储到内存的,之后的版本中innodb_stats_persistent的值默认是ON,也就是统计数据默认被存储到磁盘中。
不过InnoDB默认是以表为单位来收集和存储统计数据的,也就是说我们可以把某些表的统计数据(以及该表的索引统计数据)存储在磁盘上,把另一些表的统计数据存储在内存中。怎么做到的呢?我们可以在创建和修改表的时候通过指定STATS_PERSISTENT属性来指明该表的统计数据存储方式:
1 | CREATE TABLE 表名 (...) Engine=InnoDB, STATS_PERSISTENT = (1|0); |
当STATS_PERSISTENT=1时,表明我们想把该表的统计数据永久的存储到磁盘上,当STATS_PERSISTENT=0时,表明我们想把该表的统计数据临时的存储到内存中。如果我们在创建表时未指定STATS_PERSISTENT属性,那默认采用系统变量innodb_stats_persistent的值作为该属性的值。
问题
有同学在小册群中反应在使用基于磁盘的统计数据时,将innodb_stats_method系统变量设置成不同的值,但是发现对应的统计数据却并未发生预想的变化(可以通过SHOW INDEX FROM tbl_name或者查看mysql数据库下的innodb_index_stats表),这到底是因为啥呢?
原因
我一开始也对这个现象有点儿疑惑 ,于是不得不再次打开看吐了的源码来看。
- 比较两条记录是否相同的函数是
cmp_rec_rec_with_match,如下图所示:

其中的nulls_unequal参数是用来区别是否将两个null值认为是相等的。
- 在计算基于磁盘的统计数据时,是这样调用该函数的:

可以看到nulls_unequal参数是硬编码为FALSE。
- 在计算基于内存的统计数据时,是这样调用该函数的:

可以看到这种调用的方式就是正常的。
从实践来看,在计算基于内存的统计数据时,改变系统变量innodb_stats_method的值是起作用的,但是在计算基于磁盘的统计数据时,改变该系统变量的值是无效的。我也并不知道设计InnoDB的大叔为什么这么写,翻了翻代码也没看见这么写有什么特别的注释,之后还特意去看了MySQL文档中关于统计数据收集的相关章节,也没发现有特别声明这两者的区别。可能是一个bug?或者有啥深层次的含义?
转载至:https://zhuanlan.zhihu.com/p/93694857
8. B-Tree和Hash索引的比较
了解 B 树和哈希数据结构有助于预测在索引中使用这些数据结构的不同存储引擎上不同查询的执行情况,特别是对于MEMORY允许您选择 B 树或哈希索引的存储引擎。
B-Tree 索引特征
B 树索引可用于在使用=、 >、 >=、 <、 <=、 或BETWEEN运算符的表达式中进行列比较 。LIKE 如果 to 的参数LIKE是不以通配符开头的常量字符串,则索引也可用于比较 。例如,以下SELECT语句使用索引:
1 | SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%'; |
在第一条语句中,只考虑具有的行。在第二个语句中,只考虑具有的行。 'Patrick' <= *key_col* < 'Patricl'``'Pat' <= *key_col* < 'Pau'
以下SELECT语句不使用索引:
1 | SELECT * FROM tbl_name WHERE key_col LIKE '%Patrick%'; |
在第一条语句中,LIKE 值以通配符开头。在第二个语句中,该LIKE值不是常量。
如果使用and 超过三个字符,MySQL 使用Turbo Boyer-Moore 算法来初始化字符串的模式,然后使用此模式更快地执行搜索。 ... LIKE '%*string*%'string
*col_name* IS NULL如果*col_name*已编入索引,则使用索引进行 搜索。
任何未跨越子句中所有AND级别的 索引都 WHERE不会用于优化查询。换句话说,为了能够使用索引,必须在每个AND组中使用索引的前缀 。
以下WHERE子句使用索引:
1 | ... WHERE index_part1=1 AND index_part2=2 AND other_column=3 |
这些WHERE子句 不使用索引:
1 | /* index_part1 is not used */ |
有时 MySQL 不使用索引,即使索引可用。发生这种情况的一种情况是优化器估计使用索引将需要 MySQL 访问表中很大比例的行。(在这种情况下,表扫描可能要快得多,因为它需要更少的查找。)但是,如果这样的查询LIMIT仅用于检索某些行,MySQL 无论如何都会使用索引,因为它可以更快地找到在结果中返回几行。
哈希索引特征
哈希索引与刚才讨论的那些有一些不同的特征:
- 它们仅用于使用
=or<=>运算符的相等比较 (但速度非常快)。它们不用于比较运算符,例如<查找值范围。依赖这种单值查找的系统被称为“键值存储”;要将 MySQL 用于此类应用程序,请尽可能使用哈希索引。 - 优化器不能使用哈希索引来加速
ORDER BY操作。(这种类型的索引不能用于按顺序搜索下一个条目。) - MySQL 无法确定两个值之间大约有多少行(范围优化器使用它来决定使用哪个索引)。如果将 a
MyISAM或InnoDBtable 更改为哈希索引MEMORY表,这可能会影响某些查询。 - 只能使用整个键来搜索一行。(对于 B 树索引,键的任何最左边的前缀都可用于查找行。)
9. 索引扩展的使用
InnoDB通过向其附加主键列来自动扩展每个二级索引。考虑这个表定义:
1 | CREATE TABLE t1 ( |
该表定义了列上的主键(i1, i2)。它还k_d在列上定义了一个二级索引 (d),但在内部InnoDB扩展了该索引并将其视为列(d, i1, i2)。
在确定如何以及是否使用该索引时,优化器会考虑扩展二级索引的主键列。这可以产生更高效的查询执行计划和更好的性能。
优化器可以将扩展二级索引用于 ref、range和 index_merge索引访问、松散索引扫描访问、连接和排序优化以及 MIN()/MAX() 优化。
以下示例显示了执行计划如何受优化器是否使用扩展二级索引的影响。假设t1用这些行填充:
1 | INSERT INTO t1 VALUES |
现在考虑这个查询:
1 | EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01' |
执行计划取决于是否使用扩展索引。
当优化器不考虑索引扩展时,它只将索引k_d视为(d). EXPLAIN对于查询产生此结果:
1 | mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G |
当优化需要索引扩展到帐户,它把k_d作为(d, i1, i2)。在这种情况下,它可以使用最左边的索引前缀(d, i1)来生成更好的执行计划:
1 | mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G |
在这两种情况下,key表示优化器使用二级索引,k_d但 EXPLAIN输出显示了使用扩展索引的这些改进:
key_len从 4 个字节变为 8 个字节,表明键查找使用列d和i1,而不仅仅是d.- 该
ref值从改变const到const,const,因为键查找使用两个关键部分,没有之一。 - 的
rows计数降低从5到1,表明InnoDB应该需要检查更少的行,以产生结果。 - 该
Extra值从变化Using where; Using index到Using index。这意味着可以仅使用索引读取行,而无需查询数据行中的列。
使用扩展索引的优化器行为的差异也可以通过以下方式看到SHOW STATUS:
1 | FLUSH TABLE t1; |
前面的语句包括FLUSH TABLES和FLUSH STATUS 刷新表缓存并清除状态计数器。
没有索引扩展,SHOW STATUS产生这个结果:
1 | +-----------------------+-------+ |
使用索引扩展,SHOW STATUS产生这个结果。该 Handler_read_next值从 5 减少到 1,表明索引的使用效率更高:
1 | +-----------------------+-------+ |
系统变量 的use_index_extensions标志optimizer_switch允许控制优化器在确定如何使用InnoDB表的二级索引时是否考虑主键列 。默认情况下,use_index_extensions 已启用。要检查禁用索引扩展是否可以提高性能,请使用以下语句:
1 | SET optimizer_switch = 'use_index_extensions=off'; |
优化器对索引扩展的使用受到索引中关键部分数量 (16) 和最大密钥长度 (3072 字节) 的通常限制。
10. 优化器使用生成的列索引
MySQL 支持生成列上的索引。例如:
1 | CREATE TABLE t1 (f1 INT, gc INT AS (f1 + 1) STORED, INDEX (gc)); |
生成的列gc被定义为表达式f1 + 1。该列也被索引,优化器可以在执行计划构建期间考虑该索引。在以下查询中, WHERE子句引用gc 并且优化器考虑该列上的索引是否产生更有效的计划:
1 | SELECT * FROM t1 WHERE gc > 9; |
优化器可以使用生成列上的索引来生成执行计划,即使在查询中没有按名称直接引用这些列的情况下也是如此。如果WHERE, ORDER BY, or GROUP BY子句引用与某些索引生成列的定义匹配的表达式,则会发生这种情况 。以下查询不直接引用gc 但确实使用与 的定义匹配的表达式 gc:
1 | SELECT * FROM t1 WHERE f1 + 1 > 9; |
优化器识别出表达式f1 + 1与 的定义匹配gc并且gc被索引,因此它在执行计划构建期间考虑该索引。你可以看到这个使用 EXPLAIN:
1 | mysql> EXPLAIN SELECT * FROM t1 WHERE f1 + 1 > 9\G |
实际上,优化器已将表达式替换为与表达式f1 + 1匹配的生成列的名称。这在由EXPLAIN 显示的扩展信息中可用的重写查询中也很明显SHOW WARNINGS:
1 | mysql> SHOW WARNINGS\G |
以下限制和条件适用于优化器对生成的列索引的使用:
要使查询表达式与生成的列定义匹配,表达式必须相同并且必须具有相同的结果类型。例如,如果生成的列表达式是
f1 + 1,如果查询使用1 + f1,或者如果f1 + 1(整数表达式)与字符串进行比较,优化器将无法识别匹配项 。优化适用于这些操作符:
=,<,<=,>,>=,BETWEEN,和IN()。对于
BETWEENand 以外的运算符IN(),任一操作数都可以替换为匹配的生成列。对于BETWEENandIN(),只有第一个参数可以替换为匹配的生成列,其他参数必须具有相同的结果类型。BETWEEN并且IN()尚不支持涉及 JSON 值的比较。生成的列必须定义为至少包含一个函数调用或前一项中提到的运算符之一的表达式。表达式不能包含对另一列的简单引用。例如,
gc INT AS (f1) STORED只包含一个列引用,所以gc不考虑索引 。为了将字符串与从返回带引号的字符串的 JSON 函数计算值的索引生成列进行比较,
JSON_UNQUOTE()需要在列定义中从函数值中删除额外的引号。(对于字符串与函数结果的直接比较,JSON 比较器处理引号删除,但索引查找不会发生这种情况。)例如,不要编写如下列定义:1
doc_name TEXT AS (JSON_EXTRACT(jdoc, '$.name')) STORED
像这样写:
1
doc_name TEXT AS (JSON_UNQUOTE(JSON_EXTRACT(jdoc, '$.name'))) STORED
使用后一个定义,优化器可以检测到这两个比较的匹配项:
1
2... WHERE JSON_EXTRACT(jdoc, '$.name') = 'some_string' ...
... WHERE JSON_UNQUOTE(JSON_EXTRACT(jdoc, '$.name')) = 'some_string' ...如果没有
JSON_UNQUOTE()列定义,优化器只会检测到第一个比较的匹配项。如果优化器选择了错误的索引,可以使用索引提示来禁用它并强制优化器做出不同的选择。
生成列
前言
- 索引(index) 在SQL中可以大大提高 查询效率。
- SQL索引列中,如果使用了运算和函数,索引将无法生效。
- 如果索引列的数据量大且稳定,可以使用 生成列 来固定对索引列的计算和函数运算,并对该生成列添加索引提高查询效率。
一、生成列(Generated Columns)介绍
1.生成列语法:
1 | col_name data_type [GENERATED ALWAYS] AS (expr) |
2.生成列支持两种创建方式,分别对应关键词 VIRTUAL 和 STORED:
VIRTUAL:当行记录被访问的时候自动计算得到,不占用存储空间。支持此列上创建二级索引。 STORED:当行记录被插入或更新时候自动计算并保存起来,占用存储空间。支持此列上创建主键和二级索引。 PS: 不指定关键字时默认创建 VIRTUAL 的生成列。
3.其他:
- 一个表中可以既包含 VIRTUAL 列,又包含 STORED 列。
- 可指定列是否为NULL,是否主键,是否唯一索引,是否二级索引,是否有COMMENTS。
二、使用限制
- 支持常量,确定的内置函数及运算符。(确定性函数:对于给定的表里的数据,不同用户多次调用返回相同结果;不确定性函数: CONNECTION_ID(), CURRENT_USER(), NOW() )
- 不支持存储函数和UDF(user-defined functions,用户自定义函数)。
- 不支持存储过程及函数的参数。
- 不支持使用变量(系统变量,用户定义的变量和存储的程序局部变量)。
- 不支持子查询。
- 可以基于生成列再创建生成列,但是被依赖的生成列必须在前;如果基于基础列,则没有先后顺序。
- 自增(AUTO_INCREMENT )属性不能用在生成列上。
- 不能基于自增列创建生成列。
- 从MySQL 5.7.10开始,如果表达式求值导致截断或向函数提供不正确的输入,则CREATE TABLE语>句将以错误终止并拒绝DDL操作。
其他说明:
- 如果表达式的计算结果与声明的列类型不同,则根据通常的MySQL类型转换规则,对声明的类型进行隐式强制转换。
- 对于 CREATE TABLE … LIKE 建表方式,目标表会保留源表的生成列。
- 对于 CREATE TABLE … AS SELECT 建表方式,目标表会复制源表所有字段(包含生成列),但不复制生成列信息。
- 可以基于生成列进行表分区。
- 以STORE方式创建的生成列上创建外键约束时,不能将CASCADE,SET NULL或SET DEFAULT用作ON UPDATE引用动作,也不能将SET NULL或SET DEFAULT用作ON DELETE引用动作。
- 以STORE方式创建的生成列所依赖的基列上创建外键约束时,不能将CASCADE,SET NULL或SET DEFAULT用作ON UPDATE和ON DELETE引用动作。
- 以VIRTUAL方式创建的生成列不支持外键约束。
- 触发器不能使用生成列上的 NEW.col_name 和 OLD.col_name 值。
- 如果在生成列上进行显示的 INSERT, REPLACE和 UPDATE,则唯一允许的值是 DEFAULT 。
- 视图中也可以显示的更新生成列,但唯一允许的值也只有 DEFAULT 。
三、适用场景
- VIRTUAL生成列可以用来简化和统一查询。一个复杂的查询条件可以被定义成一个生成列,让涉及到这个表上的多个查询确保使用相同的查询条件。
- STORED生成列可以被那些快速的复杂计算条件当作物化缓存使用。
- 生成列可以被当作函数索引:用生成列定义一个函数表达式,然后在上面添加索引;此外,生成列也可用在不能直接添加索引的列上,如 json 类型字段。 对于STORED生成列,缺点就是值被存储了两次,一次在生成列中,一次在索引中。
- 如果生成列上有索引,那么优化器将识别查询中的表达式去匹配生成列的定义,然后在查询执行的时候引用生成列,即使你在查询中使用的是表达式,而不是生成列。
四、使用案例
1.不指定关键字时候默认为 VIRTUAL生成列
1 | mysql> CREATE TABLE `person` ( |
2.VIRTUAL不占用存储空间,STORED占用存储空间
创建三个表: person0: 包含VIRTUAL生成列
full_namevarchar(100) GENERATED ALWAYS AS (concat(first_name,’ ‘,last_name)) VIRTUAL person1: 与person0 表结构一样 person2: 包含STORED生成列full_namevarchar(100) GENERATED ALWAYS AS (concat(first_name,’ ‘,last_name)) STORED
向三表插入相同的大量数据,然后查询表空间文件大小如下:
1 | mysql> select name,file_size From information_schema.INNODB_SYS_TABLESPACES where name like '%person%'; |
3.一个表中可以既包含 VIRTUAL 列,又包含 STORED 列
1 | CREATE TABLE `person` ( |
4.可指定列是否为NULL,是否唯一索引,是否二级索引,是否有COMMENTS(选择部分测试),但不能使用auto_increment属性
1 | CREATE TABLE `person` ( |
5.STORED生成列可指定是否主键,VIRTUAL生成列不支持
1 | CREATE TABLE `person` ( |
6.不支持不确定性函数
1 | CREATE TABLE `person` ( |
7.可基于生成列再创建生成列,但有先后顺序,基于基础列便无先后顺序
1 | -- 基于生成列 |
8.CREATE TABLE … LIKE 建表,目标表会保留源表的生成列信息
1 | CREATE TABLE `person` ( |
9.CREATE TABLE … AS SELECT 建表,不复制生成列信息
1 | CREATE TABLE `person` ( |
10.支持基于生成列创建分区表
1 | CREATE TABLE `person` ( |
11.不论是表还是视图,显示更新生成列时仅支持值 DEFAULT
1 | CREATE TABLE `person` ( |
11.基于json字段建 生成列+索引
1 | CREATE TABLE `person` ( |
12.使用生成列的表达式进行查询亦可走索引,但条件苛刻
1 | CREATE TABLE `person` ( |
还有一些限制及特性,就不一一列举了。
作者:左轮Lee 链接:https://www.jianshu.com/p/a1a3217953a0
11. 不可见索引
MySQL 支持不可见索引;也就是说,优化器未使用的索引。该功能适用于主键以外的索引(显式或隐式)。
默认情况下,索引是可见的。为了控制可视性明确了新的索引,使用一个VISIBLE或 INVISIBLE关键字作为指标定义的一部分CREATE TABLE, CREATE INDEX或者 ALTER TABLE:
1 | CREATE TABLE t1 ( |
要更改现有索引的可见性,请 在 操作中使用 VISIBLEorINVISIBLE关键字ALTER TABLE ... ALTER INDEX:
1 | ALTER TABLE t1 ALTER INDEX i_idx INVISIBLE; |
可以从INFORMATION_SCHEMA.STATISTICS表或SHOW INDEX输出中获得有关索引是可见还是不可见的信息 。例如:
1 | mysql> SELECT INDEX_NAME, IS_VISIBLE |
不可见索引使得测试删除索引对查询性能的影响成为可能,而无需进行破坏性更改,如果需要该索引,则必须撤消该更改。对于大型表,删除和重新添加索引的成本可能很高,而使其不可见和可见是快速的就地操作。
如果优化器实际上需要或使用了一个不可见的索引,有几种方法可以注意到它的缺失对表查询的影响:
- 包含引用不可见索引的索引提示的查询会发生错误。
- Performance Schema 数据显示受影响查询的工作负载增加。
- 查询有不同的
EXPLAIN执行计划。 - 查询出现在之前没有出现在慢查询日志中。
系统变量 的use_invisible_indexes标志optimizer_switch控制优化器是否使用不可见索引来构建查询执行计划。如果标志是 off(默认值),优化器将忽略不可见索引(与引入此标志之前的行为相同)。如果标志为 on,则不可见索引保持不可见,但优化器会在执行计划构建时考虑它们。
使用SET_VAR优化器提示optimizer_switch临时更新的值 ,您可以仅在单个查询期间启用不可见索引,如下所示:
1 | mysql> EXPLAIN SELECT /*+ SET_VAR(optimizer_switch = 'use_invisible_indexes=on') */ |
索引可见性不影响索引维护。例如,索引会根据表行的更改继续更新,唯一索引可防止将重复项插入列中,无论索引是可见的还是不可见的。
如果没有显式主键的表UNIQUE 在NOT NULL列上有任何索引,它可能仍然具有有效的隐式主键。在这种情况下,第一个这样的索引在表行上放置与显式主键相同的约束,并且不能使该索引不可见。考虑以下表定义:
1 | CREATE TABLE t2 ( |
该定义不包括显式主键,但NOT NULL列上的索引对行j 施加了与主键相同的约束,并且不能使其不可见:
1 | mysql> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE; |
现在假设向表中添加了一个显式主键:
1 | ALTER TABLE t2 ADD PRIMARY KEY (i); |
不能使显式主键不可见。此外,唯一索引j不再充当隐式主键,因此可以不可见:
1 | mysql> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE; |
12. 降序索引
MySQL 支持降序索引:DESC在索引定义中不再被忽略,而是导致按降序存储键值。以前,可以以相反的顺序扫描索引,但会降低性能。可以按前向顺序扫描降序索引,这样效率更高。当最有效的扫描顺序混合了某些列的升序和其他列的降序时,降序索引还使优化器可以使用多列索引。
考虑以下表定义,其中包含两列和四个两列索引定义,用于列上的升序和降序索引的各种组合:
1 | CREATE TABLE t ( |
表定义产生四个不同的索引。优化器可以对每个ORDER BY子句执行前向索引扫描 并且不需要使用 filesort操作:
1 | ORDER BY c1 ASC, c2 ASC -- optimizer can use idx1 |
降序索引的使用受以下条件的约束:
仅
InnoDB存储引擎支持降序索引 ,具有以下限制:- 如果索引包含降序索引键列或主键包含降序索引列,则二级索引不支持更改缓冲。
- 该
InnoDBSQL解析器不使用降序索引。对于InnoDB全文搜索,这意味着FTS_DOC_ID索引表的列上所需的索引不能定义为降序索引。有关更多信息,请参阅 第 15.6.2.4 节,“InnoDB 全文索引”。
所有提供升序索引的数据类型都支持降序索引。
普通(非生成)列和生成列(
VIRTUAL和STORED)都支持降序索引。DISTINCT可以使用包含匹配列的任何索引,包括降序键部分。支持降序索引,
BTREE但不支持HASH索引。FULLTEXT或SPATIAL索引不支持降序 索引。为、和 索引 明确指定
ASC和DESC指示符会 导致错误。HASH``FULLTEXT``SPATIAL
您可以在**Extra**输出的列中看到EXPLAIN优化器能够使用降序索引,如下所示:
1 | mysql> CREATE TABLE t1 ( |
在EXPLAIN FORMAT=TREE输出中,使用降序索引是通过添加 (reverse)以下索引名称来表示的,如下所示:
1 | mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 ORDER BY a ASC\G |
降序索引
从8.0 优化器实验室版本开始,MySQL 服务器现在支持降序索引。正如我将在这篇文章中详细介绍的那样,这个新功能可用于消除对结果进行排序的需要,并提高许多查询的性能。
介绍
在此版本之前,所有索引都是按升序创建的。在解析语法本身时,不会保留元数据。例如在 MySQL 5.7 中:
| 123456789101112 | mysql 5.7> CREATE TABLE t1 (a INT, b INT, INDEX a_desc_b_asc (a DESC, b ASC));Query OK, 0 rows affected (0.47 sec) mysql 5.7> SHOW CREATE TABLE t1\G*************************** 1. row *************************** Table: t1Create Table: CREATE TABLE t1 ( a int(11) DEFAULT NULL, b int(11) DEFAULT NULL, KEY a_desc_b_asc (a,b) <– Order is not preserved) ENGINE=InnoDB DEFAULT CHARSET=latin11 row in set (0.00 sec) |
|---|---|
虽然应该注意的是 MySQL 5.7 优化器能够向后扫描升序索引(以给出降序),但它的成本更高。如下图所示,我们可以看到前向索引扫描比后向索引扫描好大约 15%。
无法支持降序索引的主要限制是优化器必须对混合顺序使用文件排序,例如ORDER BY a DESC, b ASC。
MySQL 8.0 中的改进
随着降序索引的引入,InnoDB 现在可以按降序存储条目,并且优化器将在查询中请求降序时利用它。重复上面的例子,我们可以看到在创建表时正确保留了索引顺序信息:
| 1234567891011121314 | mysql 8.0> CREATE TABLE t1 (a INT, b INT, INDEX a_desc_b_asc (a DESC, b ASC));Query OK, 0 rows affected (0.47 sec) mysql 8.0> show create table t1;+——-+——————————————————————————————————————————————————–+| Table | Create Table |+——-+——————————————————————————————————————————————————–+| t1 | CREATE TABLE t1 (a int(11) DEFAULT NULL,b int(11) DEFAULT NULL,KEY a_desc_b_asc (a DESC,b)) ENGINE=InnoDB DEFAULT CHARSET=latin1 |+——-+——————————————————————————————————————————————————–+1 row in set (0.00 sec) |
|---|---|
EXPLAIN的输出也得到了改进,以区分向后和向前索引扫描。在 MySQL-5.7 的情况下,我们对除下面显示的查询 2 和查询 6 之外的所有查询使用反向索引扫描或文件排序,因为这两个查询都只需要升序。
查询 1:SELECT * FROM t1 ORDER BY a DESC;
| 1234567 | mysql 8.0> explain SELECT * FROM t1 ORDER BY a DESC;+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+————-+| 1 | SIMPLE | t1 | NULL | index | NULL | a_desc_b_asc | 10 | NULL | 10 | 100.00 | Using index |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+————-+1 row in set, 1 warning (0.00 sec) |
|---|---|
查询 2:SELECT * FROM t1 ORDER BY a ASC;
| 1234567 | mysql 8.0> explain SELECT * FROM t1 ORDER BY a ASC;+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+———————————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+———————————-+| 1 | SIMPLE | t1 | NULL | index | NULL | a_desc_b_asc | 10 | NULL | 10 | 100.00 | Backward index scan; Using index |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+———————————-+1 row in set, 1 warning (0.00 sec) |
|---|---|
查询 3:SELECT * FROM t1 ORDER BY a DESC, b ASC;
| 1234567 | mysql 8.0> EXPLAIN SELECT * FROM t1 ORDER BY a DESC, b ASC;+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+————-+| 1 | SIMPLE | t1 | NULL | index | NULL | a_desc_b_asc | 10 | NULL | 10 | 100.00 | Using index |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+————-+1 row in set, 1 warning (0.00 sec) |
|---|---|
查询 4:SELECT * FROM t1 ORDER BY a ASC, b DESC;
| 1234567 | mysql 8.0> EXPLAIN SELECT * FROM t1 ORDER BY a ASC, b DESC;+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+———————————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+———————————-+| 1 | SIMPLE | t1 | NULL | index | NULL | a_desc_b_asc | 10 | NULL | 10 | 100.00 | Backward index scan; Using index |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+———————————-+1 row in set, 1 warning (0.00 sec) |
|---|---|
查询 5:SELECT * FROM t1 ORDER BY a DESC, b DESC;
| 1234567 | mysql 8.0> EXPLAIN SELECT * FROM t1 ORDER BY a DESC, b DESC;+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+—————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+—————————–+| 1 | SIMPLE | t1 | NULL | index | NULL | a_desc_b_asc | 10 | NULL | 10 | 100.00 | Using index; Using filesort |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+—————————–+1 row in set, 1 warning (0.01 sec) |
|---|---|
查询 5:SELECT * FROM t1 ORDER BY a ASC, b ASC;
| 1234567 | mysql 8.0> EXPLAIN SELECT * FROM t1 ORDER BY a ASC, b ASC;+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+—————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+—————————–+| 1 | SIMPLE | t1 | NULL | index | NULL | a_desc_b_asc | 10 | NULL | 10 | 100.00 | Using index; Using filesort |+—-+————-+——-+————+——-+—————+————–+———+——+——+———-+—————————–+1 row in set, 1 warning (0.00 sec) |
|---|---|
以下是当表具有一个索引a_desc_b_asc (a DESC, b ASC)时所有上述 6 个查询的性能数字。在 MySQL-5.7 中它是a_asc_b_asc(a ASC, b ASC)因为不支持降序索引。数据大小为 1000 万行。
了解性能数据:
- 我们看到查询 1 的改进,因为请求的顺序是列“a”的
DESC。 - 对于查询 2,因为请求的顺序是 ASC,我们可以看到它在 MySQL-8.0 中花费更多时间,因为我们进行了反向索引扫描(注意 MySQL-8.0 总体上表现更好,如图所示。MySQL-5.7 正向扫描正在执行与 MySQL-8.0 向后扫描的时间相同)。
- 查询 3 与查询 1 类似,请求顺序与索引顺序相同。然而,在 MySQL-5.7 中,对于任何请求混合顺序的查询,它都会求助于文件排序。因此差异是巨大的。
- 正如我们所见,查询 4 执行反向索引扫描,因此比 MySQL-8.0 中的查询 3 花费更多时间。但是,对于查询 5 和查询 6,在 MySQL-8.0 中进行前向或后向索引扫描无法满足请求的顺序
((a DESC, b DESC)/(a ASC, b ASC))。所以它使用文件排序。但是 MySQL-5.7 可以使用前向/后向索引扫描在这种情况下给出请求的顺序,因为MySQL-5.7 中会忽略ASC/DESC索引标志。 - 如果用户想避免查询 5 和查询 6 的文件排序,他/她可以更改表以添加键
(a ASC, b ASC)。此外,如果用户也想避免反向索引扫描,他/她可以添加(a ASC, b DESC)和(a DESC, b DESC)。
下面是添加点 #5 下的附加索引后 MySQL-5.7.14 和 MySQL-8.0-labs 的最终比较:
请注意,在 MySQL-5.7 中,我们无法添加额外的索引来提高上述查询的性能。此外,使用此功能,在某些情况下可以避免物化,例如在连接中的第一个表上请求混合顺序时。这些是降序索引提高性能的一些用例。范围扫描访问方法也使用降序索引。尽管并非所有范围扫描访问方法都使用降序关键部分,但我们将在未来尝试解除限制。
行为改变
随着降序索引的引入,我们删除了对作为GROUP BY一部分提到的列的升序隐式排序结果的支持。除了上述改进外,我们还看到在隐含订单但可能不需要的情况下性能有所提高。
转发至:http://mysqlserverteam.com/mysql-8-0-labs-descending-indexes-in-mysql/
终于不再对group by进行隐式排序
由于降序索引的引入,MySQL 8.0再也不会对group by操作进行隐式排序。
下面看看MySQL 5.7和8中的测试情况
1 | create table slowtech.t1(id int); |
MySQL 5.7
1 | mysql> select * from slowtech.t1 group by id; |
“Using filesort”,代表查询中有排序操作,从结果上看,id列确实也是升序输出。
MySQL 8.0
1 | mysql> select * from slowtech.t1 group by id; |
不仅结果没有升序输出,执行计划中也没有“Using filesort”。
可见,MySQL 8.0对于group by操作确实不再进行隐式排序。
从5.7升级到8.0,依赖group by隐式排序的业务可要小心咯。
转发至:https://www.cnblogs.com/ivictor/p/9072361.html
降序索引2
我们今天来介绍下MySQL 8.0 引入的新特性:倒序索引。 MySQL长期以来对索引的建立只允许正向asc存储,就算建立了desc,也是忽略掉。比如对于以下的查询,无法发挥索引的最佳性能。
- 查询一
1 | select * from tb1 where f1 = ... order by id desc; |
- 查询二
1 | select * from tb1 where f1 = ... order by f1 asc , f2 desc; |
那对于上面的查询,尤其是数据量和并发到一定峰值的时候,则对OS的资源消耗非常大。一般这样的SQL在查询计划里面会出现using filesort等状态。
比如针对下面的表t1,针对字段rank1有两个索引,一个是正序的,一个是反序的。不过在MySQL 8.0 之前的版本都是按照正序来存储。

按照rank1 正向排序的执行计划,

按照rank1 反向排序的执行计划,

从执行计划来看,反向比正向除了extra里多了Using temporary; Using filesort这两个,其他的一模一样。这两个就代表中间用到了临时表和排序,一般来说,凡是执行计划里用到了这两个的,性能几乎都不咋地。除非我这个临时表不太大,而用于排序的buffer 也足够大,那性能也不至于太差。那这两个选项到底对性能有多大影响呢? 我们分别执行这两个查询,并且查看MySQL的session级的status就大概能看出些许不同。

通过以上两张图,我们发现反向的比正向的多了很多个计数,比如通过扫描的记录数增加了10倍,而且还伴有10倍的临时表的读和写记录数。那这个开销是非常巨大的。那以上的查询是在MySQL5.7上运行的。
MySQL 8.0 给我们带来了倒序索引(Descending Indexes),也就是说反向存储的索引。 这里不要跟搜索引擎中的倒排索引混淆了,MySQL这里只是反向排序存储而已。不过这个倒序存储已经解决了很大的问题。我们再看下之前在MySQL5.7上运行的例子。
我们把数据导入到MySQL 8.0,

再把原来的索引变为倒序索引,

再次看下第二个SQL的查询计划,

很显然,用到了这个倒序索引 idx_rank1_desc,而这里的临时表等的信息消失了。 当然了,这里的组合比较多,比如我这张表的字段rank1,rank2两个可以任意组合,
- 组合一
1 | (rank1 asc,rank2 asc); |
- 组合二
1 | (rank1 desc,rank2 desc); |
- 组合三
1 | (rank1 asc,rank2 desc); |
- 组合四
1 | (rank1 desc,rank2 asc); |
我把这几个加上,
适合的查询比如
- 查询一
1 | Select * from t1 where rank1 = 11 order by rank2; |
- 查询二
1 | Select * from t1 where 1 order by rank1,rank2; |
- 查询三
Select * from t1 where 1 order by rank1 desc,rank2;
…
等等,我就不一一示范了。
转载至:https://opensource.actionsky.com/20190520-mysql8-0-index3/
从 TIMESTAMP 列索引查找
临时值TIMESTAMP作为 UTC 值存储在 列中,插入到TIMESTAMP列中和从列中检索的值 在会话时区和 UTC 之间转换。(这与CONVERT_TZ()函数执行的转换类型相同 。如果会话时区是 UTC,则实际上没有时区转换。)
由于本地时区更改的约定,例如夏令时 (DST),UTC 和非 UTC 时区之间的转换不是双向一对一的。不同的 UTC 值在另一个时区可能不会不同。以下示例显示了在非 UTC 时区中变得相同的不同 UTC 值:
1 | mysql> CREATE TABLE tstable (ts TIMESTAMP); |
笔记
要使用命名时区,例如'MET'或 'Europe/Amsterdam',必须正确设置时区表。有关说明,请参阅 第 5.1.15 节,“MySQL 服务器时区支持”。
您可以看到两个不同的 UTC 值在转换'MET'为时区时是相同的。对于给定的TIMESTAMP列查询,这种现象可能导致不同的结果 ,这取决于优化器是否使用索引来执行查询。
假设查询从前面显示的表中选择值,使用WHERE子句在ts列中搜索 单个特定值,例如用户提供的时间戳文字:
1 | SELECT ts FROM tstable |
进一步假设查询在以下条件下执行:
会话时区不是 UTC 并且具有 DST 转换。例如:
1
SET time_zone = 'MET';
TIMESTAMP由于 DST 偏移 ,列中存储的唯一 UTC 值在 会话时区中不是唯一的。(前面显示的示例说明了这是如何发生的。)该查询指定在会话时区中进入 DST 一小时内的搜索值。
在这些条件下,WHERE子句中的比较 对于非索引和索引查找以不同的方式发生,并导致不同的结果:
如果没有索引或优化器无法使用它,则在会话时区中进行比较。优化器执行表扫描,在其中检索每个
ts列值,将其从 UTC 转换为会话时区,并将其与搜索值(也在会话时区中解释)进行比较:1
2
3
4
5
6
7
8mysql> SELECT ts FROM tstable
WHERE ts = '2018-10-28 02:30:00';
+---------------------+
| ts |
+---------------------+
| 2018-10-28 02:30:00 |
| 2018-10-28 02:30:00 |
+---------------------+由于存储的
ts值转换为会话时区,因此查询可能会返回两个时间戳值,它们与 UTC 值不同但在会话时区中相等: 一个值发生在时钟更改时 DST 偏移之前,和 DST 转换后出现的一个值。如果有可用的索引,则以 UTC 进行比较。优化器执行索引扫描,首先将搜索值从会话时区转换为 UTC,然后将结果与 UTC 索引条目进行比较:
1
2
3
4
5
6
7
8mysql> ALTER TABLE tstable ADD INDEX (ts);
mysql> SELECT ts FROM tstable
WHERE ts = '2018-10-28 02:30:00';
+---------------------+
| ts |
+---------------------+
| 2018-10-28 02:30:00 |
+---------------------+在这种情况下,(转换后的)搜索值仅与索引条目匹配,并且由于不同存储的 UTC 值的索引条目也是不同的,因此搜索值只能匹配其中之一。
由于非索引和索引查找的优化器操作不同,因此查询在每种情况下都会产生不同的结果。非索引查找的结果返回会话时区中匹配的所有值。索引查找不能这样做:
- 它在存储引擎中执行,它只知道 UTC 值。
- 对于映射到相同 UTC 值的两个不同会话时区值,索引查找仅匹配相应的 UTC 索引条目并且仅返回单行。
在前面的讨论中,存储在其中的数据集 tstable恰好由不同的 UTC 值组成。在这种情况下,所示形式的所有使用索引的查询最多匹配一个索引条目。
如果索引不是UNIQUE,则表(和索引)可能存储给定 UTC 值的多个实例。例如,该ts列可能包含 UTC value 的多个实例 '2018-10-28 00:30:00'。在这种情况下,使用索引的查询将返回它们中的每一个(转换为'2018-10-28 02:30:00'结果集中的 MET 值)。使用索引的查询将转换后的搜索值与 UTC 索引条目中的单个值匹配,而不是匹配转换为会话时区中的搜索值的多个 UTC 值,这仍然是正确的。
如果返回ts会话时区中匹配的所有值很重要,则解决方法是通过IGNORE INDEX提示禁止使用索引:
1 | mysql> SELECT ts FROM tstable |
双向时区转换缺乏一对一映射的情况也出现在其他上下文中,例如使用FROM_UNIXTIME()和 UNIX_TIMESTAMP()函数执行的转换 。
7.8 优化数据库结构
作为数据库设计者,寻找最有效的方式来组织架构、表和列。与调整应用程序代码时一样,您可以最小化 I/O,将相关项放在一起,并提前计划,以便随着数据量的增加性能保持较高。从高效的数据库设计开始,团队成员可以更轻松地编写高性能的应用程序代码,并使数据库能够随着应用程序的发展和重写而持久耐用。
1. 优化数据大小
设计您的表以最小化它们在磁盘上的空间。通过减少写入磁盘和从磁盘读取的数据量,这可以带来巨大的改进。较小的表通常需要较少的主内存,而在查询执行期间正在积极处理其内容。表数据的任何空间减少也会导致可以更快处理的较小索引。
MySQL 支持许多不同的存储引擎(表类型)和行格式。对于每个表,您可以决定使用哪种存储和索引方法。为您的应用程序选择合适的表格格式可以为您带来巨大的性能提升。
表格列
- 尽可能使用最有效(最小)的数据类型。MySQL 有许多专门的类型来节省磁盘空间和内存。例如,如果可能,使用较小的整数类型来获得较小的表。
MEDIUMINT通常是一个更好的选择,INT因为一MEDIUMINT列使用的空间减少了 25%。 NOT NULL如果可能 ,将列声明为。通过更好地使用索引并消除测试每个值是否为 的开销,它使 SQL 操作更快NULL。您还可以节省一些存储空间,每列一位。如果您确实需要NULL表中的值,请使用它们。只需避免允许NULL每列中的值的默认设置 。
行格式
InnoDB``DYNAMIC默认情况下,表是使用行格式创建的 。要使用除DYNAMIC、 configure 之外的行格式innodb_default_row_format,或ROW_FORMAT在CREATE TABLEorALTER TABLE语句中显式指定选项。紧凑的行格式系列(包括
COMPACT、DYNAMIC和 )COMPRESSED以增加某些操作的 CPU 使用为代价减少了行存储空间。如果您的工作负载是受缓存命中率和磁盘速度限制的典型工作负载,它可能会更快。如果是受 CPU 速度限制的罕见情况,它可能会更慢。CHAR当使用诸如utf8mb3或 之类的可变长度字符集时 ,紧凑的行格式系列还优化了 列存储utf8mb4。与ROW_FORMAT=REDUNDANT, 占用× 字符集的最大字节长度。许多语言可以主要使用单字节字符来编写 ,因此固定的存储长度通常会浪费空间。使用紧凑的行格式系列,分配范围为到 的可变存储量CHAR(*N*)Nutf8``InnoDB*N**N*× 通过去除尾随空格,这些列的字符集的最大字节长度。最小存储长度为 *N*字节,以便在典型情况下进行就地更新。有关更多信息,请参阅 第 15.10 节,“InnoDB 行格式”。要通过以压缩形式存储表数据来进一步最小化空间,请
ROW_FORMAT=COMPRESSED在创建InnoDB表时 指定 ,或在现有表上运行 myisampack命令MyISAM。(InnoDB压缩表是可读可写的,而MyISAM压缩表是只读的。)对于
MyISAM表中,如果没有任何可变长度列(VARCHAR,TEXT,或BLOB列),一个固定大小的行格式被使用。这更快,但可能会浪费一些空间。请参阅第 16.2.3 节,“MyISAM 表存储格式”。即使您有VARCHAR带有CREATE TABLE选项的 列,您也可以暗示您想要固定长度的行ROW_FORMAT=FIXED。
索引
- 表的主索引应尽可能短。这使得识别每一行变得容易和高效。对于
InnoDB表,主键列在每个二级索引条目中都是重复的,所以如果你有很多二级索引,一个短的主键可以节省相当多的空间。 - 仅创建提高查询性能所需的索引。索引有利于检索,但会减慢插入和更新操作的速度。如果您主要通过搜索列组合来访问表,请在它们上创建单个复合索引,而不是为每一列创建单独的索引。索引的第一部分应该是最常用的列。如果在从表中选择时总是使用很多列,则索引中的第一列应该是重复次数最多的列,以获得更好的索引压缩。
- 如果很可能一个长字符串列在第一个字符数上有一个唯一的前缀,最好只索引这个前缀,使用 MySQL 支持在列的最左侧创建索引(参见第 13.1.15 节,“创建索引语句”)。较短的索引速度更快,不仅因为它们需要更少的磁盘空间,而且因为它们还会在索引缓存中提供更多的命中,从而减少磁盘搜索。
加入
- 在某些情况下,将经常扫描的表分成两个是有益的。如果它是动态格式表,则尤其如此,并且可以使用较小的静态格式表,该表可用于在扫描表时查找相关行。
- 在具有相同数据类型的不同表中声明具有相同信息的列,以加快基于相应列的联接。
- 保持列名简单,以便您可以在不同的表中使用相同的名称并简化连接查询。例如,在名为 的表中
customer,使用列名name代替customer_name。为了使您的名称可移植到其他 SQL 服务器,请考虑将它们保持在 18 个字符以内。
*正常化
- 通常,尽量保持所有数据非冗余(观察数据库理论中称为 第三范式的内容)。不要重复诸如姓名和地址之类的冗长值,而是为它们分配唯一 ID,根据需要在多个较小的表中重复这些 ID,并通过在 join 子句中引用 ID 将这些表连接到查询中。
- 如果速度比磁盘空间和保留多个数据副本的维护成本更重要,例如在分析大表中的所有数据的商业智能场景中,您可以放宽规范化规则,复制信息或创建汇总表以获得更多速度。
2. 优化 MySQL 数据类型
优化数值数据
- 对于可以表示为字符串或数字的唯一 ID 或其他值,首选数字列而不是字符串列。由于大数值可以存储在比相应字符串更少的字节中,因此传输和比较它们的速度更快,占用的内存更少。
- 如果您使用的是数字数据,在许多情况下,从数据库(使用实时连接)访问信息比访问文本文件要快。数据库中的信息可能以比文本文件更紧凑的格式存储,因此访问它涉及的磁盘访问更少。您还可以在应用程序中保存代码,因为您可以避免解析文本文件以查找行和列边界。
优化字符和字符串类型
对于字符和字符串列,请遵循以下准则:
- 当您不需要特定于语言的整理功能时,使用二进制整理顺序进行快速比较和排序操作。您可以使用
BINARY运算符在特定查询中使用二进制排序规则。 - 比较来自不同列的值时,请尽可能使用相同的字符集和排序规则声明这些列,以避免在运行查询时进行字符串转换。
- 对于小于 8KB 的列值,使用 binary
VARCHAR而不是BLOB. 该GROUP BY和ORDER BY条款可以生成临时表,这些临时表可以使用的MEMORY存储引擎,如果原来的表不包含任何BLOB列。 - 如果表包含字符串列(例如名称和地址),但许多查询不检索这些列,请考虑将字符串列拆分为单独的表,并在必要时使用带有外键的连接查询。当 MySQL 从一行中检索任何值时,它会读取一个包含该行所有列(可能还有其他相邻行)的数据块。保持每一行很小,只包含最常用的列,可以让更多的行适合每个数据块。这种紧凑的表减少了常见查询的磁盘 I/O 和内存使用。
- 当您使用随机生成的值作为
InnoDB表中的主键时,如果可能,请为其添加一个升序值作为前缀,例如当前日期和时间。当连续的主值在物理上彼此靠近时,InnoDB可以更快地插入和检索它们。 - 有关为什么数字列通常优于等效字符串列的原因,请参见第 8.4.2.1 节,“优化数字数据”。
优化 BLOB 类型
- 存储包含文本数据的大型 blob 时,请考虑先对其进行压缩。当整个表被
InnoDB或压缩时,不要使用此技术MyISAM。 - 对于包含多个列的表,要减少不使用 BLOB 列的查询的内存需求,请考虑将 BLOB 列拆分为单独的表,并在需要时使用连接查询引用它。
- 由于检索和显示 BLOB 值的性能要求可能与其他数据类型非常不同,因此您可以将特定于 BLOB 的表放在不同的存储设备上,甚至是单独的数据库实例上。例如,要检索 BLOB 可能需要大量的顺序磁盘读取,这更适合传统硬盘驱动器而不是 SSD 设备。
- 有关为什么二进制列有时比等效的 BLOB 列更可取的原因,请参阅第 8.4.2.2 节,“针对字符和字符串类型进行优化”
VARCHAR。 - 您可以将列值的散列存储在单独的列中,索引该列,并在查询中测试散列值,而不是针对很长的文本字符串测试是否相等。(使用
MD5()orCRC32()函数生成散列值。)由于散列函数可以为不同的输入生成重复的结果,因此您仍然在查询中包含一个子句 以防止错误匹配;性能优势来自更小、易于扫描的散列值索引。AND *blob_column* = *long_string_value*
3. 多表优化
一些保持单个查询快速的技术涉及将数据拆分到多个表中。当表的数量达到数千甚至数百万时,处理所有这些表的开销成为新的性能考虑。
MySQL 如何打开和关闭表
当您执行mysqladmin status 命令时,您应该看到如下内容:
1 | Uptime: 426 Running threads: 1 Questions: 11082 |
Open tables如果表少于 12 个,则 12 的值可能会有些令人费解。
MySQL 是多线程的,因此可能有许多客户端同时发出对给定表的查询。为了尽量减少多个客户端会话在同一个表上具有不同状态的问题,每个并发会话独立打开该表。这会使用额外的内存,但通常会提高性能。对于MyISAM表,每个打开表的客户端的数据文件都需要一个额外的文件描述符。(相比之下,索引文件描述符在所有会话之间共享。)
该table_open_cache和 max_connections系统变量影响服务器保持打开的文件的最大数量。如果您增加这些值中的一个或两个,您可能会遇到操作系统对每个进程打开的文件描述符数量施加的限制。许多操作系统允许您增加打开文件限制,尽管方法因系统而异。请查阅您的操作系统文档以确定是否可以增加限制以及如何增加。
table_open_cache与 相关max_connections。例如,对于 200 个并发运行连接,将表缓存大小指定为至少200 * N,其中N 是您执行的任何查询中每个连接的最大表数。您还必须为临时表和文件保留一些额外的文件描述符。
确保您的操作系统可以处理table_open_cache设置所隐含的打开文件描述符的数量 。如果 table_open_cache设置得太高,MySQL 可能会耗尽文件描述符并表现出拒绝连接或无法执行查询等症状。
还要考虑到MyISAM 存储引擎需要为每个唯一的打开表提供两个文件描述符。要增加 MySQL 可用的文件描述符的数量,请设置 open_files_limit系统变量。请参见第 B.3.2.16 节,“找不到文件和类似错误”。
打开表的缓存保持在table_open_cache条目级别 。服务器在启动时自动调整缓存大小。要明确设置大小,请table_open_cache在启动时设置 系统变量。MySQL 可能会临时打开比这更多的表来执行查询,如本节后面所述。
在以下情况下,MySQL 会关闭未使用的表并将其从表缓存中删除:
- 当缓存已满并且线程尝试打开不在缓存中的表时。
- 当缓存包含多个
table_open_cache条目并且缓存中 的表不再被任何线程使用时。 - 当发生表刷新操作时。当有人发出
FLUSH TABLES语句或执行 mysqladmin flush-tables或 mysqladmin refresh命令时,就会发生这种情况。
当表缓存填满时,服务器使用以下过程来定位要使用的缓存条目:
- 当前未使用的表被释放,从最近最少使用的表开始。
- 如果必须打开新表,但缓存已满且无法释放任何表,则缓存会根据需要临时扩展。当缓存处于临时扩展状态并且表从已使用状态变为未使用状态时,该表将关闭并从缓存中释放。
MyISAM为每个并发访问打开 一个表。这意味着如果两个线程访问同一个表,或者一个线程在同一个查询中访问该表两次(例如,通过将表连接到自身),则该表需要打开两次。每个并发打开都需要表缓存中的一个条目。任何MyISAM表的第一次打开 需要两个文件描述符:一个用于数据文件,一个用于索引文件。表的每一次额外使用只需要一个数据文件的文件描述符。索引文件描述符在所有线程之间共享。
如果您使用该语句打开一个表,则会为该线程分配一个专用的表对象。这个表对象不被其他线程共享,并且在线程调用或线程终止之前不会关闭。发生这种情况时,表将放回表缓存中(如果缓存未满)。见 第 13.2.4 节,“处理程序语句”。 HANDLER *tbl_name* OPEN``HANDLER *tbl_name* CLOSE
要确定您的表缓存是否太小,请检查 Opened_tables状态变量,该变量指示自服务器启动以来的开表操作次数:
1 | mysql> SHOW GLOBAL STATUS LIKE 'Opened_tables'; |
如果该值非常大或增加很快,即使您没有发出很多FLUSH TABLES语句,请table_open_cache在服务器启动时增加该 值。
在同一个数据库中创建多个表的缺点
如果同一个数据库目录中有很多表,打开、关闭和创建操作都很慢。如果SELECT 在许多不同的表上执行语句,当表缓存已满时会产生一些开销,因为对于必须打开的每个表,必须关闭另一个表。您可以通过增加表缓存中允许的条目数来减少这种开销。
4. MySQL内部临时表的使用
在某些情况下,服务器在处理语句时会创建内部临时表。用户无法直接控制何时发生这种情况。
服务器在以下条件下创建临时表:
UNION语句的 评估,有一些例外在后面描述。- 评估某些视图,例如使用
TEMPTABLE算法UNION、 或聚合的视图 。 - 派生表的评估(参见 第 13.2.11.8 节,“派生表”)。
- 公用表表达式的求值(参见 第 13.2.15 节,“WITH(公用表表达式)”)。
- 为子查询或半连接实现创建的表(请参阅 第 8.2.2 节,“优化子查询、派生表、视图引用和公用表表达式”)。
- 评估包含一个
ORDER BY子句和一个不同GROUP BY子句的语句,ORDER BY或者GROUP BY包含来自除连接队列中第一个表之外的表的列的语句。 - 评估
DISTINCT结合ORDER BY可能需要一个临时表。 - 对于使用
SQL_SMALL_RESULT修饰符的查询,MySQL 使用内存中临时表,除非查询还包含需要磁盘存储的元素(稍后描述)。 - 为了评估
INSERT ... SELECT从同一个表中选择和插入的语句,MySQL 创建一个内部临时表来保存 中的行SELECT,然后将这些行插入到目标表中。见 第 13.2.6.1 节,“INSERT … SELECT 语句”。 - 多表
UPDATE语句的评估 。 GROUP_CONCAT()或COUNT(DISTINCT)表达式的 评估。- 窗口函数的评估(参见 第 12.21 节,“窗口函数”)根据需要使用临时表。
要确定语句是否需要临时表,请使用 EXPLAIN并检查该 Extra列以查看它是否显示 Using temporary(请参阅 第 8.8.1 节,“使用 EXPLAIN 优化查询”)。EXPLAIN 不一定Using temporary适用于派生或物化临时表。对于使用窗口函数的语句,EXPLAIN withFORMAT=JSON总是提供有关窗口步骤的信息。如果窗口函数使用临时表,则为每个步骤指示。
某些查询条件会阻止使用内存中的临时表,在这种情况下,服务器会使用磁盘上的表:
- 表中存在
BLOB或TEXT列。但是,TempTable存储引擎是 MySQL 8.0 中内存内部临时表的默认存储引擎,从 MySQL 8.0.13 开始支持二进制大对象类型。请参阅 内部临时表存储引擎。 SELECT如果使用UNION或 ,则列表中 存在最大长度大于 512(二进制字符串的字节,非二进制字符串的字符)的任何字符串列UNION ALL。- 的
SHOW COLUMNS和DESCRIBE语句中使用BLOB作为用于某些列的类型,从而用于结果的临时表是磁盘上的表。
服务器不会为UNION满足特定条件的语句使用临时表 。相反,它从临时表创建中仅保留执行结果列类型转换所需的数据结构。表没有完全实例化,没有行写入或读取;行直接发送到客户端。结果是减少了内存和磁盘需求,并且在第一行发送到客户端之前的延迟更小,因为服务器不需要等到最后一个查询块被执行。EXPLAIN和优化器跟踪输出反映了这种执行策略: UNION RESULT 查询块不存在,因为该块对应于从临时表中读取的部分。
这些条件有资格在UNION没有临时表的情况下进行评估:
- 联合是
UNION ALL、 不是UNION或UNION DISTINCT。 - 没有全局
ORDER BY条款。 - 联合不是
{INSERT | REPLACE} ... SELECT ...语句的顶级查询块 。
内部临时表存储引擎
内部临时表可以在内存中保持并且通过被处理TempTable或 MEMORY存储引擎,或者由存储在磁盘上InnoDB存储引擎。
内存内部临时表的存储引擎
该 internal_tmp_mem_storage_engine 变量定义内存内部临时表的存储引擎。允许的值为 TempTable(默认值)和 MEMORY. 从 MySQL 8.0.27 开始,配置会话设置 internal_tmp_mem_storage_engine 需要 SESSION_VARIABLES_ADMINor SYSTEM_VARIABLES_ADMIN 权限。
的TempTable存储引擎提供了用于有效地存储VARCHAR 和VARBINARY列,以及其他二进制大对象类型例如MySQL 8.0.13的。
该temptable_max_ram 变量定义了TempTable存储引擎在开始以内存映射临时文件或InnoDB 磁盘内部临时表的形式从磁盘分配空间之前可以占用的最大 RAM 量。默认 temptable_max_ram设置为 1GiB。该temptable_use_mmap 变量(在 MySQL 8.0.16 中引入;在 MySQL 8.0.26 中已弃用)控制 超过限制时TempTable 存储引擎是使用内存映射文件还是 InnoDB磁盘内部临时表temptable_max_ram。默认设置是 temptable_use_mmap=ON。这 temptable_max_mmapMySQL 8.0.23 中引入的变量定义了 TempTable 存储引擎在开始将内部临时表数据存储到InnoDB磁盘内部临时表之前允许从内存映射文件分配的最大内存量。一个 temptable_max_mmap=0设定从内存映射文件禁用分配,有效地禁止其使用,而不管的 temptable_use_mmap设置。
1 | 笔记 |
该temptable_max_ram 设置不考虑分配给使用TempTable存储引擎的每个线程的线程本地内存块 。线程本地内存块的大小取决于线程的第一个内存分配请求的大小。如果请求小于 1MB(大多数情况下是这样),则线程本地内存块大小为 1MB。如果请求大于 1MB,则线程本地内存块的大小与初始内存请求大致相同。线程本地内存块保存在线程本地存储中,直到线程退出。
TempTable存储引擎 使用内存映射临时文件 作为内部临时表的溢出机制受以下规则控制:
- 在
tmpdir变量定义的目录中创建临时文件。 - 临时文件在创建和打开后会立即被删除,因此不会在
tmpdir目录中保持可见。临时文件占用的空间在临时文件打开时由操作系统持有。当TempTable存储引擎关闭临时文件或mysqld进程关闭时, 会回收空间 。 - 数据永远不会在 RAM 和临时文件之间、RAM 内或临时文件之间移动。
- 如果空间在由 定义的限制内可用,则新数据将存储在 RAM 中
temptable_max_ram。否则,新数据将存储在临时文件中。 - 如果在将表的某些数据写入临时文件后 RAM 中有可用空间,则剩余的表数据可能会存储在 RAM 中。
如果TempTable存储引擎配置为使用InnoDB磁盘内部临时表作为溢出机制(temptable_use_mmap=OFF或 temptable_max_mmap=0),则超出temptable_max_ram限制的内存表将转换为InnoDB磁盘内部临时表,并且属于该 限制的任何行表从内存移动到InnoDB磁盘内部临时表。该 internal_tmp_disk_storage_engine 设置(在 MySQL 8.0.16 中删除)对TempTable存储引擎溢出机制没有影响 。
在 MySQL 8.0.23 之前,如果 TempTable 存储引擎经常超出限制并在临时目录中使用过多空间用于内存映射文件,InnoDB建议使用磁盘内部临时表作为 TempTable溢出机制 temptable_max_ram。从 MySQL 8.0.23 开始,该 temptable_max_mmap变量定义了 TempTable 存储引擎从内存映射文件分配的内存量的限制,这解决了这些文件使用过多空间的风险。超出 temptable_max_ram限制通常是由于使用大型内部临时表或大量使用内部临时表造成的。 InnoDB磁盘内部临时表在会话临时表空间中创建,默认情况下位于数据目录中。有关更多信息,请参阅 第 15.6.3.5 节,“临时表空间”。
当使用MEMORY存储引擎存储内存临时表时,如果内存临时表变得太大,MySQL 会自动将其转换为磁盘表。内存中临时表的最大大小由tmp_table_size 或max_heap_table_size值定义,以较小者为准。这与使用MEMORY显式创建的表 不同 CREATE TABLE。对于此类表,只有max_heap_table_size 变量决定了表可以增长的大小,而不会转换为磁盘格式。
磁盘内部临时表的存储引擎
从 MySQL 8.0.16 开始,服务器始终使用 InnoDB存储引擎来管理磁盘上的内部临时表。
在 MySQL 8.0.15 及更早版本中,该 internal_tmp_disk_storage_engine 变量用于定义用于磁盘内部临时表的存储引擎。该变量在 MySQL 8.0.16 中被删除,用于此目的的存储引擎不再是用户可配置的。
在 MySQL 8.0.15 及更早版本中:对于公用表表达式 (CTE),用于磁盘内部临时表的存储引擎不能是MyISAM. 如果 internal_tmp_disk_storage_engine=MYISAM,则任何尝试使用磁盘临时表来实现 CTE 都会发生错误。
在 MySQL 8.0.15 及更早版本中:使用 时 internal_tmp_disk_storage_engine=INNODB,生成超出InnoDB 行或列限制的磁盘内部临时表的查询将返回Row size too large或Too many **columns 错误。解决方法是设置 internal_tmp_disk_storage_engine 为MYISAM.
内部临时表存储格式
当内存内部临时表由TempTable存储引擎管理时 ,包含 VARCHAR列、 VARBINARY列和其他二进制大对象类型列(从 MySQL 8.0.13 开始支持)的行在内存中由一个单元格数组表示,每个单元格包含 NULL 标志、数据长度和数据指针。列值在数组之后按连续顺序放置在单个内存区域中,没有填充。阵列中的每个单元格使用 16 字节的存储空间。当TempTable存储引擎超过temptable_max_ram 限制并开始从磁盘分配空间作为内存映射文件或InnoDB 磁盘内部临时表。
当内存内部临时表由MEMORY存储引擎管理时 ,使用定长行格式。VARCHAR和 VARBINARY列值填充到最大列长度,实际上将它们存储为 CHAR和BINARY列。
在 MySQL 8.0.16 之前,磁盘内部临时表由InnoDB或 MyISAM存储引擎管理(取决于 internal_tmp_disk_storage_engine 设置)。两个引擎都使用动态宽度行格式存储内部临时表。列只占用所需的存储空间,与使用固定长度行的磁盘表相比,这减少了磁盘 I/O、空间要求和处理时间。从 MySQL 8.0.16 开始, internal_tmp_disk_storage_engine 不受支持,磁盘上的内部临时表始终由InnoDB.
使用MEMORY存储引擎时,语句最初可以创建一个内存中的内部临时表,然后如果表变得太大,则将其转换为磁盘上的表。在这种情况下,可以通过跳过转换并开始在磁盘上创建内部临时表来获得更好的性能。该 big_tables变量可用于强制内部临时表的磁盘存储。
监控内部临时表的创建
在内存或磁盘上创建内部临时表时,服务器会增加该 Created_tmp_tables值。在磁盘上创建内部临时表时,服务器会增加该 Created_tmp_disk_tables 值。如果在磁盘上创建了太多内部临时表,请考虑增加 tmp_table_size和 max_heap_table_size设置。
笔记
由于已知限制, Created_tmp_disk_tables 不计算在内存映射文件中创建的磁盘临时表。默认情况下,TempTable 存储引擎溢出机制在内存映射文件中创建内部临时表。此行为由temptable_use_mmap和 temptable_max_mmap 变量控制 。
的memory/temptable/physical_ram和 memory/temptable/physical_disk性能架构器械可以用于监测 TempTable从内存和磁盘空间分配。memory/temptable/physical_ram报告分配的 RAM 量。 memory/temptable/physical_disk当内存映射文件用作 TempTable 溢出机制时,报告从磁盘分配的空间量。如果 physical_disk仪器报告的值不是 0,并且使用内存映射文件作为 TempTable 溢出机制,则temptable_max_ram在某个时刻达到了 阈值。可以在 Performance Schema 内存汇总表中查询数据,例如 memory_summary_global_by_event_name
5.数据库和表的数量限制
MySQL 对数据库的数量没有限制。底层文件系统可能对目录数量有限制。
MySQL 对表的数量没有限制。底层文件系统可能对表示表的文件数量有限制。单个存储引擎可能会施加特定于引擎的约束。InnoDB允许多达 40 亿张表。
6. 表大小限制
MySQL 数据库的有效最大表大小通常由操作系统对文件大小的限制决定,而不是由 MySQL 内部限制决定。有关操作系统文件大小限制的最新信息,请参阅特定于您的操作系统的文档。
Windows 用户,请注意 FAT 和 VFAT (FAT32) 不适合用于 MySQL 的生产用途。请改用 NTFS。
如果遇到全表错误,可能发生的原因有多种:
磁盘可能已满。
您正在使用
InnoDB表,并且InnoDB表空间文件中的空间已用完。最大表空间大小也是表的最大大小。有关表空间大小限制,请参阅 第 15.22 节,“InnoDB 限制”。通常,对于大于 1TB 的表,建议将表分区为多个表空间文件。
您已达到操作系统文件大小限制。例如,您
MyISAM在仅支持最大 2GB 文件的操作系统上使用表,并且您已达到数据文件或索引文件的此限制。您正在使用一个
MyISAM表,并且该表所需的空间超过了内部指针大小所允许的空间。MyISAM默认情况下允许数据和索引文件增长到 256TB,但此限制可以更改为最大允许大小 65,536TB(256 7 − 1 字节)。如果您需要一个
MyISAM大于默认限制的表并且您的操作系统支持大文件,则该CREATE TABLE语句支持AVG_ROW_LENGTH和MAX_ROWS选项。见 第 13.1.20 节,“CREATE TABLE 语句”。服务器使用这些选项来确定允许使用多大的表。如果指针大小对于现有表来说太小,您可以更改选项
ALTER TABLE以增加表的最大允许大小。见第 13.1.9 节,“ALTER TABLE 语句”。1
ALTER TABLE tbl_name MAX_ROWS=1000000000 AVG_ROW_LENGTH=nnn;
你
AVG_ROW_LENGTH只需要为带有BLOB或TEXT列的表指定;在这种情况下,MySQL 无法仅根据行数来优化所需的空间。要更改
MyISAM表的默认大小限制 ,请设置myisam_data_pointer_size,这将设置用于内部行指针的字节数。如果未指定该MAX_ROWS选项,则该值用于设置新表的指针大小。的值myisam_data_pointer_size可以是2到7。例如,对于使用动态存储格式的表,值4允许表最大为4GB;值 6 允许表最大为 256TB。使用固定存储格式的表具有更大的最大数据长度。有关存储格式特征,请参阅 第 16.2.3 节,“MyISAM 表存储格式”。您可以使用以下语句检查最大数据和索引大小:
1
SHOW TABLE STATUS FROM db_name LIKE 'tbl_name';
您也可以使用myisamchk -dv /path/to/table-index-file。请参阅 第 13.7.7 节“显示语句”或第 4.6.4 节“myisamchk — MyISAM 表维护实用程序”。
解决
MyISAM表的文件大小限制的其他方法 如下:- 如果你的大表是只读的,你可以使用 myisampack来压缩它。 myisampack通常将表压缩至少 50%,因此实际上您可以拥有更大的表。myisampack还可以将多个表合并为一个表。请参阅 第 4.6.6 节,“myisampack — 生成压缩的、只读的 MyISAM 表”。
- MySQL 包含一个
MERGE库,使您能够处理MyISAM与单个MERGE表具有相同结构的表集合 。请参阅第 16.7 节,“MERGE 存储引擎”。
您正在使用
MEMORY(HEAP) 存储引擎;在这种情况下,您需要增加max_heap_table_size系统变量的值 。
7. 表列数和行大小的限制
列数限制
MySQL 对每个表有 4096 列的硬限制,但对于给定表,有效最大值可能会更少。确切的列限制取决于几个因素:
- 表的最大行大小限制了列的数量(可能还有大小),因为所有列的总长度不能超过此大小。请参阅 行大小限制。
- 单个列的存储要求限制了适合给定最大行大小的列数。某些数据类型的存储要求取决于存储引擎、存储格式和字符集等因素。请参见第 11.7 节,“数据类型存储要求”。
- 存储引擎可能会施加额外的限制来限制表列数。例如,
InnoDB每个表有 1017 列的限制。请参阅第 15.22 节,“InnoDB 限制”。有关其他存储引擎的信息,请参阅 第 16 章,替代存储引擎。 - 功能键部分(参见第 13.1.15 节,“CREATE INDEX 语句”)被实现为隐藏的虚拟生成存储列,因此表索引中的每个功能键部分都计入表总列数限制。
行大小限制
给定表的最大行大小由几个因素决定:
MySQL 表的内部表示具有 65,535 字节的最大行大小限制,即使存储引擎能够支持更大的行。
BLOB并且TEXT列仅对行大小限制贡献 9 到 12 个字节,因为它们的内容与行的其余部分分开存储。InnoDB适用于本地存储在数据库页中的数据 的表的最大行大小略小于 4KB、8KB、16KB 和 32KBinnodb_page_size设置的半页 。例如,对于默认的 16KBInnoDB页大小,最大行大小略小于 8KB 。对于 64KB 页面,最大行大小略小于 16KB。请参阅 第 15.22 节,“InnoDB 限制”。如果包含 可变长度列的
InnoDB行超过最大行大小,InnoDB则为外部页外存储选择可变长度列,直到该行适合InnoDB行大小限制。对于页外存储的可变长度列,本地存储的数据量因行格式而异。有关更多信息,请参阅 第 15.10 节,“InnoDB 行格式”。不同的存储格式使用不同数量的页头和尾部数据,这会影响行可用的存储量。
- 有关
InnoDB行格式的信息,请参阅第 15.10 节,“InnoDB 行格式”。 - 有关
MyISAM存储格式的信息,请参阅 第 16.2.3 节,“MyISAM 表存储格式”。
- 有关
行大小限制示例
MySQL 最大行大小限制为 65,535 字节在以下
InnoDB和MyISAM示例中进行了演示。无论存储引擎如何,都会强制执行此限制,即使存储引擎可能能够支持更大的行。1
2
3
4
5
6mysql> CREATE TABLE t (a VARCHAR(10000), b VARCHAR(10000),
c VARCHAR(10000), d VARCHAR(10000), e VARCHAR(10000),
f VARCHAR(10000), g VARCHAR(6000)) ENGINE=InnoDB CHARACTER SET latin1;
ERROR 1118 (42000): Row size too large. The maximum row size for the used
table type, not counting BLOBs, is 65535. This includes storage overhead,
check the manual. You have to change some columns to TEXT or BLOBs1
2
3
4
5
6mysql> CREATE TABLE t (a VARCHAR(10000), b VARCHAR(10000),
c VARCHAR(10000), d VARCHAR(10000), e VARCHAR(10000),
f VARCHAR(10000), g VARCHAR(6000)) ENGINE=MyISAM CHARACTER SET latin1;
ERROR 1118 (42000): Row size too large. The maximum row size for the used
table type, not counting BLOBs, is 65535. This includes storage overhead,
check the manual. You have to change some columns to TEXT or BLOBs在下面的
MyISAM例子中,改变到列TEXT避免了65535字节的行大小限制,并且允许成功,因为操作BLOB和TEXT列仅有助于朝向行大小9至12个字节。1
2
3
4mysql> CREATE TABLE t (a VARCHAR(10000), b VARCHAR(10000),
c VARCHAR(10000), d VARCHAR(10000), e VARCHAR(10000),
f VARCHAR(10000), g TEXT(6000)) ENGINE=MyISAM CHARACTER SET latin1;
Query OK, 0 rows affected (0.02 sec)InnoDB表 的操作成功是因为更改列TEXT避免了 MySQL 65,535 字节的行大小限制,而InnoDB变长列的页外存储避免了InnoDB行大小限制。1
2
3
4mysql> CREATE TABLE t (a VARCHAR(10000), b VARCHAR(10000),
c VARCHAR(10000), d VARCHAR(10000), e VARCHAR(10000),
f VARCHAR(10000), g TEXT(6000)) ENGINE=InnoDB CHARACTER SET latin1;
Query OK, 0 rows affected (0.02 sec)可变长度列的存储包括长度字节,这些字节计入行大小。例如,一
VARCHAR(255) CHARACTER SET utf8mb3列需要两个字节来存储值的长度,因此每个值最多可以占用 767 个字节。创建表的语句
t1成功,因为列需要 32,765 + 2 个字节和 32,766 + 2 个字节,这在 65,535 字节的最大行大小范围内:1
2
3
4mysql> CREATE TABLE t1
(c1 VARCHAR(32765) NOT NULL, c2 VARCHAR(32766) NOT NULL)
ENGINE = InnoDB CHARACTER SET latin1;
Query OK, 0 rows affected (0.02 sec)create table 语句
t2失败的原因是,尽管列长度在 65,535 字节的最大长度内,但需要额外的两个字节来记录长度,导致行大小超过 65,535 字节:1
2
3
4
5
6mysql> CREATE TABLE t2
(c1 VARCHAR(65535) NOT NULL)
ENGINE = InnoDB CHARACTER SET latin1;
ERROR 1118 (42000): Row size too large. The maximum row size for the used
table type, not counting BLOBs, is 65535. This includes storage overhead,
check the manual. You have to change some columns to TEXT or BLOBs将列长度减少到 65,533 或更少允许语句成功。
1
2
3
4mysql> CREATE TABLE t2
(c1 VARCHAR(65533) NOT NULL)
ENGINE = InnoDB CHARACTER SET latin1;
Query OK, 0 rows affected (0.01 sec)对于
MyISAM表,NULL列在行中需要额外的空间来记录它们的值是否为NULL。每NULL列多占一位,四舍五入到最接近的字节。创建表的语句
t3失败,因为除了可变长度列长度字节所需的空间外,还MyISAM需要NULL列空间,导致行大小超过65,535个字节:1
2
3
4
5
6mysql> CREATE TABLE t3
(c1 VARCHAR(32765) NULL, c2 VARCHAR(32766) NULL)
ENGINE = MyISAM CHARACTER SET latin1;
ERROR 1118 (42000): Row size too large. The maximum row size for the used
table type, not counting BLOBs, is 65535. This includes storage overhead,
check the manual. You have to change some columns to TEXT or BLOBs有关列存储的信息,请参阅 第 15.10 节,“InnoDB 行格式”。
InnoDBNULLInnoDB对于 4KB、8KB、16KB 和 32KBinnodb_page_size设置,将行大小(对于本地存储在数据库页面中的数据)限制为略小于数据库页面的一半, 对于 64KB 页面限制为略小于 16KB。创建表的语句
t4失败,因为定义的列超过了 16KBInnoDB页的行大小限制。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15mysql> CREATE TABLE t4 (
c1 CHAR(255),c2 CHAR(255),c3 CHAR(255),
c4 CHAR(255),c5 CHAR(255),c6 CHAR(255),
c7 CHAR(255),c8 CHAR(255),c9 CHAR(255),
c10 CHAR(255),c11 CHAR(255),c12 CHAR(255),
c13 CHAR(255),c14 CHAR(255),c15 CHAR(255),
c16 CHAR(255),c17 CHAR(255),c18 CHAR(255),
c19 CHAR(255),c20 CHAR(255),c21 CHAR(255),
c22 CHAR(255),c23 CHAR(255),c24 CHAR(255),
c25 CHAR(255),c26 CHAR(255),c27 CHAR(255),
c28 CHAR(255),c29 CHAR(255),c30 CHAR(255),
c31 CHAR(255),c32 CHAR(255),c33 CHAR(255)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC DEFAULT CHARSET latin1;
ERROR 1118 (42000): Row size too large (> 8126). Changing some columns to TEXT or BLOB may help.
In current row format, BLOB prefix of 0 bytes is stored inline.
7.9 优化 InnoDB 表
1. 优化 InnoDB 表的存储布局
一旦您的数据达到稳定大小,或者不断增长的表增加了数十或数百兆字节,请考虑使用该
OPTIMIZE TABLE语句重新组织表并压缩任何浪费的空间。重新组织的表需要较少的磁盘 I/O 来执行全表扫描。这是一种直接的技术,可以在其他技术(例如改进索引使用或调整应用程序代码)不实用时提高性能。OPTIMIZE TABLE复制表的数据部分并重建索引。好处来自于改进了索引内的数据打包,以及减少了表空间和磁盘上的碎片。好处因每个表中的数据而异。您可能会发现某些人获得了显着的收益,而其他人则没有,或者收益会随着时间的推移而减少,直到您下次优化表。如果表很大或者正在重建的索引不适合缓冲池,则此操作可能会很慢。向表中添加大量数据后的第一次运行通常比以后运行慢得多。在 中
InnoDB,拥有 longPRIMARY KEY(具有冗长值的单个列,或形成一个 long 复合值的多个列)会浪费大量磁盘空间。行的主键值在指向同一行的所有二级索引记录中重复。(请参阅第 15.6.2.1 节,“聚集索引和二级索引”。)AUTO_INCREMENT如果主键很长,则创建一个列作为主键,或者索引一个长VARCHAR列的前缀而不是整个列。使用
VARCHAR数据类型而不是CHAR存储可变长度字符串或具有许多NULL值的列 。一个列总是占据字符来存储数据,即使该字符串是较短,或者其值 。较小的表更适合缓冲池并减少磁盘 I/O。CHAR(*N*)NNULL使用
COMPACT行格式(默认InnoDB格式)和可变长度字符集(例如utf8or )时sjis, 列占用的空间量可变,但仍至少为字节。CHAR(*N*)N对于大表或包含大量重复文本或数字数据的表,请考虑使用
COMPRESSED行格式。将数据带入缓冲池或执行全表扫描所需的磁盘 I/O 更少。在做出永久性决定之前,请测量使用COMPRESSED与COMPACT行格式相比 可以实现的压缩量 。
2. 优化 InnoDB 事务管理
要优化InnoDB事务处理,请在事务功能的性能开销和服务器的工作负载之间找到理想的平衡点。例如,如果应用程序每秒提交数千次,则可能会遇到性能问题,如果每 2-3 小时提交一次,则可能会遇到不同的性能问题。
默认的 MySQL 设置
AUTOCOMMIT=1会对繁忙的数据库服务器施加性能限制。在可行的情况下,将几个相关的数据更改操作包装到单个事务中,通过发出SET AUTOCOMMIT=0或 一个START TRANSACTION语句,然后COMMIT在进行所有更改后执行一个 语句。InnoDB如果该事务对数据库进行了修改,则必须在每个事务提交时将日志刷新到磁盘。当每次更改后都有一个提交(与默认的自动提交设置一样)时,存储设备的 I/O 吞吐量会限制每秒潜在操作的数量。或者,对于仅包含单个
SELECT语句的事务,打开AUTOCOMMIT有助于InnoDB识别只读事务并对其进行优化。有关要求,请参阅 第 8.5.3 节,“优化 InnoDB 只读事务”。避免在插入、更新或删除大量行后执行回滚。如果大事务降低了服务器性能,回滚它会使问题变得更糟,执行原始数据更改操作可能需要数倍的时间。终止数据库进程无济于事,因为回滚在服务器启动时再次启动。
为了尽量减少发生此问题的机会:
- 增加缓冲池的大小, 以便可以缓存所有数据更改更改而不是立即写入磁盘。
- 设置
innodb_change_buffering=all以便除了插入之外还缓冲更新和删除操作。 - 考虑
COMMIT在大数据更改操作期间定期发出语句,可能会将单个删除或更新分解为多个语句,以对较少数量的行进行操作。
要在发生时摆脱失控的回滚,请增加缓冲池,以便回滚受 CPU 限制并快速运行,或者杀死服务器并使用 重新启动
innodb_force_recovery=3,如第 15.18.2 节,“InnoDB 恢复”中所述。预计此问题在默认设置中很少发生,默认设置
innodb_change_buffering=all允许将更新和删除操作缓存在内存中,从而使它们首先执行得更快,并且在需要时回滚也更快。确保在处理具有许多插入、更新或删除的长时间运行的事务的服务器上使用此参数设置。如果发生意外退出时您可以承受一些最新提交的事务的丢失,则可以将该
innodb_flush_log_at_trx_commit参数设置 为 0。InnoDB无论如何尝试每秒刷新一次日志,尽管不能保证刷新。当行被修改或删除时,行和关联的 撤消日志不会立即物理删除,甚至不会在事务提交后立即删除。旧数据会一直保留到较早或同时启动的事务完成为止,以便这些事务可以访问已修改或已删除行的先前状态。因此,长时间运行的事务可以防止
InnoDB清除由不同事务更改的数据。当在长时间运行的事务中修改或删除行时,使用
READ COMMITTED和REPEATABLE READ隔离级别的其他事务 如果读取相同的行,则必须做更多的工作来重建旧数据。当长时间运行的事务修改表时,来自其他事务的对该表的查询不使用覆盖索引技术。通常可以从二级索引中检索所有结果列的查询,而是从表数据中查找适当的值。
如果发现二级索引页有一个
PAGE_MAX_TRX_ID太新,或者二级索引中的记录被删除标记,则InnoDB可能需要使用聚集索引查找记录。
3. 优化 InnoDB 只读事务
InnoDB可以避免与为已知为只读的事务设置事务 ID(TRX_ID字段)相关的开销。只有可能执行写操作或锁定读取的事务才需要事务 ID , 例如 . 消除不必要的事务 ID 减少了每次查询或数据更改语句构造读取视图时都要参考的内部数据结构的大小。 SELECT ... FOR UPDATE
InnoDB 在以下情况下检测只读事务:
事务从
START TRANSACTION READ ONLY语句开始 。在这种情况下,尝试对数据库进行更改(对于InnoDB、MyISAM或其他类型的表)会导致错误,并且事务继续以只读状态进行:1
ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction.
您仍然可以在只读事务中更改特定于会话的临时表,或者为它们发出锁定查询,因为这些更改和锁定对任何其他事务都是不可见的。
该
autocommit设置后,使交易保证是一条语句,而单个语句组成的事务是“无锁定”SELECT的语句。即,SELECT不使用FOR UPDATEorLOCK IN SHARED MODE子句的 a。事务在没有
READ ONLY选项的情况下启动,但尚未执行显式锁定行的更新或语句。在需要更新或显式锁定之前,事务保持只读模式。
因此,对于诸如报告生成器之类的读取密集型应用程序,您可以InnoDB 通过将查询分组到START TRANSACTION READ ONLYand 中 来调整查询 序列COMMIT,或者通过autocommit 在运行SELECT语句之前打开设置,或者简单地避免在查询中穿插任何数据更改语句.
1 | 笔记 |
4. 优化 InnoDB 查询
要调整InnoDB表的查询,请在每个表上创建一组适当的索引。有关详细信息,请参阅 第 8.3.1 节,“MySQL 如何使用索引”。请遵循以下InnoDB索引指南:
- 因为每个
InnoDB表都有一个 主键(无论您是否请求一个),请为每个表指定一组主键列,这些列用于最重要和时间关键的查询。 - 不要在主键中指定太多或太长的列,因为这些列值在每个二级索引中都是重复的。当索引包含不必要的数据时,读取这些数据的 I/O 和缓存它的内存会降低服务器的性能和可扩展性。
- 不要 为每一列创建单独的 二级索引,因为每个查询只能使用一个索引。很少测试的列或只有几个不同值的列上的索引可能对任何查询都没有帮助。如果您对同一个表有许多查询,测试不同的列组合,请尝试创建少量 串联索引而不是大量单列索引。如果索引包含结果集所需的所有列(称为 覆盖索引),则查询可能完全避免读取表数据。
- 如果索引列不能包含任何
NULL值,请NOT NULL在创建表时声明它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询 。 - 您可以
InnoDB使用第 8.5.3 节“优化 InnoDB 只读事务”中的技术优化表的 单查询事务 。
5. 优化 InnoDB DDL 操作
- 对表和索引(很多DDL操作
CREATE,ALTER和DROP语句)可以在网上进行。有关详细信息,请参阅第 15.12 节,“InnoDB 和在线 DDL”。 - 在线 DDL 支持添加二级索引意味着您通常可以通过创建没有二级索引的表,然后在加载数据后添加二级索引来加快创建和加载表及关联索引的过程。
- 使用
TRUNCATE TABLE空表,不。外键约束可以使语句像常规语句一样工作,在这种情况下,像和 可能是最快的命令序列 。DELETE FROM *tbl_name*``TRUNCATE``DELETEDROP TABLECREATE TABLE - 因为主键是每个
InnoDB表的存储布局的组成部分,更改主键的定义涉及重新组织整个表,始终将主键设置为CREATE TABLE语句的一部分 ,并提前计划,这样您就不需要ALTER或DROP之后的主键。
6. 优化 InnoDB 磁盘 I/O
如果您遵循 SQL 操作的数据库设计和调优技术的最佳实践,但由于大量磁盘 I/O 活动,您的数据库仍然很慢,请考虑这些磁盘 I/O 优化。如果 Unixtop工具或 Windows 任务管理器显示您的工作负载的 CPU 使用百分比低于 70%,则您的工作负载可能是磁盘受限的。
增加缓冲池大小
当表数据缓存在
InnoDB缓冲池中时,它可以被查询重复访问,而不需要任何磁盘 I/O。使用innodb_buffer_pool_size选项指定缓冲池的大小 。此内存区域非常重要,通常建议将innodb_buffer_pool_size其配置为系统内存的 50% 到 75%。有关更多信息,请参阅第 8.12.3.1 节,“MySQL 如何使用内存”。调整冲洗方式
在某些版本的 GNU/Linux 和 Unix 中,使用 Unix
fsync()调用(InnoDB默认使用)和类似方法将文件刷新到磁盘的速度非常慢。如果数据库写入性能是一个问题,请在innodb_flush_method参数设置为 的情况下进行基准测试O_DSYNC。为操作系统刷新配置阈值
默认情况下,当
InnoDB创建新的数据文件时,例如新的日志文件或表空间文件,该文件在刷新到磁盘之前会完全写入操作系统缓存,这会导致大量磁盘写入活动发生在一次。要强制从操作系统缓存中进行较小的、定期的数据刷新,您可以使用该innodb_fsync_threshold变量来定义一个阈值(以字节为单位)。当达到字节阈值时,操作系统缓存的内容将刷新到磁盘。默认值 0 强制默认行为,即仅在文件完全写入缓存后才将数据刷新到磁盘。在多个 MySQL 实例使用相同的存储设备的情况下,指定一个阈值来强制较小的、定期刷新可能是有益的。例如,创建新的 MySQL 实例及其关联的数据文件可能会导致磁盘写入活动激增,从而影响使用相同存储设备的其他 MySQL 实例的性能。配置阈值有助于避免写入活动的这种激增。
使用 fdatasync() 而不是 fsync()
在支持
fdatasync()系统调用的平台上,innodb_use_fdatasyncMySQL 8.0.26 中引入的 变量允许使用fdatasync()代替fsync()操作系统刷新。一个fdatasync()系统调用不会,除非需要后续数据检索到文件元数据刷新的变化,提供了一个潜在的性能优势。的一个子集
innodb_flush_method的设置,例如fsync,O_DSYNC和O_DIRECT使用fsync()系统调用。该innodb_use_fdatasync变量在使用这些设置时适用。在 Linux 上使用带有原生 AIO 的 noop 或期限 I/O 调度程序
InnoDB使用 Linux 上的异步 I/O 子系统(本机 AIO)对数据文件页面执行预读和写入请求。此行为innodb_use_native_aio由默认启用的配置选项控制 。对于原生 AIO,I/O 调度器的类型对 I/O 性能的影响更大。一般推荐使用 noop 和deadline I/O 调度器。执行基准测试以确定哪种 I/O 调度程序可为您的工作负载和环境提供最佳结果。
使用额外的存储设备
可以使用额外的存储设备来设置 RAID 配置。或者,
InnoDB表空间数据文件和日志文件可以放在不同的物理磁盘上。考虑非旋转存储
非旋转存储通常为随机 I/O 操作提供更好的性能;和用于顺序 I/O 操作的旋转存储。跨旋转和非旋转存储设备分发数据和日志文件时,请考虑主要对每个文件执行的 I/O 操作类型。
面向随机 I/O 的文件通常包括 file-per-table 和通用表空间数据文件、 undo 表空间 文件和 临时表空间文件。面向 I/O 的顺序文件包括
InnoDB系统表空间文件(由于 MySQL 8.0.20 之前的双写缓冲和 更改缓冲)、MySQL 8.0.20 中引入的双写文件以及日志文件,例如二进制日志 文件和重做日志 文件。
增加 I/O 容量以避免积压
如果吞吐量由于
InnoDB检查点 操作而周期性下降 ,请考虑增加innodb_io_capacity配置选项的值 。较高的值会导致更频繁的 刷新,避免可能导致吞吐量下降的工作积压。如果刷新不落后,则降低 I/O 容量
如果系统在
InnoDB冲洗操作方面没有落后 ,请考虑降低innodb_io_capacity配置选项的值 。通常,您将此选项值保持在尽可能低的值,但不要低到导致吞吐量周期性下降的程度,如前面的项目符号所述。在 Fusion-io 设备上存储系统表空间文件
您可以通过在支持原子写入的 Fusion-io 设备上存储包含双写存储区的文件来利用双写缓冲区相关的 I/O 优化。(在 MySQL 8.0.20 之前,双写缓冲区存储驻留在系统表空间数据文件中。从 MySQL 8.0.20 开始,存储区驻留在双写文件中。请参阅 第 15.6.4 节,“双写缓冲区”.) 当双写存储区文件放置在支持原子写的Fusion-io设备上时,双写缓冲区会自动禁用,所有数据文件都使用Fusion-io原子写。此功能仅在 Fusion-io 硬件上受支持,并且仅在 Linux 上为 Fusion-io NVMFS 启用。要充分利用此功能, 建议
innodb_flush_method设置O_DIRECT。1
2
3笔记
由于双写缓冲区设置是全局的,因此对于不驻留在 Fusion-io 硬件上的数据文件,双写缓冲区也被禁用。禁用压缩页面的日志记录
使用
InnoDB表 压缩功能时,重新压缩页面的图像 会在对压缩数据进行更改时写入 重做日志。此行为由 控制innodb_log_compressed_pages,默认情况下启用以防止zlib在恢复期间使用不同版本的压缩算法时可能发生的损坏。如果您确定zlib版本不会更改,请禁用innodb_log_compressed_pages以减少修改压缩数据的工作负载的重做日志生成。
7. 优化 InnoDB 配置变量
不同的设置最适用于负载较轻、可预测的服务器,而不是始终接近满负荷运行的服务器,或者遇到高活动高峰的服务器。
由于InnoDB存储引擎会自动执行许多优化,因此许多性能调优任务涉及监控以确保数据库运行良好,并在性能下降时更改配置选项。有关详细性能监控的信息,请参阅 第 15.16 节,“InnoDB 与 MySQL 性能模式的集成”InnoDB。
您可以执行的主要配置步骤包括:
- 控制
InnoDB缓存更改数据的数据更改操作的类型 ,以避免频繁的小磁盘写入。请参阅 配置更改缓冲。因为默认是缓冲所有类型的数据更改操作,所以只有在需要减少缓冲量时才更改此设置。 - 使用该
innodb_adaptive_hash_index选项打开和关闭自适应哈希索引功能 。有关更多信息,请参阅第 15.5.3 节,“自适应哈希索引”。您可以在异常活动期间更改此设置,然后将其恢复为原始设置。 InnoDB如果上下文切换是瓶颈,则 对处理的并发线程数设置限制 。请参阅 第 15.8.4 节,“为 InnoDB 配置线程并发”。- 控制执行预
InnoDB读操作的预取量 。当系统有未使用的 I/O 容量时,更多的预读可以提高查询的性能。过多的预读会导致重负载系统的性能周期性下降。请参阅 第 15.8.3.4 节,“配置 InnoDB 缓冲池预取(预读)”。 - 如果您的高端 I/O 子系统未被默认值充分利用,则增加用于读取或写入操作的后台线程数。请参阅 第 15.8.5 节,“配置后台 InnoDB I/O 线程数”。
- 控制
InnoDB在后台执行多少 I/O 。请参阅 第 15.8.7 节,“配置 InnoDB I/O 容量”。如果您观察到性能周期性下降,您可能会缩减此设置。 - 控制确定何时
InnoDB执行某些类型的后台写入的算法 。请参见 第 15.8.3.5 节,“配置缓冲池刷新”。该算法适用于某些类型的工作负载,但不适用于其他类型的工作负载,因此如果您观察到性能周期性下降,您可以禁用此功能。 - 利用多核处理器及其缓存配置,最大限度地减少上下文切换的延迟。请参见 第 15.8.8 节,“配置自旋锁轮询”。
- 防止诸如表扫描之类的一次性操作干扰存储在
InnoDB缓冲区缓存中的频繁访问的数据 。请参见 第 15.8.3.3 节,“使缓冲池扫描具有抵抗力”。 - 将日志文件调整到对可靠性和崩溃恢复有意义的大小。
InnoDB日志文件通常保持较小,以避免崩溃后启动时间过长。MySQL 5.5 中引入的优化加快了崩溃恢复过程的某些步骤 。特别是,由于改进了内存管理算法,扫描 重做日志和应用重做日志的速度更快。如果您人为地保持日志文件较小以避免启动时间过长,您现在可以考虑增加日志文件大小以减少由于重做日志记录的回收而发生的 I/O。 - 为
InnoDB缓冲池配置实例的大小和数量,这 对于具有多 GB 缓冲池的系统尤其重要。请参阅 第 15.8.3.2 节,“配置多个缓冲池实例”。 - 增加并发事务的最大数量,这显着提高了最繁忙数据库的可扩展性。请参阅第 15.6.6 节,“撤消日志”。
- 将清除操作(一种垃圾收集)移动到后台线程中。请参见 第 15.8.9 节,“清除配置”。要有效地衡量此设置的结果,请先调整其他与 I/O 相关和与线程相关的配置设置。
- 减少
InnoDB并发线程之间的切换量 ,让繁忙的服务器上的SQL操作不会排队,形成“堵车”。为该innodb_thread_concurrency选项设置一个值, 对于高性能现代系统,最多约为 32。增加innodb_concurrency_tickets选项的值 ,通常为 5000 左右。这种选项组合设置了线程数的上限InnoDB任何时候处理,并允许每个线程在被换出之前做大量的工作,这样等待线程的数量就很少,操作可以在没有过多上下文切换的情况下完成。
8. 为多表系统优化 InnoDB
如果您已配置 的非持久性优化统计(非默认配置), InnoDB计算指标 基数值表中的第一次表,启动后访问的,而不是存储在表这样的值。在将数据划分为多个表的系统上,此步骤可能需要大量时间。由于此开销仅适用于初始表打开操作,为了“预热” 一个表供以后使用,在启动后立即通过发出诸如. SELECT 1 FROM *tbl_name* LIMIT 1
优化器统计信息默认保存到磁盘,由innodb_stats_persistent 配置选项启用 。
7.10 优化 MyISAM 表
该MyISAM存储引擎执行最好的读数据或低并发操作,因为表锁限制进行同步更新的能力。在 MySQL 中,InnoDB是默认的存储引擎而不是MyISAM.
1. 优化 MyISAM 查询
加快MyISAM表查询的一些一般提示 :
为了帮助 MySQL 更好地优化查询,请在加载数据后在表上使用
ANALYZE TABLE或运行 myisamchk –analyze。这会更新每个索引部分的值,该值指示具有相同值的平均行数。(对于唯一索引,它始终为 1。)当您基于非常量表达式连接两个表时,MySQL 使用它来决定选择哪个索引。您可以通过使用和检查值来检查表分析的结果 。myisamchk –description –verbose显示索引分布信息。SHOW INDEX FROM *tbl_name*``Cardinality要根据索引对索引和数据进行排序,请使用 myisamchk –sort-index –sort-records=1 (假设您要对索引 1 进行排序)。如果您有一个唯一索引,您想根据索引按顺序从中读取所有行,那么这是加快查询速度的好方法。第一次以这种方式对大表进行排序时,可能需要很长时间。
尽量避免
SELECT对MyISAM频繁更新的表进行复杂查询,以避免由于读取器和写入器之间的争用而出现的表锁定问题。MyISAM支持并发插入:如果一个表在数据文件中间没有空闲块,你可以INSERT在其他线程从表中读取的同时向其中添加新行。如果能够做到这一点很重要,请考虑以避免删除行的方式使用该表。另一种可能性是OPTIMIZE TABLE在删除大量行后运行对表进行碎片整理。通过设置concurrent_insert变量可以改变这种行为 。您可以强制追加新行(因此允许并发插入),即使在已删除行的表中也是如此。见第 8.11.3 节,“并发插入”。对于
MyISAM表经常变化的,尽量避免所有变长列(VARCHAR,BLOB,和TEXT)。如果该表甚至包含单个可变长度列,则该表使用动态行格式。请参阅第 16 章,替代存储引擎。仅仅因为行变大而将表拆分为不同的表通常是没有用的。在访问一行时,最大的性能损失是查找该行的第一个字节所需的磁盘寻道。找到数据后,大多数现代磁盘可以以足够快的速度读取整行,适用于大多数应用程序。拆分表格会产生明显差异的唯一情况是,如果它是
MyISAM使用动态行格式的 表格,您可以更改为固定行大小,或者如果您经常需要扫描表格但不需要大部分列. 请参阅第 16 章,替代存储引擎。如果您通常按顺序检索行, 请使用。通过在对表进行大量更改后使用此选项,您可以获得更高的性能。
ALTER TABLE ... ORDER BY *expr1*, *expr2*, ...``*expr1*, *expr2*, ...如果您经常需要根据来自大量行的信息计算诸如计数之类的结果,则最好引入一个新表并实时更新计数器。以下表格的更新非常快:
1
UPDATE tbl_name SET count_col=count_col+1 WHERE key_col=constant;
这在您使用 MySQL 存储引擎时非常重要,例如
MyISAM只有表级锁定(多个读取器和单个写入器)。这也为大多数数据库系统提供了更好的性能,因为在这种情况下行锁定管理器可以做的事情更少。OPTIMIZE TABLE定期 使用以避免动态格式MyISAM表的碎片化 。请参阅 第 16.2.3 节,“MyISAM 表存储格式”。MyISAM使用DELAY_KEY_WRITE=1table 选项 声明一个表 会使索引更新更快,因为它们在表关闭之前不会刷新到磁盘。不利的一面是,如果在这样的表打开时某些东西杀死了服务器,您必须通过使用myisam_recover_options系统变量集运行服务器或在重新启动服务器之前运行 myisamchk来确保该表正常 。(但是,即使在这种情况下,使用 也不应该丢失任何东西DELAY_KEY_WRITE,因为始终可以从数据行中生成关键信息。)字符串在
MyISAM索引中自动压缩前缀和结尾空间。见 第 13.1.15 节,“CREATE INDEX 语句”。您可以通过在应用程序中缓存查询或答案,然后一起执行许多插入或更新来提高性能。在此操作期间锁定表可确保索引缓存仅在所有更新后刷新一次。
2. MyISAM 表的批量数据加载
这些性能提示补充了第 8.2.5.1 节“优化 INSERT 语句”中快速插入的一般准则。
对于
MyISAM表,SELECT如果数据文件中间没有删除的行,您可以在语句运行的同时使用并发插入来添加行 。见第 8.11.3 节,“并发插入”。通过一些额外的工作,当表有很多索引时,可以使 表的
LOAD DATA运行速度更快MyISAM。使用以下程序:- 执行
FLUSH TABLES语句或mysqladmin flush-tables命令。 - 使用myisamchk –keys-used=0 -rq *
/path/to/db/tbl_name* 删除表的所有索引使用。 - 使用 将数据插入到表中
LOAD DATA。这不会更新任何索引,因此速度非常快。 - 如果您打算将来只从表中读取,请使用myisampack 对其进行压缩。见 第 16.2.3.3 节,“压缩表特性”。
- 使用myisamchk -rq *
/path/to/db/tbl_name*重新创建索引。这会在将索引树写入磁盘之前在内存中创建索引树,这比在此期间更新索引快得多,LOAD DATA因为它避免了大量的磁盘搜索。生成的索引树也是完美平衡的。 - 执行
FLUSH TABLES语句或mysqladmin flush-tables命令。
LOAD DATA如果MyISAM插入数据的表为空,则自动执行上述优化 。自动优化和显式使用过程之间的主要区别在于,您可以让 myisamchk为索引创建分配比您希望服务器在执行LOAD DATA语句时为索引重新创建分配的临时内存多得多 。您还可以
MyISAM使用以下语句而不是myisamchk来禁用或启用表的非唯一索引 。如果使用这些语句,则可以跳过FLUSH TABLES操作:1
2ALTER TABLE tbl_name DISABLE KEYS;
ALTER TABLE tbl_name ENABLE KEYS;- 执行
要加快
INSERT对非事务性表使用多条语句执行的操作,请锁定您的表:1
2
3
4
5LOCK TABLES a WRITE;
INSERT INTO a VALUES (1,23),(2,34),(4,33);
INSERT INTO a VALUES (8,26),(6,29);
...
UNLOCK TABLES;这有利于性能,因为索引缓冲区只在所有
INSERT语句完成后刷新到磁盘一次 。通常,索引缓冲区刷新次数与INSERT语句数一样多。如果您可以使用单个INSERT.锁定还降低了多连接测试的总时间,尽管单个连接的最长等待时间可能会因为它们等待锁定而增加。假设五个客户端尝试同时执行插入,如下所示:
- 连接 1 进行 1000 次插入
- 连接 2、3 和 4 执行 1 次插入
- 连接 5 进行 1000 次插入
如果不加锁,2、3、4连接比1、5先完成。如果用加锁,2、3、4连接可能不会在1、5之前完成,但总时间应该在40%左右快点。
INSERT、UPDATE和DELETE操作在 MySQL 中非常快,但是您可以通过为执行超过大约 5 次连续插入或更新的所有内容添加锁来获得更好的整体性能。如果你这样做非常多的连续插入,你可以做一个LOCK TABLES接着一个UNLOCK TABLES在一段时间(每1000行左右),一旦允许其他线程访问表。这仍然会带来不错的性能提升。要提高
MyISAM表的性能,对于LOAD DATA和INSERT,请通过增加key_buffer_size系统变量来扩大密钥缓存 。
7.11 优化内存表
考虑将MEMORY表用于经常访问且只读或很少更新的非关键数据。在实际工作负载下针对等效项InnoDB或MyISAM表对您的应用程序进行基准测试 ,以确认任何额外的性能都值得冒丢失数据的风险,或在应用程序启动时从基于磁盘的表复制数据的开销。
为获得MEMORY表的最佳性能,请检查针对每个表的查询类型,并指定用于每个关联索引的类型,B 树索引或哈希索引。在CREATE INDEX 语句中,使用子句USING BTREEor USING HASH。B 树索引对于通过诸如>or 之类的运算符进行大于或小于比较的查询速度很快BETWEEN。哈希索引仅适用于通过=运算符查找单个值或通过运算符查找一组受限值的查询IN。
7.12 衡量绩效(基准)
要衡量性能,请考虑以下因素:
- 无论您是在测量安静系统上单个操作的速度,还是在一段时间内测量一组操作( “工作负载”)的工作方式。通过简单的测试,您通常会测试更改一个方面(配置设置、表上的索引集、查询中的 SQL 子句)如何影响性能。基准测试通常是长时间运行且精心设计的性能测试,其结果可以指示高级选择,例如硬件和存储配置,或者升级到新 MySQL 版本的时间。
- 对于基准测试,有时您必须模拟繁重的数据库工作负载才能获得准确的图片。
- 性能可能因许多不同的因素而异,以至于几个百分点的差异可能不是决定性的胜利。当您在不同的环境中进行测试时,结果可能会以相反的方式转变。
- 某些 MySQL 功能有助于或不有助于性能取决于工作负载。为了完整起见,请始终在打开和关闭这些功能的情况下测试性能。尝试与每个工作负载最重要的特点是 适应性的散列索引的
InnoDB表。
本节从单个开发人员可以执行的简单和直接测量技术发展到需要额外专业知识来执行和解释结果的更复杂的测量技术。
测量表达式和函数的速度
要测量特定 MySQL 表达式或函数的速度,请BENCHMARK()使用mysql客户端程序调用该函数。它的语法是 . 返回值始终为零,但mysql 会 打印一行,显示执行该语句所花费的时间。例如: BENCHMARK(*loop_count*,*expr*)
1 | mysql> SELECT BENCHMARK(1000000,1+1); |
这个结果是在 Pentium II 400MHz 系统上获得的。它表明 MySQL 在该系统上可以在 0.32 秒内执行 1,000,000 个简单的加法表达式。
内置的 MySQL 函数通常是高度优化的,但可能有一些例外。 BENCHMARK()是一个很好的工具,用于确定某些功能是否对您的查询有问题。
使用你自己的基准
对您的应用程序和数据库进行基准测试,找出瓶颈所在。在修复一个瓶颈(或用“虚拟”模块替换它)后,您可以继续确定下一个瓶颈。即使您的应用程序的整体性能目前是可以接受的,您至少应该为每个瓶颈制定一个计划,并决定如果有一天您真的需要额外的性能,如何解决它。
免费的基准测试套件是开源数据库基准测试,可从http://osdb.sourceforge.net/ 获得。
仅当系统负载很重时才会出现问题是很常见的。我们有很多客户在生产中拥有(经过测试的)系统并遇到负载问题时与我们联系。在大多数情况下,性能问题是由于基本数据库设计问题(例如,表扫描在高负载下不好)或操作系统或库的问题。大多数情况下,如果系统尚未投入生产,这些问题将更容易解决。
为避免此类问题,请在可能的最坏负载下对整个应用程序进行基准测试:
- 该mysqlslap程序可以是用于模拟由多个客户端同时发出查询产生的高负载有帮助的。请参阅第 4.5.8 节,“mysqlslap — 负载仿真客户端”。
- 您还可以尝试基准测试包,例如 SysBench 和 DBT2,可从 https://launchpad.net/sysbench和 http://osdldbt.sourceforge.net/#dbt2 获得。
这些程序或软件包可能会使系统瘫痪,因此请确保仅在您的开发系统上使用它们。
使用 performance_schema 测量性能
您可以查询performance_schema数据库中的表, 以查看有关服务器性能特征及其运行的应用程序的实时信息。
7.13 检查服务器线程(进程)信息
要确定您的 MySQL 服务器正在做什么,检查进程列表可能会有所帮助,该列表指示当前由服务器内执行的一组线程执行的操作。例如:
1 | mysql> SHOW PROCESSLIST\G |
1. 访问进程列表
进程列表条目的内容
每个进程列表条目都包含几条信息。以下列表使用SHOW PROCESSLIST 输出中的标签描述了它们。其他过程信息源使用类似的标签。
Id是与线程关联的客户端的连接标识符。User并Host指明与线程关联的帐户。db是线程的默认数据库,或者NULL如果没有选择。Command并State指出线程在做什么。大多数状态对应于非常快速的操作。如果一个线程在给定状态下停留了很多秒,那么可能存在需要调查的问题。
以下部分列出了可能的
Command值以及State按类别分组的值。其中一些值的含义是不言而喻的。对于其他人,提供了额外的描述。笔记
检查进程列表信息的应用程序应该知道命令和状态可能会发生变化。
Time指示线程处于当前状态的时间。在某些情况下,线程的当前时间概念可能会改变:线程可以使用 更改时间 。对于副本 SQL 线程,该值是上次复制事件的时间戳与副本主机的实际时间之间的秒数。请参阅 第 17.2.3 节,“复制线程”。SET TIMESTAMP = *value*Info指示线程正在执行的语句,或者NULL如果它不执行任何语句。对于SHOW PROCESSLIST,此值仅包含语句的前 100 个字符。要查看完整的语句,请使用SHOW FULL PROCESSLIST(或查询不同的进程信息源)。
2. 线程命令值
线程可以具有以下任何 Command值:
Binlog Dump这是复制源上的线程,用于将二进制日志内容发送到副本。
Change user该线程正在执行更改用户操作。
Close stmt该线程正在关闭准备好的语句。
Connect由连接到源的复制接收者线程和复制工作线程使用。
Connect Out副本正在连接到其源。
Create DB该线程正在执行创建数据库操作。
Daemon该线程在服务器内部,而不是为客户端连接提供服务的线程。
Debug该线程正在生成调试信息。
Delayed insert该线程是一个延迟插入处理程序。
Drop DB该线程正在执行删除数据库操作。
ErrorExecute线程正在执行准备好的语句。
Fetch该线程正在从执行准备好的语句中获取结果。
Field List该线程正在检索表列的信息。
Init DB该线程正在选择默认数据库。
Kill该线程正在杀死另一个线程。
Long Data该线程正在检索执行准备好的语句的结果中的长数据。
Ping该线程正在处理服务器 ping 请求。
Prepare该线程正在准备一个准备好的语句。
Processlist该线程正在生成有关服务器线程的信息。
Query在单线程复制应用程序线程以及复制协调器线程执行查询时为用户客户端使用。
Quit线程正在终止。
Refresh该线程正在刷新表、日志或缓存,或重置状态变量或复制服务器信息。
Register Slave该线程正在注册副本服务器。
Reset stmt该线程正在重置准备好的语句。
Set option线程正在设置或重置客户端语句执行选项。
Shutdown该线程正在关闭服务器。
Sleep线程正在等待客户端向它发送一条新语句。
Statistics该线程正在生成服务器状态信息。
Time没用过。
3. 一般线程状态
下面的列表描述了State 与一般查询处理相关联的线程值,而不是更专业的活动,如复制。其中许多仅用于查找服务器中的错误。
After create当线程在创建表的函数结束时创建表(包括内部临时表)时,就会发生这种情况。即使由于某些错误无法创建表,也会使用此状态。
altering table服务器正在执行就地
ALTER TABLE.Analyzing该线程正在计算
MyISAM表键分布(例如,forANALYZE TABLE)。checking permissions线程正在检查服务器是否具有执行语句所需的权限。
Checking table该线程正在执行表检查操作。
cleaning up线程已经处理了一个命令并准备释放内存并重置某些状态变量。
closing tables该线程正在将更改的表数据刷新到磁盘并关闭已使用的表。这应该是一个快速的操作。如果没有,请确认您没有完整的磁盘并且该磁盘没有被大量使用。
converting HEAP to ondisk该线程正在将内部临时表从
MEMORY表转换为磁盘表。copy to tmp table线程正在处理一个
ALTER TABLE语句。这种状态发生在具有新结构的表被创建之后但在行被复制到其中之前。对于处于这种状态的线程,Performance Schema 可用于获取有关复制操作的进度。请参阅 第 27.12.5 节,“性能架构阶段事件表”。
Copying to group table如果语句具有不同
ORDER BY和GROUP BY标准,各行按组排列和复制到一个临时表。Copying to tmp table服务器正在复制到内存中的临时表。
Copying to tmp table on disk服务器正在复制到磁盘上的临时表。临时结果集变得太大(参见 第 8.4.4 节,“MySQL 中的内部临时表使用”)。因此,线程将临时表从内存中更改为基于磁盘的格式以节省内存。
Creating index该线程正在处理
ALTER TABLE ... ENABLE KEYS一个MyISAM表。Creating sort index该线程正在处理
SELECT使用内部临时表解析的a 。creating table该线程正在创建一个表。这包括创建临时表。
Creating tmp table该线程正在内存或磁盘上创建临时表。如果表是在内存中创建的,但后来转换为磁盘上的表,则该操作期间的状态为
Copying to tmp table on disk。committing alter table to storage engine服务器已就地完成
ALTER TABLE并提交结果。deleting from main table服务器正在执行多表删除的第一部分。它仅从第一个表中删除,并保存用于从其他(参考)表中删除的列和偏移量。
deleting from reference tables服务器正在执行多表删除的第二部分,并从其他表中删除匹配的行。
discard_or_import_tablespace线程正在处理
ALTER TABLE ... DISCARD TABLESPACEorALTER TABLE ... IMPORT TABLESPACE语句。end这发生在最后但在清理
ALTER TABLE,CREATE VIEW,DELETE,INSERT,SELECT, 或UPDATE语句之前。对于
end状态,可能会发生以下操作:- 将事件写入二进制日志
- 释放内存缓冲区,包括用于 blob
executing线程已开始执行语句。
Execution of init_command线程正在执行
init_command系统变量值中的语句 。freeing items线程已执行命令。此状态通常后跟
cleaning up.FULLTEXT initialization服务器正准备执行自然语言全文搜索。
init出现这种情况的初始化之前
ALTER TABLE,DELETE,INSERT,SELECT,或UPDATE语句。服务器在此状态下采取的操作包括刷新二进制日志和InnoDB日志。Killed有人
KILL向线程发送了一条语句,它应该在下次检查终止标志时中止。该标志在 MySQL 的每个主要循环中都被检查,但在某些情况下,线程可能仍然需要很短的时间。如果该线程被某个其他线程锁定,则一旦其他线程释放其锁,kill 就会生效。Locking system tables线程试图锁定系统表(例如,时区或日志表)。
logging slow query该线程正在向慢查询日志写入一条语句。
login连接线程的初始状态,直到客户端通过身份验证成功。
manage keys服务器正在启用或禁用表索引。
Opening system tables该线程正在尝试打开系统表(例如,时区或日志表)。
Opening tables该线程正在尝试打开一个表。这应该是一个非常快的过程,除非有什么东西阻止了打开。例如,一个
ALTER TABLE或一个LOCK TABLE语句可以阻止打开一个表,直到该语句完成。还值得检查您的table_open_cache价值是否足够大。对于系统表,
Opening system tables改为使用状态。optimizing服务器正在为查询执行初始优化。
preparing此状态发生在查询优化期间。
Purging old relay logs该线程正在删除不需要的中继日志文件。
query end此状态发生在处理查询之后但在该
freeing items状态之前 。Receiving from client服务器正在从客户端读取数据包。
Removing duplicates查询的使用
SELECT DISTINCT方式使得 MySQL 无法在早期优化掉不同的操作。因此,在将结果发送到客户端之前,MySQL 需要一个额外的阶段来删除所有重复的行。removing tmp table线程在处理
SELECT语句后删除内部临时表。如果没有创建临时表,则不使用此状态。rename该线程正在重命名表。
rename result table线程正在处理一条
ALTER TABLE语句,创建了新表,并正在重命名它以替换原始表。Reopen tables线程获得了表的锁,但在获得锁后注意到底层表结构发生了变化。它释放了锁,关闭了表,并试图重新打开它。
Repair by sorting修复代码正在使用排序来创建索引。
preparing for alter table服务器正准备执行就地
ALTER TABLE.Repair done该线程已完成对
MyISAM表的多线程修复 。Repair with keycache修复码是通过密钥缓存一一创建密钥。这比
Repair by sorting.Rolling back该线程正在回滚事务。
Saving state对于
MyISAM修复或分析等表操作,线程正在将新表状态保存到.MYI文件头中。状态包括诸如行数、AUTO_INCREMENT计数器和密钥分布等信息。Searching rows for update该线程正在执行第一阶段以在更新它们之前找到所有匹配的行。如果
UPDATE正在更改用于查找相关行的索引,则必须这样做 。Sending dataMySQL 8.0.17 之前:线程正在读取和处理
SELECT语句的行 ,并向客户端发送数据。由于在此状态期间发生的操作往往会执行大量磁盘访问(读取),因此它通常是给定查询生命周期中运行时间最长的状态。MySQL 8.0.17 及更高版本:此状态不再单独指示,而是包含在Executing状态中。Sending to client服务器正在向客户端写入数据包。
setup线程正在开始
ALTER TABLE操作。Sorting for group该线程正在执行排序以满足 a
GROUP BY。Sorting for order该线程正在执行排序以满足
ORDER BY.Sorting index该线程正在对索引页进行排序,以便在
MyISAM表优化操作期间进行更有效的访问。Sorting result对于
SELECT语句,这类似于Creating sort index,但适用于非临时表。starting语句执行开始时的第一阶段。
statistics服务器正在计算统计信息以制定查询执行计划。如果一个线程长时间处于这种状态,则服务器可能是磁盘绑定执行其他工作。
System lock该线程已被调用
mysql_lock_tables(),此后线程状态尚未更新。这是一种非常普遍的状态,可能由于多种原因而发生。例如,线程将请求或等待表的内部或外部系统锁。这可能发生
InnoDB在执行期间等待表级锁时LOCK TABLES。如果此状态是由外部锁请求引起的,并且您没有使用多个访问相同 表的mysqld服务器,则MyISAM可以使用该--skip-external-locking选项禁用外部系统锁 。但是,默认情况下禁用外部锁定,因此该选项很可能不起作用。对于SHOW PROFILE,这种状态意味着线程正在请求锁定(而不是等待它)。对于系统表,
Locking system tables改为使用状态。update线程正准备开始更新表。
Updating该线程正在搜索要更新的行并正在更新它们。
updating main table服务器正在执行多表更新的第一部分。它只更新第一个表,并保存用于更新其他(参考)表的列和偏移量。
updating reference tables服务器正在执行多表更新的第二部分,并更新其他表中匹配的行。
User lock线程将要请求或正在等待通过
GET_LOCK()调用请求的咨询锁 。对于SHOW PROFILE,这种状态意味着线程正在请求锁定(而不是等待它)。User sleep线程调用了一个
SLEEP()调用。Waiting for commit lockFLUSH TABLES WITH READ LOCK正在等待提交锁。waiting for handler commit与查询处理的其他部分相比,线程正在等待事务提交。
Waiting for tables线程收到通知,指出表的基础结构已更改,需要重新打开表以获取新结构。但是,要重新打开该表,它必须等到所有其他线程都关闭了该表。
如果另一个线程使用
FLUSH TABLES了相关表上的或以下语句之一,则会发生此通知 : , , , , , 或 。FLUSH TABLES *tbl_name*ALTER TABLERENAME TABLEREPAIR TABLEANALYZE TABLEOPTIMIZE TABLEWaiting for table flush线程正在执行
FLUSH TABLES并等待所有线程关闭它们的表,或者线程收到通知,表明表的基础结构已更改,需要重新打开表以获取新结构。但是,要重新打开该表,它必须等到所有其他线程都关闭了该表。如果另一个线程使用
FLUSH TABLES了相关表上的或以下语句之一,则会发生此通知 : , , , , , 或 。FLUSH TABLES *tbl_name*ALTER TABLERENAME TABLEREPAIR TABLEANALYZE TABLEOPTIMIZE TABLEWaiting for *lock_type* lock服务器正在等待
THR_LOCK从元数据锁定子系统获取 锁或锁,其中 *lock_type*表示锁的类型。此状态表示等待 a
THR_LOCK:Waiting for table level lock
这些状态表示等待元数据锁定:
Waiting for event metadata lockWaiting for global read lockWaiting for schema metadata lockWaiting for stored function metadata lockWaiting for stored procedure metadata lockWaiting for table metadata lockWaiting for trigger metadata lock
有关表锁定指示器的信息,请参阅 第 8.11.1 节,“内部锁定方法”。有关元数据锁定的信息,请参阅第 8.11.4 节,“元数据锁定”。要查看哪些锁阻塞了锁请求,请使用第 27.12.13 节“性能模式锁表”中描述的 性能模式锁表。
Waiting on cond线程正在等待条件变为真的通用状态。没有可用的特定状态信息。
Writing to net服务器正在向网络写入数据包。
4. 复制源线程状态
以下列表显示了您可能会在复制源线程State列中看到的最常见状态Binlog Dump。如果Binlog Dump在源上没有看到任何 线程,这意味着复制没有运行;也就是说,当前没有连接副本。
在 MySQL 8.0.26 中,对检测名称进行了不兼容的更改,包括线程阶段的名称,包含术语“ master ”,更改为 “ source ”,“ slave ”,更改为 “ replica ”和“ mts ”(对于 “多线程从属”),更改为 “ mta ”(对于“多线程应用程序”))。使用这些检测名称的监控工具可能会受到影响。如果不兼容的更改对您有影响,请将 terminology_use_previous系统变量设置BEFORE_8_0_26为使 MySQL Server 使用上一个列表中指定的对象的旧版本名称。这使依赖旧名称的监视工具能够继续工作,直到可以更新它们以使用新名称。
terminology_use_previous使用会话范围 设置 系统变量以支持单个功能,或将全局范围设置为所有新会话的默认值。当使用全局范围时,慢查询日志包含名称的旧版本。
Finished reading one binlog; switching to next binlog该线程已完成读取二进制日志文件,并正在打开下一个要发送到副本的文件。
Master has sent all binlog to slave; waiting for more updates从 MySQL 8.0.26 开始:
Source has sent all binlog to replica; waiting for more updates该线程已从二进制日志中读取所有剩余更新并将它们发送到副本。该线程现在处于空闲状态,等待因源上发生的新更新而导致的二进制日志中出现新事件。
Sending binlog event to slave从 MySQL 8.0.26 开始:
Sending binlog event to replica二进制日志由events组成,其中事件通常是更新加上一些其他信息。线程从二进制日志中读取了一个事件,现在将其发送到副本。
Waiting to finalize termination线程停止时发生的非常短暂的状态。
5. 复制 I/O(接收器)线程状态
以下列表显示了您在State副本服务器上的复制 I/O(接收方)线程的列中看到的最常见状态 。此状态也出现在Replica_IO_State由SHOW REPLICA STATUS(或 MySQL 8.0.22 之前, SHOW REPLICA STATUS)显示的 列中 ,因此您可以使用该语句很好地了解正在发生的情况。
在 MySQL 8.0.26 中,对检测名称进行了不兼容的更改,包括线程阶段的名称,包含术语“ master ”,更改为 “ source ”,“ slave ”,更改为 “ replica ”和“ mts ”(对于 “多线程从属”),更改为 “ mta ”(对于“多线程应用程序”))。使用这些检测名称的监控工具可能会受到影响。如果不兼容的更改对您有影响,请将 terminology_use_previous系统变量设置BEFORE_8_0_26为使 MySQL Server 使用上一个列表中指定的对象的旧版本名称。这使依赖旧名称的监视工具能够继续工作,直到可以更新它们以使用新名称。
terminology_use_previous使用会话范围 设置 系统变量以支持单个功能,或将全局范围设置为所有新会话的默认值。当使用全局范围时,慢查询日志包含名称的旧版本。
Checking master version从 MySQL 8.0.26 开始:
Checking source version与源的连接建立后发生的非常短暂的状态。
Connecting to master从 MySQL 8.0.26 开始:
Connecting to source该线程正在尝试连接到源。
Queueing master event to the relay log从 MySQL 8.0.26 开始:
Queueing source event to the relay log线程已读取事件并将其复制到中继日志,以便 SQL 线程可以处理它。
Reconnecting after a failed binlog dump request该线程正在尝试重新连接到源。
Reconnecting after a failed master event read从 MySQL 8.0.26 开始:
Reconnecting after a failed source event read该线程正在尝试重新连接到源。当再次建立连接时,状态变为
Waiting for master to send event。Registering slave on master从 MySQL 8.0.26 开始:
Registering replica on source在与源建立连接后非常短暂地出现的一种状态。
Requesting binlog dump与源的连接建立后发生的非常短暂的状态。线程从请求的二进制日志文件名和位置开始向源发送对其二进制日志内容的请求。
Waiting for its turn to commit如果 启用
replica_preserve_commit_order或 ,slave_preserve_commit_order则当副本线程正在等待较旧的工作线程提交时发生的状态 。Waiting for master to send event从 MySQL 8.0.26 开始:
Waiting for source to send event该线程已连接到源并正在等待二进制日志事件到达。如果源空闲,这可能会持续很长时间。如果等待持续
replica_net_timeout或slave_net_timeout秒,发生超时。此时,线程认为连接已断开并尝试重新连接。Waiting for master update从 MySQL 8.0.26 开始:
Waiting for source updateConnecting to master或 之前的初始状态Connecting to source。Waiting for slave mutex on exit从 MySQL 8.0.26 开始:
Waiting for replica mutex on exit线程停止时短暂出现的状态。
Waiting for the slave SQL thread to free enough relay log space从 MySQL 8.0.26 开始:
Waiting for the replica SQL thread to free enough relay log space您正在使用一个非零
relay_log_space_limit值,并且中继日志已经变得足够大,以至于它们的组合大小超过了这个值。I/O(接收器)线程等待直到 SQL(应用程序)线程通过处理中继日志内容释放足够的空间,以便它可以删除一些中继日志文件。Waiting to reconnect after a failed binlog dump request如果二进制日志转储请求失败(由于断开连接),线程在休眠时进入此状态,然后定期尝试重新连接。可以使用
CHANGE REPLICATION SOURCE TO语句(来自 MySQL 8.0.23)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)指定重试之间的间隔 。Waiting to reconnect after a failed master event read从 MySQL 8.0.26 开始:
Waiting to reconnect after a failed source event read读取时发生错误(由于断开连接)。在尝试重新连接之前,线程正在休眠由
CHANGE REPLICATION SOURCE TO语句(来自 MySQL 8.0.23)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)设置的秒数,默认为 60。
6. 复制 SQL 线程状态
以下列表显示了您可能会在State副本服务器上的复制 SQL 线程列中看到的最常见状态。
在 MySQL 8.0.26 中,对检测名称进行了不兼容的更改,包括线程阶段的名称,包含术语“ master ”,更改为 “ source ”,“ slave ”,更改为 “ replica ”和“ mts ”(对于 “多线程从属”),更改为 “ mta ”(对于“多线程应用程序”))。使用这些检测名称的监控工具可能会受到影响。如果不兼容的更改对您有影响,请将 terminology_use_previous系统变量设置BEFORE_8_0_26为使 MySQL Server 使用上一个列表中指定的对象的旧版本名称。这使依赖旧名称的监视工具能够继续工作,直到可以更新它们以使用新名称。
terminology_use_previous使用会话范围 设置 系统变量以支持单个功能,或将全局范围设置为所有新会话的默认值。当使用全局范围时,慢查询日志包含名称的旧版本。
Making temporary file (append) before replaying LOAD DATA INFILE该线程正在执行一条
LOAD DATA语句,并将数据附加到一个临时文件中,该文件包含副本从中读取行的数据。Making temporary file (create) before replaying LOAD DATA INFILE该线程正在执行一条
LOAD DATA语句并创建一个临时文件,其中包含副本从中读取行的数据。仅当原始LOAD DATA语句由运行低于 MySQL 5.0.3 的 MySQL 版本的源记录时,才会遇到此状态 。Reading event from the relay log线程从中继日志中读取了一个事件,以便可以处理该事件。
Slave has read all relay log; waiting for more updates从 MySQL 8.0.26 开始:
Replica has read all relay log; waiting for more updates该线程已经处理了中继日志文件中的所有事件,现在正在等待 I/O(接收器)线程将新事件写入中继日志。
Waiting for an event from Coordinator使用多线程副本(
replica_parallel_workers或slave_parallel_workers大于 1),其中一个副本工作线程正在等待来自协调器线程的事件。Waiting for slave mutex on exit从 MySQL 8.0.26 开始:
Waiting for replica mutex on exit线程停止时发生的非常短暂的状态。
Waiting for Slave Workers to free pending events从 MySQL 8.0.26 开始:
Waiting for Replica Workers to free pending events当 Workers 正在处理的事件的总大小超过
replica_pending_jobs_size_max或slave_pending_jobs_size_max系统变量的大小时,就会发生此等待操作 。当大小低于此限制时,协调器恢复调度。此状态仅在replica_parallel_workers或slave_parallel_workers设置为大于 0 时发生。Waiting for the next event in relay log之前的初始状态
Reading event from the relay log。Waiting until MASTER_DELAY seconds after master executed event从 MySQL 8.0.26 开始:
Waiting until SOURCE_DELAY seconds after master executed eventSQL 线程已读取事件,但正在等待副本延迟结束。此延迟设置为
SOURCE_DELAY|MASTER_DELAY该选项的CHANGE REPLICATION SOURCE TO语句(从MySQL 8.0.23)或CHANGE MASTER TO声明(之前的MySQL 8.0.23)。
InfoSQL 线程 的列也可能显示语句的文本。这表明线程已经从中继日志中读取了一个事件,从中提取了语句,并且可能正在执行它。
7. 复制连接线程状态
这些线程状态发生在副本服务器上,但与连接线程相关联,而不是与 I/O 或 SQL 线程相关联。
在 MySQL 8.0.26 中,对检测名称进行了不兼容的更改,包括线程阶段的名称,包含术语“ master ”,更改为 “ source ”,“ slave ”,更改为 “ replica ”和“ mts ”(对于 “多线程从属”),更改为 “ mta ”(对于“多线程应用程序”))。使用这些检测名称的监控工具可能会受到影响。如果不兼容的更改对您有影响,请将 terminology_use_previous系统变量设置BEFORE_8_0_26为使 MySQL Server 使用上一个列表中指定的对象的旧版本名称。这使依赖旧名称的监视工具能够继续工作,直到可以更新它们以使用新名称。
terminology_use_previous使用会话范围 设置 系统变量以支持单个功能,或将全局范围设置为所有新会话的默认值。当使用全局范围时,慢查询日志包含名称的旧版本。
Changing master从 MySQL 8.0.26 开始:
Changing replication source线程正在处理
CHANGE REPLICATION SOURCE TO语句(来自 MySQL 8.0.23)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)。Killing slave该线程正在处理一个
STOP REPLICA语句。Opening master dump table此状态发生在 之后
Creating table from master dump。Reading master dump table data此状态发生在 之后
Opening master dump table。Rebuilding the index on master dump table此状态发生在 之后
Reading master dump table data。
8. NDB Cluster 线程状态
Committing events to binlogOpening mysql.ndb_apply_statusProcessing events该线程正在处理二进制日志记录事件。
Processing events from schema table该线程正在执行模式复制的工作。
Shutting downSyncing ndb table schema operation and binlog这用于为 NDB 提供正确的模式操作二进制日志。
Waiting for allowed to take ndbcluster global schema lock该线程正在等待获取全局模式锁的权限。
Waiting for event from ndbcluster服务器充当 NDB Cluster 中的 SQL 节点,并连接到集群管理节点。
Waiting for first event from ndbclusterWaiting for ndbcluster binlog update to reach current positionWaiting for ndbcluster global schema lock该线程正在等待释放另一个线程持有的全局模式锁。
Waiting for ndbcluster to startWaiting for schema epoch线程正在等待模式纪元(即全局检查点)。
9. 事件调度器线程状态
这些状态发生在事件调度程序线程、为执行调度事件而创建的线程或终止调度程序的线程。
Clearing调度程序线程或正在执行事件的线程正在终止并且即将结束。
Initialized调度程序线程或执行事件的线程已被初始化。
Waiting for next activation调度程序有一个非空的事件队列,但下一次激活是在未来。
Waiting for scheduler to stop线程发出
SET GLOBAL event_scheduler=OFF并等待调度程序停止。Waiting on empty queue调度程序的事件队列是空的并且正在休眠。
8. Mysql集群
8.1 复制
复制使数据从一台 MySQL 数据库服务器(称为源)复制到一台或多台 MySQL 数据库服务器(称为副本)。默认情况下复制是异步的;副本不需要永久连接以从源接收更新。根据配置,您可以复制所有数据库、选定数据库,甚至是数据库中选定的表。
MySQL 中复制的优点包括:
- 横向扩展解决方案 - 在多个副本之间分散负载以提高性能。在此环境中,所有写入和更新都必须在源服务器上进行。然而,读取可能发生在一个或多个副本上。该模型可以提高写入性能(因为源专用于更新),同时显着提高越来越多的副本的读取速度。
- 数据安全——因为副本可以暂停复制过程,所以可以在副本上运行备份服务而不会破坏相应的源数据。
- 分析 - 可以在源上创建实时数据,而信息分析可以在副本上进行,而不会影响源的性能。
- 远程数据分发 - 您可以使用复制创建本地数据副本供远程站点使用,而无需永久访问源。
MySQL 8.0 支持不同的复制方法。传统的方法是基于从源的二进制日志中复制事件,并且需要在源和副本之间同步日志文件和其中的位置。基于全局事务标识符(GTID)的较新方法是事务性的,因此不需要处理日志文件或这些文件中的位置,这大大简化了许多常见的复制任务。只要在源上提交的所有事务也已应用到副本上,使用 GTID 的复制就可以保证源和副本之间的一致性。
MySQL 中的复制支持不同类型的同步。最初的同步类型是单向异步复制,其中一台服务器充当源,而一台或多台其他服务器充当副本。这与作为 NDB Cluster 特征的同步复制形成对比
在 MySQL 8.0 中,除了内置的异步复制之外,还支持半同步复制。使用半同步复制,在返回到执行事务的会话之前对源块执行提交,直到至少一个副本确认它已接收并记录事务的事件;MySQL 8.0 还支持延迟复制,以便副本故意落后于源至少指定的时间;有许多解决方案可用于在服务器之间设置复制,使用的最佳方法取决于数据的存在和您使用的引擎类型。
有两种核心类型的复制格式,基于语句的复制 (SBR),它复制整个 SQL 语句,以及基于行的复制 (RBR),它只复制更改的行。您还可以使用第三种类型,即基于混合的复制 (MBR)。
您可以使用复制来解决许多不同的问题,包括性能、支持不同数据库的备份,以及作为减轻系统故障的更大解决方案的一部分。
1. GTID
GTID介绍1
MySQL · 引擎特性 · 基于GTID复制实现的工作原理
GTID (Global Transaction IDentifier) 是全局事务标识。它具有全局唯一性,一个事务对应一个GTID。唯一性不仅限于主服务器,GTID在所有的从服务器上也是唯一的。一个GTID在一个服务器上只执行一次,从而避免重复执行导致数据混乱或主从不一致。
在传统的复制里面,当发生故障需要主从切换时,服务器需要找到binlog和pos点,然后将其设定为新的主节点开启复制。相对来说比较麻烦,也容易出错。在MySQL 5.6里面,MySQL会通过内部机制自动匹配GTID断点,不再寻找binlog和pos点。我们只需要知道主节点的ip,端口,以及账号密码就可以自动复制。
GTID的组成部分:
GDIT由两部分组成:GTID = source_id:transaction_id。 其中source_id是产生GTID的服务器,即是server_uuid,在第一次启动时生成(sql/mysqld.cc: generate_server_uuid()),并保存到DATADIR/auto.cnf文件里。transaction_id是序列号(sequence number),在每台MySQL服务器上都是从1开始自增长的顺序号,是事务的唯一标识。例如:3E11FA47-71CA-11E1-9E33-C80AA9429562:23 GTID 的集合是一组GTIDs,可以用source_id+transaction_id范围表示,例如:3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5 复杂一点的:如果这组 GTIDs 来自不同的 source_id,各组 source_id 之间用逗号分隔;如果事务序号有多个范围区间,各组范围之间用冒号分隔,例如:3E11FA47-71CA-11E1-9E33-C80AA9429562:23,3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
转载至:http://mysql.taobao.org/monthly/2020/05/09/
GTID介绍2
一、GTID的概述:
1、全局事物标识:global transaction identifieds。
2、GTID事物是全局唯一性的,且一个事务对应一个GTID。
3、一个GTID在一个服务器上只执行一次,避免重复执行导致数据混乱或者主从不一致。
4、GTID用来代替classic的复制方法,不在使用binlog+pos开启复制。而是使用master_auto_postion=1的方式自动匹配GTID断点进行复制。
5、MySQL-5.6.5开始支持的,MySQL-5.6.10后开始完善。
6、在传统的slave端,binlog是不用开启的,但是在GTID中,slave端的binlog是必须开启的,目的是记录执行过的GTID(强制)。
二、GTID的组成部分:
前面是server_uuid:后面是一个序列号
例如:server_uuid:sequence number
7800a22c-95ae-11e4-983d-080027de205a:10
UUID:每个mysql实例的唯一ID,由于会传递到slave,所以也可以理解为源ID。
Sequence number:在每台MySQL服务器上都是从1开始自增长的序列,一个数值对应一个事务。
三、GTID比传统复制的优势:
1、更简单的实现failover,不用以前那样在需要找log_file和log_Pos。
2、更简单的搭建主从复制。
3、比传统复制更加安全。
4、GTID是连续没有空洞的,因此主从库出现数据冲突时,可以用添加空事物的方式进行跳过。
四、GTID的工作原理:
1、master更新数据时,会在事务前产生GTID,一同记录到binlog日志中。
2、slave端的i/o 线程将变更的binlog,写入到本地的relay log中。
3、sql线程从relay log中获取GTID,然后对比slave端的binlog是否有记录。
4、如果有记录,说明该GTID的事务已经执行,slave会忽略。
5、如果没有记录,slave就会从relay log中执行该GTID的事务,并记录到binlog。
6、在解析过程中会判断是否有主键,如果没有就用二级索引,如果没有就用全部扫描。
要点:
1、slave在接受master的binlog时,会校验master的GTID是否已经执行过(一个服务器只能执行一次)。
2、为了保证主从数据的一致性,多线程只能同时执行一个GTID。
六、使用GTID搭建mysql的主从复制的主要参数:
[mysqld]
#GTID:
gtid_mode=on
enforce_gtid_consistency=on
server_id=2003306 #每天实例的server_id都要不一样
#binlog
log-bin=mysqlbin
log-slave-updates=1 #允许下端接入slave
binlog_format=row #强烈建议,其他格式可能造成数据不一致
#relay log
skip_slave_start=1
注意:建议使用mysql-5.6.5以上的最新版本。
(二)、启动GTID的两种方法:
方法一、
1、如果是在已经跑的服务器,你需要重启一下mysql server。
2、启动之前,一定要先关闭master的写入,保证所有slave端都已经和master端数据保持同步。
3、所有slave需要加上skip_slave_start=1的配置参数,避免启动后还是使用老的复制协议。
方法二、
1、如果是新搭建的服务器,直接启动就行了。
七、master-slave搭建的注意事项:
(一)、使用GTID的方式,把salve端挂载master端:
1、启动以后最好不要立即执行事务,而是先change master上。
2、然后在执行事务,当然知不是必须的。
3、使用下面的sql切换slave到新的master。
stop slave;
change master to
master_host = 192.168.100.200,
master_port = 3306,
master_user = abobo,
master_password=123,
master_auto_position = 1;
(二)、如果给已经运行的GTID的master端添加一个新的slave
有两种方法:
方法一、适用于master也是新建不久的情况。
1、如果你的master所有的binlog还在。可以选择类似于上面的方法,安装slave,直接change master to到master端。
2、原理是直接获取master所有的GTID并执行。
3、优点:简单方便。
4、缺点:如果binlog太多,数据完全同步需要时间较长,并且master一开始就启用了GTUD。
方法二、适用于拥有较大数据的情况。(推荐)
1、通过master或者其他slave的备份搭建新的slave。(看第三部分)
2、原理:获取master的数据和这些数据对应的GTID范围,然后通过slave设置@@global.gtid_purged跳过备份包含的gtid。
3、优点:是可以避免第一种方法的不足。
4、缺点:相对来说有点复杂。
(三)、通过备份搭建新的slave:(方法二的扩展)
两种方法:
方法一、mysqldump的方式:
1、在备份的时候指定–master-data=2(来保存binlog的文件号和位置的命令)。
2、使用mysqldump的命令在dump文件里可以看到下面两个信息:
SET @@SESSION.SQL_LOG_BIN=0;
SET @@GLOBAL.GTID_PURGED=’7800a22c-95ae-11e4-983d-080027de205a:1-8’;
3、将备份还原到slave后,使用change master to命令挂载master端。
注意:在mysql5.6.9以后的命令才支持这个功能。
方法二、percona Xtrabackup
1、Xtrabackup_binlog_info文件中,包含global.gtid_purged=’XXXXXX:XXXX’的信息。
2、然后到slave去手工的 SET GLOBAL.GTID_PURGED=’XXXXXX:XXXX’。
3、恢复备份,开启change master to 命令。
注意:如果系统运行了很久,无法找到GTID的变好了,可以通过上面的方式进行查找。
八、GTID如何跳过事务冲突:
1、这个功能主要跳过事务,代替原来的set global sql_slave_skip_counter = 1。
2、由于在这个GTID必须是连续的,正常情况同一个服务器产生的GTID是不会存在空缺的。所以不能简单的skip掉一个事务,只能通过注入空事物的方法替换掉一个实际操作事务。
3、注入空事物的方法:
stop slave;
set gtid_next=’xxxxxxx:N’;
begin;commit;
set gtid_next=’AUTOMAIC’;
start slave;
4、这里的xxxxx:N 也就是你的slave sql thread报错的GTID,或者说是你想要跳过的GTID。
转载至:https://cloud.tencent.com/developer/article/1401313
GTID官方文档
本节解释使用全局事务标识符的基于事务的复制 (GTID)。使用 GTID 时,可以识别和跟踪每个事务,因为它在原始服务器上提交并由任何副本应用;这意味着在启动新副本或故障转移到新源时,无需使用 GTID 来引用日志文件或这些文件中的位置,这大大简化了这些任务。因为基于GTID的复制完全是基于事务的,所以很容易判断源和副本是否一致;只要在源上提交的所有事务也在副本上提交,就可以保证两者之间的一致性。您可以将基于语句或基于行的复制与 GTID 一起使用。
GTID 始终保留在源和副本之间。这意味着您始终可以通过检查其二进制日志来确定应用于任何副本的任何事务的来源。此外,一旦在给定服务器上提交具有给定 GTID 的事务,该服务器将忽略具有相同 GTID 的任何后续事务。因此,在源上提交的事务只能在副本上应用一次,这有助于保证一致性。
GTID 格式和存储
全局唯一全局事务标识符 (GTID) 是在源服务器(源)上创建并与提交的每个事务相关联的唯一标识符。此标识符不仅对于它起源的服务器是唯一的,而且对于给定复制拓扑中的所有服务器也是唯一的。
单调递增,只记录更新事务GTID 分配区分在源上提交的客户端事务和在副本上复制的复制事务。当客户端事务在源上提交时,它会被分配一个新的 GTID,前提是该事务已写入二进制日志。客户端事务保证具有单调增加的 GTID,生成的数字之间没有间隙。如果客户端事务未写入二进制日志(例如,因为事务被过滤掉,或者事务是只读的),则不会在源服务器上为其分配 GTID。
存储在文件复制的事务保留与分配给源服务器上的事务相同的 GTID。GTID 在复制事务开始执行之前就存在,即使复制事务没有写入副本上的二进制日志,或者在副本上被过滤掉,GTID 也会持久化。MySQL 系统表mysql.gtid_executed用于保留 MySQL 服务器上应用的所有事务的分配 GTID,但存储在当前活动的二进制日志文件中的事务除外。
跳过已执行的GTIDGTID 的自动跳过功能意味着在源上提交的事务只能在副本上应用一次,这有助于保证一致性。一旦在给定服务器上提交了具有给定 GTID 的事务,该服务器将忽略执行具有相同 GTID 的后续事务的任何尝试。不会引发错误,并且不会执行事务中的任何语句。
并发执行如果具有给定 GTID 的事务已开始在服务器上执行,但尚未提交或回滚,则任何尝试在具有相同 GTID 块的服务器上启动并发事务。服务器既不开始执行并发事务,也不将控制权返回给客户端。一旦事务的第一次尝试提交或回滚,在同一 GTID 上阻塞的并发会话可能会继续进行。如果第一次尝试回滚,一个并发会话将继续尝试事务,并且在同一 GTID 上阻塞的任何其他并发会话保持阻塞状态。如果第一次尝试提交,所有并发会话将停止被阻塞,并自动跳过事务的所有语句。
GTID 表示为一对坐标,以冒号 ( :)分隔,如下所示:
1 | GTID = source_id:transaction_id |
该*source_id标识的原始服务器。通常,源的 server_uuid用于此目的。的transaction_id*是通过在事务提交源上的顺序确定一个序列号。例如,要提交的第一个事务1为其 *transaction_id,而要在同一原始服务器上提交的第十个事务分配transaction_id*为 10。事务不可能0在 GTID 中具有序列号。例如,最初在具有 UUID 的服务器上提交的第 23 个事务 3E11FA47-71CA-11E1-9E33-C80AA9429562具有以下 GTID:
1 | 3E11FA47-71CA-11E1-9E33-C80AA9429562:23 |
服务器实例上 GTID 的序列号上限是有符号 64 位整数的非负值数(2 的 63 次方减 1,或 9,223,372,036,854,775,807)。如果服务器用完 GTID,它将采取由 指定的操作 binlog_error_action。从 MySQL 8.0.23 开始,当服务器实例接近限制时会发出警告消息。
GTID 集
GTID 集是包含一个或多个单个 GTID 或 GTID 范围的集合。GTID 集以多种方式在 MySQL 服务器中使用。
来自同一服务器的一系列 GTID 可以合并为一个表达式,如下所示:
1 | 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5 |
mysql.gtid_executed 表
GTID 存储在数据库中名为gtid_executed,的表 中 mysql。该表中的一行包含它所代表的每个 GTID 或一组 GTID,原始服务器的 UUID,以及该组的开始和结束事务 ID;对于仅引用单个 GTID 的行,这最后两个值是相同的。
该mysql.gtid_executed表是在安装或升级 MySQL 服务器时创建的(如果它不存在),使用CREATE TABLE 类似于此处显示的语句:
1 | CREATE TABLE gtid_executed ( |
GTID 的生命周期包括以下步骤:*
- 在源上执行并提交事务。这个客户端事务被分配了一个由源的 UUID 和这个服务器上尚未使用的最小非零事务序列号组成的 GTID。GTID 被写入源的二进制日志(在日志中紧跟在事务本身之前)。如果客户端事务未写入二进制日志(例如,因为事务被过滤掉,或者事务是只读的),则不会为其分配 GTID。
- 如果为事务分配了 GTID,则 GTID 通过在事务开始时将其写入二进制日志(作为
Gtid_log_event)在提交时以原子方式持久化 。每当二进制日志轮换或服务器关闭时,服务器都会将写入前一个二进制日志文件的所有事务的 GTID 写入mysql.gtid_executed表中。 - 如果为事务分配了 GTID,则通过将 GTID 添加到
gtid_executed系统变量 (@@GLOBAL.gtid_executed) 中的一组 GTID 中,该 GTID 将非原子地(在事务提交后不久)外化。此 GTID 集包含所有已提交 GTID 事务集的表示,它在复制中用作表示服务器状态的令牌。启用二进制日志记录(根据源的要求),gtid_executed系统变量中的 GTID 集 是所应用事务的完整记录,但mysql.gtid_executed表不是,因为最近的历史记录仍在当前的二进制日志文件中。 - 在将二进制日志数据传输到副本并存储在副本的中继日志中(使用此过程的既定机制,请参阅 第 17.2 节,“复制实现”,了解详细信息)后,副本读取 GTID 并设置其
gtid_next系统的值 变量作为此 GTID。这告诉副本必须使用此 GTID 记录下一个事务。重要的是要注意副本设置gtid_next在会话上下文中。 - 副本验证没有线程尚未获得 GTID 的所有权
gtid_next以处理事务。通过首先读取和检查复制事务的 GTID,在处理事务本身之前,副本不仅保证没有具有此 GTID 的先前事务已应用于副本,而且还没有其他会话已读取此 GTID 但尚未提交相关事务。因此,如果多个客户端尝试同时应用同一个事务,服务器会通过只让其中一个执行来解决这个问题。该gtid_owned系统变量(@@GLOBAL.gtid_owned) 副本显示当前正在使用的每个 GTID 以及拥有它的线程的 ID。如果 GTID 已经被使用,则不会引发错误,并且使用自动跳过功能来忽略事务。 - 如果尚未使用 GTID,则副本将应用复制的事务。由于
gtid_next设置为源已经分配的 GTID,副本不会尝试为该事务生成新的 GTID,而是使用存储在gtid_next. - 如果在副本上启用了二进制日志记录,则 GTID 在提交时通过在事务开始时将其写入二进制日志(作为
Gtid_log_event)以原子方式持久化 。每当二进制日志轮换或服务器关闭时,服务器都会将写入前一个二进制日志文件的所有事务的 GTID 写入mysql.gtid_executed表中。 - 如果在副本上禁用了二进制日志记录,则 GTID 会通过将其直接写入
mysql.gtid_executed表以原子方式持久化 。MySQL 在事务中附加一条语句以将 GTID 插入表中。从 MySQL 8.0 开始,此操作对于 DDL 语句和 DML 语句都是原子的。在这种情况下,该mysql.gtid_executed表是应用在副本上的事务的完整记录。 - 在副本上提交复制事务后不久,GTID 通过将其添加到副本的
gtid_executed系统变量 (@@GLOBAL.gtid_executed) 中的一组 GTID 以非原子方式外部化 。至于源,这个 GTID 集包含所有提交的 GTID 事务集的表示。如果在副本上禁用了二进制日志记录,则该mysql.gtid_executed表也是应用在副本上的事务的完整记录。如果在副本上启用了二进制日志记录,这意味着某些 GTID 仅记录在二进制日志中,则gtid_executed系统变量中的 GTID 集是唯一完整的记录。
GTID 自动定位
GTID 取代了之前确定开始、停止或恢复源和副本之间数据流所需的文件偏移对。当使用 GTID 时,副本与源同步所需的所有信息都是直接从复制数据流中获取的。
使用 GTID 进行故障转移和横向扩展
全局事务标识符被添加到 MySQL 复制中,目的是简化复制数据流的一般管理,特别是故障转移活动。每个标识符唯一地标识一组组成事务的二进制日志事件。GTID 在将更改应用于数据库方面发挥着关键作用:服务器会自动跳过任何具有服务器识别为它之前处理过的标识符的事务。此行为对于自动复制定位和正确的故障转移至关重要。
简单的复制。 在新服务器上复制所有标识符和事务的最简单方法是将新服务器制作为具有完整执行历史的源的副本,并在两台服务器上启用全局事务标识符。
复制开始后,新服务器从源复制整个二进制日志,从而获取有关所有 GTID 的所有信息。
这种方法简单有效,但需要副本从源读取二进制日志;新副本有时需要较长时间才能赶上源,因此这种方法不适合快速故障转移或从备份恢复。本节介绍如何通过将二进制日志文件复制到新服务器来避免从源获取所有执行历史记录。
将数据和事务复制到副本。 当源服务器之前处理了大量事务时,执行整个事务历史可能会很耗时,这可能是设置新副本时的主要瓶颈。为了消除这个要求,可以将数据集的快照、二进制日志和源服务器包含的全局事务信息导入到新副本中。拍摄快照的服务器可以是源,也可以是其副本之一,但您必须确保服务器在复制数据之前已处理所有必需的事务。
使用 GTID 进行复制的限制
由于基于 GTID 的复制依赖于事务,因此在使用 MySQL 时不支持某些在 MySQL 中可用的功能。
不支持非事务引擎(从库报错,stopslave; start slave; 忽略)
不支持create table … select 语句复制(主库直接报错)
不允许在一个SQL同时更新一个事务引擎和非事务引擎的表
在一个复制组中,必须要求统一开启GTID或是关闭GTID
开启GTID需要重启(5.7中可能不需要)
开启GTID后,就不在使用原来的传统的复制方式
对于create temporary table 和drop temporary table语句不支持
不支持sql_slave_skip_counte
涉及非事务性存储引擎的更新。 使用 GTID 时,使用非MyISAM 事务性存储引擎(例如 InnoDB.
此限制是由于对使用非事务存储引擎的表的更新与对同一事务中使用事务存储引擎的表的更新混合可能导致多个 GTID 分配给同一事务的事实。
当源和副本对同一表的各自版本使用不同的存储引擎时,也可能发生此类问题,其中一个存储引擎是事务性的,另一个不是。另请注意,定义为对非事务性表进行操作的触发器可能是导致这些问题的原因。
在刚才提到的任何一种情况下,事务和 GTID 之间的一一对应关系都被破坏了,导致基于 GTID 的复制无法正常运行。
在线服务器上更改 GTID 模式
为了能够安全地配置在线服务器的复制模式,了解复制的一些关键概念很重要。本节解释了这些概念,是在尝试修改在线服务器的复制模式之前必须阅读的内容。
MySQL 中可用的复制模式依赖于不同的技术来识别记录的事务。复制使用的事务类型如下:
- GTID 事务由格式为 的全局事务标识符 (GTID) 标识
UUID:NUMBER。日志中的每个 GTID 事务总是以Gtid_log_event. GTID 事务可以使用 GTID 或使用文件名和位置来寻址。 - 匿名事务没有分配 GTID,MySQL 确保日志中的每个匿名事务都以
Anonymous_gtid_log_event. 在以前的版本中,匿名交易之前没有任何特定事件。匿名交易只能使用文件名和位置来处理。
开启 GTID 在线交易
本节介绍如何在已经在线并使用匿名事务的服务器上启用 GTID 事务和可选的自动定位。此过程不需要使服务器脱机,适合在生产中使用。但是,如果您可以在启用 GTID 事务时使服务器脱机,则该过程会更容易。
在开始之前,请确保服务器满足以下前提条件:
- 拓扑中的所有服务器都必须使用 MySQL 5.7.6 或更高版本。除非 拓扑中的所有服务器都使用此版本,否则您无法在任何单个服务器上在线启用 GTID 事务。
- 所有服务器都
gtid_mode设置为默认值OFF。
GTID的特点
一个事务对应唯一一个ID,一个GTID对应的事务在同一台服务器上只能执行一次。
GTID复制与普通复制最大的区别就是 不需要指定二进制文件名和位置
当一个事务在主库端执行并提交时,产生GTID,一同记录到binlog日志中;binlog中先记录gtid,紧跟着再记录事务相关的操作。
MySQL提供了选项可以控制跳过某些gtid事务,防止slave第一次启动复制时执master上的所有事务而导致耗时过久。
那么GTID复制是怎么实现自动同步,自动对应位置的呢? 例如:ServerC <—–ServerA —-> ServerB
主机:ServerA
备机:ServerB,ServerC
当主机serverA挂了之后,需要将serverB提升为主机(可以通过MHA实现),serverC连接到serverB上。
此时,serverC先在自己的二进制文件中找到从serverA传过来的最新的GTID,将最新的GTID传给serverB,然后serverB就会从这个GTID的下个GTID开始发送事务给serverC。
这种自我寻找复制位置的模式减少了事务丢失的可能性和故障恢复的时间。
GTID工作原理深入理解 gtid在master和slave上持久化保存,即使删除了日志,也会记录到previous_gtid中。
binlog文件中记录的格式是先记录gtid,然后再记录事务相关的操作。
gtid_next 是基于会话的,不同会话的gtid_next不同。
如果mysql是5.6版本,那么主从必须开启log_slave_updates参数,此时slave对比自己的binlog查看是否有记录。如果mysql是5.7版本,那么主从不需要开启此参数(级联主从除外),mysql5.7提供了gtid_excuted系统表来记录复制的信息,以此减少从库的压力。
例如”gtid_executed 5ad9cb8e-2092-11e7-ac95-000c29bf823d:1-6”,表示该server_uuid上执行了从1到6的事务。

5.6版本,slave在应用relay log的时候不是提交后才将gtid写入到自己的binlog中,而是判断gtid不存在后立即写入binlog。通过这种在执行事务前先检查gtid并写gtid到binlog中的机制,可以保证没有其他会话读取了该gtid却没有提交而导致事务重复执行的情况。
如果当前会话读取了该gtid,并将该gtid立即写入binlog(不管是否已经执行该事务),那么其他会话总能读取到binlog中的该gtid,然后其他会话就会放弃该事务。总之,一个事务是不允许多个会话并行执行或多次执行的。
- slave在应用relaylog的时产生的binlog中的gtid是master的gtid,所以在整个复制架构中GTID是不变的,即使在多个连环主从中也不会变。例如:
1 | ServerA --->ServerB ---->ServerC |
原文链接:https://blog.csdn.net/wangxin3618/article/details/82984807
2. 配置复制
基于二进制日志文件位置的复制配置概述
本节描述基于二进制日志文件位置方法的 MySQL 服务器之间的复制,其中作为源(数据库更改发生的地方)运行的 MySQL 实例将更新和更改作为“事件”写入二进制日志。根据记录的数据库更改,二进制日志中的信息以不同的日志格式存储。副本配置为从源读取二进制日志并在副本的本地数据库上执行二进制日志中的事件。
每个副本都会收到一份二进制日志全部内容的副本。副本有责任决定应该执行二进制日志中的哪些语句。除非您另外指定,否则源二进制日志中的所有事件都在副本上执行。如果需要,您可以将副本配置为仅处理适用于特定数据库或表的事件。
每个副本都会记录二进制日志坐标:它从源读取和处理的文件名和文件中的位置。这意味着多个副本可以连接到源并执行同一二进制日志的不同部分。由于副本控制此过程,因此单个副本可以与服务器连接和断开连接,而不会影响源的操作。此外,由于每个副本都记录了二进制日志中的当前位置,因此副本可能会断开连接,重新连接,然后继续处理。
源和每个副本必须配置一个唯一的 ID(使用server_id系统变量)。此外,每个副本都必须配置有关源主机名、日志文件名和该文件中位置的信息。可以使用副本上的CHANGE REPLICATION SOURCE TO语句(来自 MySQL 8.0.23)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)从 MySQL 会话中控制这些详细信息。
要将副本配置为在正确的点开始复制过程,您需要在其二进制日志中记录源的当前坐标。
选择数据快照的方法
如果源数据库包含现有数据,则需要将此数据复制到每个副本。有多种方法可以从源数据库转储数据。以下部分描述了可能的选项。
要选择转储数据库的适当方法,请在以下选项中进行选择:
- 使用mysqldump工具创建要复制的所有数据库的转储。这是推荐的方法,尤其是在使用
InnoDB. - 如果您的数据库存储在二进制可移植文件中,您可以将原始数据文件复制到副本。这比使用mysqldump并在每个副本上导入文件更有效,因为它会
INSERT在重播语句时跳过更新索引的开销 。InnoDB不推荐使用这样的存储引擎。 - 使用 MySQL Server 的克隆插件将所有数据从现有副本传输到克隆。
1 | 小费 |
8.2 多源复制
MySQL 多源复制使副本能够并行接收来自多个直接源的事务。在多源复制拓扑中,副本为它应该从中接收事务的每个源创建一个复制通道。
您可以选择实施多源复制来实现以下目标:
- 将多个服务器备份到单个服务器。
- 合并表碎片。
- 将来自多个服务器的数据合并到单个服务器。
多源复制在应用事务时不实现任何冲突检测或解决,如果需要,这些任务留给应用程序。
1 | 笔记 |
多源副本也可以设置为多线程副本,方法是将系统变量 replica_parallel_workers(来自 MySQL 8.0.26) slave_parallel_workers设置为大于 0 的值。当您在多源副本上执行此操作时,每个通道在副本上有指定数量的应用程序线程,加上一个协调器线程来管理它们。您不能为单个通道配置应用程序线程的数量。
配置多源复制
多源复制拓扑至少需要配置两个源和一个副本。在这些教程中,我们假设您有两个源source1和 source2和一个副本 replicahost。副本从每个源复制一个数据库,db1来自 source1和db2来自 source2。
多源复制拓扑中的源可以配置为使用基于 GTID 的复制或基于二进制日志位置的复制。
为基于 GTID 的复制配置多源副本
如果多源复制拓扑中的源具有现有数据,则可以在开始复制之前使用相关数据预配副本,从而节省时间。在多源复制拓扑中,数据目录的克隆或复制不能用于为副本提供来自所有源的数据,您可能还希望仅从每个源复制特定数据库。因此,供应此类副本的最佳策略是使用mysqldump在每个源上创建适当的转储文件,然后使用 mysql客户端在副本上导入转储文件。
如果使用的是基于GTID的复制,则需要注意mysqldump在dump输出中放置的SET @@GLOBAL.gtid_purged语句 。此语句将在源上执行的事务的 GTID 传输到副本,副本需要此信息。但是,对于比从一个源配置一个新的空副本更复杂的任何情况,您需要检查该语句对副本所使用的 MySQL 版本的影响,并相应地处理该语句。
8.3 复制和二进制日志选项和变量
此变量指定服务器 ID。 server_id默认设置为 1。可以使用此默认 ID 启动服务器,但是当启用二进制日志记录时,如果您没有server_id明确设置以指定服务器 ID,则会发出一条信息性消息。
对于在复制拓扑中使用的服务器,则必须为每个复制服务器的唯一服务器ID,在范围从1到2 32 - 1 “独特的”意味着每个ID必须是从由使用每隔一个ID不同复制拓扑中的任何其他源或副本。
如果服务器 ID 设置为 0,则进行二进制日志记录,但服务器 ID 为 0 的源拒绝来自副本的任何连接,服务器 ID 为 0 的副本拒绝连接到源。请注意,尽管您可以将服务器 ID 动态更改为非零值,但这样做并不能立即启动复制。您必须更改服务器 ID,然后重新启动服务器以初始化副本。
详细参考官方文档:https://dev.mysql.com/doc/refman/8.0/en/replication-options.html
复制安全
为了防止对复制源服务器和副本之间存储和传输的数据进行未经授权的访问,请使用您为安装中的任何 MySQL 实例选择的安全措施设置所有涉及的服务器,
- 设置源和副本以使用加密连接来传输二进制日志,从而保护动态数据。必须使用
CHANGE REPLICATION SOURCE TO| 激活这些连接的加密。CHANGE MASTER TO声明,除了设置服务器以支持加密的网络连接。 - 加密源和副本上的二进制日志文件和中继日志文件,以保护这些静态数据以及二进制日志缓存中使用的任何数据。使用
binlog_encryption系统变量激活二进制日志加密。 - 对复制应用程序应用权限检查,这有助于保护复制通道免受未经授权或意外使用特权或不需要的操作。权限检查是通过设置一个
PRIVILEGE_CHECKS_USER帐户来实现的,MySQL 使用该 帐户来验证您是否已授权该通道的每个特定事务。
对于组复制,二进制日志加密和权限检查可以用作复制组成员的安全措施。您还应该考虑加密组成员之间的连接,包括组通信连接和分布式恢复连接,并应用 IP 地址许可名单以排除不受信任的主机。
要使用加密连接传输复制期间所需的二进制日志,源服务器和副本服务器都必须支持加密网络连接。如果任一服务器不支持加密连接(因为尚未为其编译或配置),则无法通过加密连接进行复制。
为复制设置加密连接类似于为客户端/服务器连接设置加密连接。您必须获得(或创建)可在源上使用的合适的安全证书,以及每个副本上的类似证书(来自同一证书颁发机构)。您还必须获得合适的密钥文件。
加密二进制日志文件和中继日志文件
从 MySQL 8.0.14 开始,可以对二进制日志文件和中继日志文件进行加密,帮助保护这些文件和其中包含的潜在敏感数据不被外部攻击者滥用,以及被操作系统用户未经授权查看。被存储。用于文件的加密算法 AES(高级加密标准)密码算法内置于 MySQL 服务器,无法配置。
您可以通过将binlog_encryption系统变量设置为在 MySQL 服务器上启用此加密 ON。OFF是默认值。系统变量为二进制日志文件和中继日志文件设置加密。不需要在服务器上启用二进制日志来启用加密,因此您可以加密没有二进制日志的副本上的中继日志文件。要使用加密,必须安装并配置密钥环组件或插件以提供 MySQL 服务器的密钥环服务。
复制权限检查
默认情况下,当其他服务器已经接受的事务应用于副本或组成员时,MySQL 复制(包括组复制)不会执行权限检查。从 MySQL 8.0.18 开始,您可以创建具有适当权限的用户帐户来应用通常在通道上复制的事务,并PRIVILEGE_CHECKS_USER使用CHANGE REPLICATION SOURCE TO语句(来自 MySQL 8.0.23)或 将其指定为复制应用程序的 帐户CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)。MySQL 然后根据用户帐户的权限检查每个事务,以验证您是否已授权该通道的操作。管理员也可以安全地使用该帐户来申请或重新申请交易 mysqlbinlog输出,例如从通道上的复制错误中恢复。
PRIVILEGE_CHECKS_USER帐户 的使用有助于保护复制通道,防止未经授权或意外使用特权或不需要的操作。该 PRIVILEGE_CHECKS_USER帐户在以下情况下提供了额外的安全层:
- 您正在组织网络上的服务器实例和另一个网络上的服务器实例(例如云服务提供商提供的实例)之间进行复制。
- 您希望将多个内部部署或非现场部署作为单独的单元进行管理,而无需为所有部署授予一个管理员帐户权限。
- 您希望拥有一个管理员帐户,使管理员能够仅执行与复制通道及其复制的数据库直接相关的操作,而不是在服务器实例上拥有广泛的特权。
复制解决方案
要将复制用作备份解决方案,请将数据从源复制到副本,然后备份副本。副本可以暂停和关闭,而不会影响源的运行操作,因此您可以生成“实时”数据的有效快照, 否则需要关闭源。
备份数据库的方式取决于其大小以及您是仅备份数据还是备份数据和副本状态,以便在发生故障时重建副本。因此有两种选择:
- 如果您使用复制作为解决方案来备份源上的数据,并且您的数据库大小不是太大,那么mysqldump工具可能是合适的。
- 对于较大的数据库,mysqldump不切实际或效率低下,您可以改为备份原始数据文件。使用原始数据文件选项还意味着您可以备份二进制和中继日志,以便在副本发生故障时重新创建副本。
为保证复制文件的完整性,应在副本服务器关闭时备份 MySQL 副本上的原始数据文件。如果 MySQL 服务器仍在运行,后台任务可能仍在更新数据库文件,尤其是那些涉及具有后台进程的存储引擎的任务,例如InnoDB. 使用InnoDB,这些问题应该在崩溃恢复期间解决,但是由于可以在备份过程中关闭副本服务器而不影响源的执行,因此利用此功能是有意义的。
处理副本意外停止
为了使复制对服务器意外停止(有时被描述为崩溃安全)具有弹性,副本必须有可能在停止之前恢复其状态。本节介绍复制期间副本意外停止的影响,以及如何配置副本以获得最佳恢复机会以继续复制。
副本意外停止后,重新启动时,复制 SQL 线程必须恢复有关哪些事务已执行的信息。恢复所需的信息存储在副本的应用程序元数据存储库中。从 MySQL 8.0 开始,此存储库默认创建为InnoDB名为的表 mysql.slave_relay_log_info. 通过使用这个事务存储引擎,信息总是可以在重新启动时恢复。应用程序元数据存储库的更新与事务一起提交,这意味着记录在该存储库中的副本进度信息始终与已应用于数据库的信息一致,即使在服务器意外停止的情况下也是如此。
DML 事务和原子 DDL 更新mysql.slave_relay_log_info表中副本的应用程序元数据存储库中的复制位置, 同时将更改应用于数据库,作为原子操作。在所有其他情况下,包括不完全原子的 DDL 语句和不支持原子 DDL 的豁免存储引擎,mysql.slave_relay_log_info如果服务器意外停止,表可能会丢失与复制数据关联的更新。在这种情况下恢复更新是一个手动过程。
副本从意外停止中恢复的恢复过程因副本的配置而异。恢复过程的细节受所选择的复制方法、副本是单线程还是多线程以及相关系统变量的设置的影响。恢复过程的总体目标是确定在意外停止发生之前已在副本的数据库上应用了哪些事务,并检索和应用副本在意外停止后错过的事务。
- 对于基于 GTID 的复制,恢复过程需要副本已经接收或提交的事务的 GTID。可以使用 GTID 自动定位从源检索丢失的事务,它会自动将源的事务与副本的事务进行比较并识别丢失的事务。
- 对于基于文件位置的复制,恢复过程需要一个准确的复制 SQL 线程(应用程序)位置,显示应用在副本上的最后一个事务。基于该位置,复制 I/O 线程(接收器)从源的二进制日志中检索应从该点开始应用于副本的所有事务。
使用基于 GTID 的复制可以最轻松地将复制配置为对意外停止具有弹性。GTID 自动定位意味着副本可以可靠地识别和检索丢失的事务,即使应用事务的顺序存在间隙。
使用复制进行横向扩展
您可以将复制用作横向扩展解决方案;也就是说,您希望在一些合理的限制内将数据库查询的负载分散到多个数据库服务器上。
因为复制从一个源到一个或多个副本的分发工作,所以在具有大量读取和少量写入/更新的环境中使用横向扩展复制效果最佳。大多数网站都属于这一类别,用户在其中浏览网站、阅读文章、帖子或查看产品。更新仅在会话管理期间发生,或者在进行购买或向论坛添加评论/消息时发生。
在这种情况下,复制使您能够将读取分布到副本上,同时仍然使您的 Web 服务器能够在需要写入时与源通信。
在横向扩展期间使用复制来提高性能

如果负责数据库访问的代码部分已被正确抽象/模块化,则将其转换为使用复制设置运行应该非常顺利和容易。更改数据库访问的实现以将所有写入发送到源,并将读取发送到源或副本。如果您的代码没有这种抽象级别,那么设置一个复制系统会给您机会和动力来清理它。首先创建一个实现以下功能的包装库或模块:
safe_writer_connect()safe_reader_connect()safe_reader_statement()safe_writer_statement()
safe_在每个函数名称中意味着该函数负责处理所有错误条件。您可以为函数使用不同的名称。重要的是要有一个统一的接口,用于连接读取、连接写入、读取和写入。
然后转换您的客户端代码以使用包装器库。起初,这可能是一个痛苦而可怕的过程,但从长远来看,这是值得的。使用刚才描述的方法的所有应用程序都能够利用源/副本配置,即使是涉及多个副本的配置。代码更容易维护,添加故障排除选项也很简单。您只需要修改一两个函数(例如,记录每条语句花费的时间,或者发出的语句中哪个语句给了您错误)。
如果您编写了大量代码,您可能希望通过编写转换脚本来自动执行转换任务。理想情况下,您的代码使用一致的编程风格约定。如果没有,那么无论如何你最好重写它,或者至少通过并手动调整它以使用一致的样式。
提高复制性能
随着连接到源的副本数量的增加,负载虽然很小,但也会增加,因为每个副本都使用到源的客户端连接。此外,由于每个副本都必须接收源二进制日志的完整副本,因此源上的网络负载也可能会增加并造成瓶颈。
如果您使用连接到一个源的大量副本,并且该源也忙于处理请求(例如,作为横向扩展解决方案的一部分),那么您可能希望提高复制过程的性能。
提高复制过程性能的一种方法是创建更深层次的复制结构,使源能够仅复制到一个副本,而其余副本则可以连接到该主副本以满足其各自的复制要求。

为此,您必须按如下方式配置 MySQL 实例:
- 源 1 是所有更改和更新都写入数据库的主要源。两个源服务器上都启用了二进制日志记录,这是默认设置。
- Source 2 是服务器 Source 1 的副本,它为复制结构中的其余副本提供复制功能。源 2 是唯一允许连接到源 1 的机器。源 2
--log-slave-updates启用了该 选项(这是默认设置)。使用此选项,来自 Source 1 的复制指令也会写入 Source 2 的二进制日志,以便将它们复制到真正的副本。 - 副本 1、副本 2 和副本 3 充当源 2 的副本,并复制来自源 2 的信息,这实际上包括源 1 上记录的升级。
故障切换期间切换源
您可以使用CHANGE REPLICATION SOURCE TO 语句(来自 MySQL 8.0.23)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)告诉副本更改为新源 。副本不会检查源上的数据库是否与副本上的数据库兼容;它只是从新源二进制日志中的指定坐标开始读取和执行事件。在故障转移情况下,组中的所有服务器通常都从同一个二进制日志文件中执行相同的事件,因此更改事件源不应影响数据库的结构或完整性,前提是您小心地制作改变。
使用复制的冗余,初始结构

在这个图中,MySQL Source保存源数据库,MySQL Replica主机是副本,Web Client机器发出数据库读取和写入。仅发出读取(并且通常连接到副本)的 Web 客户端不会显示,因为它们在发生故障时不需要切换到新服务器。
每个 MySQL 副本(Replica 1、Replica 2和Replica 3)都是一个运行的副本,启用二进制日志记录,并使用 --log-slave-updates=OFF. 因为当--log-slave-updates=OFF指定时,副本从源接收的更新不会记录在二进制日志 中,所以每个副本上的二进制日志最初都是空的。如果由于某种原因MySQL Source变得不可用,您可以选择其中一个副本作为新源。例如,如果您选择Replica 1,则所有内容 Web Clients都应重定向到 Replica 1,它将更新写入其二进制日志。Replica 2然后Replica 3应该从Replica 1.
运行副本的原因 --log-slave-updates=OFF是为了防止副本接收两次更新,以防您导致其中一个副本成为新源。如果Replica 1已--log-slave-updates 启用,这是默认设置,它会将收到的任何更新写入Source自己的二进制日志中。这意味着,当Replica 2从改变 Source到Replica 1作为其源,它可以接收更新Replica 1 ,它已经从收到的Source。
一旦新的复制设置就位,您需要告诉每个 Web Client人将其语句定向到 Replica 1. 从那时起,Web Clientto 发送的所有更新语句Replica 1都被写入 的二进制日志 Replica 1,然后包含Replica 1自 Source停止后发送到的每个更新语句。
源故障后使用复制的冗余

当Source再次可用时,您应该使它成为Replica 1. 为此,请发出Source相同的 CHANGE REPLICATION SOURCE TO| CHANGE MASTER TO发表于Replica 2及Replica 3之前发表的声明。Source然后成为它在离线时丢失Replica 1的Web Client写入的副本并获取 它们。
要Source再次创建源,请使用前面的过程,就好像Replica 1不可用并且Source要成为新源一样。在此过程中,不要忘了运行 RESET MASTER在Replica 1制作之前Replica 1, Replica 2和Replica 3 副本Source。如果您不这样做,副本可能会从Web Client应用程序中获取在Source变得不可用之前的过时写入。
您应该意识到副本之间没有同步,即使它们共享相同的源,因此某些副本可能远远领先于其他副本。这意味着在某些情况下,前面示例中概述的过程可能无法按预期工作。然而,在实践中,所有副本上的中继日志应该相对靠近。
使用异步连接故障切换切换源
从 MySQL 8.0.22 开始,您可以使用异步连接故障转移机制在从副本到其源的现有连接失败后自动建立到新源的异步(源到副本)复制连接。异步连接故障转移机制可用于使副本与共享数据的多个 MySQL 服务器或服务器组保持同步。潜在源服务器的列表存储在副本上,如果连接失败,则会根据您设置的加权优先级从列表中选择新的源。
1 | 重要的 |
使用异步连接故障转移机制的要求如下:
- GTID 必须在源和副本 (
gtid_mode=ON) 上使用,并且SOURCE_AUTO_POSITION|MASTER_AUTO_POSITION的选项CHANGE REPLICATION SOURCE TO|CHANGE MASTER TO必须在副本上启用语句,以便 GTID 自动定位用于连接到源。 - 通道的源列表中的所有源服务器上必须存在相同的复制用户帐户和密码。此帐户用于连接到每个源。您可以为不同的频道设置不同的帐户。
- 必须授予复制用户帐户
SELECT对性能架构表的权限,例如,通过发出GRANT SELECT ON performance_schema.* TO '*repl_user*'; - 不能在用于启动复制的语句中指定复制用户帐户和密码,因为它们需要在自动重新启动以连接到备用源时可用。必须使用
CHANGE REPLICATION SOURCE TO|为通道设置它们。CHANGE MASTER TO副本上的语句,并记录在复制元数据存储库中。
8.4 半同步复制
除了内置的异步复制,MySQL 8.0 还支持由插件实现的半同步复制接口。本节讨论什么是半同步复制及其工作原理。以下部分介绍了半同步复制的管理界面以及如何安装、配置和监控它。
MySQL 复制默认是异步的。源将事件写入其二进制日志,副本在准备好时请求它们。源不知道副本是否或何时检索并处理了事务,并且不能保证任何事件都到达任何副本。对于异步复制,如果源崩溃,它提交的事务可能不会传输到任何副本。在这种情况下,从源到副本的故障转移可能会导致故障转移到缺少与源相关的事务的服务器。
对于完全同步复制,当源提交事务时,所有副本在源返回执行事务的会话之前也已提交事务。完全同步复制意味着可以随时从源故障转移到任何副本。完全同步复制的缺点是完成事务可能会有很多延迟。
半同步复制介于异步复制和完全同步复制之间。源等待至少一个副本接收并记录事件(所需的副本数量是可配置的),然后提交事务。源不会等待所有副本都确认收到,它只需要来自副本的确认,而不是事件已在副本端完全执行和提交。因此,半同步复制保证如果源崩溃,它提交的所有事务都已传输到至少一个副本。
与异步复制相比,半同步复制提供了改进的数据完整性,因为当提交成功返回时,知道数据至少存在于两个地方。在半同步源从所需数量的副本收到确认之前,事务将被搁置且未提交。
与完全同步复制相比,半同步复制速度更快,因为它可以配置为平衡您对数据完整性的要求(确认收到事务的副本数量)和提交速度,由于需要等待而速度较慢复制品。
1 | 重要的 |
与异步复制相比,半同步复制的性能影响是增加数据完整性的权衡。减速量至少是将提交发送到副本并等待副本确认接收的 TCP/IP 往返时间。这意味着半同步复制最适合通过快速网络进行通信的近距离服务器,而对于通过慢速网络进行通信的远程服务器则最差。半同步复制还通过限制二进制日志事件从源发送到副本的速度来限制繁忙会话的速率。当一个用户太忙时,这会减慢速度,这在某些部署情况下很有用。
8.5 延迟复制
MySQL 支持延迟复制,以便副本服务器故意比源服务器晚至少指定的时间执行事务。
在 MySQL 8.0 中,延迟复制的方法取决于两个时间戳, immediate_commit_timestamp以及 original_commit_timestamp。如果复制拓扑中的所有服务器都运行 MySQL 8.0 或更高版本,则使用这些时间戳测量延迟复制。如果直接源或副本没有使用这些时间戳,则使用 MySQL 5.7 的延迟复制实现。本节描述所有使用这些时间戳的服务器之间的延迟复制。
8.6 组复制
本章介绍 MySQL 组复制以及如何安装、配置和监控组。MySQL 组复制使您能够创建弹性、高可用、容错的复制拓扑。
组可以在具有自动主选举的单主模式下运行,其中一次只有一台服务器接受更新。或者,可以以多主模式部署组,其中所有服务器都可以接受更新,即使它们是并发发布的。
有一个内置的组成员资格服务,可以在任何给定时间点保持组视图一致并可供所有服务器使用。服务器可以离开和加入组,视图会相应更新。有时服务器可能会意外离开组,在这种情况下,故障检测机制会检测到这一点并通知组视图已更改。这都是自动的。
Group Replication 保证数据库服务持续可用。但是,重要的是要了解,如果组成员之一变得不可用,则必须使用连接器、负载平衡器、路由器或某种形式的中间件。Group Replication 没有内置的方法来执行此操作。
组复制作为 MySQL 服务器的插件提供。您可以按照本章中的说明在组中所需的每个服务器实例上配置插件、启动组以及监视和管理组。部署一组 MySQL 服务器实例的另一种方法是使用 InnoDB Cluster。
1 | 小费 |
本章结构如下:
- 第 18.1 节,“组复制背景”介绍了组以及组复制的工作原理。
- 第 18.2 节,“入门”解释了如何配置多个 MySQL 服务器实例来创建一个组。
- 第 18.3 节“要求和限制” 解释了组复制的架构和设置要求和限制。
- 第 18.4 节,“监控组复制”解释了如何监控组。
- 第 18.5 节,“组复制操作”解释了如何使用组。
- 第 18.6 节,“组复制安全性”解释了如何保护组。
- 第 18.7 节,“组复制性能”解释了如何微调组的性能。
- 第 18.8 节,“升级组复制”解释了如何升级组。
- 第 18.9 节,“组复制系统变量”是特定于组复制的系统变量的参考。
- 第 18.10 节“常见问题” 提供了有关部署和操作 Group Replication 的一些技术问题的答案。
1. 组复制背景
本节提供有关 MySQL 组复制的背景信息。
创建容错系统的最常见方法是使组件变得冗余,换句话说,组件可以被移除并且系统应该继续按预期运行。这带来了一系列挑战,将此类系统的复杂性提升到了一个完全不同的水平。具体来说,复制数据库必须处理这样一个事实,即它们需要维护和管理多台服务器,而不仅仅是一台服务器。此外,由于服务器正在合作创建组,因此必须处理其他几个经典的分布式系统问题,例如网络分区或脑裂场景。
因此,最终的挑战是将数据库和数据复制的逻辑与多个服务器以一致且简单的方式协调的逻辑融合。换句话说,让多个服务器就系统状态和系统经历的每一次更改的数据达成一致。这可以概括为让服务器就每个数据库状态转换达成一致,以便它们都作为一个单独的数据库进行处理,或者最终收敛到相同的状态。这意味着它们需要作为(分布式)状态机运行。
MySQL Group Replication 提供分布式状态机复制,服务器之间具有很强的协调性。当服务器属于同一组时,它们会自动协调自己。该组可以在具有自动主选举的单主模式下运行,其中一次只有一台服务器接受更新。或者,对于更高级的用户,可以以多主模式部署该组,其中所有服务器都可以接受更新,即使它们是并发发布的。这种能力的代价是应用程序必须解决此类部署所施加的限制。
有一个内置的组成员资格服务,可以在任何给定时间点保持组视图一致并可供所有服务器使用。服务器可以离开和加入组,视图会相应更新。有时服务器可能会意外离开组,在这种情况下,故障检测机制会检测到这一点并通知组视图已更改。这都是自动的。
对于要提交的事务,组中的大多数人必须就全局事务序列中给定事务的顺序达成一致。决定提交或中止事务由每个服务器单独完成,但所有服务器做出相同的决定。如果存在网络分区,导致成员无法达成一致的分裂,那么在解决此问题之前系统不会进行。因此,还有一个内置的、自动的、裂脑保护机制。
所有这些都由提供的群组通信系统 (GCS) 协议提供支持。这些提供了故障检测机制、组成员服务以及安全且完全有序的消息传递。所有这些属性都是创建一个系统的关键,该系统可确保在服务器组中一致地复制数据。这项技术的核心是 Paxos 算法的实现。它充当组通信引擎。
源到副本复制
传统的 MySQL复制 提供了一种简单的源到副本复制方法。源是主要的,并且有一个或多个副本,它们是次要的。源应用事务,提交它们,然后它们稍后(因此异步)发送到副本以重新执行(在基于语句的复制中)或应用(在基于行的复制中)。它是一个无共享系统,默认情况下所有服务器都有数据的完整副本。
MySQL 异步复制

还有半同步复制,它为协议增加了一个同步步骤。这意味着主节点在申请时等待辅助节点确认它已收到交易。只有这样,主服务器才会恢复提交操作。
MySQL 半同步复制

在这两张图中,有一个经典的异步 MySQL 复制协议(以及它的半同步变体)的图表。不同实例之间的箭头表示服务器之间交换的消息或服务器与客户端应用程序之间交换的消息。
组复制
组复制是一种可用于实现容错系统的技术。复制组是一组服务器,每个服务器都有自己的完整数据副本(无共享复制方案),并通过消息传递相互交互。通信层提供了一组保证,例如原子消息和全序消息传递。这些是非常强大的属性,可以转化为非常有用的抽象,人们可以借助这些抽象来构建更高级的数据库复制解决方案。
MySQL Group Replication 建立在这些属性和抽象之上,并实现了多源更新无处不在的复制协议。复制组由多个服务器组成,组中的每个服务器可以随时独立执行事务。但是,所有读写事务只有在获得组批准后才会提交。换句话说,对于任何读写事务,组都需要决定是否提交,因此提交操作不是来自原始服务器的单方面决定。只读事务不需要组内协调并立即提交。
当读写事务准备在原始服务器上提交时,服务器会自动广播写入值(已更改的行)和相应的写入集(已更新行的唯一标识符)。因为交易是通过原子广播发送的,组中的所有服务器要么接收交易,要么都不接收。如果他们收到它,那么他们都以与之前发送的其他交易相同的顺序接收它。因此,所有服务器都以相同的顺序接收同一组交易,并为交易建立全局总顺序。
但是,在不同服务器上并发执行的事务之间可能存在冲突。在称为认证的过程中,通过检查和比较两个不同并发事务的写集来检测此类冲突 . 在认证过程中,冲突检测在行级别进行:如果在不同服务器上执行的两个并发事务更新同一行,则存在冲突。冲突解决过程指出,首先排序的事务在所有服务器上提交,第二个排序的事务中止,因此在原始服务器上回滚并被组中的其他服务器删除。例如,如果 t1 和 t2 在不同站点并发执行,都更改同一行,并且 t2 在 t1 之前排序,则 t2 赢得冲突,t1 回滚。这实际上是分布式首次提交获胜规则。请注意,如果两个事务必然经常发生冲突,
为了应用和外部化经过认证的事务,如果不破坏一致性和有效性,组复制允许服务器偏离约定的事务顺序。Group Replication 是一个最终一致性系统,这意味着一旦传入流量减慢或停止,所有组成员都具有相同的数据内容。当流量在流动时,交易可以以稍微不同的顺序外部化,或者在其他成员之前在某些成员上外部化。例如,在多主模式下,本地事务可能会在认证后立即外部化,尽管尚未应用全局顺序中较早的远程事务。当认证过程确定交易之间没有冲突时,这是允许的。在单主模式下,在主服务器上,并发、无冲突的本地事务有可能以与 Group Replication 同意的全局顺序不同的顺序提交和外部化。在不接受来自客户端的写入的辅助节点上,事务始终以约定的顺序提交和外部化。非冲突的本地事务可能以与 Group Replication 同意的全局顺序不同的顺序提交和外部化。在不接受来自客户端的写入的辅助节点上,事务始终以约定的顺序提交和外部化。非冲突的本地事务可能以与 Group Replication 同意的全局顺序不同的顺序提交和外部化。在不接受来自客户端的写入的辅助节点上,事务始终以约定的顺序提交和外部化。
下图描述了 MySQL Group Replication 协议,通过将其与 MySQL Replication(甚至 MySQL 半同步复制)进行比较,您可以看到一些差异。为了清楚起见,这张图片中缺少一些潜在的共识和 Paxos 相关信息。
MySQL 组复制协议

2. 组复制用例
组复制使您能够通过将系统状态复制到一组服务器来创建具有冗余的容错系统。即使一些服务器随后出现故障,只要不是全部或大多数,系统仍然可用。根据发生故障的服务器数量,该组可能会降低性能或可扩展性,但它仍然可用。服务器故障是孤立的和独立的。它们由依赖于分布式故障检测器的组成员服务进行跟踪,该检测器能够在任何服务器自愿或由于意外停止而离开组时发出信号。有一个分布式恢复过程,以确保当服务器加入组时,它们会自动更新。无需服务器故障转移,多源更新无处不在,确保在单个服务器发生故障时,即使更新也不会被阻止。总而言之,MySQL Group Replication 保证数据库服务持续可用。
尽管数据库服务可用,但在服务器意外退出的情况下,连接到它的客户端必须重定向或故障转移到不同的服务器,了解这一点很重要。这不是 Group Replication 试图解决的问题。连接器、负载平衡器、路由器或某种形式的中间件更适合处理此问题。
总而言之,MySQL Group Replication 提供了一个高可用、高弹性、可靠的 MySQL 服务。
示例用例
以下示例是组复制的典型用例。
- 弹性复制- 需要非常流畅的复制基础架构的环境,其中服务器的数量必须动态增加或减少,并且副作用尽可能少。例如,用于云的数据库服务。
- 高可用分片- 分片是实现写入横向扩展的流行方法。使用 MySQL Group Replication 实现高可用分片,其中每个分片映射到一个复制组。
- 替代异步源副本复制- 在某些情况下,使用单个源服务器使其成为单点争用。在某些情况下,写入整个组可能会证明更具可扩展性。
- 自治系统- 此外,您可以纯粹为复制协议中内置的自动化部署 MySQL 组复制(已在本章和前几章中进行了描述)。
3. 多主模式和单主模式
单主模式
在单主模式 ( group_replication_single_primary_mode=ON) 中,该组有一个设置为读写模式的主服务器。组中的所有其他成员都设置为只读模式(带有super_read_only=ON)。主服务器通常是引导组的第一台服务器。加入该组的所有其他服务器了解主服务器并自动设置为只读模式。
在单主模式下,组复制强制只有单个服务器写入组,因此与多主模式相比,一致性检查可以不那么严格,并且不需要额外小心处理 DDL 语句。该选项 group_replication_enforce_update_everywhere_checks 启用或禁用组的严格一致性检查。以单主模式部署时,或将组更改为单主模式时,必须将此系统变量设置为 OFF。
指定为主服务器的成员可以通过以下方式更改:
- 如果现有的主节点离开组,无论是自愿还是意外,都会自动选出一个新的主节点。
- 您可以使用该
group_replication_set_as_primary()功能将特定成员指定为新的主要成员 。 - 如果使用该
group_replication_switch_to_single_primary_mode()函数将运行在多主模式下的组更改为单主模式运行,则会自动选举新的主,或者您可以通过函数指定新主。
仅当所有组成员都运行 MySQL 8.0.13 或更高版本时才能使用这些功能。当新的主服务器被自动选举或手动指定时,它会自动设置为可读写,其他组成员保持为从服务器,因此为只读。如图显示了这个过程。
新初选

在这种情况下,在新的主节点赶上旧的主节点之前,读写事务可能会导致冲突并被回滚,而只读事务可能会导致读取失效。Group Replication 的流量控制机制将快速成员和慢速成员之间的差异降至最低,如果它被激活并正确调整,则会减少发生这种情况的机会。
初选算法
成员在选举初选时考虑的因素依次如下:
考虑的第一个因素是运行最低 MySQL 服务器版本的成员。如果所有组成员都运行 MySQL 8.0.17 或更高版本,则成员首先按其发布的补丁版本进行排序。如果任何成员运行 MySQL Server 5.7 或 MySQL 8.0.16 或更低版本,则成员首先按其发行版的主要版本排序,并忽略补丁版本。
如果多个成员运行最低 MySQL 服务器版本,则考虑的第二个因素是每个成员的成员权重,由成员的
group_replication_member_weight系统变量指定 。如果该组的任何成员正在运行 MySQL Server 5.7,而此系统变量不可用,则忽略此因素。所述
group_replication_member_weight系统变量指定在0-100范围内的数。所有成员的权重默认为 50,因此将权重设置为低于此值以降低他们的排序,而设置高于此的权重以增加他们的排序。您可以使用此加权功能来优先使用更好的硬件或确保在主服务器的计划维护期间故障转移到特定成员。如果多个成员运行最低 MySQL 服务器版本,并且这些成员中有多个成员具有最高成员权重(或成员权重被忽略),则考虑的第三个因素是每个成员生成的服务器 UUID 的字典顺序,由
server_uuid系统变量指定 。具有最低服务器 UUID 的成员被选为主要成员。该因素充当有保证且可预测的决胜局,以便所有组成员在无法由任何重要因素决定的情况下做出相同的决定。
多主模式
在多主模式 ( group_replication_single_primary_mode=OFF) 中,没有成员具有特殊作用。任何与其他组成员兼容的成员在加入组时都设置为读写模式,并且可以处理写入事务,即使它们是并发发出的。
如果成员停止接受写入事务,例如,在服务器意外退出的情况下,连接到它的客户端可以重定向或故障转移到任何其他处于读写模式的成员。Group Replication 本身不处理客户端故障转移,因此您需要使用中间件框架(例如MySQL Router 8.0、代理、连接器或应用程序本身)来安排它。
客户端故障转移

组复制是一个最终一致性系统。这意味着一旦传入流量减慢或停止,所有组成员都具有相同的数据内容。当流量在流动时,事务可以先于其他成员在其他成员上被外部化,特别是如果某些成员的写入吞吐量比其他成员少,从而产生过时读取的可能性。在多主模式下,速度较慢的成员也会积累过多的交易积压以进行认证和申请,从而导致更大的冲突和认证失败风险。为了限制这些问题,您可以激活和调整 Group Replication 的流量控制机制,以尽量减少快速成员和慢速成员之间的差异。
从 MySQL 8.0.14 开始,如果你想对组中的每个事务都有事务一致性保证,你可以使用group_replication_consistency 系统变量来做到这一点 。您可以选择适合您组的工作负载和数据读取和写入优先级的设置,同时考虑到提高一致性所需的同步的性能影响。您还可以为各个会话设置系统变量,以保护特别对并发敏感的事务。
交易检查
当一个组以多主模式部署时,会检查事务以确保它们与该模式兼容。在多主模式下部署 Group Replication 时,会进行以下严格的一致性检查:
- 如果事务在 SERIALIZABLE 隔离级别下执行,则在与组同步时其提交将失败。
- 如果事务对具有级联约束的外键的表执行,则在与组同步时提交失败。
检查由group_replication_enforce_update_everywhere_checks 系统变量控制 。在多主模式下,系统变量通常应设置为ON,但可以通过将系统变量设置为 来选择性地停用检查OFF。以单主模式部署时,系统变量必须设置为 OFF。
4. 组复制服务
组成员资格
在 MySQL Group Replication 中,一组服务器形成一个复制组。一个组有一个名称,它采用 UUID 的形式。该组是动态的,服务器可以随时离开(自愿或非自愿)并加入。每当服务器加入或离开时,该组都会自行调整。
如果服务器加入该组,它会通过从现有服务器获取丢失的状态来自动使自己保持最新状态。如果一个服务器离开该组,例如它因维护而被关闭,剩余的服务器会注意到它已经离开并自动重新配置该组。
Group Replication 有一个组成员身份服务,用于定义哪些服务器在线并参与该组。在线服务器列表称为 视图。组中的每个服务器都具有一致的视图,即哪些服务器是在给定时间积极参与组的成员。
组成员不仅必须就事务提交达成一致,还必须就当前视图达成一致。如果现有成员同意新服务器应成为组的一部分,则组将重新配置以将该服务器集成到其中,从而触发视图更改。如果服务器离开该组,无论是否自愿,该组都会动态重新安排其配置并触发视图更改。
在成员自愿离开组的情况下,它首先启动动态组重新配置,在此期间所有成员必须在没有离开服务器的情况下就新视图达成一致。但是,如果成员非自愿离开组,例如因为它意外停止或网络连接断开,则它无法启动重新配置。在这种情况下,Group Replication 的故障检测机制会在很短的时间后识别出该成员已经离开,并建议重新配置没有故障成员的组。与自愿离开的成员一样,重新配置需要组中大多数服务器的同意。但是,如果小组无法达成一致,例如,因为它以这样一种方式分区,即没有大多数服务器在线,系统无法动态更改配置,并阻止以防止裂脑情况。这种情况需要管理员的干预。
成员可能会暂时离线,然后在故障检测机制检测到其故障之前,以及在重新配置组以删除成员之前再次尝试重新加入组。在这种情况下,重新加入的成员会忘记其先前的状态,但如果其他成员向其发送针对其崩溃前状态的消息,则可能会导致包括可能的数据不一致在内的问题。如果这种情况下的成员参与 XCom 的共识协议,则可能会导致 XCom 在失败前后做出不同的决定,从而为同一轮共识提供不同的价值。
为了应对这种可能性,从 MySQL 5.7.22 和 MySQL 8.0 开始,组复制会检查同一服务器的新化身尝试加入组而其旧化身(具有相同的地址和端口号)仍然存在的情况列为会员。新的化身被阻止加入该组,直到可以通过重新配置删除旧的化身。请注意,如果已添加等待期 group_replication_member_expel_timeout 系统变量以允许成员在被驱逐之前有更多时间重新连接到组,如果被怀疑的成员在怀疑超时之前重新连接到组,则它可以作为当前的化身再次活跃在组中。当成员超过驱逐超时并被驱逐出组时,或者当组复制在服务器上因STOP GROUP_REPLICATION语句或服务器故障而停止时,它必须作为新的化身重新加入。
容错
MySQL Group Replication 建立在 Paxos 分布式算法的实现之上,以提供服务器之间的分布式协调。因此,它需要大多数服务器处于活动状态才能达到法定人数并做出决定。这对系统在不影响自身及其整体功能的情况下可以容忍的故障数量有直接影响。f 那么容忍故障所需的服务器数 (n)是n = 2 x f + 1。
实际上,这意味着要容忍一个故障,该组中必须有三台服务器。因此,如果一台服务器出现故障,仍有两台服务器组成多数(三分之二)并允许系统继续自动做出决策并继续前进。但是,如果第二台服务器非自愿地发生故障 ,则该组(仅剩一台服务器)会阻塞,因为没有多数人可以做出决定。
以下是说明上述公式的小表格。
| 团体人数 | 多数 | 允许的即时故障 |
|---|---|---|
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 3 | 2 |
| 6 | 4 | 2 |
| 7 | 4 | 3 |
8.7 组复制插件架构
MySQL Group Replication 是一个 MySQL 插件,它建立在现有的 MySQL 复制基础架构上,利用了二进制日志、基于行的日志记录和全局事务标识符等特性。它与当前的 MySQL 框架集成,例如性能模式或插件和服务基础架构。下图展示了一个框图,描述了 MySQL Group Replication 的整体架构。
组复制插件框图

MySQL Group Replication 插件包括一组用于捕获、应用和生命周期的 API,它们控制插件如何与 MySQL 服务器交互。有接口可以使信息从服务器流向插件,反之亦然。这些接口将 MySQL Server 核心与 Group Replication 插件隔离开来,并且大多是放置在事务执行管道中的钩子。在一个方向上,从服务器到插件,有事件通知,例如服务器启动、服务器恢复、服务器准备接受连接以及服务器即将提交事务。在另一个方向,插件指示服务器执行诸如提交或中止正在进行的事务等操作,
Group Replication 插件架构的下一层是一组组件,当通知路由到它们时会做出反应。捕获组件负责跟踪与正在执行的事务相关的上下文。应用程序组件负责在数据库上执行远程事务。恢复组件管理分布式恢复,并负责通过选择捐赠者、管理追赶过程和对捐赠者失败做出反应来使加入组的服务器保持最新状态。
继续向下堆栈,复制协议模块包含复制协议的特定逻辑。它处理冲突检测,接收事务并将其传播到组。
Group Replication 插件架构的最后两层是 Group Communication System (GCS) API,以及基于 Paxos 的组通信引擎 (XCom) 的实现。GCS API 是一个高级 API,它抽象了构建复制状态机所需的属性(参见 第 18.1 节,“组复制背景”)。因此,它将消息传递层的实现与插件的其余上层分离。组通信引擎处理与复制组成员的通信。
8.8 部署
组中的每个 MySQL 服务器实例都可以运行在独立的物理主机上,这是部署 Group Replication 的推荐方式。本节介绍如何使用三个 MySQL 服务器实例创建复制组,每个实例都运行在不同的主机上。有关在同一主机上部署多个运行 Group Replication 的 MySQL 服务器实例的信息,请参阅 第 18.2.2 节,“在本地部署 Group Replication”,例如用于测试目的。
组架构

8.9 要求和限制
要用于组复制的服务器实例必须满足以下要求。
基础设施
InnoDB 存储引擎。 数据必须存储在
InnoDB事务存储引擎中。事务以乐观的方式执行,然后在提交时检查冲突。如果有冲突,为了保持整个组的一致性,一些事务会被回滚。这意味着需要一个事务存储引擎。此外,InnoDB提供了一些附加功能,可以在与 Group Replication 一起操作时更好地管理和处理冲突。使用其他存储引擎,包括临时MEMORY存储引擎,可能会导致组复制错误。您可以通过disabled_storage_engines在组成员上设置系统变量来阻止使用其他存储引擎 ,例如:1
disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY"
主键。 组要复制的每个表都必须有一个定义的主键,或等效的主键,其中等效键是非空的唯一键。这些键需要作为表中每一行的唯一标识符,使系统能够通过准确识别每个事务修改了哪些行来确定哪些事务发生冲突。Group Replication 有其自己的内置主键或主键等效项检查集,并且不使用由
sql_require_primary_key系统变量执行的检查 。你可以设置sql_require_primary_key=ON对于正在运行组复制的服务器实例,您可以设置|REQUIRE_TABLE_PRIMARY_KEY_CHECK选项。 用于组复制通道的语句。但是,请注意,您可能会发现某些在 Group Replication 的内置检查下允许的事务在您设置或 时执行的检查下不允许 。CHANGE REPLICATION SOURCE TOCHANGE MASTER TOON``sql_require_primary_key=ON``REQUIRE_TABLE_PRIMARY_KEY_CHECK=ON网络性能。 MySQL Group Replication 旨在部署在服务器实例彼此非常接近的集群环境中。组的性能和稳定性会受到网络延迟和网络带宽的影响。所有组成员之间必须始终保持双向通信。如果服务器实例的入站或出站通信被阻止(例如,防火墙或连接问题),则该成员无法在组中工作,并且组成员(包括有问题的成员)可能无法报告受影响服务器实例的正确成员状态。
组复制限制
组大小限制可以成为单个复制组成员的 MySQL 服务器的最大数量为 9。如果更多成员尝试加入该组,他们的请求将被拒绝。此限制已通过测试和基准测试确定为安全边界,其中该组在稳定的局域网上可靠运行。
交易规模限制如果单个事务导致消息内容大到无法在 5 秒窗口内通过网络在组成员之间复制消息,则成员可能会被怀疑失败,然后被驱逐,只是因为他们正忙于处理消息交易。由于内存分配问题,大型事务也可能导致系统变慢。
8.10 组复制操作
配置在线群组
您可以在 Group Replication 运行时使用一组依赖于组操作协调器的函数来配置在线组。这些功能由 8.0.13 及更高版本的 Group Replication 插件安装。
在配置整个组时,操作的分布式特性意味着它们与 Group Replication 插件的许多进程交互,因此您应该注意以下几点:
您可以在任何地方发出配置操作。 如果要使成员 A 成为新的主要成员,则无需调用成员 A 上的操作。所有操作都以协调方式在所有组成员上发送和执行。此外,操作的这种分布式执行具有不同的后果:如果调用成员死亡,任何已经运行的配置进程将继续在其他成员上运行。万一调用成员死亡,您仍然可以使用监控功能确保其他成员成功完成操作。
所有成员必须在线。 为了简化迁移或选举过程并保证它们尽可能快,组中不得包含任何当前处于分布式恢复过程中的成员,否则配置操作将被您发出语句的成员拒绝。
在配置更改期间,任何成员都不能加入组。 在协调配置更改期间尝试加入组的任何成员都会离开组并取消其加入过程。
一次只能配置一种。 正在执行配置更改的组不能接受任何其他组配置更改,因为并发配置操作可能会导致成员发散。
所有成员必须运行 MySQL 8.0.13 或更高版本。 由于配置操作的分布式特性,所有成员必须识别它们才能执行它们。因此,如果组中存在任何运行 MySQL Server 版本 8.0.12 或更低版本的服务器,则该操作将被拒绝。
交易一致性保证
理解事务一致性保证
在分布式一致性保证方面,无论是在正常还是故障修复操作中,Group Replication 一直是一个最终的一致性系统。这意味着一旦传入流量减慢或停止,所有组成员都具有相同的数据内容。与系统一致性相关的事件可以分为控制操作,可以是手动的,也可以是由故障自动触发的;和数据流操作。
对于 Group Replication,可以从一致性方面进行评估的控制操作有:
- 成员加入或离开,组复制的第 18.5.3 节“分布式恢复”和写保护涵盖了这一点 。
- 网络故障,由防护模式涵盖。
- 在单主组中,主故障转移,也可以是由
group_replication_set_as_primary().
分布式恢复
每当成员加入或重新加入复制组时,它必须赶上组成员在加入之前或离开时应用的事务。这个过程称为分布式恢复。
加入成员首先检查其group_replication_applier通道的中继日志,以 了解它已从组中收到但尚未应用的任何事务。如果加入成员之前在组中,它可能会在离开之前找到未应用的事务,在这种情况下,它会将这些作为第一步应用。组的新成员没有任何申请。
此后,加入成员连接到在线现有成员进行状态转移。加入成员将在加入之前或离开时发生在组中的所有交易转移,这些交易由现有成员(称为捐赠者)提供。接下来,加入成员应用在此状态转移正在进行时发生在组中的事务。当这个过程完成后,加入的成员已经赶上了组内剩余的服务器,开始正常加入组内。
在分布式恢复期间,组复制使用这些方法的组合进行状态转移:
- 使用克隆插件功能的远程克隆操作,可从 MySQL 8.0.17 获得。要启用这种状态转移方法,您必须在组成员和加入成员上安装克隆插件。Group Replication 会自动配置所需的克隆插件设置并管理远程克隆操作。
- 从捐赠者的二进制日志复制并在加入成员上应用交易。此方法使用
group_replication_recovery在捐赠者和加入成员之间建立的名为的标准异步复制通道 。
网络分区
每当需要复制的更改发生时,团队都需要达成共识。这是常规事务的情况,但对于组成员身份更改和一些保持组一致的内部消息传递也是必需的。共识要求大多数小组成员就给定的决定达成一致。当大多数组成员丢失时,该组无法前进并阻塞,因为它无法确保多数或法定人数。
当有多个非自愿故障时,仲裁可能会丢失,导致大多数服务器突然从组中删除。例如,在一组 5 台服务器中,如果其中 3 台同时处于静默状态,则大多数服务器都会受到损害,因此无法达到法定人数。事实上,剩下的两个无法判断其他 3 个服务器是否已崩溃,或者网络分区是否单独隔离了这 2 个,因此无法自动重新配置组。
另一方面,如果服务器自愿退出组,它们会指示组应该重新配置自己。实际上,这意味着正在离开的服务器会告诉其他人它正在离开。这意味着其他成员可以正确地重新配置组,保持成员资格的一致性并重新计算多数。比如上面5台服务器,3台同时离开的场景,如果3台离开的服务器一一提醒组员要离开,那么成员可以从5调整到2,同时时间,在发生这种情况时确保法定人数。
8.11 组复制安全
组复制 IP 地址权限
组复制插件允许您指定可以接受传入组通信系统连接的主机的许可名单。如果您在服务器 s1 上指定了许可名单,那么当服务器 s2 正在建立与 s1 的连接以进行组通信时,s1 在接受来自 s2 的连接之前首先检查许可名单。如果 s2 在许可名单中,则 s1 接受连接,否则 s1 拒绝 s2 的连接尝试。从 MySQL 8.0.22 开始,系统变量 group_replication_ip_allowlist用于指定许可名单,对于 MySQL 8.0.22 之前的版本,使用系统变量 group_replication_ip_whitelist。新系统变量的工作方式与旧系统变量相同,只是术语发生了变化。
对于主机名,名称解析仅在另一台服务器发出连接请求时发生。无法解析的主机名不会被考虑用于许可名单验证,并且会在错误日志中写入警告消息。对解析的主机名执行前向确认反向 DNS (FCrDNS) 验证。
1 | 警告 |
使用安全套接字层 (SSL) 保护组通信连接
安全套接字可用于组成员之间的组通信连接。Group Replication 系统变量 group_replication_ssl_mode用于激活对组通信连接使用 SSL 并指定连接的安全模式。默认设置意味着不使用 SSL。
保护分布式恢复连接
来自二进制日志的状态传输需要具有正确权限的复制用户,以便 Group Replication 可以建立直接的成员到成员复制通道。相同的复制用户用于所有组成员的分布式恢复。如果组成员已设置为支持使用远程克隆操作作为分布式恢复的一部分(可从 MySQL 8.0.17 获得),则此复制用户也用作捐赠者上的克隆用户,并且需要正确的权限也适合这个角色。
为了保护用户凭据,您可以要求 SSL 用于与用户帐户的连接,并且(从 MySQL 8.0.21 开始)您可以在启动组复制时提供用户凭据,而不是将它们存储在副本状态表中。此外,如果您使用缓存 SHA-2 身份验证,则必须在组成员上设置 RSA 密钥对。
8.12 组复制性能
微调群通讯线程
组通信线程 (GCT) 在加载组复制插件时循环运行。GCT 接收来自组和插件的消息,处理仲裁和故障检测相关任务,发送一些保持活动的消息,并处理来自/到服务器/组的传入和传出事务。GCT 等待队列中的传入消息。当没有消息时,GCT 等待。在某些情况下,通过在实际进入睡眠之前将此等待配置为更长一点(进行主动等待)可以证明是有益的。这是因为另一种选择是让操作系统从处理器中切换出 GCT 并进行上下文切换。
流量控制
组复制确保事务仅在组中的大多数成员收到它并就所有并发发送的事务之间的相对顺序达成一致后才提交。如果对组的写入总数不超过组中任何成员的写入容量,则此方法很有效。如果确实如此,并且某些成员的写入吞吐量比其他成员少,尤其是小于写入器成员,则这些成员可能会开始落后于写入器。
让一些成员落后于群体会带来一些问题后果,特别是对这些成员的读取可能会将非常旧的数据外部化。根据成员落后的原因,组中的其他成员可能必须保存或多或少的复制上下文才能满足来自慢速成员的潜在数据传输请求。
然而,复制协议中有一种机制可以避免在快速成员和慢速成员之间在应用的事务方面有太多距离。这被称为流量控制机制。它试图解决几个目标:
- 保持成员足够近,使成员之间的缓冲和不同步成为一个小问题;
- 快速适应不断变化的条件,例如不同的工作负载或组中的更多作者;
- 为每个成员公平分享可用的写入容量;
- 不减少吞吐量超过绝对必要的,以避免浪费资源。
消息压缩
对于在线组成员之间发送的消息,组复制默认启用消息压缩。是否压缩特定消息取决于您使用group_replication_compression_threshold 系统变量配置的阈值 。压缩负载大于指定字节数的消息。
组复制使用 LZ4 压缩算法来压缩组中发送的消息。请注意,LZ4 压缩算法支持的最大输入大小为 2113929216 字节。

当网络带宽成为瓶颈时,消息压缩可以在组通信级别提供高达 30-40% 的吞吐量改进。这在负载大量服务器的环境中尤为重要。组内N 个参与者之间互连的 TCP 对等性质 使发送方发送N次相同数量的数据。此外,二进制日志可能表现出高压缩率。这使得压缩成为包含大型事务的组复制工作负载的引人注目的功能。
消息分片
当 Group Replication 组成员之间发送异常大的消息时,可能会导致某些组成员被报告为失败并被驱逐出组。这是因为 Group Replication 的组通信引擎(XCom,Paxos 变体)使用的单线程处理消息的时间过长,因此某些组成员可能会报告接收方失败。从 MySQL 8.0.16 开始,默认情况下,大消息会自动拆分为单独发送并由收件人重新组合的片段。
当消息的所有片段都已被所有组成员接收并重新组合时,片段消息的消息传递被视为完成。分段消息在其标头中包含信息,这些信息使成员在消息传输期间加入以恢复在其加入之前发送的较早分段。如果加入成员未能恢复碎片,它会将自己从组中驱逐。
XCom 缓存管理
组复制(XCom,Paxos 变体)的组通信引擎包括一个缓存,用于在组成员之间交换的消息(及其元数据)作为共识协议的一部分。在其他功能中,消息缓存用于恢复在一段时间后无法与其他组成员通信后重新连接到组的成员丢失的消息。
增加缓存大小
如果某个成员缺席的时间不足以将其从组中驱逐,则它可以通过从另一个成员的 XCom 消息缓存中检索丢失的事务来重新连接并重新开始参与该组。但是,如果成员不在期间发生的事务已从其他成员的 XCom 消息缓存中删除,因为达到了最大大小限制,则该成员无法以这种方式重新连接。
当当前无法访问的成员可能需要恢复的消息从消息缓存中删除时,组复制的组通信系统 (GCS) 会通过警告消息提醒您。此警告消息记录在所有活动组成员上(每个无法访问的成员仅记录一次)。尽管组成员无法确定无法访问的成员看到的最后一条消息是哪条消息,但警告消息表明缓存大小可能不足以支持您选择的成员被驱逐之前的等待时间。
减少缓存大小
XCom 消息缓存大小的最小设置为 1 GB,最高为 MySQL 8.0.20。从 MySQL 8.0.21 开始,最小设置为 134217728 字节 (128 MB),这允许在可用内存量受限的主机上进行部署。group_replication_message_cache_size 如果主机位于不稳定的网络上,则不建议使用非常低的 设置,因为较小的消息缓存会使组成员在暂时失去连接后更难重新连接。
如果重新连接的成员无法从 XCom 消息缓存中检索到它需要的所有消息,则该成员必须离开该组并重新加入该组,以便使用分布式恢复从另一个成员的二进制日志中检索丢失的事务。从 MySQL 8.0.21 开始,已离开组的成员默认进行 3 次自动重新加入尝试,因此重新加入组的过程仍然可以在没有操作员干预的情况下进行。但是,与从 XCom 消息缓存中检索消息相比,使用分布式恢复重新加入的过程要长得多且复杂得多,因此成员需要更长的时间才能变得可用,并且可能会影响组的性能。在稳定的网络上,
对故障检测和网络分区的响应
Group Replication 的故障检测机制旨在识别不再与该组通信的组成员,并在他们似乎失败时将其驱逐。拥有故障检测机制增加了该组包含大多数正常工作成员的机会,因此来自客户端的请求得到正确处理。
通常,所有组成员定期与所有其他组成员交换消息。如果某个群组成员在 5 秒内没有收到来自某个特定成员的任何消息,那么当此检测期结束时,就会产生对该成员的怀疑。当怀疑超时时,被怀疑的成员被认为失败,并被逐出组。被驱逐的成员从其他成员看到的成员列表中删除,但不知道它已被驱逐出组,因此认为自己在线,其他成员无法访问。如果该成员实际上并未失败(例如,由于临时网络问题刚刚断开连接)并且能够恢复与其他成员的通信,
组成员(包括失败成员本身)对这些情况的响应可以在过程中的多个点进行配置。默认情况下,如果怀疑成员失败,会发生以下行为:
- 在 MySQL 8.0.20 之前,当创建一个怀疑时,它会立即超时。疑似成员一旦被群组认定为过期嫌疑人,将立即承担开除责任。该成员可能会在超时后继续存活几秒钟,因为会定期执行对过期怀疑的检查。从 MySQL 8.0.21 开始,在怀疑超时之前增加了 5 秒的等待时间,怀疑成员将被驱逐。
- 如果被驱逐的成员恢复通信并意识到它被驱逐,直到 MySQL 8.0.20,它不会尝试重新加入组。从 MySQL 8.0.21 开始,它会自动尝试 3 次重新加入组(每次尝试之间间隔 5 分钟),如果此自动重新加入过程不起作用,它将停止尝试重新加入组。
- 当被驱逐的成员不想重新加入组时,它会切换到超级只读模式并等待操作员注意。(例外是在从 MySQL 8.0.12 到 8.0.15 的版本中,默认情况下成员会自行关闭。从 MySQL 8.0.16 开始,该行为已更改为与 MySQL 5.7 中的行为相匹配。)
您可以使用本节中描述的组复制配置选项永久或临时更改这些行为,以满足您的系统要求和优先级。如果您遇到由较慢的网络或机器、意外瞬时中断率很高的网络或计划中的网络中断引起的不必要的驱逐,请考虑增加驱逐超时和自动重新加入尝试。从 MySQL 8.0.21 开始,默认设置已朝着这个方向更改,以减少在这些情况下需要操作员干预以恢复被驱逐成员的频率。请注意,虽然成员正在经历上述任何默认行为,但它不接受写入,如果成员仍在与客户通信,则仍然可以进行读取,随着时间的推移,过时读取的可能性会增加。如果避免过时读取对您来说比避免操作员干预更重要,请考虑减少驱逐超时和自动重新加入尝试或将它们设置为零。
由于网络分区,没有失败的成员可能会失去与复制组的一部分(但不是全部)的联系。例如,在一组 5 个服务器 (S1,S2,S3,S4,S5) 中,如果 (S1,S2) 和 (S3,S4,S5) 之间存在断开连接,则存在网络分区。第一组 (S1,S2) 现在属于少数,因为它无法联系超过一半的组。由少数群体中的成员处理的任何事务都会被阻止,因为该群体的大多数人无法访问,因此该群体无法达到法定人数。有关此方案的详细说明,请参阅 第 18.5.4 节,“网络分区”. 在这种情况下,默认行为是少数和多数成员都留在组中,继续接受交易(尽管他们在少数成员上被阻止),并等待操作员干预。此行为也是可配置的。
驱逐超时
在 Group Replication 组成员怀疑另一个成员(或其自身)之前,有一个最初的 5 秒检测期。当另一个成员对它的怀疑(或它自己对自己的怀疑)超时时,一个小组成员就会被开除。在那之后,在驱逐机构检测并实施驱逐之前可能会流逝更短的时间。 group_replication_member_expel_timeout 指定组成员在创建怀疑和驱逐可疑成员之间等待的时间段(以秒为单位),称为驱逐超时。可疑成员UNREACHABLE在此等待期间被列为,但不会从组的成员列表中删除。
- 如果可疑成员在等待期结束时的怀疑超时之前再次变为活动状态,则该成员将在 XCom 的消息缓存中应用由其余组成员缓冲的所有消息并进入
ONLINE状态,无需操作员干预。在这种情况下,该成员被组视为同一个化身。 - 如果可疑成员仅在怀疑超时后才变为活动状态并且能够恢复通信,则它会收到被驱逐的视图,并在那时意识到它已被驱逐。您可以使用
group_replication_autorejoin_triesMySQL 8.0.16 中提供的 系统变量,使成员此时自动尝试重新加入组。从 MySQL 8.0.21 开始,默认情况下会激活此功能,并且成员会进行 3 次自动重新加入尝试。如果自动重新加入过程未成功或未尝试,则被驱逐的成员将遵循由 指定的退出操作group_replication_exit_state_action。
不可达多数超时
默认情况下,由于网络分区而成为少数成员的成员不会自动离开该组。您可以使用系统变量 group_replication_unreachable_majority_timeout 设置成员在与大多数组成员失去联系后等待的秒数,然后退出组。设置超时意味着您不需要主动监视网络分区后属于少数群体的服务器,并且可以避免由于以下原因造成裂脑情况(具有两个版本的组成员资格)的可能性不适当的干预。
在决定是否设置不可到达的多数超时时,请考虑以下几点:
- 在对称组中,例如有两台或四台服务器的组,如果两个分区包含相同数量的服务器,则两个组都认为自己是少数并进入
ERROR状态。在这种情况下,该组没有功能分区。 - 当少数群体存在时,少数群体处理的任何事务都会被接受,但由于少数服务器无法达到法定人数而被阻止,直到
STOP GROUP_REPLICATION在这些服务器上发出或达到无法到达的多数超时为止 。 - 如果不设置不可到达的多数超时,则少数组中的服务器永远不会
ERROR自动进入 状态,您必须手动停止它们。 - 如果在检测到多数丢失后在少数组中的服务器上设置了无法到达的多数超时,则设置该超时无效。
自动重新加入
当自动重新加入未激活时,成员在恢复通信后立即接受其驱逐,并继续执行group_replication_exit_state_action 系统变量指定的操作 。在此之后,需要手动干预将成员带回组。如果您可以容忍过时读取的可能性并希望最大限度地减少手动干预的需要,则使用自动重新加入功能是合适的,尤其是在临时网络问题经常导致成员被驱逐的情况下。
使用自动重新加入,当达到成员的驱逐或无法达到的多数超时时,它会尝试重新加入(使用当前插件选项值),然后继续进行进一步的自动重新加入尝试,直到指定的尝试次数。自动重新加入尝试失败后,成员将在下一次尝试前等待 5 分钟。自动重新加入尝试和它们之间的时间称为自动重新加入过程。如果指定的尝试次数在成员重新加入或停止的情况下已用完,则成员将继续执行group_replication_exit_state_action 系统变量指定的操作 。
退出动作
MySQL 8.0.12 和 MySQL 5.7.24 中提供 的 系统变量指定了当成员由于错误或问题而无意中离开组并且无法自动重新加入或不尝试时,组复制会执行什么操作。请注意,在被驱逐的成员的情况下,该成员直到重新连接到该组才知道它已被驱逐,因此仅当该成员设法重新连接,或者该成员对自己提出怀疑并且驱逐自己。
按影响顺序,退出动作如下:
- 如果
READ_ONLY是退出动作实例MySQL的切换通过设置系统变量超只读模式super_read_only来ON。当成员处于超级只读模式时,客户端无法进行任何更新,即使他们拥有CONNECTION_ADMIN特权(或已弃用的SUPER特权)。但是,客户端仍然可以读取数据,并且由于不再进行更新,因此过时读取的可能性会随着时间的推移而增加。因此,使用此设置,您需要主动监控服务器的故障。此退出操作是 MySQL 8.0.15 的默认设置。执行此退出操作后,成员的状态将显示ERROR在组视图中。 - 如果
OFFLINE_MODE是退出动作实例通过设置系统变量切换MySQL来离线模式offline_mode来ON。当成员处于离线模式时,已连接的客户端用户将在其下一个请求时断开连接,并且不再接受连接,具有CONNECTION_ADMIN特权(或已弃用SUPER特权)的客户端用户除外 。组复制还将系统变量设置super_read_only为ON,因此客户端无法进行任何更新,即使它们已与CONNECTION_ADMIN或SUPER特权。此退出操作可防止更新和过时读取(具有规定权限的客户端用户读取除外),并使 MySQL Router 等代理工具能够识别服务器不可用并重定向客户端连接。它还使实例保持运行,以便管理员可以在不关闭 MySQL 的情况下尝试解决问题。此退出操作可从 MySQL 8.0.18 获得。执行此退出操作后,成员的状态将显示ERROR在组视图中(不是OFFLINE,这意味着成员具有可用的组复制功能但当前不属于组)。 - 如果
ABORT_SERVER是退出操作,则实例关闭 MySQL。指示成员关闭自己可以防止所有过时的读取和客户端更新,但这意味着 MySQL 服务器实例不可用并且必须重新启动,即使问题本可以在没有该步骤的情况下解决。此退出操作是 MySQL 8.0.12(添加系统变量时)到 MySQL 8.0.15(含)的默认设置。执行此退出操作后,该成员将从组视图中的服务器列表中删除。
请记住,无论设置退出操作,都需要操作员干预,因为已用尽其自动重新加入尝试(或从未尝试过)并已被逐出组的前成员在没有重新启动组的情况下不允许重新加入复制。退出操作仅影响客户端是否仍然可以读取无法重新加入组的服务器上的数据,以及服务器是否保持运行。
8.13 升级组复制
将不同的成员版本合并为一个组
Group Replication 根据 Group Replication 插件捆绑的 MySQL Server 版本进行版本控制。例如,如果一个成员正在运行 MySQL 5.7.26,那么它就是 Group Replication 插件的版本。要在组成员问题上检查 MySQL 服务器的版本:
为获得最佳兼容性和性能,组的所有成员应运行相同版本的 MySQL Server,因此也应运行相同版本的组复制。但是,在升级在线组的过程中,为了最大限度地提高可用性,您可能需要同时运行具有不同 MySQL 服务器版本的成员。根据 MySQL 版本之间所做的更改,您可能会在这种情况下遇到不兼容问题。例如,如果某个功能在主要版本之间已被弃用,则将这些版本合并到一个组中可能会导致依赖于已弃用功能的成员失败。反过来,
为了防止这些问题,组复制包括兼容性策略,使您能够安全地将运行不同版本 MySQL 的成员组合到同一组中。成员应用这些策略来决定是正常加入组,还是以只读模式加入,或不加入组,这取决于哪种选择会导致加入成员和组现有成员的安全操作。在升级方案中,每台服务器都必须离开组、升级并使用其新服务器版本重新加入组。此时,该成员为其新服务器版本应用策略,该版本可能与它最初加入组时应用的策略有所不同。
作为管理员,您可以通过适当配置服务器并发出START GROUP_REPLICATION 声明来指示任何服务器尝试加入任何组。加入或不加入组,或以只读模式加入组的决定,由加入成员在您尝试将其添加到组后自己做出和实施。加入成员接收有关当前组成员的 MySQL Server 版本的信息,评估自己与这些成员的兼容性,并应用在其自己的 MySQL Server 版本中使用的策略(而不是现有成员使用的策略)来决定它是否是兼容的。
组复制离线升级
要对 Group Replication 组执行离线升级,请从组中删除每个成员,执行成员升级,然后照常重新启动组。在多主组中,您可以按任意顺序关闭成员。在单个主要组中,首先关闭每个辅助,然后最后关闭主要。有关如何从组中删除成员并关闭 MySQL ,请参阅 第 18.8.3.2 节,“升级组复制成员”。
组下线后,升级所有成员。有关如何执行升级,请参阅 第 2.11 节,“升级 MySQL”。升级完所有成员后,重新启动成员。
如果在离线时升级复制组的所有成员,然后重新启动该组,则成员使用新版本的 Group Replication 通信协议版本加入,从而成为该组的通信协议版本。如果您需要允许早期版本的成员加入,您可以使用该 group_replication_set_communication_protocol() 功能降级通信协议版本,指定具有最旧安装服务器版本的潜在组成员的 MySQL 服务器版本。
组复制在线升级
本节说明升级组成员所需的步骤。此过程是第 18.8.3.3 节,“组复制在线升级方法”中描述的方法的一部分 。升级组成员的过程对所有方法都是通用的,因此首先进行说明。您加入升级成员的方式可能取决于您采用的方法,以及其他因素,例如该组是在单主模式还是多主模式下运行。您如何使用就地或供应方法升级服务器实例不会影响此处描述的方法。
升级成员的过程包括将其从组中移除,按照您选择的成员升级方法,然后将升级后的成员重新加入组。建议在单主组中升级成员的顺序是升级所有辅助节点,然后最后升级主节点。如果主节点在次节点之前升级,则选择使用旧 MySQL 版本的新主节点,但不需要此步骤。
8.14 常见问题
本节提供常见问题的解答。
一个组中 MySQL 服务器的最大数量是多少? 一个组最多可以包含 9 个服务器。尝试将另一台服务器添加到具有 9 个成员的组会导致加入请求被拒绝。此限制已通过测试和基准测试确定为安全边界,其中该组在稳定的局域网上可靠运行。
组中的服务器如何连接? 组中的服务器通过打开对等 TCP 连接来连接到组中的其他服务器。这些连接仅用于组内服务器之间的内部通信和消息传递。该地址由group_replication_local_address 变量配置 。
group_replication_bootstrap_group 选项有什么用? 引导标志指示成员 创建一个组并充当初始种子服务器。加入组的第二个成员需要要求引导组的成员动态更改配置,以便将其添加到组中。
成员需要在两种情况下引导组。最初创建组时,或关闭并重新启动整个组时。
如何为分布式恢复过程设置凭据? 您可以group_replication_recovery使用CHANGE REPLICATION SOURCE TO语句(来自 MySQL 8.0.23)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前) 将用户凭证永久设置为通道的凭证。或者,从 MySQL 8.0.21 开始,您可以在START GROUP_REPLICATION 每次启动 Group Replication 时在语句中指定它们。
使用CHANGE REPLICATION SOURCE TO|设置的用户凭据 CHANGE MASTER TO以纯文本形式存储在服务器上的复制元数据存储库中,但指定的用户凭据 START GROUP_REPLICATION仅保存在内存中,并通过STOP GROUP_REPLICATION语句或服务器关闭删除。使用 START GROUP_REPLICATION因此以指定用户凭据可以帮助固定组复制服务器免受未经授权的访问。但是,此方法与group_replication_start_on_boot 系统变量指定的自动启动 Group Replication 不兼容 。有关更多信息,请参阅 第 18.6.3.1 节,“分布式恢复的安全用户凭证”。
我可以使用组复制扩展我的写负载吗? 不是直接的,但 MySQL 组复制是一种无共享的完整复制解决方案,其中组中的所有服务器复制相同数量的数据。因此,如果组中的一个成员作为事务提交操作的结果将 N 个字节写入存储,那么大约 N 个字节也将写入其他成员的存储,因为事务在任何地方都被复制。
但是,考虑到其他成员不必像原始成员最初执行事务时那样进行相同数量的处理,因此他们可以更快地应用更改。事务以仅用于应用行转换的格式复制,而无需再次重新执行事务(基于行的格式)。
此外,鉴于更改以基于行的格式传播和应用,这意味着它们以优化和紧凑的格式接收,并且与原始成员相比,可能会减少所需的 IO 操作数量。
总而言之,您可以通过将无冲突事务分散到组中的不同成员来扩展处理。而且您可能会横向扩展一小部分 IO 操作,因为远程服务器仅接收对稳定存储的读取-修改-写入更改所需的更改。
在相同的工作负载下,与简单复制相比,组复制是否需要更多的网络带宽和 CPU? 预计会有一些额外的负载,因为服务器需要不断地相互交互以进行同步。很难量化还有多少数据。它还取决于组的大小(三台服务器对带宽要求的压力小于组中的九台服务器)。
此外,内存和 CPU 占用空间更大,因为服务器同步部分和群组消息的工作更复杂。
我可以跨广域网部署组复制吗? 可以,但是每个成员之间的网络连接 必须可靠并具有合适的性能。低延迟、高带宽网络连接是实现最佳性能的必要条件。
如果仅网络带宽是一个问题,则 可以使用第 18.7.3 节“消息压缩”来降低所需的带宽。但是,如果网络丢弃数据包,导致重传和更高的端到端延迟,吞吐量和延迟都会受到负面影响。
警告 当任何组成员之间的网络往返时间 (RTT) 为 5 秒或更长时,您可能会遇到问题,因为内置的故障检测机制可能会被错误触发。
如果出现临时连接问题,成员是否会自动重新加入群组? 这取决于连接问题的原因。如果连接问题是暂时性的并且重新连接足够快以致故障检测器不知道它,那么服务器可能不会从组中删除。如果是“长期”连接问题,则故障检测器最终会怀疑存在问题,并将服务器从组中删除。
从 MySQL 8.0 开始,有两个设置可用于增加成员留在或重新加入组的机会:
group_replication_member_expel_timeout 增加从产生怀疑(在最初的 5 秒检测期之后发生)到驱逐成员之间的时间。您可以设置最长 1 小时的等待时间。从 MySQL 8.0.21 开始,默认设置了 5 秒的等待时间。
group_replication_autorejoin_tries 使成员在被驱逐或无法达到的多数超时后尝试重新加入组。该成员每隔五分钟进行指定次数的自动重新加入尝试。从 MySQL 8.0.21 开始,默认情况下会激活此功能,并且成员会进行 3 次自动重新加入尝试。
如果服务器被从组中驱逐并且任何自动重新加入尝试都没有成功,您需要重新加入它。换句话说,从组中明确删除服务器后,您需要手动重新加入它(或让脚本自动执行此操作)。
何时将成员从组中排除? 如果成员变得沉默,其他成员将其从组配置中删除。实际上,当成员崩溃或网络断开时,可能会发生这种情况。
在给定成员的给定超时过后检测到故障,并创建了一个没有静默成员的新配置。
当一个节点明显滞后时会发生什么? 没有方法可以定义何时自动从组中驱逐成员的策略。您需要找出成员落后的原因并解决该问题或将成员从组中删除。否则,如果服务器太慢以至于触发了流量控制,那么整个组也会变慢。流量控制可以根据您的需要进行配置。
怀疑组内有问题,是否有专门的成员负责触发重新配置? 不,组中没有负责触发重新配置的特殊成员。
任何成员都可以怀疑有问题。所有成员都需要(自动)同意给定成员失败。一名成员负责通过触发重新配置将其从组中驱逐。哪个成员负责开除该成员不是您可以控制或设置的。
我可以使用 Group Replication 进行分片吗? Group Replication 旨在提供高可用的副本集;数据和写入在组中的每个成员上重复。为了扩展到超出单个系统所能提供的范围,您需要一个围绕多个组复制集构建的编排和分片框架,其中每个副本集维护和管理总数据集的给定分片或分区。这种类型的设置,通常称为“分片集群”,允许您线性地、无限制地扩展读取和写入。
更多问题参考:https://dev.mysql.com/doc/refman/8.0/en/group-replication-frequently-asked-questions.html
9. Mysql 分区
9.1 分区概述
在 MySQL 8.0 中,分区支持由 InnoDB和 NDB存储引擎提供。
SQL 标准没有提供太多关于数据存储物理方面的指导。SQL 语言本身旨在独立于它所使用的模式、表、行或列的任何数据结构或媒体。尽管如此,大多数先进的数据库管理系统已经发展出一些方法来确定用于存储特定数据片段的物理位置,如文件系统、硬件或什至两者。在 MySQL 中, InnoDB存储引擎长期以来一直支持表空间的概念(请参阅第 15.6.3 节,“表空间”) 和 MySQL 服务器,即使在引入分区之前,也可以配置为使用不同的物理目录来存储不同的数据库
分区使这个概念更进一步,它使您能够根据可以根据需要大量设置的规则在文件系统中分布各个表的部分。实际上,表的不同部分作为单独的表存储在不同的位置。用户选择的完成数据划分的规则称为分区函数,在 MySQL 中可以是模数、对一组范围或值列表的简单匹配、内部散列函数或线性散列函数。该函数根据用户指定的分区类型进行选择,并将用户提供的表达式的值作为其参数。此表达式可以是列值、作用于一个或多个列值的函数,或者一组一个或多个列值,具体取决于所使用的分区类型。
在、 和 [ ] 分区的情况下RANGE,分区列的值传递给分区函数,该函数返回一个整数值,表示应存储该特定记录的分区编号。这个函数必须是非常量和非随机的。它可能不包含任何查询,但可以使用在 MySQL 中有效的 SQL 表达式,只要该表达式返回一个或一个整数 ,使得列表线性散列
表的分区表达式中使用的所有列都必须是表可能具有的每个唯一键的一部分,包括任何主键。这意味着不能对由以下 SQL 语句创建的像这样的表进行分区:
1 | CREATE TABLE tnp ( |
由于键pk和uk 没有共同的列,因此没有可用于分区表达式的列。在这种情况下可能的解决方法包括将name列添加到表的主键、将id列添加到uk或干脆完全删除唯一键。
9.2 分区类型
本节讨论 MySQL 8.0 中可用的分区类型。其中包括此处列出的类型:
- 范围分区。 这种类型的分区根据落在给定范围内的列值将行分配给分区。
- LIST 分区。 类似于分区 by
RANGE,不同之处在于分区是根据与一组离散值中的一个匹配的列来选择的。 - 哈希分区。 使用这种类型的分区,根据用户定义的表达式返回的值选择分区,该表达式对要插入表的行中的列值进行操作。该函数可以由任何在 MySQL 中有效的表达式组成,该表达式产生一个非负整数值。
LINEAR HASH也可以使用此类型的扩展名。 - KEY分区。 这种类型的分区类似于分区 by
HASH,只是只提供了一个或多个要评估的列,并且 MySQL 服务器提供了自己的散列函数。这些列可以包含非整数值,因为 MySQL 提供的散列函数保证整数结果,而不管列数据类型如何。LINEAR KEY也可以使用此类型的扩展名 。
数据库分区的一个非常常见的用途是按日期分隔数据。一些数据库系统支持显式日期分区,而 MySQL 在 8.0 中没有实现。但是,在 MySQL 中创建基于DATE、 TIME、 或 DATETIME列或基于使用这些列的表达式的分区方案并不困难 。
1.范围分区
按范围分区的表的分区方式是,每个分区都包含分区表达式值位于给定范围内的行。范围应该是连续的但不重叠,并使用VALUES LESS THAN运算符定义 。
基于时间间隔的分区方案。 如果您希望在 MySQL 8.0 中实现基于范围或时间间隔的分区方案,您有两个选择:
2. RANGE COLUMNS使用DATE或 DATETIME列作为分区列 对表进行分区。
该表可以通过多种方式按范围进行分区,具体取决于您的需要。一种方法是使用 store_id列。例如,您可能决定通过添加PARTITION BY RANGE如下所示的子句来对表进行 4 种分区 :
1 | CREATE TABLE employees ( |
在此分区方案中,与在商店 1 到 5 工作的员工对应的所有行都存储在 partition 中p0,而在商店 6 到 10 中工作的员工 则存储在 partition 中p1,依此类推。每个分区都是按顺序定义的,从最低到最高。这是PARTITION BY RANGE语法的要求;if ... elseif ...在这方面,您可以将其视为类似于C 或 Java 中的一系列 语句。
这是很容易确定包含数据的新行 (72, 'Mitchell', 'Wilson', '1998-06-25', NULL, 13)插入分区p2,但是当你的链增加了21会发生什么ST店?在这种方案下,没有规则覆盖store_id 大于 20的行,因此会导致错误,因为服务器不知道将它放在哪里。您可以通过在语句中使用“ catchall ” VALUES LESS THAN子句来防止这种情况发生,该子句CREATE TABLE提供大于显式命名的最高值的所有值:
1 | CREATE TABLE employees ( |
2. LIST 分区
MySQL 中的列表分区在很多方面类似于范围分区。与分区 by 一样RANGE,每个分区都必须明确定义。这两种分区的主要区别在于,在列表分区中,每个分区是根据一组值列表之一中的列值的成员资格来定义和选择的,而不是根据一组连续范围中的一个列值的成员资格。值。这是通过使用where 是列值或基于列值的表达式并返回整数值,然后通过 a 定义每个分区来完成的 ,其中 是逗号分隔的整数列表。
要以这样的方式对该表进行分区,使属于同一区域的商店的行存储在同一分区中,您可以使用CREATE TABLE 此处显示的语句:
1 | CREATE TABLE employees ( |
3. 列分区
接下来的两节讨论 COLUMNS partitioning,它们是RANGE和LIST partitioning 的变体 。COLUMNS分区允许在分区键中使用多列。为了在分区中放置行以及确定要检查哪些分区以在分区修剪中匹配行,所有这些列都被考虑在内。
此外,RANGE COLUMNS分区和LIST COLUMNS分区都支持使用非整数列来定义值范围或列表成员。允许的数据类型如下表所示:
所有整数类型:
TINYINT、SMALLINT、MEDIUMINT、INT(INTEGER) 和BIGINT。(这与通过RANGE和 进行分区相同LIST。)-
不支持使用与日期或时间相关的其他数据类型的列作为分区列。
范围列分区类似于范围分区,但允许您使用基于多个列值的范围来定义分区。此外,您可以使用整数类型以外的类型列来定义范围。
RANGE COLUMNS分区RANGE在以下方面与分区有很大不同:
RANGE COLUMNS不接受表达式,只接受列名。RANGE COLUMNS接受一列或多列的列表。
现在考虑一个类似的表rc1,它使用 RANGE COLUMNS两列分区a并b 在COLUMNS子句中引用,创建如下所示:
1 | CREATE TABLE rc1 ( |
如果我们插入与rc1 刚刚插入的行完全相同的行,行r1的分布就会大不相同:
1 | mysql> INSERT INTO rc1 VALUES (5,10), (5,11), (5,12); |
这是因为我们比较的是行而不是标量值。我们可以将插入的行值与VALUES THAN LESS THAN用于p0在 table 中定义分区的子句中 的限制行值进行比较rc1,如下所示:
1 | mysql> SELECT (5,10) < (5,12), (5,11) < (5,12), (5,12) < (5,12); |
2 个元组(5,10)和 (5,11)评估为小于 (5,12),因此它们存储在 partition 中 p0。由于 5 不小于 5 且 12 不小于 12,(5,12)因此认为不小于(5,12),并存储在 partition 中p1。
LIST COLUMNS 分区
MySQL 8.0 提供对LIST COLUMNS分区的支持。这是LIST分区的一种变体, 它允许使用多个列作为分区键,并将整数类型以外的数据类型的列用作分区列;您可以使用字符串类型 DATE、 和 DATETIME列。
假设您的企业在 12 个城市拥有客户,出于销售和营销目的,您将其组织成 4 个区域,每个区域 3 个城市,如下表所示:
| 地区 | 城市 |
|---|---|
| 1 | Oskarshamn、Högsby、Mönsterås |
| 2 | ‘Vimmerby’, ‘Hultsfred’, ‘Västervik’ |
| 3 | ‘Nässjö’, ‘Eksjö’, ‘Vetlanda’ |
| 4 | ‘Uppvidinge’, ‘Alvesta’, ‘Växjo’ |
通过LIST COLUMNS分区,您可以为客户数据创建一个表,该表根据客户所在城市的名称将一行分配给与这些区域对应的 4 个分区中的任何一个
如下所示:
1 | CREATE TABLE customers_1 ( |
4. 哈希分区
分区依据HASH主要用于确保数据在预定数量的分区之间均匀分布。对于范围或列表分区,您必须明确指定给定的列值或列值集应存储在哪个分区中;使用散列分区,这个决定会为您处理,您只需要根据要散列的列值和分区表要划分的分区数指定列值或表达式。
要使用分区对表进行HASH分区,必须在CREATE TABLE语句后附加一个子句,其中 是一个返回整数的表达式。这可以简单地是类型为 MySQL 的整数类型之一的列的名称。此外,您很可能希望在后面加上,其中 是一个正整数,表示要将表划分为的分区数。
MySQL 8.0 还支持一种HASH称为线性散列的分区 变体, 它采用更复杂的算法来确定插入到分区表中的新行的位置。有关此算法的说明,请参见 第 24.2.4.1 节,“线性哈希分区”。
MySQL 还支持线性散列,它与常规散列的不同之处在于,线性散列使用线性二次幂算法,而常规散列使用散列函数值的模数。
5. KEY 分区
按键分区类似于按哈希分区,不同之处在于哈希分区使用用户定义的表达式,键分区的哈希函数由 MySQL 服务器提供。NDB Cluster MD5()用于此目的;对于使用其他存储引擎的表,服务器使用自己的内部散列函数。
6. 子分区
子分区(也称为 复合分区)是对分区表中每个分区的进一步划分。考虑以下 CREATE TABLE语句:
1 | CREATE TABLE ts (id INT, purchased DATE) |
表ts有 3 个RANGE 分区。每个分区-的p0,p1和p2-is进一步分成2子分区。实际上,整个表被划分为多个 3 * 2 = 6分区。但是,由于PARTITION BY RANGE子句的作用,其中的前 2 个只存储purchased列中值小于 1990 的那些记录。
可以对由RANGE或分区的表进行子分区 LIST。子分区可以使用HASH或 KEY分区。这也称为 复合分区。
7. MySQL 分区如何处理 NULL
MySQL 中的分区没有禁止 NULL作为分区表达式的值,无论它是列值还是用户提供的表达式的值。尽管允许 NULL用作必须产生整数的表达式的值,但重要的是要记住它 NULL不是数字。MySQL 的分区实现将其NULL视为小于任何非NULL值,就像那样 ORDER BY。
这意味着NULL不同类型的分区之间的处理方式不同,并且如果您没有做好准备,可能会产生您意想不到的行为。在这种情况下,我们在本节讨论每个 MySQL 分区类型NULL在确定应存储行的分区时如何处理值,并为每个分区提供示例。
9.3 分区管理
有多种使用 SQL 语句修改分区表的方法;可以使用ALTER TABLE语句的分区扩展来添加、删除、重新定义、合并或拆分现有分区 。还有一些方法可以获取有关分区表和分区的信息。
要更改表的分区方案,只需要使用ALTER TABLE带有*partition_options*选项的语句,该 语句与 CREATE TABLE创建分区表的语法相同;此选项(也)始终以关键字开头PARTITION BY。
要重新分区此表,以便使用id列值作为键的基础按键将其分区为两个分区,您可以使用以下语句:
1 | ALTER TABLE trb3 PARTITION BY KEY(id) PARTITIONS 2; |
ALTER TABLE ... ENGINE = ...仅更改表使用的存储引擎,并保持表的分区方案不变。仅当目标存储引擎提供分区支持时,该语句才会成功。您可以使用 ALTER TABLE ... REMOVE PARTITIONING删除表的分区;
RANGE 和 LIST 分区的管理
范围和列表分区的添加和删除以类似的方式处理,因此我们将在本节中讨论这两种分区的管理。
可以使用带有选项的语句来完成 从由任一RANGE或由分区的表中删除分区 。假设您创建了一个按范围分区的表,然后使用以下and 语句填充了 10 条记录: LISTALTER TABLEDROP PARTITIONCREATE TABLEINSERT
1 | mysql> CREATE TABLE tr (id INT, name VARCHAR(50), purchased DATE) |
您可以看到哪些项目应该插入到分区中 p2,如下所示:
1 | mysql> SELECT * FROM tr |
您还可以使用分区选择来获取此信息,如下所示:
1 | mysql> SELECT * FROM tr PARTITION (p2); |
要删除名为 的分区p2,请执行以下命令:
1 | mysql> ALTER TABLE tr DROP PARTITION p2; |
HASH和KEY分区的管理
您不能从由HASH或分区的表中删除分区,或者KEY以与从RANGE或分区的表相同的方式 删除分区 LIST。但是,您可以合并HASH或KEY 使用分区ALTER TABLE ... COALESCE PARTITION。假设一个clients 包含客户端数据的表被划分为 12 个分区,创建如下所示:
1 | CREATE TABLE clients ( |
要将分区数从 12 减少到 8,请执行以下 ALTER TABLE语句:
1 | mysql> ALTER TABLE clients COALESCE PARTITION 4; |
尝试删除比表中更多的分区会导致如下错误:
1 | mysql> ALTER TABLE clients COALESCE PARTITION 18; |
要将clients表的分区数 从 12 增加到 18,请使用 ALTER TABLE ... ADD PARTITION如下所示:
1 | ALTER TABLE clients ADD PARTITION PARTITIONS 6; |
交换分区和子分区与表
在 MySQL 8.0 中,可以使用 将表分区或子分区与表交换,其中 是分区表, 是要与未分区表交换的分区或子 分区,前提是以下语句为真: ALTER TABLE *pt* EXCHANGE PARTITION *p* WITH TABLE *nt*ptppt**nt
- 表*
nt*本身没有分区。 - 表*
nt*不是临时表。 - 表*
pt和 的结构nt*在其他方面相同。 - 表不
nt包含外键引用,并且没有其他表有任何引用 的外键nt。 - 中没有*
nt位于分区定义边界之外的 行p*。如果WITHOUT VALIDATION使用,则此条件不适用。 - 对于
InnoDB表,两个表使用相同的行格式。要确定InnoDB表的行格式 ,请查询INFORMATION_SCHEMA.INNODB_TABLES. nt没有任何使用该DATA DIRECTORY选项的分区。InnoDBMySQL 8.0.14 及更高版本中的表取消了此限制。
除了ALTER, INSERT和 CREATE通常需要的权限ALTER TABLE 声明,你必须有 DROP执行权限 ALTER TABLE ... EXCHANGE PARTITION。
用非分区表交换分区
假设e已经使用以下 SQL 语句创建并填充了一个分区表:
1 | CREATE TABLE e ( |
现在我们创建了一个e named的非分区副本e2。这可以使用 mysql客户端完成,如下所示:
1 | mysql> CREATE TABLE e2 LIKE e; |
要将p0table 中的 分区e与 table交换e2,您可以使用 ALTER TABLE,如下所示:
1 | mysql> ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2; |
分区维护
可以使用用于此类目的的 SQL 语句对分区表执行许多表和分区维护任务。
分区表的表维护可以使用语句来实现CHECK TABLE, OPTIMIZE TABLE, ANALYZE TABLE,和 REPAIR TABLE,其被支持的分区表。
您可以使用许多扩展来 ALTER TABLE直接在一个或多个分区上执行此类操作,如下表所述:
重建分区。 重建分区;这与删除存储在分区中的所有记录,然后重新插入它们具有相同的效果。这对于碎片整理的目的非常有用。
例子:
1
ALTER TABLE t1 REBUILD PARTITION p0, p1;
优化分区。 如果已经从一个分区删除大量行或如果已进行许多改变,以与可变长度行分区表(即,具有VARCHAR, BLOB或 TEXT列),可以使用 ALTER TABLE ... OPTIMIZE PARTITION以回收任何未使用的空间和整理分区数据文件。
例子:
1 | ALTER TABLE t1 OPTIMIZE PARTITION p0, p1; |
使用
OPTIMIZE PARTITION给定分区上相当于跑步CHECK PARTITION,ANALYZE PARTITION和REPAIR PARTITION在该分区上。一些 MySQL 存储引擎,包括
InnoDB,不支持按分区优化;在这些情况下,ALTER TABLE ... OPTIMIZE PARTITION分析并重建整个表,并导致发出适当的警告。(Bug #11751825, Bug #42822) 使用ALTER TABLE ... REBUILD PARTITIONandALTER TABLE ... ANALYZE PARTITION来避免这个问题。分析分区。 这会读取并存储分区的密钥分布。
例子:
1
ALTER TABLE t1 ANALYZE PARTITION p3;
修复分区。 这将修复损坏的分区。
例子:
1
ALTER TABLE t1 REPAIR PARTITION p0,p1;
通常,
REPAIR PARTITION当分区包含重复键错误时失败。您可以使用ALTER IGNORE TABLE此选项,在这种情况下,由于存在重复键而无法移动的所有行都将从分区中删除(错误 #16900947)。检查分区。 您可以使用
CHECK TABLE与用于非分区表的方式非常相似的方式来检查分区是否存在错误。例子:
1
ALTER TABLE trb3 CHECK PARTITION p1;
该语句告诉您
p1表分区中 的数据或索引t1是否已损坏。如果是这种情况,请使用ALTER TABLE ... REPAIR PARTITION修复分区。通常,
CHECK PARTITION当分区包含重复键错误时失败。您可以使用ALTER IGNORE TABLE此选项,在这种情况下,语句将返回分区中发现重复键违规的每一行的内容。仅报告表的分区表达式中列的值。(错误 #16900947)
获取分区信息
从 MySQL 8.0.16 开始,当对分区表进行插入、删除或更新时,二进制日志会记录有关分区和(如果有)发生行事件的子分区的信息。为发生在不同分区或子分区中的修改创建新行事件,即使涉及的表是相同的。因此,如果一个事务涉及三个分区或子分区,则会生成三个行事件。对于更新事件,将记录“之前”图像和“之后”图像的分区信息。如果指定-v或 将显示分区信息--verbose使用mysqlbinlog查看二进制日志时的选项。仅在使用基于行的日志记录时才记录分区信息 ( binlog_format=ROW)。
SHOW TABLE STATUS 分区表 的输出与非分区表的输出相同,只是该Create_options列包含字符串partitioned。该 Engine列包含表的所有分区使用的存储引擎的名称。
您还可以从 中获取有关分区的信息 INFORMATION_SCHEMA,其中包含一个 PARTITIONS表。
可以使用 确定给定SELECT查询 中涉及分区表的哪些分区 EXPLAIN。该 partitions列 EXPLAIN输出列出了哪些记录将被查询匹配的分区。
假设trb1创建并填充了一个表,如下所示:
1 | CREATE TABLE trb1 (id INT, name VARCHAR(50), purchased DATE) |
您可以查看在诸如 的查询中使用了哪些分区 SELECT * FROM trb1;,如下所示:
1 | mysql> EXPLAIN SELECT * FROM trb1\G |
在这种情况下,将搜索所有四个分区。但是,当将使用分区键的限制条件添加到查询时,您可以看到仅扫描包含匹配值的那些分区,如下所示:
1 | mysql> EXPLAIN SELECT * FROM trb1 WHERE id < 5\G |
EXPLAIN 还提供有关使用的密钥和可能的密钥的信息:
1 | mysql> ALTER TABLE trb1 ADD PRIMARY KEY (id); |
如果EXPLAIN用于检查针对未分区表的查询,则不会产生错误,但该partitions列的值始终为 NULL。
所述rows的柱 EXPLAIN的输出显示表中的行的总数。
9.4 分区修剪
称为分区修剪的优化基于一个相对简单的概念,可以描述为“不要扫描没有匹配值的分区”。假设t1通过以下语句创建了一个分区表 :
1 | CREATE TABLE t1 ( |
假设您希望从如下SELECT语句中获得结果 :
1 | SELECT fname, lname, region_code, dob |
很容易看出,应该返回的行都不在分区p0或 分区中p3;也就是说,我们只需要在分区中搜索 p1并p2找到匹配的行。通过限制搜索,与扫描表中的所有分区相比,可以在查找匹配行上花费更少的时间和精力。这种“切除”不需要的分区被称为 修剪。当优化器可以在执行此查询时使用分区修剪时,查询的执行速度可以比针对包含相同列定义和数据的非分区表执行相同查询快一个数量级。
只要WHERE条件可以简化为以下两种情况之一,优化器就可以执行修剪 :
*partition_column* = *constant**partition_column* IN (*constant1*, *constant2*, ..., *constantN*)
在第一种情况下,优化器简单地评估给定值的分区表达式,确定哪个分区包含该值,并仅扫描该分区。在许多情况下,等号可以与另一个算术比较来代替,包括<,>, <=,>=,和 <>。BETWEEN在WHERE子句中使用的一些查询 也可以利用分区修剪。请参阅本节后面的示例。
在第二种情况下,优化器评估列表中每个值的分区表达式,创建匹配分区的列表,然后仅扫描此分区列表中的分区。
9.5 分区选择
WHERE支持 为匹配给定条件的行显式选择分区和子分区。分区选择类似于分区修剪,因为只检查特定分区是否匹配,但在两个关键方面有所不同:
- 要检查的分区由语句的发布者指定,不像分区修剪是自动的。
- 尽管分区修剪仅适用于查询,但查询和许多 DML 语句都支持显式选择分区。
此处列出了支持显式分区选择的 SQL 语句:
9.6 分区的限制和限制
本节讨论当前对 MySQL 分区支持的限制。
禁止的构造。 分区表达式中不允许使用以下结构:
- 存储过程、存储函数、可加载函数或插件。
- 声明的变量或用户变量。
算术和逻辑运算符。 允许在分区表达式中 使用算术运算符 +、 -和 *。但是,结果必须是整数值或NULL(除非在[LINEAR] KEY本章其他地方讨论的分区情况下
服务器 SQL 模式。 采用用户定义分区的表不会保留创建它们时有效的 SQL 模式。正如本手册其他地方所讨论的(参见 第 5.1.11 节,“服务器 SQL 模式”),许多 MySQL 函数和运算符的结果可能会根据服务器 SQL 模式而改变。因此,在创建分区表后任何时候改变SQL模式,都可能导致该类表的行为发生重大变化,很容易导致数据损坏或丢失。由于这些原因,强烈建议您不要在创建分区表后更改服务器 SQL 模式。
对于服务器 SQL 模式中的一个这样的更改,使分区表无法使用,请考虑以下 CREATE TABLE语句,只有在NO_UNSIGNED_SUBTRACTION模式有效时才能成功执行
性能考虑。 以下列表给出了分区操作对性能的一些影响:
文件系统操作。 分区和重新分区操作(例如
ALTER TABLEwithPARTITION BY ...、REORGANIZE PARTITION或REMOVE PARTITIONING)取决于文件系统操作的实现。这意味着这些操作的速度受文件系统类型和特征、磁盘速度、交换空间、操作系统的文件处理效率以及与文件处理相关的 MySQL 服务器选项和变量等因素的影响。特别是,您应该确保large_files_support启用并open_files_limit正确设置。分区和重新分区操作涉及InnoDB通过启用innodb_file_per_table.另请参阅 最大分区数。
表锁。 通常,对表执行分区操作的进程会对该表进行写锁。对此类表的读取相对不受影响;挂起
INSERT和UPDATE操作在分区操作完成后立即执行。有关InnoDB此限制的特定例外情况,请参阅 分区操作。索引;分区修剪。 与非分区表一样,正确使用索引可以显着加快对分区表的查询。此外,设计分区表和对这些表的查询以利用 分区修剪可以显着提高性能。有关更多信息,请参阅 第 24.4 节,“分区修剪”。
分区表支持索引条件下推。请参见第 8.2.1.6 节,“索引条件下推优化”。
负载数据的性能。 在 MySQL 8.0 中,
LOAD DATA使用缓冲来提高性能。您应该知道缓冲区每个分区使用 130 KB 内存来实现这一点。
最大分区数。 不使用NDB存储引擎的给定表的最大可能分区数是 8192。这个数字包括子分区。
分区 InnoDB 表不支持外键。 使用InnoDB 存储引擎的分区表不支持外键。更具体地说,这意味着以下两个陈述是正确的:
InnoDB使用用户定义分区 的表的定义不得包含外键引用;不能InnoDB对定义包含外键引用的表进行分区。- 任何
InnoDB表定义都不能包含对用户分区表的外键引用;任何InnoDB具有用户定义分区的表都不能包含由外键引用的列。
更改表…订购。 对分区表运行 的语句仅导致对每个分区内的行进行排序。
修改主键对 REPLACE 语句的影响。 在某些情况下(请参阅 第 24.6.1 节,“分区键、主键和唯一键”)可能需要修改表的主键。请注意,如果您的应用程序使用REPLACE 语句并且您这样做,则这些语句的结果可能会发生巨大变化。
全文索引。 分区表不支持FULLTEXT 索引或搜索。
空间列。 具有空间数据类型(例如POINT 或GEOMETRY不能在分区表中使用)的列。
临时表。 临时表不能分区。
日志表。 无法对日志表进行分区;ALTER TABLE ... PARTITION BY ...此类表上的 语句因错误而失败。
分区键的数据类型。 分区键必须是整数列或解析为整数的表达式。ENUM不能使用使用列的表达式 。列或表达式值也可能是NULL; 见第 24.2.7 节,“MySQL 分区如何处理 NULL”。
子查询。 分区键可能不是子查询,即使该子查询解析为整数值或NULL.
键分区不支持列索引前缀。 创建按键分区的表时,分区键中使用列前缀的任何列都不会在表的分区功能中使用。
子分区的问题。 子分区必须使用HASH或 KEY分区。只有 RANGE和LIST分区可以被子分区;HASH并且 KEY分区不能再分区。
数据目录和索引目录选项。 表级DATA DIRECTORY和INDEX DIRECTORY选项被忽略(参见错误 #32091)。您可以将这些选项用于InnoDB表的单个分区或子分区。从 MySQL 8.0.21 开始,DATA DIRECTORY子句中指定的目录必须为 InnoDB.
修复和重建分区表。 分区表支持 语句CHECK TABLE、 OPTIMIZE TABLE、 ANALYZE TABLE和 REPAIR TABLE。
分区和子分区的文件名分隔符。 表分区和子分区文件名包括生成的分隔符,例如#P#和 #SP#。此类分隔符的字母大小写可能会有所不同,不应依赖于此。
分区键、主键和唯一键
本节讨论分区键与主键和唯一键的关系。控制这种关系的规则可以表示如下:分区表的分区表达式中使用的所有列必须是该表可能具有的每个唯一键的一部分。
换句话说,表上的每个唯一键都必须使用表分区表达式中的每一列。(这也包括表的主键,因为它根据定义是唯一键。本节稍后将讨论这种特殊情况。)
与存储引擎相关的分区限制
在 MySQL 8.0 中,分区支持实际上不是由 MySQL 服务器提供的,而是由表存储引擎自己的或本机分区处理程序提供的。在 MySQL 8.0 中,只有InnoDB 存储引擎提供本机分区处理程序。这意味着不能使用任何其他存储引擎创建分区表。
1 | 笔记 |
用户定义的分区和 NDB 存储引擎(NDB Cluster)。 按KEY(包括 LINEAR KEY)分区是NDB存储引擎支持的唯一分区类型 。在 NDB Cluster 的正常情况下,不可能使用 [ LINEAR]以外的任何分区类型创建 NDB Cluster 表KEY,并且尝试这样做会失败并出现错误。
升级分区表。 执行升级时,KEY必须转储并重新加载分区的表 。
与函数相关的分区限制
天花板()和地板()。 这些函数中的每一个仅在传递一个精确数字类型的参数时才返回一个整数,例如INT类型之一 或 DECIMAL。这意味着,例如,以下CREATE TABLE语句失败并出现错误,如下所示:
带有 WEEK 说明符的 EXTRACT() 函数。 EXTRACT()当用作 时 ,函数返回的值 取决于系统变量的值 。因此, 当将单位指定为 时,不允许作为分区函数。
10. Mysql 连接和API
Python 开发者指南
使用Connector/Python连接MySQL
该connect()构造函数创建到MySQL服务器的连接并返回一个 MySQLConnection对象。
以下示例显示了如何连接到 MySQL 服务器:
1 | import mysql.connector |
也可以使用connection.MySQLConnection() 类创建连接对象 :
1 | from mysql.connector import (connection) |
两种形式(使用connect() 构造函数或直接使用类)都是有效的且功能相同,但 usingconnect()是首选,并且被本手册中的大多数示例使用。
要处理连接错误,请使用该try 语句并使用errors.Error 异常捕获所有错误 :
1 | import mysql.connector |
在字典中定义连接参数并使用 **运算符是另一种选择:
1 | import mysql.connector |
使用连接器/Python 插入数据
插入或更新数据也是使用称为游标的处理程序结构来完成的。当您使用事务性存储引擎,如InnoDB(在MySQL 5.5和更高的默认设置),则必须提交 的序列后的数据 INSERT, DELETE以及 UPDATE报表。
此示例显示如何插入新数据。第二个 INSERT取决于第一个新创建的主键的值 。该示例还演示了如何使用扩展格式。任务是添加一个明天开始上班的新员工,工资设置为50000。
1 | from __future__ import print_function |
我们首先打开一个到 MySQL 服务器的连接,并将连接对象存储 在变量中cnx。然后我们 使用连接的 方法创建一个新的游标,默认情况下是一个 MySQLCursor对象 cursor()。
我们可以通过调用数据库函数来计算明天,但为了清楚起见,我们使用datetime模块在 Python 中进行 。
这两个INSERT语句都存储在称为add_employee和 的变量中add_salary。请注意,第二个 INSERT语句使用扩展的 Python 格式代码。
新员工的信息存储在元组中 data_employee。执行插入新员工的查询,我们使用游标对象的属性检索emp_no列(一 AUTO_INCREMENT列) 的新插入值lastrowid。
接下来,我们使用emp_no保存数据的字典中的变量为新员工插入新工资 。execute()如果发生错误,则将此字典传递给游标对象的 方法。
由于默认情况下连接器/Python 关闭 自动提交,并且 MySQL 5.5 及更高版本InnoDB默认使用事务表,因此有必要使用连接的commit()方法提交更改。您也可以 使用该 方法回滚rollback()。
使用连接器/Python 查询数据
以下示例显示如何 使用使用连接的 方法创建的游标查询数据 cursor()。返回的数据被格式化并打印在控制台上。
任务是选择 1999 年雇用的所有员工,并将他们的姓名和雇用日期打印到控制台。
1 | import datetime |
输出应该是这样的:
1 | .. |
11. MySQL NDB Cluster 8.0
本章提供有关 MySQL NDB Cluster 的信息,它是适用于分布式计算环境的 MySQL 高可用性、高冗余版本。最新的 NDB Cluster 版本系列使用NDB存储引擎的第 8 版 (也称为 NDBCLUSTER)来支持在集群中运行多台带有 MySQL 服务器和其他软件的计算机。NDB Cluster 8.0 现已作为 General Availability (GA) 版本提供(从 8.0.19 版开始),包含NDB存储引擎的8.0 版 。NDB Cluster 7.6 和 NDB Cluster 7.5,仍然作为 GA 版本提供,使用 7.6 和 7.5 的版本NDB, 分别。以前的 GA 版本仍可用于生产,NDB Cluster 7.4 和 NDB Cluster 7.3,分别包含NDB版本 7.4 和 7.3。不再支持或维护 NDB 7.2 和旧版本系列。
11.1 一般信息
MySQL NDB Cluster 使用 MySQL 服务器和 NDB存储引擎。NDBOracle 构建的标准 MySQL Server 8.0 二进制文件中不包含对存储引擎的支持 。相反,Oracle 的 NDB Cluster 二进制文件的用户应该升级到支持平台的 NDB Cluster 的最新二进制版本——这些包括应该适用于大多数 Linux 发行版的 RPM。从源代码构建的 NDB Cluster 8.0 用户应使用为 MySQL 8.0 提供的源代码,并使用提供 NDB 支持所需的选项进行构建。
1 | 重要的 |
支持的平台。 NDB Cluster 目前可用,并在许多平台上受支持。
可用性。 NDB Cluster 二进制和源包可用于支持的平台
NDB Cluster 版本号。 NDB 8.0 遵循与 MySQL Server 8.0 系列版本相同的发布模式,从 MySQL 8.0.13 和 MySQL NDB Cluster 8.0.13 开始。在本手册和其他 MySQL 文档中,我们使用以“ NDB ”开头的版本号来标识这些和更高版本的 NDB Cluster 版本 。此版本号是NDBCLUSTERNDB 8.0 版本中使用的 存储引擎的版本号,与 NDB Cluster 8.0 版本所基于的 MySQL 8.0 服务器版本相同。
NDB Cluster 软件中使用的版本字符串。 由 MySQL NDB Cluster 发行版提供的mysql客户端显示的版本字符串使用以下格式:
1 | mysql-mysql_server_version-cluster |
与标准 MySQL 8.0 版本的兼容性。 虽然许多标准 MySQL 模式和应用程序可以使用 NDB Cluster 工作,但使用 NDB Cluster 运行时,未修改的应用程序和数据库模式可能会略微不兼容或性能欠佳
这些问题中的大部分都可以克服,但这也意味着您不太可能切换现有的应用程序数据存储(例如,当前使用的应用程序数据存储),MyISAM 或InnoDB使用 NDB存储引擎而不考虑更改模式的可能性、查询和应用程序。没有编译 的mysqldNDB支持(即,在没有-DWITH_NDBCLUSTER_STORAGE_ENGINE或其别名的情况下构建 -DWITH_NDBCLUSTER)不能作为使用它构建的mysqld 的替代品。
11.2 NDB 集群概述
NDB Cluster是一种在无共享系统中实现内存数据库集群的技术。无共享架构使系统能够使用非常便宜的硬件,并且对硬件或软件的特定要求最低。
NDB Cluster 旨在没有任何单点故障。在无共享系统中,每个组件都应该有自己的内存和磁盘,不推荐或不支持使用网络共享、网络文件系统和 SAN 等共享存储机制。
NDB簇集成标准MySQL服务器称为一个内存集群存储引擎NDB (其代表“ Ñ etwork d ATA乙ASE ”)。在我们的文档中,该术语NDB是指特定于存储引擎的设置部分,而“ MySQL NDB Cluster ”是指一个或多个 MySQL 服务器与 NDB存储引擎的组合。
NDB Cluster 由一组计算机组成,称为hosts,每台计算机 运行一个或多个进程。这些进程称为 节点,可能包括 MySQL 服务器(用于访问 NDB 数据)、数据节点(用于存储数据)、一个或多个管理服务器,以及可能的其他专用数据访问程序。NDB Cluster 中这些组件的关系如下所示:

所有这些程序一起工作形成一个 NDB Cluster(参见 第 23.5 节,“NDB Cluster 程序”。当数据由 NDB存储引擎存储时,表(和表数据)存储在数据节点中。这些表可以直接从集群中的所有其他 MySQL 服务器(SQL 节点)。因此,在集群中存储数据的工资单应用程序中,如果一个应用程序更新了员工的工资,则所有其他查询此数据的 MySQL 服务器都可以立即看到此更改。
尽管 NDB Cluster SQL 节点使用mysqld 服务器守护进程,但它在许多关键方面与 MySQL 8.0 发行版提供的 mysqld二进制文件不同,并且两个版本的 mysqld不可互换。
此外,未连接到 NDB Cluster 的 MySQL 服务器无法使用NDB存储引擎,也无法访问任何 NDB Cluster 数据。
NDB Cluster 的数据节点中存储的数据可以进行镜像;集群可以处理单个数据节点的故障,除了由于丢失事务状态而中止少量事务之外没有其他影响。因为事务应用程序需要处理事务失败,所以这不应成为问题的根源。
单个节点可以停止和重新启动,然后可以重新加入系统(集群)。滚动重启(其中所有节点依次重启)用于进行配置更改和软件升级(请参阅 第 23.6.5 节,“执行 NDB Cluster 的滚动重启”)。滚动重启也用作在线添加新数据节点过程的一部分(请参阅第 23.6.7 节,“在线添加 NDB Cluster 数据节点”)。有关数据节点、它们在 NDB Cluster 中的组织方式以及它们如何处理和存储 NDB Cluster 数据的更多信息,请参阅 第 23.2.2 节,“NDB Cluster 节点、节点组、片段副本和分区”。
可以使用NDBNDB Cluster 管理客户端中的-native功能和NDB Cluster 分发中包含的ndb_restore程序来备份和恢复 NDB Cluster 数据库 。有关更多信息,请参阅 第 23.6.8 节,“NDB Cluster 的在线备份”和 第 23.5.23 节,“ndb_restore - 还原 NDB Cluster 备份”。您还可以在mysqldump和 MySQL 服务器中使用为此目的提供的标准 MySQL 功能 。有关更多信息,请参阅 第 4.5.4 节,“mysqldump — 数据库备份程序”。
NDB Cluster 节点可以采用不同的传输机制进行节点间通信;大多数实际部署中都使用标准 100 Mbps 或更快以太网硬件上的 TCP/IP。
1. 核心概念
NDBCLUSTER (也称为NDB)是一种内存存储引擎,提供高可用性和数据持久性功能。
的NDBCLUSTER存储引擎可以与一系列故障切换和负载平衡选项进行配置,但最简单的是开始在集群级别的存储引擎。NDB Cluster 的NDB存储引擎包含一组完整的数据,仅依赖于集群本身内的其他数据。
NDB Cluster 的“ Cluster ”部分是独立于 MySQL 服务器配置的。在 NDB Cluster 中,集群的每个部分都被认为是一个 节点。
1 | 笔记 |
有三种类型的集群节点,在最小的 NDB 集群配置中,至少有三个节点,这些类型中的每一种:
管理节点:此类节点的作用是管理 NDB Cluster 内的其他节点,执行提供配置数据、启动和停止节点以及运行备份等功能。由于此节点类型管理其他节点的配置,因此应先启动此类型的节点,然后再启动任何其他节点。管理节点使用命令ndb_mgmd启动。
数据节点:这种类型的节点存储集群数据。数据节点与片段副本数一样多,乘以片段数。例如,对于两个片段副本,每个副本有两个片段,您需要四个数据节点。一个分片副本足以存储数据,但不提供冗余;因此,建议有两个(或更多)片段副本以提供冗余,从而提供高可用性。使用命令ndbd启动数据节点 或 ndbmtd。
NDB Cluster 表通常完全存储在内存中而不是磁盘上(这就是我们将 NDB Cluster 称为 内存数据库的原因)。但是,一些 NDB Cluster 数据可以存储在磁盘上。
SQL 节点:这是一个访问集群数据的节点。在 NDB Cluster 的情况下,SQL 节点是使用
NDBCLUSTER存储引擎的传统 MySQL 服务器 。SQL 节点是一个以和 选项开始的mysqld进程 ,本章其他地方对此进行了解释,可能还有其他 MySQL 服务器选项。--ndbcluster--ndb-connectstringSQL 节点实际上只是一种特殊类型的 API 节点,它指定任何访问 NDB Cluster 数据的应用程序。API 节点的另一个示例是用于恢复集群备份的 ndb_restore实用程序。可以使用 NDB API 编写此类应用程序
1 | 重要的 |
集群的配置涉及配置集群中的每个单独节点并在节点之间设置单独的通信链接。NDB Cluster 目前的设计意图是数据节点在处理器能力、内存空间和带宽方面是同质的。此外,为了提供单点配置,整个集群的所有配置数据都位于一个配置文件中。
管理服务器管理集群配置文件和集群日志。集群中的每个节点都从管理服务器检索配置数据,因此需要一种方法来确定管理服务器所在的位置。当数据节点中发生有趣的事件时,节点会将有关这些事件的信息传输到管理服务器,然后管理服务器将信息写入集群日志。
此外,可以有任意数量的集群客户端进程或应用程序。其中包括标准 MySQL 客户端、 NDB特定 API 程序和管理客户端。这些将在接下来的几段中进行描述。
标准 MySQL 客户端。 NDB Cluster 可以与用 PHP、Perl、C、C++、Java、Python、Ruby 等编写的现有 MySQL 应用程序一起使用。此类客户端应用程序向充当 NDB Cluster SQL 节点的 MySQL 服务器发送 SQL 语句并从其接收响应,其方式与它们与独立 MySQL 服务器交互的方式大致相同。
NDB 客户端程序。 可以编写客户端程序,直接从NDBCLUSTER存储引擎访问 NDB Cluster 数据,绕过任何可能连接到集群的 MySQL 服务器,使用NDB API,一种高级 C++ API。对于不需要数据的 SQL 接口的特殊用途,此类应用程序可能很有用。
管理客户。 这些客户端连接到管理服务器并提供用于正常启动和停止节点、启动和停止消息跟踪(仅限调试版本)、显示节点版本和状态、启动和停止备份等的命令。此类程序的一个示例是NDB Cluster 提供的 ndb_mgm管理客户端.可以使用MGM API编写此类应用程序 ,这是一种直接与一个或多个 NDB Cluster 管理服务器通信的 C 语言 API。有关更多信息,请参阅 MGM API。
Oracle 还提供 MySQL Cluster Manager,它提供了一个高级命令行界面,可简化许多复杂的 NDB Cluster 管理任务,例如重新启动具有大量节点的 NDB Cluster。MySQL Cluster Manager client 还支持用于获取和设置大多数节点配置参数以及与 NDB Cluster 相关的mysqld服务器选项和变量的值的命令。
事件日志。 NDB Cluster 按类别(启动、关闭、错误、检查点等)、优先级和严重性记录事件。事件日志有此处列出的两种类型:
- 集群日志:为整个集群保留所有所需的可报告事件的记录。
- 节点日志:一个单独的日志,也为每个单独的节点保留。
检查站。 一般来说,当数据被保存到磁盘时,就说已经到达了一个检查点。更具体到 NDB Cluster,检查点是所有提交的事务都存储在磁盘上的时间点。关于NDB存储引擎,有两种类型的检查点,它们协同工作以确保维护集群数据的一致视图。这些显示在以下列表中:
本地检查点(LCP):这是一个特定于单个节点的检查点;然而,LCP 或多或少同时发生在集群中的所有节点上。LCP 通常每隔几分钟发生一次;精确的时间间隔会有所不同,取决于节点存储的数据量、集群活动水平和其他因素。
NDB 8.0 支持部分 LCP,在某些情况下可以显着提高性能。请参阅 启用部分 LCP 并控制它们使用的存储量的参数
EnablePartialLcp和RecoveryWork配置参数的说明 。全局检查点 (GCP):GCP 每隔几秒发生一次,此时所有节点的事务都已同步并将重做日志刷新到磁盘。
2. 集群节点、节点组、片段副本和分区
本节讨论 NDB Cluster 划分和复制数据以进行存储的方式。
在接下来的几段中将讨论一些对理解该主题至关重要的概念。
数据节点。 一个ndbd或ndbmtd进程,它存储一个或多个片段副本——即分配给节点所属节点组的分区副本 (在本节后面讨论)。
每个数据节点应位于单独的计算机上。虽然也可以在一台计算机上托管多个数据节点进程,但通常不推荐这种配置。
当提到ndbd或ndbmtd进程时 ,术语“节点”和“数据节点”通常可以互换使用 ;在提到的地方,管理节点(ndb_mgmd 进程)和 SQL 节点(mysqld进程)在本讨论中是这样指定的。
节点组。 一个节点组由一个或多个节点组成,并存储分区或片段副本集(请参阅下一项)。
NDB Cluster 中的节点组数量不可直接配置;它是数据节点数量和片段副本数量(NoOfReplicas 配置参数)的函数,如下所示:
1 | [# of node groups] = [# of data nodes] / NoOfReplicas |
因此,具有 4 个数据节点的 NDB Cluster 有 4 个节点组 if NoOfReplicas在config.ini文件中设置为 1,如果NoOfReplicas设置为 2,则有2 个节点组,如果 设置为 4,则有 1 个节点组 NoOfReplicas。
1 | 笔记 |
您可以在线向正在运行的 NDB Cluster 添加新的节点组(以及新的数据节点)
分区。 这是集群存储的一部分数据。每个节点负责保留分配给它的任何分区的至少一个副本(即至少一个片段副本)可用于集群。
NDB Cluster 默认使用的分区数量取决于数据节点的数量和数据节点使用的 LDM 线程的数量,如下所示:
1 | [# of partitions] = [# of data nodes] * [# of LDM threads] |
当使用运行ndbmtd 的数据节点时,LDM 线程的数量由 的设置控制 MaxNoOfExecutionThreads。使用ndbd时只有一个 LDM 线程,这意味着集群分区与参与集群的节点一样多。使用时,这也是这种情况 ndbmtd与 MaxNoOfExecutionThreads设置为3或更小。(您应该知道 LDM 线程的数量随此参数的值而增加,但不是以严格的线性方式增加,并且设置它时还有其他限制;MaxNoOfExecutionThreads 有关更多信息,请参阅 的说明 。)
NDB 和用户定义的分区。 NDB Cluster 通常会NDBCLUSTER自动对表进行分区 。但是,也可以对表使用用户定义的分区NDBCLUSTER。这受到以下限制:
- 在带有表的生产中 仅支持
KEY和LINEAR KEY分区方案NDB。 - 可以为任何
NDB表显式定义的最大分区数是 ,NDB Cluster 中的节点组数如本节前面所讨论的那样确定。为数据节点进程运行ndbd 时,设置 LDM 线程数无效(因为 仅适用于ndbmtd);在这种情况下,为了执行此计算,可以将此值视为等于 1。8 * [*number of LDM threads*] * [*number of node groups*]ThreadConfig
片段复制品。 这是集群分区的副本。节点组中的每个节点都存储一个分片副本。有时也称为 分区副本。分片副本数等于每个节点组的节点数。
一个分片副本完全属于单个节点;一个节点可以(并且通常确实)存储多个片段副本。
下图说明了一个 NDB 集群,它有四个运行ndbd 的数据节点,排列在两个节点组中,每个节点组有两个;节点 1 和 2 属于节点组 0,节点 3 和 4 属于节点组 1。
1 | 笔记 |
具有两个节点组的 NDB 集群

集群存储的数据分为四个分区,编号为 0、1、2 和 3。每个分区以多个副本的形式存储在同一个节点组上。分区存储在备用节点组上,如下所示:
- 分区 0 存储在节点组 0 上;一个 主副本片段 (主副本)被存储在节点1和一个 备份片段复制品 (分区的备份副本)被存储在节点2上。
- 分区1存储在另一个节点组(节点组1)上;该分区的主分片副本在节点 3 上,其备份分片副本在节点 4 上。
- Partition 2存储在节点组0上。但是,它的两个分片副本的放置与Partition 0相反;对于 Partition 2,主分片副本存储在节点 2 上,备份存储在节点 1 上。
- 分区3存储在节点组1上,它的两个分片副本的放置与分区1的位置相反,即其主分片副本位于节点4,备份位于节点3。
这对于 NDB Cluster 的持续运行意味着什么:只要参与 cluster 的每个节点组至少有一个节点在运行,则 cluster 拥有所有数据的完整副本并保持可用。下图中对此进行了说明。
2x2 NDB 集群所需的节点
在这个例子中,集群由两个节点组组成,每个节点组由两个数据节点组成。每个数据节点都运行一个ndbd实例。节点组 0 中的至少一个节点和节点组 1 中的至少一个节点的任意组合足以使集群保持“活跃”。但是,如果来自单个节点组的两个节点都出现故障,则由另一个节点组中剩余的两个节点组成的组合是不够的。在这种情况下,集群丢失了整个分区,因此无法再提供对所有 NDB 集群数据的完整集的访问。
单个 NDB Cluster 实例支持的最大节点组数为 48。
3. 硬件、软件和网络要求
NDB Cluster 的优势之一是它可以在商用硬件上运行,除了需要大量 RAM 外,在这方面没有异常要求,因为所有实时数据存储都在内存中完成。(可以使用磁盘数据表来减少这种需求
自然,多个更快的 CPU 可以提高性能。其他 NDB Cluster 进程的内存要求相对较小。
NDB Cluster 的软件要求也适中。主机操作系统不需要任何不寻常的模块、服务、应用程序或配置来支持 NDB Cluster。对于支持的操作系统,标准安装就足够了。MySQL 软件要求很简单:只需要 NDB Cluster 的生产版本。仅仅为了能够使用 NDB Cluster,自己编译 MySQL 并不是绝对必要的。我们假设您使用的是适合您平台的二进制文件,可从 NDB Cluster 软件下载页面获取
对于节点之间的通信,NDB Cluster 支持任何标准拓扑中的 TCP/IP 网络,每个主机的最低预期是一个标准的 100 Mbps 以太网卡,加上一个交换机、集线器或路由器,为整个集群提供网络连接. 我们强烈建议 NDB Cluster 在其自己的子网上运行,该子网不与不构成 cluster 一部分的机器共享,原因如下:
安全。 NDB Cluster 节点之间的通信未以任何方式加密或屏蔽。保护 NDB Cluster 内传输的唯一方法是在受保护的网络上运行 NDB Cluster。如果您打算将 NDB Cluster 用于 Web 应用程序,则该集群绝对应该位于您的防火墙后面,而不是位于您网络的非军事区 ( DMZ ) 或其他地方。
有关更多信息,请参阅 第 23.6.17.1 节,“NDB Cluster 安全性和网络问题”。
效率。 在私有或受保护网络上设置 NDB Cluster 使集群能够独占使用集群主机之间的带宽。为 NDB Cluster 使用单独的开关不仅有助于防止未经授权访问 NDB Cluster 数据,还确保 NDB Cluster 节点免受网络上其他计算机之间传输造成的干扰。为了增强可靠性,您可以使用双交换机和双卡来消除网络作为单点故障;许多设备驱动程序支持此类通信链接的故障转移。
网络通信和延迟。 NDB Cluster 需要数据节点和 API 节点(包括 SQL 节点)之间,以及数据节点和其他数据节点之间的通信,以执行查询和更新。这些进程之间的通信延迟可以直接影响观察到的用户查询性能和延迟。此外,为了在节点静默故障的情况下保持一致性和服务,NDB Cluster 使用心跳和超时机制将来自节点的长期通信丢失视为节点故障。这会导致冗余减少。回想一下,为了保持数据一致性,当节点组中的最后一个节点出现故障时,NDB Cluster 会关闭。因此,为了避免增加强制关机的风险,
数据或 API 节点的故障会导致所有涉及该故障节点的未提交事务中止。在数据节点恢复服务之前,数据节点恢复需要从幸存的数据节点同步故障节点的数据,并重新建立基于磁盘的重做和检查点日志。此恢复可能需要一些时间,在此期间集群以减少的冗余运行。
心跳依赖于所有节点及时产生心跳信号。如果节点过载,由于与其他程序共享而导致机器 CPU 不足,或者由于交换而遇到延迟,这可能无法实现。如果心跳生成被充分延迟,其他节点会将响应缓慢的节点视为失败。
在某些情况下,这种将慢速节点视为故障节点的处理方式可能是也可能不是可取的,这取决于节点的慢速操作对集群其余部分的影响。当设置超时值等 HeartbeatIntervalDbDb和 HeartbeatIntervalDbApi对NDB集群,必须小心照顾,以实现快速检测,故障转移和恢复服务,同时避免潜在的昂贵误报。
如果数据节点之间的通信延迟预计高于 LAN 环境中的预期(大约 100 µs),则必须增加超时参数以确保任何允许的延迟周期都在配置的超时范围内。以这种方式增加超时对检测故障的最坏情况时间以及服务恢复时间具有相应的影响。
LAN 环境通常可以配置为稳定的低延迟,这样它们就可以通过快速故障转移提供冗余。可以在 TCP 级别(NDB Cluster 正常运行的地方)可见的最小且受控的延迟下恢复单个链路故障。WAN 环境可能提供一系列延迟,以及故障转移时间较慢的冗余。单个链路故障可能需要在端到端连接恢复之前传播路由更改。在 TCP 级别,这可能表现为单个通道上的大延迟。在这些场景中观察到的最坏情况 TCP 延迟与 IP 层在故障周围重新路由的最坏情况时间有关。
4. 新特性
以下列表显示了 NDB Cluster 8.0 中可能引起关注的主要更改和新功能:
- 兼容性增强。
NDB与其他 MySQL 存储引擎相比, 以下更改减少了长期存在的非本质行为差异:- 与 MySQL 服务器并行开发。
- 平台支持说明。 NDB 8.0 在平台支持方面进行了以下更改:
NDBCLUSTER不再支持 32 位平台。从 NDB 8.0.21 开始,NDB 构建过程会检查系统架构,如果它不是 64 位平台则中止。- 现在可以
NDB从 64 位ARMCPU 的源代码构建。目前,此支持仅限源代码,我们不为此平台提供任何预编译的二进制文件。
- 数据库和表名。 NDB 8.0 删除了之前对数据库和表标识符的 63 字节限制。这些标识符现在最多可以使用 64 个字节,对于使用其他 MySQL 存储引擎的此类对象。请参阅 第 23.2.7.11 节,“在 NDB Cluster 8.0 中解决的以前的 NDB Cluster 问题”。
- 外键的生成名称。
NDB现在使用模式 来命名内部生成的外键。
- 架构和元数据分发和同步。 NDB 8.0 使用 MySQL 数据字典将架构信息分发给加入集群的 SQL 节点,并在现有 SQL 节点之间同步新的架构更改。以下列表描述了与此集成工作相关的各个增强功能:
- 架构分布增强。 该
NDB模式分配协调员,负责处理架构操作并跟踪他们的进展,已经在NDB 8.0被延长,以确保一个模式操作过程中使用的资源在它的结论被释放。以前,部分工作是由模式分发客户端完成的;由于客户端并不总是拥有所有需要的状态信息,这已经改变了,当客户端决定在完成之前放弃模式操作并且没有通知协调器时,这可能导致资源泄漏。
- 架构分布增强。 该
更多新特性可以参考官方文档:https://dev.mysql.com/doc/refman/8.0/en/mysql-cluster-what-is-new.html
5. 使用 InnoDB 的 MySQL 服务器与 NDB Cluster 的比较
MySQL Server 提供了多种存储引擎选择。由于双方NDB并 InnoDB可以作为事务的MySQL存储引擎,MySQL服务器的用户有时会感兴趣的NDB集群。他们认为 NDB可能的替代方案或升级到InnoDBMySQL 8.0 中的默认存储引擎。虽然NDB与 InnoDB有着共同的特点,也有在体系结构和实现的差异,使一些现有的MySQL服务器的应用程序和使用方案可以很好的适用于NDB簇,但不是所有的人。
在本节中,我们将讨论和比较NDBNDB 8.0InnoDB使用的存储引擎与MySQL 8.0 中使用的存储引擎的一些特性。接下来的几节提供了技术比较。在许多情况下,必须根据具体情况决定何时何地使用 NDB Cluster,并考虑所有因素。虽然为每个可能的使用场景提供细节超出了本文档的范围,但我们还尝试提供一些非常通用的指导,说明某些常见类型的应用程序NDB相对于 InnoDB后端的相对适用性 。
NDB 和 InnoDB 存储引擎之间的差异
该NDB存储引擎采用分布式,无共享架构,这会导致它从行为不同的方式实现 InnoDB在多种方式。对于那些不习惯使用 的人来说 NDB,由于其在事务、外键、表限制和其他特性方面的分布式特性,可能会出现意外行为。如下表所示:
InnoDB 和 NDB 存储引擎之间的差异
| 特征 | InnoDB (MySQL 8.0) |
NDB 8.0 |
|---|---|---|
| MySQL 服务器版本 | 8.0 | 8.0 |
InnoDB 版本 |
InnoDB 8.0.26 |
InnoDB 8.0.26 |
| 导航台集群版本 | 不适用 | NDB 8.0.27/8.0.27 |
| 存储限制 | 64TB | 128TB |
| 外键 | 是的 | 是的 |
| 事务 | 所有标准类型 | READ COMMITTED |
| MVCC | 是的 | 不 |
| 数据压缩 | 是的 | 否(可以压缩 NDB 检查点和备份文件) |
| 大行支持 (> 14K) | 支持VARBINARY, VARCHAR, BLOB,和 TEXT列 |
仅支持BLOB和 TEXT列(使用这些类型存储非常大量的数据会降低 NDB 性能) |
| 复制支持 | 使用 MySQL Replication 进行异步和半同步复制;MySQL组复制 | NDB Cluster 内的自动同步复制;NDB Clusters 之间的异步复制,使用 MySQL 复制(不支持半同步复制) |
| 读取操作的扩展 | 是(MySQL 复制) | 是(NDB Cluster 中的自动分区;NDB Cluster 复制) |
| 写入操作的扩展 | 需要应用程序级分区(分片) | 是(NDB Cluster 中的自动分区对应用程序是透明的) |
| 高可用性 (HA) | 内置,来自 InnoDB 集群 | 是(专为 99.999% 的正常运行时间而设计) |
| 节点故障恢复和故障转移 | 从 MySQL 组复制 | 自动(NDB 架构中的关键元素) |
| 节点故障恢复时间 | 30 秒或更长 | 通常 < 1 秒 |
| 实时性能 | 不 | 是的 |
| 内存表 | 不 | 是(一些数据可以选择存储在磁盘上;内存和磁盘数据存储都是持久的) |
| NoSQL 访问存储引擎 | 是的 | 是(多个 API,包括 Memcached、Node.js/JavaScript、Java、JPA、C++ 和 HTTP/REST) |
| 并发和并行写入 | 是的 | 多达 48 个写入器,针对并发写入进行了优化 |
| 冲突检测和解决(多源) | 是(MySQL 组复制) | 是的 |
| 哈希索引 | 不 | 是的 |
| 在线添加节点 | 使用 MySQL Group Replication 读/写副本 | 是(所有节点类型) |
| 在线升级 | 是(使用复制) | 是的 |
| 在线架构修改 | 是的,作为 MySQL 8.0 的一部分 | 是的 |
NDB 和 InnoDB 工作负载
NDB Cluster 具有一系列独特的属性,使其成为需要高可用性、快速故障转移、高吞吐量和低延迟的应用程序的理想选择。由于其分布式架构和多节点实现,NDB Cluster 也有特定的限制,可能会导致某些工作负载无法正常运行。下表显示了与某些常见类型的数据库驱动应用程序工作负载相关的存储引擎NDB和 InnoDB存储引擎之间行为的许多主要差异:
InnoDB 和 NDB 存储引擎之间的差异,数据驱动的应用程序工作负载的常见类型。
| 工作量 | InnoDB |
导航台集群 ( NDB) |
|---|---|---|
| 大容量 OLTP 应用程序 | 是的 | 是的 |
| DSS 应用程序(数据集市、分析) | 是的 | 有限(跨 OLTP 数据集的联接操作大小不超过 3TB) |
| 自定义应用程序 | 是的 | 是的 |
| 打包的应用程序 | 是的 | 受限(应该主要是主键访问);NDB Cluster 8.0 支持外键 |
| 网内电信应用(HLR、HSS、SDP) | 不 | 是的 |
| 会话管理和缓存 | 是的 | 是的 |
| 电子商务应用 | 是的 | 是的 |
| 用户配置文件管理,AAA 协议 | 是的 | 是的 |
NDB 和 InnoDB 功能使用总结
在将应用程序功能要求InnoDB与 with的功能进行比较时 NDB,有些与一种存储引擎的兼容性明显高于另一种。
下表根据每个功能通常更适合的存储引擎列出了支持的应用程序功能。
表 23.4 根据每个功能通常更适合的存储引擎支持的应用程序功能
首选申请要求 InnoDB
外键
笔记
NDB Cluster 8.0 支持外键
全表扫描
非常大的数据库、行或事务
以外的交易
READ COMMITTED
首选申请要求 NDB
- 写缩放
- 99.999% 的正常运行时间
- 在线添加节点和在线模式操作
- 多个 SQL 和 NoSQL API(请参阅 NDB Cluster API:概述和概念)
- 实时性能
- 限制使用
BLOB列 - 支持外键,尽管它们的使用可能会影响高吞吐量下的性能
6. NDB Cluster 的已知限制
与某些 MySQL 功能相关的一些 SQL 语句在与NDB表一起使用时会产生错误,如下表所述:
临时表。 不支持临时表。尝试创建使用
NDB存储引擎的临时表 或更改要使用的现有临时表NDB失败,并显示错误 表存储引擎 ‘ndbcluster’ 不支持创建选项 ‘TEMPORARY’。NDB 表中的索引和键。 NDB Cluster 表上的键和索引受到以下限制:
列宽。 尝试在
NDB宽度大于 3072 字节的表列上创建索引会 成功,但实际上只有前 3072 字节用于索引。在这种情况下,警告指定的键太长;max key length is 3072 bytes发出,一条SHOW CREATE TABLE语句显示索引的长度为3072。使用 HASH 键和 NULL。 在唯一键和主键中使用可为空的列意味着使用这些列的查询将作为全表扫描处理。要解决此问题,请创建 column
NOT NULL,或重新创建不带该USING HASH选项的索引。前缀。 没有前缀索引;只能索引整个列。(
NDB列索引的大小始终与列的宽度(以字节为单位)相同,最多包括 3072 字节,如本节前面所述。另请参阅 第 23.2.7.6 节,“NDB Cluster 中不支持或缺少的功能” , 以获取更多信息。)位列。 甲
BIT列不能是一个主键,唯一键,或指数,也不能是复合主键,唯一键,或索引的一部分。AUTO_INCREMENT 列。 与其他 MySQL 存储引擎一样,
NDB存储引擎AUTO_INCREMENT每个表最多可以处理一 列。但是,对于没有显式主键的 NDB 表,AUTO_INCREMENT列会自动定义并用作 “隐藏”主键。因此,您不能定义具有显式AUTO_INCREMENT列的表, 除非该列也使用该PRIMARY KEY选项声明。试图创建一个表AUTO_INCREMENT不是表的主键的列,并且使用NDB存储引擎,失败并出现错误。
外键限制。 NDB 8.0 中对外键约束的支持与 提供的类似
InnoDB,但受以下限制:如果它不是表的主键,则作为外键引用的每一列都需要一个显式的唯一键。
ON UPDATE CASCADE当引用是父表的主键时不支持。这是因为主键的更新是通过删除旧行(包含旧主键)加上插入新行(带有新主键)来实现的。这对
NDB内核来说是不可见的 ,它认为这两行是相同的,因此无法知道这个更新应该是级联的。ON DELETE CASCADE如果子表包含一个或多个TEXT或BLOB类型的任何列,也不支持 。(错误 #89511,错误 #27484882)SET DEFAULT不支持。(也不支持InnoDB。)该
NO ACTION关键字被接受,但作为治疗RESTRICT。NO ACTION是标准 SQL 关键字,是 MySQL 8.0 中的默认关键字。(也与 相同InnoDB。)在 NDB Cluster 的早期版本中,当使用外键引用另一个表中的索引创建表时,即使索引中列的顺序不匹配,有时似乎也可以创建外键,因为适当的错误并不总是在内部返回。此问题的部分修复改进了在大多数情况下内部使用的错误;但是,如果父索引是唯一索引,这种情况仍有可能发生。(错误#18094360)
有关更多信息,请参阅 第 13.1.20.5 节“外键约束”和 第 1.7.3.2 节“外键约束”。
NDB Cluster 和几何数据类型。 表支持 几何数据类型(
WKT和WKB)NDB。但是,不支持空间索引。字符集和二进制日志文件。 目前,
ndb_apply_status和ndb_binlog_index表是使用latin1(ASCII) 字符集创建的。由于此表中记录了二进制日志的名称,因此这些表中未正确引用使用非拉丁字符命名的二进制日志文件。这是一个已知问题,我们正在努力解决。(错误#50226)要解决此问题,命名二进制日志文件或设置任何时候只使用Latin-1字符
--basedir,--log-bin或--log-bin-index选择。使用用户定义的分区创建 NDB 表。 NDB Cluster 中对用户定义分区的支持仅限于 [
LINEAR]KEY分区。在 语句中ENGINE=NDB或 语句中使用任何其他分区类型都会导致错误。ENGINE=NDBCLUSTERCREATE TABLE可以覆盖此限制,但不支持在生产设置中使用此限制。有关详细信息,请参阅用户定义的分区和 NDB 存储引擎(NDB Cluster)。
默认分区方案。 默认情况下,所有 NDB Cluster 表都
KEY使用表的主键作为分区键进行分区。如果没有为表显式设置主键,则使用存储引擎自动创建的“隐藏”主键NDB。有关这些和相关问题的其他讨论,请参阅第 24.2.5 节,“密钥分区”。CREATE TABLE不允许ALTER TABLE使用和 语句导致用户分区NDBCLUSTER表不满足以下两个要求中的一个或两个,并且会失败并显示错误:- 该表必须具有显式主键。
- 表分区表达式中列出的所有列都必须是主键的一部分。
例外。 如果
NDBCLUSTER使用空列列表(即使用PARTITION BY [LINEAR] KEY())创建用户分区 表 ,则不需要显式主键。NDBCLUSTER 表的最大分区数。
NDBCLUSTER使用用户定义分区时 ,可以为表定义的最大分区数为 每个节点组 8 个。(有关NDB Cluster 节点组的更多信息,请参阅第 23.2.2 节,“NDB Cluster 节点、节点组、片段副本和分区”。不支持删除分区。 无法使用 删除
NDB表中的 分区ALTER TABLE ... DROP PARTITION。NDB 表支持对ALTER TABLE—ADD PARTITION、REORGANIZE PARTITION和COALESCE PARTITION—的其他分区扩展 ,但使用复制等未优化。请参见第 24.3.1 节,“RANGE 和 LIST 分区的管理” 和第 13.1.9 节,“ALTER TABLE 语句”。JSON 数据类型。 NDB 8.0 提供的mysqld 中的表
JSON支持 MySQL数据类型。NDB一个
NDB表最多可以有 3JSON列。NDB API 没有处理
JSON数据的特殊规定 ,它只是将BLOB数据视为 数据。处理数据JSON必须由应用程序执行。
NDB Cluster 错误处理
启动、停止或重新启动节点可能会引起临时错误,从而导致某些事务失败。这些包括以下情况:
- 临时错误。 首次启动节点时,您可能会看到错误 1204临时故障、分发更改和类似的临时错误。
- 节点故障导致的错误。 任何数据节点的停止或故障都可能导致许多不同的节点故障错误。(但是,在执行计划的集群关闭时,不应有中止的事务。)
在这两种情况中的任何一种情况下,生成的任何错误都必须在应用程序中进行处理。这应该通过重试事务来完成。
NDB Cluster 中不支持或缺少的功能
NDB表 不支持其他存储引擎支持的许多功能。尝试在 NDB Cluster 中使用这些功能中的任何一个都不会导致本身或本身的错误;但是,在期望支持或强制执行这些功能的应用程序中可能会发生错误。引用这些特性的语句,即使被 有效地忽略 NDB,也必须在语法上和其他方面有效。
索引前缀。
NDB表 不支持索引前缀 。如果前缀被用作诸如语句索引规范的一部分CREATE TABLE,ALTER TABLE或者CREATE INDEX,不被创建的前缀NDB。包含索引前缀和创建或修改
NDB表的语句在语法上仍然必须有效。例如,以下语句总是失败,错误 1089前缀键不正确;使用的键部分不是字符串,使用的长度比键部分长,或者存储引擎不支持唯一前缀键,无论存储引擎如何:1
2
3
4
5CREATE TABLE t1 (
c1 INT NOT NULL,
c2 VARCHAR(100),
INDEX i1 (c2(500))
);发生这种情况的原因是 SQL 语法规则,即没有索引的前缀可能大于自身。
保存点和回滚。 保存点和回滚到保存点将被忽略,如
MyISAM.提交的持久性。 磁盘上没有持久提交。提交被复制,但不能保证日志在提交时刷新到磁盘。
复制。 不支持基于语句的复制。在设置集群复制时使用
--binlog-format=ROW(或--binlog-format=MIXED)。有关更多信息,请参阅 第 23.7 节,“NDB Cluster 复制”。使用全局事务标识符 (GTID) 的复制与 NDB Cluster 不兼容,并且在 NDB Cluster 8.0 中不受支持。使用
NDB存储引擎时不要启用 GTID ,因为这很可能导致问题,包括 NDB Cluster 复制失败。NDB Cluster 不支持半同步复制。
生成的列。 该
NDB存储引擎不支持虚拟生成列的索引。与其他存储引擎一样,您可以在存储的生成列上创建索引,但您应该记住,
NDB它DataMemory用于存储生成列以及IndexMemory索引。有关示例,请参阅 NDB Cluster 中的 JSON 列和间接索引。NDB Cluster 将存储的生成列中的更改写入二进制日志,但不记录对虚拟列所做的更改。这不应影响 NDB Cluster 复制或
NDB与其他 MySQL 存储引擎之间的复制。
与 NDB 集群中的性能相关的限制
以下性能问题特定于 NDB Cluster 或在 NDB Cluster 中特别明显:
- 范围扫描。 由于对
NDB存储引擎的顺序访问,存在查询性能问题;与使用MyISAM或 中的任一个相比,进行多次范围扫描的成本也相对更高InnoDB。 - 范围内记录的可靠性。 该
Records in range统计数据可用,但尚未经过完全测试或官方支持。在某些情况下,这可能会导致非最佳查询计划。如有必要,您可以使用USE INDEX或FORCE INDEX更改执行计划。有关如何执行此操作的更多信息,请参阅第 8.9.4 节,“索引提示”。 - 唯一的哈希索引。
USING HASH如果NULL作为键的一部分给出,则 创建的唯一哈希索引不能用于访问表 。
NDB Cluster 独有的问题
以下是特定于NDB存储引擎的限制 :
机器架构。 集群中使用的所有机器必须具有相同的架构。也就是说,所有托管节点的机器都必须是 big-endian 或 little-endian,并且您不能混合使用两者。例如,您不能在 PowerPC 上运行管理节点,该管理节点指导在 x86 机器上运行的数据节点。此限制不适用于仅运行mysql 的机器 或可能访问集群 SQL 节点的其他客户端。
二进制日志。 NDB Cluster 对二进制日志有以下限制或限制:
sql_log_bin对数据操作没有影响;但是,它支持模式操作。- NDB Cluster 无法为具有
BLOB列但没有主键的表生成二进制日志。 - 只有以下模式操作记录在集群二进制日志中,该日志不在执行语句的mysqld 上:
模式操作。 当任何数据节点重新启动时,模式操作(DDL 语句)将被拒绝。执行在线升级或降级时也不支持架构操作。
片段副本数。 由
NoOfReplicas数据节点配置参数确定的片段副本 数是 NDB Cluster 存储的所有数据的副本数。将此参数设置为 1 表示只有一个副本;在这种情况下,没有提供冗余,并且数据节点的丢失导致数据丢失。为了保证冗余,从而即使数据节点发生故障也能保留数据,请将此参数设置为 2,这是生产中的默认值和推荐值。支持设置
NoOfReplicas为大于 2 的值(最多为 4),但不需要防止数据丢失。
与多个 NDB Cluster 节点相关的限制
多个 SQL 节点。 以下是与使用多个 MySQL 服务器作为 NDB Cluster SQL 节点相关的问题,并且特定于 NDBCLUSTER存储引擎:
- 未分发的存储程序。 使用
NDB存储引擎的表都支持存储过程、存储函数、触发器和计划事件 ,但这些不会在充当 Cluster SQL 节点的 MySQL 服务器之间自动传播,必须在每个 SQL 节点上单独重新创建。请参阅 NDB Cluster 中的存储例程和触发器。 - 没有分布式表锁。 A
LOCK TABLES只对发出锁的 SQL 节点起作用;集群中没有其他 SQL 节点“看到”这个锁。对于将表作为其操作的一部分进行锁定的任何语句发出的锁也是如此。(有关示例,请参见下一项。) - ALTER TABLE 操作。
ALTER TABLE运行多个 MySQL 服务器(SQL 节点)时未完全锁定。(如上一条所述,NDB Cluster 不支持分布式表锁。)
多个管理节点。 使用多个管理服务器时:
- 如果任何管理服务器在同一主机上运行,则必须在连接字符串中为节点提供明确的 ID,因为节点 ID 的自动分配不能在同一主机上的多个管理服务器之间工作。如果每个管理服务器都驻留在不同的主机上,则不需要这样做。
- 当管理服务器启动时,它首先检查同一 NDB Cluster 中的任何其他管理服务器,并在成功连接到其他管理服务器后使用其配置数据。这意味着管理服务器
--reload和--initial启动选项将被忽略,除非管理服务器是唯一运行的服务器。这也意味着,当对具有多个管理节点的 NDB Cluster 执行滚动重启时,如果(且仅当)它是此 NDB Cluster 中运行的唯一管理服务器,则管理服务器会读取自己的配置文件。请参阅 第 23.6.5 节,“执行 NDB Cluster 的滚动重启”, 想要查询更多的信息。
多个网络地址。 不支持每个数据节点有多个网络地址。使用这些可能会导致问题:在数据节点发生故障的情况下,SQL 节点等待确认数据节点已关闭但从未收到它,因为通往该数据节点的另一条路由保持打开状态。这可以有效地使集群无法运行。
11.3 NDB 集群安装
本节描述了规划、安装、配置和运行 NDB Cluster 的基础知识。尽管第 23.4 节“NDB 集群的配置”中的示例 提供了有关各种集群选项和配置的更深入的信息,但遵循此处概述的指南和程序的结果应该是一个可用的 NDB 集群,它满足以下 最低要求数据的可用性和保护。
有关在发布版本之间升级或降级 NDB Cluster 的信息,请参阅 第 23.3.7 节,“升级和降级 NDB Cluster”。
本节涵盖硬件和软件要求;网络问题;安装 NDB 集群;基本配置问题;启动、停止和重启集群;加载示例数据库;并执行查询。
NDB Cluster 还提供 NDB Cluster Auto-Installer(现已弃用),这是一个基于 Web 的图形安装程序,作为 NDB Cluster 发行版的一部分。Auto-Installer 可用于在一台(用于测试)或多台主机上执行 NDB Cluster 的基本安装和设置。有关更多信息,请参阅 第 23.3.8 节,“NDB Cluster 自动安装程序(不再支持)”。
假设。 以下部分对集群的物理和网络配置进行了一些假设。这些假设将在接下来的几段中讨论。
群集节点和主机计算机。 集群由四个节点组成,每个节点都在一个单独的主机上,每个节点在典型的以太网网络上都有一个固定的网络地址,如下所示:
示例集群中节点的网络地址
| 节点 | IP地址 |
|---|---|
| 管理节点(mgmd) | 198.51.100.10 |
| SQL 节点 ( mysqld ) | 198.51.100.20 |
| 数据节点“A”(ndbd) | 198.51.100.30 |
| 数据节点“B”(ndbd) | 198.51.100.40 |
NDB Cluster 多计算机设置

网络寻址。 在简单(和可靠性)的利益,这种 操作方法仅使用数字IP地址。但是,如果 DNS 解析在您的网络上可用,则可以在配置集群时使用主机名代替 IP 地址。或者,您可以使用该hosts 文件
潜在的主机文件问题。 由于某些操作系统(包括某些 Linux 发行版)/etc/hosts在安装过程中设置系统自己的主机名的方式,尝试将主机名用于集群节点时会出现一个常见问题。考虑两台具有主机名ndb1和 的机器 ndb2,它们都在 cluster网络域中。Red Hat Linux(包括 CentOS 和 Fedora 等一些衍生产品)在这些机器的/etc/hosts文件中放置以下条目 :
1 | # ndb1 /etc/hosts: |
网络硬件。 每台机器上都安装了标准的 100 Mbps 或 1 Gb 以太网卡,以及适当的卡驱动程序,并且所有四台主机都通过标准发布的以太网网络设备(例如交换机)连接。(所有机器应该使用具有相同吞吐量的网卡。也就是说,集群中的四台机器都应该有 100 Mbps 的卡, 或者所有四台机器都应该有 1 Gbps 的卡。) NDB Cluster 工作在 100 Mbps 的网络中;然而,千兆以太网提供了更好的性能。
在 Linux 上安装 NDB Cluster
本节介绍了 Linux 和其他类 Unix 操作系统上 NDB Cluster 的安装方法。虽然接下来的几节涉及 Linux 操作系统,但那里给出的说明和过程应该很容易适应其他受支持的类 Unix 平台。有关特定于 Windows 系统的手动安装和设置说明,请参阅 第 23.3.2 节,“在 Windows 上安装 NDB Cluster”。
每个 NDB Cluster 主机计算机都必须安装正确的可执行程序。运行 SQL 节点的主机必须在其上安装了 MySQL 服务器二进制文件 ( mysqld )。管理节点需要管理服务器守护进程(ndb_mgmd);数据节点需要数据节点守护程序(ndbd或ndbmtd)。无需在管理节点主机和数据节点主机上安装 MySQL Server 二进制文件。建议您还在管理服务器主机上安装管理客户端 ( ndb_mgm )。
在 Linux 上安装 NDB Cluster 可以使用来自 Oracle 的预编译二进制文件(作为 .tar.gz 存档下载)、RPM 包(也可以从 Oracle 获得)或源代码来完成。所有这三种安装方法都在下面的部分中进行了描述。
无论使用哪种方法,在启动 cluster 之前,仍然需要在安装 NDB Cluster 二进制文件后为所有 cluster 节点创建配置文件。
具体安装方式可以参考官网:https://dev.mysql.com/doc/refman/8.0/en/mysql-cluster-install-linux-binary.html
11.4 NDB Cluster 的配置
可以参考官网:https://dev.mysql.com/doc/refman/8.0/en/mysql-cluster-install-configuration.html
https://dev.mysql.com/doc/refman/8.0/en/mysql-cluster-configuration.html
11.5 NDB Cluster 复制
NDB Cluster 支持异步复制,通常简称为 “复制”。本节说明如何设置和管理配置,在该配置中,一组作为 NDB Cluster 运行的计算机复制到第二台计算机或一组计算机。我们假设读者对本手册其他地方讨论的标准 MySQL 复制有一定的了解。(参见第 17 章,复制)。
笔记
NDB Cluster 不支持使用 GTID 进行复制;NDB存储引擎也不支持半同步复制和组复制。
正常(非集群)复制涉及源服务器和副本服务器,源之所以如此命名是因为要复制的操作和数据源自它,而副本是这些的接收者。在 NDB Cluster 中,复制在概念上非常相似,但在实践中可能更复杂,因为它可以扩展以涵盖许多不同的配置,包括在两个完整集群之间进行复制。尽管 NDB Cluster 本身依赖于NDB集群功能的存储引擎,但它没有必要 NDB用作复制表副本的副本的存储引擎(请参阅 从 NDB 复制到其他存储引擎)。但是,为了获得最大可用性,可以(并且更可取)从一个 NDB Cluster 复制到另一个集群,我们讨论的正是这种情况,如下图所示:
NDB Cluster-to-Cluster 复制布局

在这种情况下,复制过程是记录源集群的连续状态并将其保存到副本集群的过程。此过程由称为 NDB 二进制日志注入器线程的特殊线程完成,该线程在每个 MySQL 服务器上运行并生成二进制日志 ( binlog)。该线程确保生成二进制日志的集群中的所有更改——而不仅仅是那些通过 MySQL 服务器影响的更改——以正确的序列化顺序插入到二进制日志中。我们将 MySQL 源服务器和副本服务器称为复制服务器或复制节点,将它们之间的数据流或通信线路称为 复制通道。
12. Mysql 语句学习
12.1 基础教程篇
先创建测试表插入数据
1 | CREATE TABLE shop ( |
任务:找出最贵商品的数量、经销商和价格。
这可以通过子查询轻松完成:
1 | SELECT article, dealer, price |
其他解决方案是使用 LEFT JOIN 或者 按价格降序对所有行进行排序,并使用 MySQL 特定的LIMIT子句仅获取第一行:
1 | SELECT s1.article, s1.dealer, s1.price |
1 | SELECT article, dealer, price |
任务:找出每篇文章的最高价格。
1 | SELECT article, MAX(price) AS price |
任务:对于每件商品,找到价格最贵的经销商。
1 | SELECT article, dealer, price |
前面的示例使用相关子查询,这可能效率低下(请参阅第 13.2.10.7 节,“相关子查询”)。解决该问题的其他可能性是在FROM子句或 a 中 使用不相关的子查询LEFT JOIN。
1 | SELECT s1.article, dealer, s1.price |
LEFT JOIN:
1 | SELECT s1.article, s1.dealer, s1.price |
其LEFT JOIN工作原理是当 s1.price为最大值时,没有 s2.price更大的值,因此对应的s2.article值为 NULL。见第 13.2.9.2 节,“JOIN 子句”。
12.2 数据定义语句
1. 原子数据定义语句支持
MySQL 8.0 支持原子数据定义语言 (DDL) 语句。此功能称为原子 DDL。原子 DDL 语句将与 DDL 操作关联的数据字典更新、存储引擎操作和二进制日志写入合并为一个原子操作。该操作要么被提交,适用的更改将保留到数据字典、存储引擎和二进制日志中,要么被回滚,即使服务器在操作期间停止。
1 | 笔记 |
通过在 MySQL 8.0 中引入 MySQL 数据字典,原子 DDL 成为可能。在早期的 MySQL 版本中,元数据存储在元数据文件、非事务表和特定于存储引擎的字典中,这需要中间提交。MySQL 数据字典提供的集中式事务元数据存储消除了这一障碍,使得将 DDL 语句操作重构为原子性成为可能。
2. ALTER DATABASE 语句
1 | ALTER {DATABASE | SCHEMA} [db_name] |
ALTER DATABASE使您能够更改数据库的整体特征。这些特征存储在数据字典中。此语句需要ALTER对数据库的特权。ALTER SCHEMA是 的同义词ALTER DATABASE。
如果省略数据库名称,则该语句适用于默认数据库。在这种情况下,如果没有默认数据库,则会发生错误。
一条ALTER DATABASE语句会阻塞,直到所有已经访问了正在更改的数据库中的对象的并发事务都已提交。相反,访问数据库中对象的写入事务在并发ALTER DATABASE 块中被更改,直到ALTER DATABASE已提交。
如果克隆插件用于克隆本地或远程数据目录,克隆中的数据库将保留它们在源数据目录中的只读状态。只读状态不影响克隆过程本身。如果不希望在克隆中具有相同的数据库只读状态,则必须在克隆过程完成后使用ALTER DATABASE 对克隆的操作为克隆显式更改该选项。
3. ALTER EVENT 语句
1 | ALTER |
该ALTER EVENT语句更改现有事件的一个或多个特征,而无需删除和重新创建它。每个DEFINER、ON SCHEDULE、 ON COMPLETION、COMMENT、 ENABLE/DISABLE和 DO子句的语法 与使用 with 时完全相同CREATE EVENT。
任何用户都可以更改在该用户拥有EVENT权限的数据库上定义的事件。当用户执行成功的ALTER EVENT语句时,该用户将成为受影响事件的定义者。
ALTER EVENT 仅适用于现有事件:
1 | mysql> ALTER EVENT no_such_event |
在以下每个示例中,假设命名事件 myevent的定义如下所示:
1 | CREATE EVENT myevent |
以下语句将计划myevent从立即开始的每六小时一次更改为 每十二小时一次,从语句运行时间开始的四小时开始:
1 | ALTER EVENT myevent |
可以在单个语句中更改事件的多个特征。此示例将执行的 SQL 语句更改为myevent从 中删除所有记录 的 SQL 语句mytable;它还更改事件的时间表,使其在此ALTER EVENT语句运行后一天执行一次 。
1 | ALTER EVENT myevent |
4. ALTER FUNCTION 语句
1 | ALTER FUNCTION func_name [characteristic ...] |
此语句可用于更改存储函数的特性。一个ALTER FUNCTION语句中可以指定多个更改 。但是,您不能使用此语句更改存储函数的参数或主体;要进行此类更改,您必须删除并使用DROP FUNCTION和重新创建函数CREATE FUNCTION。
您必须具有ALTER ROUTINE 该功能的权限。(该权限会自动授予函数创建者。)如果启用了二进制日志记录,则该ALTER FUNCTION 语句可能还需要该 SUPER权限
5. ALTER INSTANCE 语句
1 | ALTER INSTANCE instance_action |
ALTER INSTANCE定义适用于 MySQL 服务器实例的操作。该声明支持以下行动:
ALTER INSTANCE {ENABLE | DISABLE} INNODB REDO_LOG此操作启用或禁用
InnoDB重做日志记录。默认情况下启用重做日志记录。此功能仅用于将数据加载到新的 MySQL 实例中。该语句不会写入二进制日志。此操作是在 MySQL 8.0.21 中引入的。1
2警告
不要在生产系统上禁用重做日志记录。虽然在禁用重做日志记录时允许关闭和重新启动服务器,但在禁用重做日志记录时服务器意外停止会导致数据丢失和实例损坏。
ALTER INSTANCE ROTATE INNODB MASTER KEY
此操作轮换用于
InnoDB表空间加密的主加密密钥 。密钥轮换需要ENCRYPTION_KEY_ADMIN或SUPER权限。要执行此操作,必须安装和配置密钥环插件。ALTER INSTANCE ROTATE BINLOG MASTER KEY
此操作轮换用于二进制日志加密的二进制日志主密钥。
ALTER INSTANCE RELOAD TLS
此操作根据定义上下文的系统变量的当前值重新配置 TLS 上下文。它还更新反映活动上下文值的状态变量。
``` ALTER INSTANCE RELOAD KEYRING
1
2
3
4
5
6
7
8
9
10
11
如果安装了密钥环组件,此操作会告知该组件重新读取其配置文件并重新初始化任何密钥环内存数据。
#### 6. ALTER LOGFILE GROUP 语句
```sql
ALTER LOGFILE GROUP logfile_group
ADD UNDOFILE 'file_name'
[INITIAL_SIZE [=] size]
[WAIT]
ENGINE [=] engine_name
此语句将UNDO名为“ *file_name”的文件添加到现有日志文件组logfile_group*。一个 ALTER LOGFILE GROUP语句只有一个ADD UNDOFILE子句。没有 DROP UNDOFILE当前支持条款。
1 | 笔记 |
7. ALTER PROCEDURE 语句
1 | ALTER PROCEDURE proc_name [characteristic ...] |
此语句可用于更改存储过程的特性。一个ALTER PROCEDURE语句中可以指定多个更改 。但是,您不能使用此语句更改存储过程的参数或主体;要进行此类更改,您必须删除并使用DROP PROCEDURE和重新创建过程CREATE PROCEDURE。
您必须拥有ALTER ROUTINE 该过程的特权。默认情况下,该权限会自动授予过程创建者。可以通过禁用automatic_sp_privileges系统变量来更改此行为 。
8. ALTER SERVER 语句
1 | ALTER SERVER server_name |
更改 的服务器信息 *server_name*,调整CREATE SERVER语句中允许的任何选项 。mysql.servers表中的相应字段会相应更新。此语句需要 SUPER特权。
例如,要更新USER选项:
1 | ALTER SERVER s OPTIONS (USER 'sally'); |
ALTER SERVER导致隐式提交。
9. ALTER TABLE 语句
1 | ALTER TABLE tbl_name |
ALTER TABLE改变表的结构。例如,您可以添加或删除列、创建或销毁索引、更改现有列的类型或重命名列或表本身。您还可以更改特征,例如用于表或表注释的存储引擎。
表选项
*table_options*意味着可以在可以使用的一种表选项CREATE TABLE语句,比如ENGINE, AUTO_INCREMENT, AVG_ROW_LENGTH,MAX_ROWS, ROW_FORMAT,或TABLESPACE。
使用表选项ALTER TABLE提供了一种改变单个表特征的便捷方式。例如:
如果
t1当前不是InnoDB表,则此语句将其存储引擎更改为InnoDB:1
ALTER TABLE t1 ENGINE = InnoDB;
要将
InnoDB表更改为使用压缩行存储格式:1
ALTER TABLE t1 ROW_FORMAT = COMPRESSED;
该
ENCRYPTION子句为InnoDB表启用或禁用页级数据加密。必须安装并配置密钥环插件以启用加密。要重置当前的自动增量值:
1
ALTER TABLE t1 AUTO_INCREMENT = 13;
您不能将计数器重置为小于或等于当前使用的值的值。对于
InnoDB和MyISAM,如果该值小于或等于当前AUTO_INCREMENT列中的最大值,则该值将重置为当前最大AUTO_INCREMENT列值加一。要更改默认表字符集:
1
ALTER TABLE t1 CHARACTER SET = utf8;
添加(或更改)表格注释:
1
ALTER TABLE t1 COMMENT = 'New table comment';
ALTER TABLE与在现有通用表空间、 每个表文件表 空间和 系统表空间之间TABLESPACE移动InnoDB表 的 选项一起 使用。MySQL NDB Cluster 8.0 支持设置
NDB_TABLE选项来控制表的分区平衡(片段计数类型)、从任何副本读取功能、完整复制或这些的任何组合,作为ALTER TABLE语句的表注释的一部分, 以相同的方式至于CREATE TABLE,如本例所示:1
ALTER TABLE t1 COMMENT = "NDB_TABLE=READ_BACKUP=0,PARTITION_BALANCE=FOR_RA_BY_NODE";
ENGINE_ATTRIBUTE和SECONDARY_ENGINE_ATTRIBUTE选项(从 MySQL 8.0.21 开始可用)用于指定主存储引擎和辅助存储引擎的表、列和索引属性。这些选项保留供将来使用。不能更改索引属性。必须删除索引,然后将其添加回所需的更改,这可以在单个ALTER TABLE语句中执行。
性能和空间要求
ALTER TABLE 使用以下算法之一处理操作:
COPY:对原表的一个副本进行操作,将表数据从原表逐行复制到新表中。不允许并发 DML。INPLACE:操作避免复制表数据,但可能会就地重建表。在操作的准备和执行阶段可能会短暂地对表进行独占元数据锁定。通常,支持并发 DML。INSTANT:操作只修改数据字典中的元数据。在准备和执行过程中不对表采取独占元数据锁,表数据不受影响,操作瞬间完成。允许并发 DML。(在 MySQL 8.0.12 中引入)
该ALGORITHM条款是可选的。如果ALGORITHM省略该 子句,MySQL 使用 支持它的ALGORITHM=INSTANT存储引擎和 ALTER TABLE子句。否则,ALGORITHM=INPLACE使用。如果 ALGORITHM=INPLACE不支持, ALGORITHM=COPY则使用。
指定ALGORITHM子句要求操作对支持它的子句和存储引擎使用指定的算法,否则将失败并显示错误。指定ALGORITHM=DEFAULT与省略ALGORITHM子句相同。
ALTER TABLE使用该 COPY算法的操作等待正在修改表的其他操作完成。对表副本应用更改后,将数据复制过来,删除原始表,并将表副本重命名为原始表的名称。当ALTER TABLE 操作执行时,原始表可以被其他会话读取(除了很快指出的例外)。ALTER TABLE操作开始后开始的表的更新和写入将暂停,直到新表准备好,然后自动重定向到新表。该表的临时副本是在原表的数据库目录中创建的,除非它是一个RENAME TO 将表移动到驻留在不同目录中的数据库的操作。
前面提到的例外是 ALTER TABLE在准备从表和表定义缓存中清除过时的表结构时阻止读取(不仅仅是写入)。此时,它必须获取排他锁。为此,它等待当前读取器完成,并阻止新的读取和写入。
ALTER TABLE使用该COPY算法 的操作会阻止并发 DML 操作。仍然允许并发查询。也就是说,表复制操作始终至少包括LOCK=SHARED(允许查询但不允许 DML)的并发限制。您可以LOCK通过指定来进一步限制支持子句的 操作的并发性LOCK=EXCLUSIVE,这会阻止 DML 和查询。有关更多信息,请参阅 并发控制。
要强制将COPY算法用于 ALTER TABLE否则不会使用它的操作,请指定ALGORITHM=COPY或启用old_alter_table系统变量。如果old_alter_table设置与 ALGORITHM值不是 DEFAULT的ALGORITHM 子句之间存在冲突 ,则该子句优先。
对于InnoDB表,对位于共享表空间中的表ALTER TABLE使用该COPY算法 的 操作 可以增加表空间使用的空间量。此类操作需要与表中的数据加上索引一样多的额外空间。对于驻留在共享表空间中的表,操作期间使用的额外空间不会释放回操作系统,就像驻留在每个表文件表 空间中的表一样。
ALTER TABLE支持该INPLACE算法的操作包括:
ALTER TABLE``InnoDB在线 DDL功能支持的操作 。请参阅 第 15.12.1 节,“在线 DDL 操作”。重命名表。MySQL 重命名与表对应的文件,*
tbl_name*而不进行复制。(您也可以使用该RENAME TABLE语句重命名表。请参阅 第 13.1.36 节,“RENAME TABLE 语句”。)专门为重命名的表授予的权限不会迁移到新名称。它们必须手动更改。仅修改表元数据的操作。这些操作是即时的,因为服务器不接触表内容。仅元数据操作包括:
重命名列。在 NDB Cluster 8.0.18 及更高版本中,也可以在线执行此操作。
更改列的默认值(
NDB表除外 )。通过 在有效成员值列表的末尾添加新的枚举或集合成员来 修改
ENUM或SET列的定义 ,只要数据类型的存储大小不改变。例如,向具有 8 个成员的列添加成员将每个值所需的存储空间从 1 个字节更改为 2 个字节;这需要一个表副本。在列表中间添加成员会导致现有成员重新编号,这需要表副本。SET更改空间列的定义以删除
SRID属性。(添加或更改SRID属性确实需要重新构建并且无法就地完成,因为服务器必须验证所有值是否都具有指定的 SRID 值。)从 MySQL 8.0.14 开始,当这些条件适用时,更改列字符集:
从 MySQL 8.0.14 开始,当这些条件适用时,更改生成的列:
- 对于
InnoDB表,修改生成的存储列但不更改其类型、表达式或可为空性的语句。 - 对于非
InnoDB表,修改生成的存储或虚拟列但不更改它们的类型、表达式或可为空性的语句。
此类更改的一个示例是对列注释的更改。
- 对于
重命名索引。
添加或删除二级索引,用于
InnoDB和NDB表。请参阅 第 15.12.1 节,“在线 DDL 操作”。对于
NDB表,在可变宽度列上添加和删除索引的操作。这些操作在线进行,无需复制表,也不会在大部分持续时间内阻塞并发 DML 操作。请参阅第 23.6.11 节,“在 NDB Cluster 中使用 ALTER TABLE 进行在线操作”。使用
ALTER INDEX操作修改索引可见性。DEFAULT如果生成的列表达式中不涉及修改的列,则 包含依赖于具有值的列的生成列的表的列修改。例如,更改NULL单独列的 属性可以就地完成,而无需重建表。
ALTER TABLE支持该INSTANT算法的操作 包括:
- 添加一列。此功能称为“即时
ADD COLUMN”。限制适用。请参阅 第 15.12.1 节,“在线 DDL 操作”。 - 添加或删除虚拟列。
- 添加或删除列默认值。
- 修改
ENUM或SET列的定义 。与上述相同的限制适用于ALGORITHM=INSTANT。 - 更改索引类型。
- 重命名表。与上述相同的限制适用于
ALGORITHM=INSTANT。
添加和删除列
用于ADD向表中添加新列,以及 DROP删除现有列。是标准 SQL 的 MySQL 扩展。 DROP *col_name*
要在表格行内的特定位置添加列,请使用 FIRST或。默认是最后添加列。 AFTER *col_name*
如果一个表只包含一列,则不能删除该列。如果您打算删除该表,请改用该 DROP TABLE语句。
如果从表中删除列,列也会从它们所属的任何索引中删除。如果构成索引的所有列都被删除,则该索引也会被删除。如果您使用 CHANGE或MODIFY缩短列上存在索引的列,并且生成的列长度小于索引长度,MySQL 会自动缩短索引。
对于ALTER TABLE ... ADD,如果该列具有使用非确定性函数的表达式默认值,则该语句可能会产生警告或错误。
重命名、重新定义和重新排序列
在CHANGE,MODIFY, RENAME COLUMN,和ALTER 条款允许的名称和现有列的定义被改变。它们具有以下比较特征:
CHANGE:- 可以重命名列并更改其定义,或两者兼而有之。
- 具有比
MODIFY或 更强大的功能RENAME COLUMN,但牺牲了某些操作的便利性。CHANGE如果不重命名列,则需要两次命名该列,如果仅重命名则需要重新指定列定义。 - 使用
FIRST或AFTER,可以对列重新排序。
MODIFY:- 可以更改列定义但不能更改其名称。
- 比
CHANGE更改列定义而不重命名它更方便。 - 使用
FIRST或AFTER,可以对列重新排序。
RENAME COLUMN:- 可以更改列名称,但不能更改其定义。
- 比重
CHANGE命名列而不更改其定义更方便。
ALTER:仅用于更改列默认值。
1 | 警告 |
主键和索引
DROP PRIMARY KEY删除 主键。如果没有主键,则会发生错误。
ALTER TABLE 分区操作
与分区相关的子句 forALTER TABLE可与分区表一起使用以进行重新分区、添加、删除、丢弃、导入、合并和拆分分区,以及执行分区维护。
只需在分区表上使用*
partition_options* with 子句即可ALTER TABLE根据partition_options. 此子句始终以 开头PARTITION BY,并遵循与适用于*partition_options*子句 for 相同的语法和其他规则CREATE TABLE(有关更多详细信息,请参阅第 13.1.20 节,“CREATE TABLE 语句”),还可用于对现有表进行分区尚未分区。例如,考虑如下所示定义的(非分区)表:1
2
3
4CREATE TABLE t1 (
id INT,
year_col INT
);可以
HASH使用该id列作为分区键,通过以下语句将该表分区为 8 个分区:1
2
3ALTER TABLE t1
PARTITION BY HASH(id)
PARTITIONS 8;
MySQL 支持ALGORITHM带有 [SUB]PARTITION BY [LINEAR] KEY. ALGORITHM=1导致服务器在计算分区中行的位置时使用与 MySQL 5.1 相同的键哈希函数; ALGORITHM=2意味着服务器采用默认情况下为KEYMySQL 5.5 及更高版本中的新分区表实现和使用的密钥散列函数。(使用 MySQL 5.5 及更高版本中使用的密钥散列函数创建的分区表不能被 MySQL 5.1 服务器使用。)不指定该选项与使用ALGORITHM=2. 此选项主要用于升级或降级[LINEAR] KEYMySQL 5.1 和更高版本的 MySQL 之间的分区表,或者用于创建由MySQL 5.5 或更高版本服务器分区KEY或 LINEAR KEY在 MySQL 5.1 服务器上使用的表。
假设您已创建分区表,如下所示:
1 | CREATE TABLE t1 ( |
DROP PARTITION可用于删除一个或多个RANGE或LIST 分区。此语句不能与HASH或KEY 分区一起使用 ;相反,使用COALESCE PARTITION(见本节后面部分)。存储在*partition_names*列表中命名的已删除分区中的任何数据都将被 丢弃。例如,给定t1先前定义的表 ,您可以删除名为p0和的分区, p1如下所示:
1 | ALTER TABLE t1 DROP PARTITION p0, p1; |
DROP PARTITION不适用于使用NDB 存储引擎的表。
在DISCARD PARTITION ... TABLESPACE和 IMPORT PARTITION ... TABLESPACE选项延长 传输表空间功能个别 InnoDB表分区。每个 InnoDB表分区都有自己的表空间文件(.ibdfile)。可 传输表空间功能可以轻松地将表空间从正在运行的 MySQL 服务器实例复制到另一个正在运行的实例,或者在同一实例上执行还原。这两个选项都采用一个或多个分区名称的逗号分隔列表。例如:
1 | ALTER TABLE t1 DISCARD PARTITION p2, p3 TABLESPACE; |
要从选定分区中删除行,请使用该 TRUNCATE PARTITION选项。此选项采用一个或多个逗号分隔的分区名称列表。考虑t1由该语句创建的表:
1 | CREATE TABLE t1 ( |
要从 partition 删除所有行p0,请使用以下语句:
1 | ALTER TABLE t1 TRUNCATE PARTITION p0; |
刚刚显示的语句与以下DELETE语句具有相同的效果:
1 | DELETE FROM t1 WHERE year_col < 1991; |
截断多个分区时,分区不必是连续的:这可以大大简化分区表上的删除操作,否则WHERE如果使用DELETE语句完成,则需要非常复杂的条件。例如,此语句从分区中删除所有行, p1并且p3:
1 | ALTER TABLE t1 TRUNCATE PARTITION p1, p3; |
DELETE 此处显示了 等效语句:
1 | DELETE FROM t1 WHERE |
TRUNCATE PARTITION仅删除行;它不会改变表本身或其任何分区的定义。
要验证行是否已删除,请INFORMATION_SCHEMA.PARTITIONS使用如下查询检查 表:
1 | SELECT PARTITION_NAME, TABLE_ROWS |
COALESCE PARTITION可与按 分区的表一起使用,HASH或 KEY减少分区数 *number*。假设您已经t2按如下方式创建了表:
1 | CREATE TABLE t2 ( |
要将使用的分区数 t2从 6 减少到 4,请使用以下语句:
1 | ALTER TABLE t2 COALESCE PARTITION 2; |
ALTER TABLE 和生成的列
ALTER TABLE允许生成的列操作是ADD, MODIFY,和CHANGE。
可以添加生成的列。
1
2CREATE TABLE t1 (c1 INT);
ALTER TABLE t1 ADD COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED;可以修改生成列的数据类型和表达式。
1
2CREATE TABLE t1 (c1 INT, c2 INT GENERATED ALWAYS AS (c1 + 1) STORED);
ALTER TABLE t1 MODIFY COLUMN c2 TINYINT GENERATED ALWAYS AS (c1 + 5) STORED;如果没有其他列引用生成的列,则可以重命名或删除它们。
1
2
3CREATE TABLE t1 (c1 INT, c2 INT GENERATED ALWAYS AS (c1 + 1) STORED);
ALTER TABLE t1 CHANGE c2 c3 INT GENERATED ALWAYS AS (c1 + 1) STORED;
ALTER TABLE t1 DROP COLUMN c3;虚拟生成列不能更改为存储生成列,反之亦然。要解决此问题,请删除该列,然后使用新定义添加它。
1
2
3CREATE TABLE t1 (c1 INT, c2 INT GENERATED ALWAYS AS (c1 + 1) VIRTUAL);
ALTER TABLE t1 DROP COLUMN c2;
ALTER TABLE t1 ADD COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED;非生成列可以更改为存储列,但不能更改为虚拟生成列。
1
2CREATE TABLE t1 (c1 INT, c2 INT);
ALTER TABLE t1 MODIFY COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED;存储但不是虚拟生成的列可以更改为非生成列。存储的生成值成为非生成列的值。
1
2CREATE TABLE t1 (c1 INT, c2 INT GENERATED ALWAYS AS (c1 + 1) STORED);
ALTER TABLE t1 MODIFY COLUMN c2 INT;
10. ALTER TABLESPACE 语句
1 | ALTER [UNDO] TABLESPACE tablespace_name |
该语句与NDB和 InnoDB表空间一起使用。它可用于向NDB表空间添加新数据文件或从表空间中删除数据文件 。它还可用于重命名 NDB Cluster 磁盘数据表空间、重命名 InnoDB通用表空间、加密 InnoDB通用表空间或将InnoDB撤消表空间标记 为活动或非活动。
UNDOMySQL 8.0.14 中引入 的关键字与SET {ACTIVE | INACTIVE}子句一起使用以将InnoDB撤消表空间标记为活动或非活动。
数据文件一旦创建,其大小就不能改变;但是,您可以使用其他ALTER TABLESPACE ... ADD DATAFILE 语句将更多数据文件添加到 NDB 表空间。
11. ALTER VIEW 语句
1 | ALTER |
此语句更改视图的定义,该定义必须存在。语法类似于用于CREATE VIEW看到第13.1.23,“CREATE VIEW声明”)。此语句需要视图的CREATE VIEW和 DROP权限,以及SELECT语句中引用的每个列的一些权限 。 ALTER VIEW仅允许定义者或具有SET_USER_ID特权(或已弃用SUPER特权)的用户使用 。
12. CREATE EVENT 语句
1 | CREATE |
此语句创建并安排新事件。除非启用了事件调度程序,否则事件不会运行。
有效CREATE EVENT声明的最低要求如下:
- 关键字
CREATE EVENT加上事件名称,它在数据库模式中唯一标识事件。 - 一个
ON SCHEDULE子句,用于确定事件执行的时间和频率。 - 一个
DO子句,其中包含要由事件执行的 SQL 语句。
这是一个最小CREATE EVENT语句的示例:
1 | CREATE EVENT myevent |
13. CREATE INDEX 语句
1 | CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name |
列前缀关键部分
对于字符串列,可以创建仅使用列值的前导部分 的索引,使用 语法来指定索引前缀长度: *col_name*(*length*)
- 前缀可以指定
CHAR,VARCHAR,BINARY,和VARBINARY关键零部件。 - 必须为
BLOB和TEXT关键部分指定 前缀。此外,BLOB和TEXT列可以只对索引InnoDB,MyISAM和BLACKHOLE表。 - 前缀限制以字节为单位。
此处显示的语句使用列的前 10 个字符创建索引name(假设该列 name具有非二进制字符串类型):
1 | CREATE INDEX part_of_name ON customer (name(10)); |
如果列中的名称通常在前 10 个字符中不同,则使用此索引执行的查找不应比使用从整个name列创建的索引慢得多 。此外,为索引使用列前缀可以使索引文件更小,这可以节省大量磁盘空间,还可以加快INSERT操作速度 。
功能关键部件
例如,在下表中,给定t1行的索引条目包括完整 col1值和col2由其前 10 个字符组成的值的前缀 :
1 | CREATE TABLE t1 ( |
MySQL 8.0.13 及更高版本支持索引表达式值而不是列或列前缀值的功能键部分。使用功能关键部件可以对未直接存储在表中的值进行索引。例子:
1 | CREATE TABLE t1 (col1 INT, col2 INT, INDEX func_index ((ABS(col1)))); |
UNIQUE支持包含功能关键部分的索引。但是,主键不能包含功能键部分。主键要求存储生成的列,但功能键部分实现为虚拟生成的列,而不是存储的生成列。
SPATIAL和FULLTEXT 索引不能有功能关键部分。
功能关键部件支持索引无法以其他方式索引的值,例如JSON 值。但是,这必须正确完成才能达到预期的效果。例如,此语法不起作用:
1 | CREATE TABLE employees ( |
语法失败,因为:
- 该
->>运营商转变成JSON_UNQUOTE(JSON_EXTRACT(...))。 JSON_UNQUOTE()返回数据类型为 的值,LONGTEXT因此为隐藏的生成列分配了相同的数据类型。- MySQL 不能
LONGTEXT在键部分没有前缀长度的情况下索引指定的列,并且在功能键部分中不允许使用前缀长度。
要索引该JSON列,您可以尝试使用该CAST()函数,如下所示:
1 | CREATE TABLE employees ( |
隐藏的生成列被分配了VARCHAR(30)可以被索引的 数据类型。但是这种方法在尝试使用索引时会产生一个新问题:
CAST()返回带有排序规则的字符串utf8mb4_0900_ai_ci(服务器默认排序规则)。JSON_UNQUOTE()返回带有排序规则的字符串utf8mb4_bin(硬编码)。
因此,前面表定义中的索引表达式WHERE与以下查询中的子句表达式之间存在排序规则不匹配 :
1 | SELECT * FROM employees WHERE data->>'$.name' = 'James'; |
不使用索引,因为查询中的表达式和索引不同。为了支持这种方案的功能键部分,优化自动去除 CAST()寻找一个索引使用时,但仅如果查询表达式的索引表达式匹配的排序规则。对于要使用的具有功能键部分的索引,以下两种解决方案中的任何一种都有效(尽管它们在效果上有所不同):
解决方案 1. 为索引表达式分配与以下相同的排序规则
JSON_UNQUOTE():1
2
3
4
5
6
7
8
9
10CREATE TABLE employees (
data JSON,
INDEX idx ((CAST(data->>"$.name" AS CHAR(30)) COLLATE utf8mb4_bin))
);
INSERT INTO employees VALUES
('{ "name": "james", "salary": 9000 }'),
('{ "name": "James", "salary": 10000 }'),
('{ "name": "Mary", "salary": 12000 }'),
('{ "name": "Peter", "salary": 8000 }');
SELECT * FROM employees WHERE data->>'$.name' = 'James';该
->>操作是一样的JSON_UNQUOTE(JSON_EXTRACT(...)),并JSON_UNQUOTE()返回与归类的字符串utf8mb4_bin。因此比较区分大小写,并且只有一行匹配:1
2
3
4
5+------------------------------------+
| data |
+------------------------------------+
| {"name": "James", "salary": 10000} |
+------------------------------------+解决方案 2. 在查询中指定完整表达式:
1
2
3
4
5
6
7
8
9
10CREATE TABLE employees (
data JSON,
INDEX idx ((CAST(data->>"$.name" AS CHAR(30))))
);
INSERT INTO employees VALUES
('{ "name": "james", "salary": 9000 }'),
('{ "name": "James", "salary": 10000 }'),
('{ "name": "Mary", "salary": 12000 }'),
('{ "name": "Peter", "salary": 8000 }');
SELECT * FROM employees WHERE CAST(data->>'$.name' AS CHAR(30)) = 'James';CAST()返回一个带有 collation 的字符串utf8mb4_0900_ai_ci,因此比较不区分大小写并且两行匹配:1
2
3
4
5
6+------------------------------------+
| data |
+------------------------------------+
| {"name": "james", "salary": 9000} |
| {"name": "James", "salary": 10000} |
+------------------------------------+
请注意,虽然优化器支持CAST()使用索引生成的列自动剥离,但以下方法不起作用,因为它会产生不同的结果,有和没有索引(Bug#27337092):
1 | mysql> CREATE TABLE employees ( |
唯一索引
一个UNIQUE索引创建的约束,使得该指数的所有值必须是不同的。如果您尝试添加具有与现有行匹配的键值的新行,则会发生错误。如果为UNIQUE索引中的列指定前缀值 ,则列值在前缀长度内必须是唯一的。一个UNIQUE 索引,可以多次NULL进行,可以包含列的值NULL。
如果表的PRIMARY KEYor UNIQUE NOT NULL索引由单个整数类型的列组成,则可以使用 _rowid来引用SELECT语句中的索引列 ,如下所示:
_rowid``PRIMARY KEY如果存在PRIMARY KEY由单个整数列组成的列,则指该列。如果有PRIMARY KEY但它不包含单个整数列,_rowid则不能使用。- 否则,如果该索引由单个整数列组成,则
_rowid引用第一个UNIQUE NOT NULL索引中的列。如果第一个UNIQUE NOT NULL索引不包含单个整数列,_rowid则不能使用。
全文索引
FULLTEXT索引仅支持 InnoDB和 MyISAM表格,并且可以只包括 CHAR, VARCHAR和 TEXT列。索引总是发生在整个列上;不支持列前缀索引,如果指定,则忽略任何前缀长度。
多值索引
从 MySQL 8.0.17 开始,InnoDB支持多值索引。多值索引是在存储值数组的列上定义的二级索引。阿 “正常”索引具有用于每个数据记录(1:1)一个索引记录。多值索引可以为单个数据记录 (N:1) 包含多个索引记录。多值索引用于索引JSON数组。例如,在以下 JSON 文档中的邮政编码数组上定义的多值索引为每个邮政编码创建一个索引记录,每个索引记录引用相同的数据记录。
1 | { |
创建多值索引
您可以在创建一个多值指数 CREATE TABLE, ALTER TABLE或 CREATE INDEX说明。这需要CAST(... AS ... ARRAY)在索引定义中使用,它将JSON数组中的相同类型标量值转换为 SQL 数据类型数组。然后使用 SQL 数据类型数组中的值透明地生成一个虚拟列;最后,在虚拟列上创建功能索引(也称为虚拟索引)。它是在形成多值索引的 SQL 数据类型数组的虚拟值列上定义的功能索引。
以下列表中的示例显示了在名为 的表的列zips上的数组$.zipcode上 创建多值索引的三种不同方式。在每种情况下,JSON 数组都被转换为整数值的 SQL 数据类型数组 。 JSON``custinfo``customers``UNSIGNED
CREATE TABLE只要:1
2
3
4
5
6CREATE TABLE customers (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
custinfo JSON,
INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) )
);
使用多值索引
当在WHERE子句中指定以下函数时,优化器使用多值索引来获取记录 :
我们可以通过customers使用以下CREATE TABLEandINSERT 语句创建和填充表来 证明这一点 :
1 | mysql> CREATE TABLE customers ( |
首先,我们在执行三个查询 customers表,利用每一个 MEMBER OF(), JSON_CONTAINS()和 JSON_OVERLAPS(),从这里显示每个查询的结果:
1 | mysql> SELECT * FROM customers |
接下来,我们运行EXPLAIN前三个查询中的每一个:
1 | mysql> EXPLAIN SELECT * FROM customers |
刚刚显示的三个查询都不能使用任何键。为了解决这个问题,我们可以zipcode在JSON 列 ( custinfo) 中的数组上添加一个多值索引 ,如下所示:
1 | mysql> ALTER TABLE customers |
当我们EXPLAIN再次运行前面的语句时,我们现在可以观察到查询可以(并且确实)使用zips刚刚创建的索引:
1 | mysql> EXPLAIN SELECT * FROM customers |
多值索引可以定义为唯一键。如果定义为唯一键,则尝试插入多值索引中已存在的值会返回重复键错误。如果已存在重复值,则尝试添加唯一的多值索引会失败,如下所示:
1 | mysql> ALTER TABLE customers DROP INDEX zips; |
多值索引的限制和限制
多值索引受此处列出的限制和限制的约束:
每个多值索引只允许一个多值键部分。但是,
CAST(... AS ... ARRAY)表达式可以引用JSON文档中的多个数组,如下所示:1
CAST(data->'$.arr[*][*]' AS UNSIGNED ARRAY)
在这种情况下,与 JSON 表达式匹配的所有值都作为单个平面数组存储在索引中。
具有多值键部分的索引不支持排序,因此不能用作主键。出于同样的原因,不能使用
ASCorDESC关键字定义多值索引。多值索引不能是覆盖索引。
多值索引的每条记录的最大值数取决于单个撤消日志页面上可以存储的数据量,即 65221 字节(64K 减去 315 字节的开销),这意味着最大总数键值的长度也是 65221 字节。密钥的最大数量取决于各种因素,这会阻止定义特定限制。例如,测试显示多值索引允许每条记录多达 1604 个整数键。达到限制时,将报告类似于以下内容的错误:错误 3905 (HY000):超出多值索引 ‘idx’ 每条记录的最大值数 1 个值。
多值键部分中唯一允许的表达式类型是
JSON表达式。该表达式不需要引用插入索引列的 JSON 文档中的现有元素,但本身必须在语法上有效。由于同一聚集索引记录的索引记录分散在多值索引中,因此多值索引不支持范围扫描或仅索引扫描。
外键规范中不允许使用多值索引。
不能为多值索引定义索引前缀。
不支持在线创建多值索引,这意味着该操作使用
ALGORITHM=COPY. 请参阅 性能和空间要求。多值索引不支持以下两种字符集和排序规则组合以外的字符集和排序规则:
binary具有默认binary排序规则 的字符集utf8mb4具有默认utf8mb4_0900_as_cs排序规则 的字符集。
与
InnoDB表列上的其他索引一样 ,不能使用USING HASH;创建多值索引。尝试这样做会导致警告:此存储引擎不支持 HASH 索引算法,改为使用存储引擎默认值。(USING BTREE照常支持。)
空间索引
在MyISAM, InnoDB, NDB,和 ARCHIVE存储引擎支持空间列,比如POINT和 GEOMETRY。(第 11.4 节,“空间数据类型”,描述了空间数据类型。)但是,对空间列索引的支持因引擎而异。根据以下规则可以使用空间列上的空间索引和非空间索引。
空间列上的空间索引具有以下特征:
- 仅适用于
InnoDB和MyISAM表。SPATIAL INDEX为其他存储引擎指定会 导致错误。 - 从 MySQL 8.0.12 开始,空间列 上的 索引必须是
SPATIAL索引。因此,SPATIAL关键字是可选的,但对于在空间列上创建索引是隐含的。 - 仅适用于单个空间列。不能在多个空间列上创建空间索引。
- 索引列必须是
NOT NULL. - 禁止列前缀长度。每列的全宽都有索引。
- 不允许用于主键或唯一索引。
在空间列非空间索引(与创建 INDEX,UNIQUE或 PRIMARY KEY)具有以下特征:
- 允许任何支持空间列的存储引擎,除了
ARCHIVE. NULL除非索引是主键,否则 列可以是。- 非
SPATIAL索引的索引类型取决于存储引擎。目前使用的是B-tree。 - 允许,可以有一个列
NULL仅值InnoDB,MyISAM和MEMORY表。
表 13.1 每个存储引擎的索引类型
| 存储引擎 | 允许的索引类型 |
|---|---|
InnoDB |
BTREE |
MyISAM |
BTREE |
MEMORY/HEAP |
HASH, BTREE |
NDB |
HASH, BTREE(见正文注释) |
Table 13.2 InnoDB Storage Engine Index Characteristics
| Index Class | Index Type | Stores NULL VALUES | Permits Multiple NULL Values | IS NULL Scan Type | IS NOT NULL Scan Type |
|---|---|---|---|---|---|
| Primary key | BTREE |
No | No | N/A | N/A |
| Unique | BTREE |
Yes | Yes | Index | Index |
| Key | BTREE |
Yes | Yes | Index | Index |
FULLTEXT |
N/A | Yes | Yes | Table | Table |
SPATIAL |
N/A | No | No | N/A | N/A |
Table 13.3 MyISAM Storage Engine Index Characteristics
| Index Class | Index Type | Stores NULL VALUES | Permits Multiple NULL Values | IS NULL Scan Type | IS NOT NULL Scan Type |
|---|---|---|---|---|---|
| Primary key | BTREE |
No | No | N/A | N/A |
| Unique | BTREE |
Yes | Yes | Index | Index |
| Key | BTREE |
Yes | Yes | Index | Index |
FULLTEXT |
N/A | Yes | Yes | Table | Table |
SPATIAL |
N/A | No | No | N/A | N/A |
Table 13.4 MEMORY Storage Engine Index Characteristics
| Index Class | Index Type | Stores NULL VALUES | Permits Multiple NULL Values | IS NULL Scan Type | IS NOT NULL Scan Type |
|---|---|---|---|---|---|
| Primary key | BTREE |
No | No | N/A | N/A |
| Unique | BTREE |
Yes | Yes | Index | Index |
| Key | BTREE |
Yes | Yes | Index | Index |
| Primary key | HASH |
No | No | N/A | N/A |
| Unique | HASH |
Yes | Yes | Index | Index |
| Key | HASH |
Yes | Yes | Index | Index |
Table 13.5 NDB Storage Engine Index Characteristics
| Index Class | Index Type | Stores NULL VALUES | Permits Multiple NULL Values | IS NULL Scan Type | IS NOT NULL Scan Type |
|---|---|---|---|---|---|
| Primary key | BTREE |
No | No | Index | Index |
| Unique | BTREE |
Yes | Yes | Index | Index |
| Key | BTREE |
Yes | Yes | Index | Index |
| Primary key | HASH |
No | No | Table (see note 1) | Table (see note 1) |
| Unique | HASH |
Yes | Yes | Table (see note 1) | Table (see note 1) |
| Key | HASH |
Yes | Yes | Table (see note 1) | Table (see note 1) |
####14. CREATE LOGFILE GROUP 语句
1 | CREATE LOGFILE GROUP logfile_group |
此语句创建一个名为的新日志文件组 *logfile_group*,其中包含一个 UNDO名为“ *undo_file*”的文件。一个 CREATE LOGFILE GROUP语句只有一个ADD UNDOFILE子句。
在任何给定时间,每个 NDB Cluster 实例只能有一个日志文件组。
可选INITIAL_SIZE参数设置 UNDO文件的初始大小;如果未指定,则默认为128M(128 兆字节)。可选 UNDO_BUFFER_SIZE参数设置UNDO日志文件组缓冲区使用的大小;作为默认值UNDO_BUFFER_SIZE就是 8M(8兆字节); 此值不能超过可用的系统内存量。这两个参数都以字节为单位指定。您可以选择在其中一个或两个后面使用一个数量级的单字母缩写,类似于 中使用的那些my.cnf。通常,这是字母M(兆字节)或G(千兆字节)之一。
1 | mysql> CREATE LOGFILE GROUP lg1 |
15. CREATE PROCEDURE 和 CREATE FUNCTION 语句
1 | CREATE |
这些语句用于创建存储例程(存储过程或函数)。也就是说,指定的例程为服务器所知。默认情况下,存储的例程与默认数据库相关联。要将例程与给定数据库显式关联,请*db_name.sp_name*在创建时指定名称 。
该CREATE FUNCTION语句也用于 MySQL 以支持可加载函数。要调用存储过程,请使用该 CALL语句(请参阅 第 13.2.1 节,“CALL 语句”)。要调用存储的函数,请在表达式中引用它。该函数在表达式计算期间返回一个值。
一个IN参数传送一个值的过程。该过程可能会修改该值,但当该过程返回时,调用者看不到该修改。一个OUT参数传送从过程返回给调用者的值。它的初始值 NULL在过程中,当过程返回时,它的值对调用者可见。一个 INOUT参数是由呼叫者初始化,可以由程序进行修改,并且由所述方法制备的任何变化是可见的呼叫者时,过程返回。
对于每个OUT或INOUT 参数,在CALL调用过程的语句中传递一个用户定义的变量, 以便在过程返回时可以获取其值。如果从另一个存储过程或函数中调用该过程,还可以将例程参数或局部例程变量作为OUT orINOUT参数传递。如果从触发器内部调用过程,也可以作为 或参数传递 。
1 | mysql> delimiter // |
1 | mysql> CREATE FUNCTION hello (s CHAR(20)) |
16. CREATE SERVER 语句
1 | CREATE SERVER server_name |
此语句创建与FEDERATED存储引擎一起使用的服务器的定义 。该CREATE SERVER语句servers在mysql 数据库的表中创建一个新行 。
17. CREATE SPATIAL REFERENCE SYSTEM 语句
1 | CREATE OR REPLACE SPATIAL REFERENCE SYSTEM |
此语句创建 空间参考系统(SRS) 定义并将其存储在数据字典中。
18. CREATE TABLE 语句
1 | CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name |
临时表
TEMPORARY创建表时 可以使用关键字。一个TEMPORARY表只在当前会话中可见,而当会话关闭时自动删除。
表克隆和复制
用于CREATE TABLE ... LIKE根据另一个表的定义创建一个空表,包括原始表中定义的任何列属性和索引
1 | CREATE TABLE new_tbl LIKE orig_tbl; |
要从另一个表创建一个表,请在SELECT语句末尾添加一条 CREATE TABLE语句:
1 | CREATE TABLE new_tbl AS SELECT * FROM orig_tbl; |
1 | mysql> SELECT * FROM foo; |
对于 table 中的每一行,foo都会插入一行,bar其中包含来自foo新列的值 和默认值。
在IGNORE和REPLACE 选项指示如何处理使用复制表时复制唯一键值的行 SELECT声明。
更多参数配置参考:https://dev.mysql.com/doc/refman/8.0/en/create-table.html
二级索引和生成的列
InnoDB支持虚拟生成列上的二级索引。不支持其他索引类型。在虚拟列上定义的二级索引有时称为“虚拟索引”。
可以在一个或多个虚拟列或虚拟列和常规列的组合或存储的生成列上创建二级索引。包含虚拟列的二级索引可以定义为UNIQUE.
在虚拟生成列上创建二级索引时,生成的列值将在索引的记录中具体化。如果索引是一个 覆盖索引(一个包括所有由查询检索的列),生成列值从物化值在索引结构中检索,而不是计算“在飞行中”。
索引生成的列以提供 JSON 列索引
如别处所述,JSON 列不能直接索引。要创建一个间接引用此类列的索引,您可以定义一个生成列来提取应该建立索引的信息,然后在生成的列上创建一个索引,如下例所示:
1 | mysql> CREATE TABLE jemp ( |
19. CREATE TABLESPACE 语句
1 | CREATE [UNDO] TABLESPACE tablespace_name |
该语句用于创建表空间。精确的语法和语义取决于所使用的存储引擎。在标准 MySQL 版本中,这始终是一个InnoDB 表空间。MySQL NDB Cluster 还支持使用NDB存储引擎的表空间 。
20. CREATE TRIGGER 语句
1 | CREATE |
此语句创建一个新触发器。触发器是与表关联的命名数据库对象,并在表发生特定事件时激活。触发器与名为 的表相关联,该表 *tbl_name*必须引用一个永久表。您不能将触发器与TEMPORARY表或视图相关联 。
触发器名称存在于架构命名空间中,这意味着所有触发器在架构中都必须具有唯一的名称。不同模式中的触发器可以具有相同的名称。
该DEFINER子句确定在触发器激活时检查访问权限时要使用的安全上下文,如本节后面所述。
*trigger_time*是触发动作时间。它可以是BEFORE或 AFTER指示触发器在要修改的每一行之前或之后激活。
基本列值检查发生在触发器激活之前,因此您不能使用BEFORE触发器将不适合该列类型的值转换为有效值。
*trigger_event*指示激活触发器的操作类型。这些 *trigger_event*值是允许的:
INSERT:只要向表中插入新行(例如,通过、 和 语句)INSERT, 触发器就会激活。LOAD DATAREPLACEUPDATE:每当修改行(例如,通过UPDATE语句)时,触发器就会激活 。DELETE:只要从表中删除一行(例如,通过DELETE和REPLACE语句),触发器就会激活 。 表上的DROP TABLE和TRUNCATE TABLE语句不会激活此触发器,因为它们不使用DELETE. 删除分区也不会激活DELETE触发器。
21. CREATE VIEW 语句
1 | CREATE |
该CREATE VIEW语句创建一个新视图,或者如果OR REPLACE给出了子句,则替换现有视图。如果视图不存在, CREATE OR REPLACE VIEW则与CREATE VIEW. 如果视图确实存在,则 CREATE OR REPLACE VIEW替换它。
一些视图是可更新的。也就是说,你可以在语句,如使用它们UPDATE, DELETE或 INSERT更新基础表的内容。要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。还有某些其他构造使视图不可更新。
22. DROP DATABASE 语句
1 | DROP {DATABASE | SCHEMA} [IF EXISTS] db_name |
DROP DATABASE删除数据库中的所有表并删除数据库。对这种说法要 非常小心!要使用 DROP DATABASE,您需要DROP对数据库具有 权限。 DROP SCHEMA是 的同义词DROP DATABASE。
重要的
删除数据库时,不会自动删除专门为该数据库授予的权限。它们必须手动删除。
23. DROP EVENT 语句
1 | DROP EVENT [IF EXISTS] event_name |
此语句删除名为 的事件 *event_name*。该事件立即停止活动,并从服务器中完全删除。
如果事件不存在,则会导致错误ERROR 1517 (HY000): Unknown event ‘ event_name‘。您可以覆盖它并使语句为不存在的事件生成警告,而不是使用IF EXISTS.
此语句需要EVENT 要删除的事件所属的架构的特权。
24. DROP INDEX 语句
1 | DROP INDEX index_name ON tbl_name |
DROP INDEX删除*index_name*从表中 命名的索引 *tbl_name*。此语句映射ALTER TABLE到删除索引的语句。
25. 其他DROP语句
DROP SERVER 语句
1 | DROP SERVER [ IF EXISTS ] server_name |
删除名为 的服务器的服务器定义 *server_name*。mysql.servers表中相应的行被删除。
DROP LOGFILE GROUP 语句
1 | DROP LOGFILE GROUP logfile_group |
此语句删除名为 的日志文件组 *logfile_group*。日志文件组必须已经存在,否则会导致错误。
DROP PROCEDURE 和 DROP FUNCTION 语句
1 | DROP {PROCEDURE | FUNCTION} [IF EXISTS] sp_name |
这些语句用于删除存储例程(存储过程或函数)。即,从服务器中删除指定的例程。
DROP SPATIAL REFERENCE SYSTEM 语句
1 | DROP SPATIAL REFERENCE SYSTEM |
此语句从数据字典中删除 空间参考系统(SRS) 定义。它需要SUPER特权。
例子:
1 | DROP SPATIAL REFERENCE SYSTEM 4120; |
DROP TABLE 语句
1 | DROP [TEMPORARY] TABLE [IF EXISTS] |
DROP TABLE删除一张或多张表。您必须拥有DROP 每个表的权限。
小心这种说法!对于每个表,它删除表定义和所有表数据。如果表已分区,则该语句将删除表定义、其所有分区、存储在这些分区中的所有数据以及与删除的表关联的所有分区定义。
删除表也会删除该表的所有触发器。
DROP TABLE导致隐式提交,除非与TEMPORARY 关键字一起使用。
DROP TABLESPACE 语句
1 | DROP [UNDO] TABLESPACE tablespace_name |
此语句删除先前使用CREATE TABLESPACE. 它由NDB和 InnoDB存储引擎支持。
UNDOMySQL 8.0.14 中引入 的关键字必须指定以删除撤消表空间。只能CREATE UNDO TABLESPACE删除使用语法创建的撤消表空间 。撤消表空间必须处于某种empty状态才能被删除。
DROP TRIGGER 语句
1 | DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name |
此语句删除触发器。模式(数据库)名称是可选的。如果架构被省略,触发器将从默认架构中删除。
DROP VIEW 语句
1 | DROP VIEW [IF EXISTS] |
DROP VIEW删除一个或多个视图。您必须拥有DROP 每个视图的权限。
如果参数列表中命名的任何视图不存在,则语句失败并显示错误,按名称指示它无法删除哪些不存在的视图,并且不进行任何更改。
RENAME TABLE 语句
1 | RENAME TABLE |
RENAME TABLE重命名一个或多个表。您必须具有ALTER与 DROP原始表的权限,以及CREATE与 INSERT新表的权限。
TRUNCATE TABLE 语句
1 | TRUNCATE [TABLE] tbl_name |
TRUNCATE TABLE完全清空一张桌子。它需要DROP 特权。从逻辑上讲,TRUNCATE TABLE类似于DELETE删除所有行的 语句,或一系列DROP TABLE 和CREATE TABLE语句。
为了实现高性能,TRUNCATE TABLE绕过了删除数据的 DML 方法。因此,它不会导致ON DELETE触发器触发,不能对InnoDB具有父子外键关系的表执行,也不能像 DML 操作一样回滚。但是,TRUNCATE TABLE如果服务器在操作期间停止,则对使用原子 DDL 支持的存储引擎的表的操作要么完全提交,要么回滚。
12.3 数据操作语句
1. CALL 语句
1 | CALL sp_name([parameter[,...]]) |
该CALL语句调用先前使用 定义的存储过程 CREATE PROCEDURE。
可以在没有括号的情况下调用不带参数的存储过程。即,CALL p()和 CALL p是等价的。
CALL可以使用声明为OUT或INOUT参数的参数将值传递回其调用者 。当过程返回时,客户端程序还可以获得例程中执行的最终语句影响的行数: 在 SQL 级别,调用 ROW_COUNT()函数;从 C API 调用该 mysql_affected_rows()函数。
要使用OUTorINOUT参数从过程中取回值 ,请通过用户变量传递参数,然后在过程返回后检查变量的值。(如果您从另一个存储过程或函数中调用该过程,则还可以将例程参数或局部例程变量作为INorINOUT 参数INOUT传递。)对于参数,请在将其传递给过程之前初始化其值。以下过程具有OUT该过程设置为当前服务器版本的参数,以及 INOUT该过程从其当前值递增 1 的值:
1 | CREATE PROCEDURE p (OUT ver_param VARCHAR(25), INOUT incr_param INT) |
在调用过程之前,初始化要作为INOUT参数传递的变量。调用该过程后,可以看到设置或修改了两个变量的值:
1 | mysql> SET @increment = 10; |
2. DELETE 语句
DELETE 是从表中删除行的 DML 语句。
一个DELETE语句可以用开始 WITH子句来定义内访问的公共表表达式 DELETE。
1 | DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name [[AS] tbl_alias] |
该DELETE语句从中删除行 *tbl_name*并返回删除的行数。
可选WHERE子句中的条件标识要删除的行。如果没有WHERE 子句,则删除所有行。
*where_condition*是一个表达式,对于要删除的每一行,它的计算结果为真。
3. DO声明
1 | DO expr [, expr] ... |
DO执行表达式但不返回任何结果。在大多数方面, DO是 的简写,但优点是当您不关心结果时它会稍微快一点。 SELECT *expr*, ...
DO主要用于具有副作用的函数,例如 RELEASE_LOCK().
示例:此SELECT语句暂停,但也会产生一个结果集:
1 | mysql> SELECT SLEEP(5); |
DO,另一方面,暂停而不产生结果集。:
1 | mysql> DO SLEEP(5); |
这可能很有用,例如在存储函数或触发器中,它们禁止生成结果集的语句。
DO只执行表达式。它不能在所有可以使用的情况下SELECT使用。例如,DO id FROM t1无效,因为它引用了一个表。
4. HANDLER 语句
1 | HANDLER tbl_name OPEN [ [AS] alias] |
该HANDLER语句提供对表存储引擎接口的直接访问。它可用于 InnoDB和MyISAM表。
该HANDLER ... OPEN语句打开一个表,使其可以使用后续HANDLER ... READ语句访问。这个表对象不被其他会话共享,并且在会话调用HANDLER ... CLOSE或会话终止之前不会关闭 。
如果您使用别名打开表,则其他HANDLER语句对打开的表的进一步引用必须使用别名而不是表名。如果不使用别名,而是使用由数据库名称限定的表名打开表,则进一步引用必须使用非限定表名。例如,对于使用 打开的表 mydb.mytable,进一步的引用必须使用 mytable。
第一个HANDLER ... READ语法获取指定索引满足给定值且满足 WHERE条件的行。如果您有一个多列索引,请将索引列值指定为逗号分隔的列表。为索引中的所有列指定值,或为索引列的最左侧前缀指定值。假设一个索引my_idx包括三个名为列col_a, col_b以及col_c以该顺序。该HANDLER语句可以为索引中的所有三列或最左侧前缀中的列指定值。例如:
1 | HANDLER ... READ my_idx = (col_a_val,col_b_val,col_c_val) ... |
要使用HANDLER接口来引用表的PRIMARY KEY,请使用带引号的标识符 PRIMARY:
1 | HANDLER tbl_name READ `PRIMARY` ... |
第二种HANDLER ... READ语法按与WHERE条件匹配的索引顺序从表中获取一行 。
第三种HANDLER ... READ语法以与WHERE条件匹配的自然行顺序从表中获取一行 。它比需要全表扫描时更快 。自然行顺序是行在表数据文件中的存储顺序。此语句也适用于表,但没有这样的概念,因为没有单独的数据文件。
如果没有LIMIT子句,所有形式的 HANDLER ... READ单行都可以获取。要返回特定数量的行,请包含一个 LIMIT子句。它具有与SELECT语句相同的语法
使用HANDLER 接口代替普通SELECT 语句有几个原因:
HANDLER快于SELECT:HANDLER使移植到使用类似低级ISAM接口的MySQL 应用程序变得更容易。(请参阅第 15.20 节,“InnoDB memcached 插件”以了解适应使用键值存储范例的应用程序的替代方法。)HANDLER使您能够以难以(甚至不可能)完成的方式遍历数据库SELECT。HANDLER在使用为数据库提供交互式用户界面的应用程序时,该 界面是查看数据的更自然的方式。
HANDLER是一个有点低级的声明。例如,它不提供一致性。也就是说, HANDLER ... OPEN它不 走表的快照,并不能 锁定表。这意味着在HANDLER ... OPEN发出一条语句后,可以修改表数据(由当前会话或其他会话),并且这些修改可能只是部分可见HANDLER ... NEXT或HANDLER ... PREV扫描。
一个打开的处理程序可以被关闭并标记为重新打开,在这种情况下处理程序会失去它在表中的位置。当以下两种情况都为真时,就会发生这种情况:
- 任何会话
FLUSH TABLES在处理程序的表上执行或 DDL 语句。 - 打开处理程序的会话执行
HANDLER使用表的非语句。
TRUNCATE TABLEfor a table 关闭用HANDLER OPEN.打开的表的所有处理程序 。
如果 table is flushed with was opening with ,则处理程序被隐式刷新并丢失其位置。
5. 导入表语句
1 | IMPORT TABLE FROM sdi_file [, sdi_file] ... |
该IMPORT TABLE语句MyISAM根据.sdi(序列化字典信息)元数据文件中包含的信息导入 表 。IMPORT TABLE 需要FILE读取.sdi表内容文件的CREATE权限,以及要创建表的 权限。
可以使用mysqldump从一台服务器导出表 以写入 SQL 语句文件,并使用mysql导入另一台服务器以处理转储文件。IMPORT TABLE 使用“原始”表文件提供了一种更快的替代方法。
在导入之前,提供表内容的文件必须放置在导入服务器的适当架构目录中,并且该.sdi文件必须位于服务器可访问的目录中。例如, .sdi文件可以放在secure_file_priv 系统变量命名的目录中,或者(如果 secure_file_priv为空)在服务器数据目录下的目录中。
6. INSERT 语句
1 | INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] |
INSERT将新行插入到现有表中。的INSERT ... VALUES, INSERT ... VALUES ROW()和 INSERT ... SET 形式的语句插入基于明确指定的值的行。该INSERT ... SELECT表单插入从另一个表或多个表中选择的行。您还可以INSERT ... TABLE 在 MySQL 8.0.19 及更高版本中使用 从单个表插入行。 INSERT与ON DUPLICATE KEY UPDATE子句使现有的行,如果要被插入的行会导致在重复的值被更新 UNIQUE索引或PRIMARY KEY。在 MySQL 8.0.19 及更高版本中,可以使用带有一个或多个可选列别名的行别名ON DUPLICATE KEY UPDATE来引用要插入的行。
INSERT … SELECT 语句
使用INSERT ... SELECT,您可以从SELECT 语句的结果中快速将多行插入到一个表中,该语句可以从一个或多个表中进行选择。例如:
1 | INSERT INTO tbl_temp2 (fld_id) |
从 MySQL 8.0.19 开始,您可以使用 TABLE语句代替 SELECT,如下所示:
1 | INSERT INTO ta TABLE tb; |
TABLE tb相当于SELECT * FROM tb。在将源表中的所有列插入到目标表中时它会很有用,并且不需要使用 WHERE 进行过滤。此外,TABLE可以使用对来自一列或多列的行 进行排序ORDER BY,并且可以使用LIMIT 子句限制插入的行数。
INSERT … ON DUPLICATE KEY UPDATE 语句
如果指定ON DUPLICATE KEY UPDATE 要插入会在导致重复值条款和行UNIQUE索引或者PRIMARY KEY,一个UPDATE旧行的发生。例如,如果 columna声明为UNIQUE并包含 value 1,则以下两个语句具有类似的效果:
1 | INSERT INTO t1 (a,b,c) VALUES (1,2,3) |
效果并不完全相同:对于一个自增列的 InnoDB表a,INSERT语句增加自增值,但UPDATE不增加 。
如果 columnb也是唯一的, INSERT则等效于以下 UPDATE语句:
1 | UPDATE t1 SET c=c+1 WHERE a=1 OR b=2 LIMIT 1; |
如果a=1 OR b=2匹配多个行,只有 一个行被更新。通常,您应该尽量避免ON DUPLICATE KEY UPDATE 在具有多个唯一索引的表上使用子句。
从 MySQL 8.0.19 开始,可以为行使用别名,可以选择插入一个或多个列,在VALUESor SET子句之后,并在AS关键字之前 。使用行别名 new,前面显示的VALUES()用于访问新列值的语句 可以写成如下所示的形式:
1 | INSERT INTO t1 (a,b,c) VALUES (1,2,3),(4,5,6) AS new |
此外,如果您使用列别名 m, n, 和 p,则可以在赋值子句中省略行别名并编写相同的语句,如下所示:
1 | INSERT INTO t1 (a,b,c) VALUES (1,2,3),(4,5,6) AS new(m,n,p) |
以这种方式使用列别名时,您仍然必须在VALUES子句后面使用行别名,即使您没有在赋值子句中直接使用它。
从 MySQL 8.0.20 开始,在 UPDATE 子句中使用 VALUES() 的 INSERT … SELECT … ON DUPLICATE KEY UPDATE 语句会抛出警告:
1 | INSERT INTO t1 |
您可以通过使用子查询来消除此类警告,如下所示:
1 | INSERT INTO t1 |
UNION不支持 从 a 引用列 。要解决此限制,请将 重写 UNION为派生表,以便将其行视为单表结果集。例如,此语句会产生错误:
1 | INSERT INTO t1 (a, b) |
相反,使用将 重写UNION为派生表的等效语句 :
1 | INSERT INTO t1 (a, b) |
由于INSERT ... SELECT语句的结果 取决于来自 的行的顺序,SELECT并且无法始终保证此顺序,因此在记录INSERT ... SELECT ON DUPLICATE KEY UPDATE源和副本的语句时可能会出现 分歧。因此,INSERT ... SELECT ON DUPLICATE KEY UPDATE对于基于语句的复制, 语句被标记为不安全。此类语句在使用基于语句的模式时会在错误日志中产生警告,并在使用MIXED模式时使用基于行的格式写入二进制日志。INSERT ... ON DUPLICATE KEY UPDATE针对具有多个唯一键或主键的表的 语句也被标记为不安全。
INSERT DELAYED 语句
1 | INSERT DELAYED ... |
该语句的DELAYED选项 INSERT是标准 SQL 的 MySQL 扩展。在以前的 MySQL 版本中,它可以用于某些类型的表(例如 MyISAM),这样当客户端使用 时 INSERT DELAYED,它会立即从服务器获得一个 OK,当表没有时,该行排队等待插入被任何其他线程使用。
DELAYEDMySQL 5.6 中不推荐使用插入和替换。在 MySQL 8.0 中, DELAYED不支持。服务器识别但忽略DELAYED关键字,将插入处理为非延迟插入,并生成 ER_WARN_LEGACY_SYNTAX_CONVERTED警告: 不再支持插入延迟。该语句已转换为 INSERT。该 DELAYED关键字计划在将来的版本中删除。
7. LOAD DATA 语句
1 | LOAD DATA |
该LOAD DATA语句以非常高的速度将文本文件中的行读取到表中。该文件可以从服务器主机或客户端主机读取,这取决于是否LOCAL给定了修饰符。 LOCAL也会影响数据解释和错误处理。
LOAD DATA是 的补码 SELECT ... INTO OUTFILE。(请参阅第 13.2.10.1 节,“SELECT … INTO 语句”。)要将表中的数据写入文件,请使用 SELECT ... INTO OUTFILE. 要将文件读回表中,请使用 LOAD DATA. 两个语句的FIELDSandLINES子句的语法 是相同的。
所述的mysqlimport实用程序提供了另一种方法来负载数据文件; 它通过向LOAD DATA服务器发送一条语句来运行 。
8. LOAD XML 语句
1 | LOAD XML |
该LOAD XML语句将 XML 文件中的数据读取到表中。在 *file_name必须作为一个字符串。所述tagname*可选的在 ROWS IDENTIFIED BY条款也必须给予作为文字串,并且必须由尖括号(包围<和>)。
LOAD XML作为以 XML 输出模式运行mysql客户端的补充(即使用--xml选项启动客户端 )。要将表中的数据写入 XML 文件,您可以 使用系统外壳中的和 选项调用mysql客户端,如下所示: --xml-e
1 | shell> mysql --xml -e 'SELECT * FROM mydb.mytable' > file.xml |
要将文件读回表中,请使用LOAD XML. 默认情况下,该<row> 元素被认为等同于数据库表行;这可以使用ROWS IDENTIFIED BY子句进行更改。
9. REPLACE 语句
1 | REPLACE [LOW_PRIORITY | DELAYED] |
REPLACE工作方式与 完全相同 INSERT,但如果表中的旧行与 aPRIMARY KEY或UNIQUE 索引的新行具有相同的值, 则在插入新行之前删除旧行。
当我们创建这个表并运行mysql客户端显示的语句时,结果如下:
1 | mysql> REPLACE INTO test VALUES (1, 'Old', '2014-08-20 18:47:00'); |
10. SELECT 语句
1 | SELECT |
在SELECT关键字之后,您可以使用许多影响语句操作的修饰符。HIGH_PRIORITY、 STRAIGHT_JOIN和以 开头的修饰符 SQL_是 MySQL 对标准 SQL 的扩展。
SELECT ... INTO形式SELECT使查询结果可以写入文件或存储在变量中。FOR UPDATE与使用页锁或行锁的存储引擎一起使用,则查询检查的行将被写锁定,直到当前事务结束。- 在
ALL和DISTINCT修饰符指定重复行是否应该返回。ALL(默认)指定应返回所有匹配的行,包括重复行。DISTINCT指定从结果集中删除重复行。指定两个修饰符是错误的。DISTINCTROW是 的同义词DISTINCT。 HIGH_PRIORITYSELECT比更新表的语句具有 更高的优先级。您应该仅将它用于非常快且必须立即完成的查询。SELECT HIGH_PRIORITY即使存在等待表空闲的更新语句,在表被锁定以供读取时发出的 查询也会运行。这会影响只使用表级锁只存储引擎(例如MyISAM,MEMORY和MERGE)。STRAIGHT_JOIN强制优化器按照它们在FROM子句中列出的顺序连接表 。如果优化器以非最佳顺序连接表,您可以使用它来加速查询。STRAIGHT_JOIN也可以在 *table_references*列表中使用。SQL_BIG_RESULTorSQL_SMALL_RESULT可以与GROUP BYorDISTINCT一起使用, 分别告诉优化器结果集有多行或很小。对于SQL_BIG_RESULT,如果创建了基于磁盘的临时表,MySQL 会直接使用它们,并且更喜欢排序而不是使用在GROUP BY元素上带有键的临时表。对于SQL_SMALL_RESULT,MySQL 使用内存中的临时表来存储结果表,而不是使用排序。这通常不需要。SQL_BUFFER_RESULT强制将结果放入临时表中。这有助于 MySQL 尽早释放表锁,并有助于将结果集发送到客户端需要很长时间的情况。此修饰符只能用于顶级SELECT语句,不能用于子查询或后续UNION。SQL_CALC_FOUND_ROWS告诉 MySQL 计算结果集中有多少行,不考虑任何LIMIT子句。然后可以使用 检索行数SELECT FOUND_ROWS()。- 在
SQL_CACHE和SQL_NO_CACHE改进剂与之前的MySQL 8.0查询缓存使用。MySQL 8.0 中删除了查询缓存。该SQL_CACHE修改被移除。SQL_NO_CACHE已弃用,无效;希望在未来的 MySQL 版本中将其删除。
SELECT … INTO 语句
SELECT ... INTO形式SELECT 使查询结果可以存储在变量中或写入文件:
SELECT ... INTO *var_list*选择列值并将它们存储到变量中。SELECT ... INTO OUTFILE将选定的行写入文件。可以指定列和行终止符以生成特定的输出格式。SELECT ... INTO DUMPFILE在没有任何格式的情况下将单行写入文件。
一个给定的SELECT语句最多可以包含一个INTO子句,尽管如SELECT语法描述所示(参见第 13.2.10 节,“SELECT 语句”),它们 INTO可以出现在不同的位置:
之前
FROM。例子:1
SELECT * INTO @myvar FROM t1;
在尾随锁定子句之前。例子:
1
SELECT * FROM t1 INTO @myvar FOR UPDATE;
在
SELECT. 例子:1
SELECT * FROM t1 FOR UPDATE INTO @myvar;
您还可以使用SELECT ... INTO OUTFILEwithVALUES语句将值直接写入文件。此处显示了一个示例:
1 | SELECT * FROM (VALUES ROW(1,2,3),ROW(4,5,6),ROW(7,8,9)) AS t |
JOIN子句
如果*table_reference*项目列表中的每个逗号都被视为等效于内部联接,则这是一种保守的扩展 。例如:
1 | SELECT * FROM t1 LEFT JOIN (t2, t3, t4) |
相当于:
1 | SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) |
在 MySQL 中JOIN,CROSS JOIN、 和INNER JOIN是句法等价物(它们可以相互替换)。在标准 SQL 中,它们不是等价的。INNER JOIN与ON子句一起 使用,CROSS JOIN不是。
一个*table_subquery在FROM 子句中也称为派生表或子查询。此类子查询必须*包含一个别名,以便为子查询结果提供表名,并且可以选择在括号中包含表列名列表。一个简单的例子如下:
1 | SELECT * FROM (SELECT 1, 2, 3) AS t1; |
一些连接示例:
1 | SELECT * FROM table1, table2; |
根据标准 SQL 进行冗余列消除和列排序,产生以下显示顺序:
- 首先,按照它们在第一个表中出现的顺序合并两个连接表的公共列
- 其次,第一个表独有的列,按照它们在该表中出现的顺序
- 第三,第二个表独有的列,按照它们在该表中出现的顺序
一个USING子句可以改写为一个 ON用于比较相应列子句。然而,虽然USING和 ON很相似,但它们并不完全相同。考虑以下两个查询:
1 | a LEFT JOIN b USING (c1, c2, c3) |
关于确定要显示哪些列以进行 SELECT *扩展,这两个连接在语义上并不相同。在USING加入选择对应列的聚结的值,而ON加入选择来自所有表的所有列。对于USING连接, SELECT *选择以下值:
1 | COALESCE(a.c1, b.c1), COALESCE(a.c2, b.c2), COALESCE(a.c3, b.c3) |
对于ON连接,SELECT *选择以下值:
1 | a.c1, a.c2, a.c3, b.c1, b.c2, b.c3 |
JOIN具有比逗号运算符 ( ,)更高的优先级,因此连接表达式 t1, t2 JOIN t3被解释为 (t1, (t2 JOIN t3)),而不是((t1, t2) JOIN t3)。这会影响使用ON子句的语句, 因为该子句只能引用连接操作数中的列,并且优先级会影响对这些操作数是什么的解释。
例子:
1 | CREATE TABLE t1 (i1 INT, j1 INT); |
JOIN优先于逗号运算符,因此ON 子句的操作数是t2and t3。因为t1.i1在任一操作数中都不是列,所以结果是Unknown column 't1.i1' in 'on clause'错误。
要处理连接,请使用以下任一策略:
用括号将前两个表显式分组,以便
ON子句的操作数是(t1, t2)andt3:1
SELECT * FROM (t1, t2) JOIN t3 ON (t1.i1 = t3.i3);
避免使用逗号运算符,
JOIN而是使用 :1
SELECT * FROM t1 JOIN t2 JOIN t3 ON (t1.i1 = t3.i3);
相同的优先级解释也适用于将逗号运算符与INNER JOIN, CROSS JOIN, LEFT JOIN, and混合在一起的语句RIGHT JOIN,所有这些都比逗号运算符具有更高的优先级。
UNION 子句
1 | SELECT ... |
UNION将多个SELECT语句的结果合并为一个结果集。例子:
1 | mysql> SELECT 1, 2; |
结果集列名和数据类型
对于列名UNION 的结果集是从第一列名取 SELECT声明。
每条SELECT语句对应位置列出的选定列 应具有相同的数据类型。例如,第一个语句选择的第一列应该与其他语句选择的第一列具有相同的类型。如果对应SELECT列的数据类型不匹配,则UNION结果中列的类型和长度 会考虑所有SELECT语句检索到的值 。例如,请考虑以下情况,其中列长度不受第一个值的长度限制 SELECT:
1 | mysql> SELECT REPEAT('a',1) UNION SELECT REPEAT('b',20); |
要强制列名相同,VALUES请将左侧的包裹 在 a 中 SELECT并使用别名,如下所示:
1 | SELECT * FROM (VALUES ROW(4,-2), ROW(5,9)) AS t(x,y) |
UNION DISTINCT 和 UNION ALL
默认情况下,从UNION结果中删除重复的行 。optional DISTINCT关键字具有相同的效果,但使其明确。使用 optionalALL 关键字,不会发生重复行删除,结果包括所有SELECT语句中的所有匹配行 。
您可以在同一个查询中混合使用UNION ALL和UNION DISTINCT。混合 UNION类型的处理方式是DISTINCT联合覆盖ALL其左侧的任何 联合。甲 DISTINCT工会可以明确,通过使用来产生UNION DISTINCT或隐式地通过使用 UNION没有以下 DISTINCT或ALL关键字。
UNION 中的 ORDER BY 和 LIMIT
要将ORDER BYor LIMIT子句应用于个人 SELECT, SELECT请将子句放在括号中并将子句放在括号内:
1 | (SELECT a FROM t1 WHERE a=10 AND B=1 ORDER BY a LIMIT 10) |
使用ORDER BYfor individual SELECT语句不暗示行在最终结果中出现的顺序,因为UNION默认情况下会生成一组无序的行。因此,ORDER BY 在此上下文中,通常与 结合使用 LIMIT,以确定要为 检索的选定行的子集 SELECT,即使它不一定影响最终UNION结果中这些行的顺序 。如果ORDER BY没有出现LIMIT在 a 中 SELECT,它会被优化掉,因为它在任何情况下都不起作用。
要使UNION结果中的行由一个接SELECT一个检索到的行集组成,请在每个中 选择一个额外的列 SELECT用作排序列,并ORDER BY在最后一个 之后的该列上添加一个排序SELECT:
1 | (SELECT 1 AS sort_col, col1a, col1b, ... FROM t1) |
要额外维护单个SELECT结果中的排序顺序 ,请向ORDER BY子句添加辅助列:
1 | (SELECT 1 AS sort_col, col1a, col1b, ... FROM t1) |
联合限制
在 一个UNION, SELECT语句是普通的 select 语句,但有以下限制:
HIGH_PRIORITY在第一个SELECT没有影响。HIGH_PRIORITY在任何后续SELECT产生语法错误。- 只有最后一个
SELECT语句可以使用INTO子句。但是,整个UNION结果将写入INTO输出目标。
MySQL 8.0 中的 UNION 处理与 MySQL 5.7 的比较
在MySQL 8.0,解析器规则 SELECT和 UNION被重构为更加一致(在同一SELECT 语法均匀地施加在每一个这样的上下文中)和减少重复。与 MySQL 5.7 相比,这项工作产生了几个用户可见的效果,这可能需要重写某些语句:
NATURAL JOIN允许可选INNER关键字 (NATURAL INNER JOIN),符合标准 SQL。允许不带括号的右深连接(例如,
... JOIN ... JOIN ... ON ... ON),符合标准 SQL。STRAIGHT_JOIN现在允许一个USING子句,类似于其他内部连接。解析器接受围绕查询表达式的括号。例如,
(SELECT ... UNION SELECT ...)被允许。另请参见 第 13.2.10.4 节,“带括号的查询表达式”。解析器更好地符合记录的
SQL_CACHE和SQL_NO_CACHE查询修饰符的允许放置。联合的左手嵌套,以前只允许在子查询中,现在允许在顶级语句中。例如,此语句现在被接受为有效:
1
(SELECT 1 UNION SELECT 1) UNION SELECT 1;
锁定子句 (
FOR UPDATE,LOCK IN SHARE MODE) 只允许在非UNION查询中使用。这意味着括号必须用于SELECT包含锁定子句的语句。此声明不再被接受为有效:1
SELECT 1 FOR UPDATE UNION SELECT 1 FOR UPDATE;
相反,写这样的语句:
1
(SELECT 1 FOR UPDATE) UNION (SELECT 1 FOR UPDATE);
带括号的查询表达式
带括号的查询表达式也用作查询表达式,因此通常由查询块组成的查询表达式也可能由带括号的查询表达式组成:
1 | (SELECT * FROM t1 ORDER BY a) UNION (SELECT * FROM t2 ORDER BY b) ORDER BY z; |
你不能有一个带有尾随ORDER BYor的查询块LIMIT,而不将它包裹在括号中,但括号可以以各种方式用于强制执行:
要
LIMIT对每个查询块强制执行:1
(SELECT 1 LIMIT 1) UNION (SELECT 2 LIMIT 1);
要
LIMIT对查询块和整个查询表达式强制执行:1
(SELECT 1 LIMIT 1) UNION (SELECT 2 LIMIT 1) LIMIT 1;
强制执行
LIMIT整个查询表达式(不带括号):1
SELECT 1 UNION SELECT 2 LIMIT 1;
混合执行:
LIMIT在第一个查询块和整个查询表达式上:1
(SELECT 1 LIMIT 1) UNION SELECT 2 LIMIT 1;
11. 子查询
子查询是SELECT另一个语句中的语句。
支持 SQL 标准要求的所有子查询形式和操作,以及一些特定于 MySQL 的功能。
下面是一个子查询的例子:
1 | SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2); |
在这个例子中,SELECT * FROM t1 ...是 外部查询(或外部语句),(SELECT column1 FROM t2)是子查询。我们说子查询嵌套在外部查询中,实际上可以将子查询嵌套在其他子查询中,达到相当的深度。子查询必须始终出现在括号内。
作为标量操作数的子查询
在最简单的形式中,子查询是返回单个值的标量子查询。标量子查询是一个简单的操作数,您几乎可以在单个列值或文字合法的任何地方使用它,并且您可以期望它具有所有操作数都具有的那些特征:数据类型、长度、它可以是NULL,等等。例如:
1 | CREATE TABLE t1 (s1 INT, s2 CHAR(5) NOT NULL); |
此中的子查询SELECT 返回单个值 ( 'abcde'),该值 ( ) 的数据类型为CHAR,长度为 5,字符集和排序规则等于当时生效的默认值 CREATE TABLE,并指示列中的值可以是 NULL。标量子查询所选值的可空性不会被复制,因为如果子查询结果为空,则结果为NULL。对于刚刚显示的子查询,如果t1为空,结果将是NULL即使 s2是NOT NULL。
使用子查询的比较
下面是一个不能使用连接进行的常见形式子查询比较的示例。它查找 tablet1中column1 值等于 table 中最大值的 所有行 t2:
1 | SELECT * FROM t1 |
这是另一个示例,这对于连接也是不可能的,因为它涉及对其中一个表进行聚合。它查找表中t1包含在给定列中出现两次的值的所有行:
1 | SELECT * FROM t1 AS t |
对于子查询与标量的比较,子查询必须返回标量。为了将子查询与行构造函数进行比较,子查询必须是一个行子查询,它返回具有与行构造函数相同数量的值的行。
带有 ANY、IN 或 SOME 的子查询
句法:
1 | operand comparison_operator ANY (subquery) |
该ANY关键字,它必须遵循一个比较操作符,表示“回报TRUE 如果比较TRUE的 ANY列的子查询返回的值。”例如:
1 | SELECT s1 FROM t1 WHERE s1 > ANY (SELECT s1 FROM t2); |
假设表t1 中有一行包含(10)。表达是 TRUE如果表t2包含 (21,14,7)因为有一个值 7在t2小于 10。表达式为 FALSEif table t2contains (20,10),或者 tablet2为空。如果表包含 ,则表达式未知(即 NULL)。 t2``(NULL,NULL,NULL)
当与子查询一起使用时,该词IN是 的别名= ANY。因此,这两个语句是相同的:
1 | SELECT s1 FROM t1 WHERE s1 = ANY (SELECT s1 FROM t2); |
这个词SOME是 的别名 ANY。因此,这两个语句是相同的:
1 | SELECT s1 FROM t1 WHERE s1 <> ANY (SELECT s1 FROM t2); |
这个词SOME很少使用,但这个例子说明了为什么它可能有用。对大多数人来说,英语短语“ a is not equal to any b ”的意思是 “没有 b 等于 a ”,但这不是 SQL 语法的意思。语法的意思是 “有一些 b 与 a 不相等。”<> SOME改为使用 有助于确保每个人都理解查询的真正含义。
从MySQL 8.0.19开始,可以使用 TABLE在一个标量 IN,ANY或 SOME子查询中提供的表格只包含一列。如果t2只有一列,则本节前面显示的语句可以如下所示编写,在每种情况下替换TABLE t2为SELECT s1 FROM t2:
1 | SELECT s1 FROM t1 WHERE s1 > ANY (TABLE t2); |
ALL 子查询
句法:
1 | operand comparison_operator ALL (subquery) |
这个词ALL,它必须遵循一个比较操作符,表示“回报TRUE如果比较TRUE的ALL 列的子查询返回的值。” 例如:
1 | SELECT s1 FROM t1 WHERE s1 > ALL (SELECT s1 FROM t2); |
行子查询
标量或列子查询返回单个值或一列值。一个行子查询是一个子查询变体返回单个行,因此可以返回多个列值。行子查询比较的合法运算符是:
1 | = > < >= <= <> != <=> |
这里有两个例子:
1 | SELECT * FROM t1 |
带有 EXISTS 或 NOT EXISTS 的子查询
如果一个子查询都返回任何行,是 和是 。例如: EXISTS *subquery*``TRUE``NOT EXISTS *subquery*``FALSE
1 | SELECT column1 FROM t1 WHERE EXISTS (SELECT * FROM t2); |
传统上,EXISTS子查询以 开头 SELECT *,但也可以以SELECT 5或SELECT column1 或任何开头 。MySQL 会忽略SELECT此类子查询中的 列表,因此没有区别。
对于前面的示例,如果t2包含任何行,甚至只有NULL值的行,则EXISTS条件为 TRUE。这实际上是一个不太可能的例子,因为[NOT] EXISTS子查询几乎总是包含相关性。下面是一些更现实的例子:
一个或多个城市有什么样的商店?
1
2
3SELECT DISTINCT store_type FROM stores
WHERE EXISTS (SELECT * FROM cities_stores
WHERE cities_stores.store_type = stores.store_type);没有城市有什么样的商店?
1
2
3SELECT DISTINCT store_type FROM stores
WHERE NOT EXISTS (SELECT * FROM cities_stores
WHERE cities_stores.store_type = stores.store_type);所有城市都有什么样的商店?
1
2
3
4
5
6SELECT DISTINCT store_type FROM stores s1
WHERE NOT EXISTS (
SELECT * FROM cities WHERE NOT EXISTS (
SELECT * FROM cities_stores
WHERE cities_stores.city = cities.city
AND cities_stores.store_type = stores.store_type));
最后一个示例是双嵌套NOT EXISTS查询。也就是说,它在一个NOT EXISTS子句中有一个NOT EXISTS 子句。形式上,它回答了“一个城市是否存在一个商店不在其中 Stores”的问题?但更容易说嵌套NOT EXISTS回答了 “是 为所有人?*x* TRUE*y*”
在 MySQL 8.0.19 及更高版本中,您还可以在子查询中使用NOT EXISTS或NOT EXISTSwith TABLE,如下所示:
1 | SELECT column1 FROM t1 WHERE EXISTS (TABLE t2); |
结果SELECT *与WHERE在子查询中使用with no子句时的结果相同。
派生表
派生表是在查询FROM子句范围内生成表的表达式。例如,SELECT 语句FROM子句中的子查询是一个派生表:
1 | SELECT ... FROM (subquery) [AS] tbl_name ... |
该JSON_TABLE()函数生成一个表并提供另一种创建派生表的方法:
1 | SELECT * FROM JSON_TABLE(arg_list) [AS] tbl_name ... |
为了说明起见,假设您有这个表:
1 | CREATE TABLE t1 (s1 INT, s2 CHAR(5), s3 FLOAT); |
以下是如何在FROM 子句中使用子查询,使用示例表:
1 | INSERT INTO t1 VALUES (1,'1',1.0); |
结果:
1 | +------+------+------+ |
这是另一个示例:假设您想知道分组表的一组总和的平均值。这不起作用:
1 | SELECT AVG(SUM(column1)) FROM t1 GROUP BY column1; |
但是,此查询提供了所需的信息:
1 | SELECT AVG(sum_column1) |
请注意,子查询 ( sum_column1) 中使用的列名在外部查询中被识别。
派生表的列名来自其选择列表:
1 | mysql> SELECT * FROM (SELECT 1, 2, 3, 4) AS dt; |
要显式提供列名,请在派生表名后面加上一个带括号的列名列表:
1 | mysql> SELECT * FROM (SELECT 1, 2, 3, 4) AS dt (a, b, c, d); |
横向派生表
派生表通常不能引用(依赖)同一FROM子句中前面表的列。从 MySQL 8.0.14 开始,派生表可以定义为横向派生表,以指定允许此类引用。
非横向派生表使用第 13.2.11.8 节“派生表”中讨论的语法指定。除了关键字LATERAL在派生表规范之前指定之外,横向派生表的语法与非横向派生表的语法相同。的 LATERAL关键字必须先于每个表以用作横向派生表。
以下讨论显示横向派生表如何使某些 SQL 操作成为可能,而这些 SQL 操作无法用非横向派生表完成或需要较低效率的变通方法。
假设我们要解决这个问题:给定一个销售人员表(每行描述销售人员的成员)和所有销售表(每行描述销售:销售人员、客户、金额, 日期),确定每个销售人员最大销售额的规模和客户。这个问题可以通过两种方式解决。
解决问题的第一种方法:对于每个销售人员,计算最大销售规模,并找到提供此最大值的客户。在 MySQL 中,可以这样做:
1 | SELECT |
该查询效率低下,因为它为每个销售人员计算了两次最大大小(一次在第一个子查询中,一次在第二个子查询中)。
但是,该查询在 SQL-92 中是非法的,因为派生表不能依赖于同一FROM子句中的其他表 。派生表必须在查询的持续时间内保持不变,不包含对其他FROM子句表的列的引用。如所写,查询产生此错误:
1 | ERROR 1054 (42S22): Unknown column 'salesperson.id' in 'where clause' |
在 SQL:1999 中,如果派生表前面有LATERAL关键字(这意味着 “此派生表依赖于其左侧的先前表”),则查询变得合法:
1 | SELECT |
横向派生表不需要是恒定的,并且每次由顶级查询处理它所依赖的前一个表中的新行时都会更新。
解决问题的第二种方法:如果SELECT列表中的子查询可以返回多个列,则可以使用不同的解决方案 :
1 | SELECT |
这是有效但非法的。它不起作用,因为此类子查询只能返回单个列:
1 | ERROR 1241 (21000): Operand should contain 1 column(s) |
重写查询的一种尝试是从派生表中选择多个列:
1 | SELECT |
但是,这也行不通。派生表依赖于该salesperson表,因此如果没有LATERAL:
1 | ERROR 1054 (42S22): Unknown column 'salesperson.id' in 'where clause' |
添加LATERAL关键字使查询合法:
1 | SELECT |
简而言之,这LATERAL是解决刚刚讨论的两种方法中所有缺点的有效解决方案。
子查询错误
有一些错误仅适用于子查询。本节介绍它们。
不支持的子查询语法:
1
2
3
4ERROR 1235 (ER_NOT_SUPPORTED_YET)
SQLSTATE = 42000
Message = "This version of MySQL doesn't yet support
'LIMIT & IN/ALL/ANY/SOME subquery'"这意味着 MySQL 不支持如下语句:
1
SELECT * FROM t1 WHERE s1 IN (SELECT s2 FROM t2 ORDER BY s1 LIMIT 1)
来自子查询的列数不正确:
1
2
3ERROR 1241 (ER_OPERAND_COL)
SQLSTATE = 21000
Message = "Operand should contain 1 column(s)"在这样的情况下会发生此错误:
1
SELECT (SELECT column1, column2 FROM t2) FROM t1;
如果目的是行比较,您可以使用返回多列的子查询。在其他上下文中,子查询必须是标量操作数。见 第 13.2.11.5 节,“行子查询”。
来自子查询的行数不正确:
1
2
3ERROR 1242 (ER_SUBSELECT_NO_1_ROW)
SQLSTATE = 21000
Message = "Subquery returns more than 1 row"对于子查询必须最多返回一行但返回多行的语句,会发生此错误。考虑以下示例:
1
SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);
如果
SELECT column1 FROM t2只返回一行,则前一个查询有效。如果子查询返回多于一行,则会出现错误 1242。在这种情况下,查询应改写为:1
SELECT * FROM t1 WHERE column1 = ANY (SELECT column1 FROM t2);
子查询中错误使用的表:
1
2
3
4Error 1093 (ER_UPDATE_TABLE_USED)
SQLSTATE = HY000
Message = "You can't specify target table 'x'
for update in FROM clause"在以下情况下会发生此错误,该情况尝试修改表并从子查询中的同一表中进行选择:
1
UPDATE t1 SET column2 = (SELECT MAX(column1) FROM t1);
优化子查询
开发正在进行中,因此没有优化提示是长期可靠的。以下列表提供了一些您可能想使用的有趣技巧。
将子句从外部移动到子查询内部。例如,使用这个查询:
1
2SELECT * FROM t1
WHERE s1 IN (SELECT s1 FROM t1 UNION ALL SELECT s1 FROM t2);而不是这个查询:
1
2SELECT * FROM t1
WHERE s1 IN (SELECT s1 FROM t1) OR s1 IN (SELECT s1 FROM t2);再举一个例子,使用这个查询:
1
SELECT (SELECT column1 + 5 FROM t1) FROM t2;
而不是这个查询:
1
SELECT (SELECT column1 FROM t1) + 5 FROM t2;
对子查询的限制
- 通常,您不能在子查询中修改表并从同一个表中进行选择。例如,此限制适用于以下形式的语句:
1 | DELETE FROM t WHERE ... (SELECT ... FROM t ...); |
例外:如果对于修改后的表,您使用的是派生表并且该派生表被具体化而不是合并到外部查询中,则上述禁令不适用。
在 MySQL 8.0.14 之前,
FROM子句中的子查询不能是相关子查询。它们在查询执行期间整体具体化(评估以生成结果集),因此无法按外部查询的每一行评估它们。优化器延迟实现直到需要结果,这可以避免实现。MySQL 不支持
LIMIT某些子查询运算符的子查询:1
2
3
4mysql> SELECT * FROM t1
WHERE s1 IN (SELECT s2 FROM t2 ORDER BY s1 LIMIT 1);
ERROR 1235 (42000): This version of MySQL doesn't yet support
'LIMIT & IN/ALL/ANY/SOME subquery'MySQL 允许子查询引用具有数据修改副作用的存储函数,例如将行插入表中。例如,如果
f()插入行,则以下查询可以修改数据:1
SELECT ... WHERE x IN (SELECT f() ...);
此行为是 SQL 标准的扩展。在 MySQL 中,它可能会产生不确定的结果,因为
f()根据优化器选择如何处理给定查询,它 可能会针对给定查询的不同执行执行不同的次数。对于基于语句或混合格式的复制,这种不确定性的一个含义是这样的查询可能会在源及其副本上产生不同的结果。
12. TABLE语句
TABLE 是 MySQL 8.0.19 中引入的 DML 语句,它返回命名表的行和列。
1 | TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]] |
该TABLE语句在某些方面的作用类似于SELECT. 鉴于存在名为 的表t,以下两个语句产生相同的输出:
1 | TABLE t; |
1 | mysql> TABLE t ORDER BY b LIMIT 3 OFFSET 2; |
为了限制返回哪些表列,过滤超出使用ORDER BY 和LIMIT或两者可以完成的行,使用 SELECT。
TABLE 可以与临时表一起使用。
13. UPDATE 语句
UPDATE 是修改表中行的 DML 语句。
一个UPDATE语句可以用开始WITH子句来定义内访问的公共表表达式 UPDATE。请参阅第 13.2.15 节,“WITH(公用表表达式)”。
单表语法:
1 | UPDATE [LOW_PRIORITY] [IGNORE] table_reference |
多表语法:
1 | UPDATE [LOW_PRIORITY] [IGNORE] table_references |
14. VALUES 语句
VALUES是 MySQL 8.0.19 中引入的 DML 语句,它将一组一行或多行作为表返回。换句话说,它是一个表值构造函数,它也用作独立的 SQL 语句。
1 | VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number] |
该VALUES语句由VALUES关键字后跟一个或多个行构造函数的列表组成,以逗号分隔。行构造函数由ROW()行构造函数子句和括在括号中的一个或多个标量值的值列表组成。值可以是任何 MySQL 数据类型的文字或解析为标量值的表达式。
ROW()不能为空(但提供的每个标量值都可以是NULL)。ROW()同一VALUES语句中的 每个 语句在其值列表中必须具有相同数量的值。
1 | mysql> VALUES ROW(1,-2,3), ROW(5,7,9), ROW(4,6,8) ORDER BY column_1; |
VALUES可以在您可以使用的许多情况下使用SELECT,包括此处列出的那些:
使用
UNION,如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17mysql> SELECT 1,2 UNION SELECT 10,15;
+----+----+
| 1 | 2 |
+----+----+
| 1 | 2 |
| 10 | 15 |
+----+----+
2 rows in set (0.00 sec)
mysql> VALUES ROW(1,2) UNION VALUES ROW(10,15);
+----------+----------+
| column_0 | column_1 |
+----------+----------+
| 1 | 2 |
| 10 | 15 |
+----------+----------+
2 rows in set (0.00 sec)也可以以这种方式将构造的多于一行的表联合在一起,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12mysql> VALUES ROW(1,2), ROW(3,4), ROW(5,6)
> UNION VALUES ROW(10,15),ROW(20,25);
+----------+----------+
| column_0 | column_1 |
+----------+----------+
| 1 | 2 |
| 3 | 4 |
| 5 | 6 |
| 10 | 15 |
| 20 | 25 |
+----------+----------+
5 rows in set (0.00 sec)UNION在这种情况下, 您也可以(并且通常最好)完全省略 并使用单个**VALUES**语句,如下所示:1
2
3
4
5
6
7
8
9
10mysql> VALUES ROW(1,2), ROW(3,4), ROW(5,6), ROW(10,15), ROW(20,25);
+----------+----------+
| column_0 | column_1 |
+----------+----------+
| 1 | 2 |
| 3 | 4 |
| 5 | 6 |
| 10 | 15 |
| 20 | 25 |
+----------+----------+
15. 常用表表达式CTE
公用表表达式 (CTE) 是一个命名的临时结果集,它存在于单个语句的范围内,并且可以在该语句中稍后引用,可能多次引用。以下讨论描述了如何编写使用 CTE 的语句。
*常用表表达式
要指定公用表表达式,请使用 WITH包含一个或多个逗号分隔子句的子句。每个子条款提供一个产生结果集的子查询,并将名称与子查询相关联。下面的示例定义名为的CTE cte1和cte2中 WITH子句,并且是指在它们的顶层SELECT下面的WITH子句:
1 | WITH |
在包含该WITH子句的语句中 ,可以引用每个 CTE 名称来访问相应的 CTE 结果集。
给定 CTE 的列名的确定发生如下:
如果 CTE 名称后面是带括号的名称列表,则这些名称是列名称:
1
2
3
4
5
6
7WITH cte (col1, col2) AS
(
SELECT 1, 2
UNION ALL
SELECT 3, 4
)
SELECT col1, col2 FROM cte;
WITH在这些上下文中允许 使用子句:
WITH同一级别 只允许一个子句。WITH跟WITH在同一级别是不允许的,所以这是非法的:
1 | WITH cte1 AS (...) WITH cte2 AS (...) SELECT ... |
要使语句合法,请使用一个 WITH子句,用逗号分隔子句:
1 | WITH cte1 AS (...), cte2 AS (...) SELECT ... |
递归CTE示例:
1 | root@sb1 09:41:34>WITH RECURSIVE cte (n) AS |
递归CTE需要加RECURSIVE关键字,使用Union all来产生结果
1 | SELECT ...定义初始化值,不引用自身, 同时初始化值的列也定义了cte上的列的个数和类型,可以用cast重定义 |
递归的部分不可以包含:
1 | Aggregate functions such as SUM() |
CTE也就是common table expressions是sql标准里的语法,很多数据库都能够支持,MySQL也在8.0版本里加入了CTE功能,本文主要简单的介绍下该语法的用法,由于笔者对server层了解不深,本文不探讨代码层
CTE与derived table最大的不同之处是
- 可以自引用,递归使用(recursive cte
- 在语句级别生成独立的临时表. 多次调用只会执行一次
- 一个cte可以引用另外一个cte
- 一个CTE语句其实和CREATE [TEMPORARY] TABLE类似,但不需要显式的创建或删除,也不需要创建表的权限。更准确的说,CTE更像是一个临时的VIEW
12.4 事务和锁定语句
1. START TRANSACTION、COMMIT 和 ROLLBACK 语句
1 | START TRANSACTION |
这些语句提供对事务使用的控制 :
START TRANSACTION或BEGIN开始新的交易。COMMIT提交当前事务,使其更改永久化。ROLLBACK回滚当前事务,取消其更改。SET autocommit禁用或启用当前会话的默认自动提交模式。
默认情况下,MySQL 在启用自动提交模式的情况下运行 。这意味着,当不在事务内部时,每个语句都是原子的,就好像它被START TRANSACTION和包围一样COMMIT。您不能使用ROLLBACK来撤消效果;但是,如果在语句执行过程中发生错误,则该语句将被回滚。
有些语句不能回滚。通常,这些包括数据定义语言 (DDL) 语句,例如创建或删除数据库的语句,创建、删除或更改表或存储例程的语句。
你应该设计你的交易不包括这样的陈述。如果您在无法回滚的事务中尽早发出语句,然后另一条语句失败,则在这种情况下,无法通过发出ROLLBACK 语句来回滚事务的全部效果 。
2. 导致隐式提交的语句
本节中列出的语句(以及它们的任何同义词)隐式地结束当前会话中活动的任何事务,就好像您COMMIT在执行语句之前已经完成了一样。
大多数这些语句在执行后也会导致隐式提交。目的是在它自己的特殊事务中处理每个这样的语句。事务控制和锁定语句是例外:如果隐式提交在执行之前发生,另一个不会在执行之后发生。
定义或修改数据库对象的数据定义语言 (DDL) 语句。
隐式使用或修改
mysql数据库中的表的语句。事务控制和锁定语句。
BEGIN,LOCK TABLES,SET autocommit = 1(如果该值不是 1),START TRANSACTION,UNLOCK TABLES.UNLOCK TABLES仅当任何表当前已被锁定LOCK TABLES以获取非事务表锁时才提交事务。UNLOCK TABLES后续不会发生提交,FLUSH TABLES WITH READ LOCK因为后一个语句不获取表级锁。行政声明。
ANALYZE TABLE,CACHE INDEX,CHECK TABLE,FLUSH,LOAD INDEX INTO CACHE,OPTIMIZE TABLE,REPAIR TABLE,RESET(但不是RESET PERSIST)。复制控制语句。
START REPLICA,STOP REPLICA,RESET REPLICA,CHANGE REPLICATION SOURCE TO,CHANGE MASTER TO
3. SAVEPOINT、ROLLBACK TO SAVEPOINT 和 RELEASE SAVEPOINT 语句
1 | SAVEPOINT identifier |
InnoDB支持SQL语句 SAVEPOINT, ROLLBACK TO SAVEPOINT, RELEASE SAVEPOINT和可选WORK 关键字 ROLLBACK。
该SAVEPOINT语句设置了一个名称为 的命名事务保存点 *identifier*。如果当前事务有一个同名的保存点,则删除旧的保存点并设置一个新的保存点。
该ROLLBACK TO SAVEPOINT语句将事务回滚到指定的保存点而不终止事务。修改当前事务的保存点被设置后,行所做的是在百废待兴回滚,但InnoDB也 不会释放被保存点后存储在内存中的行锁。(对于新插入的行,锁信息由存储在行中的事务ID携带;锁不单独存储在内存中。在这种情况下,行锁在undo中释放。)晚于指定的保存点被删除。
如果该ROLLBACK TO SAVEPOINT语句返回以下错误,则表示不存在具有指定名称的保存点:
1 | ERROR 1305 (42000): SAVEPOINT identifier does not exist |
该RELEASE SAVEPOINT语句从当前事务的保存点集中删除指定的保存点。不会发生提交或回滚。如果保存点不存在,则会出错。
如果执行 aCOMMIT或未ROLLBACK命名保存点的a , 将删除当前事务的所有保存点。
当调用存储的函数或激活触发器时,会创建一个新的保存点级别。先前级别上的保存点变得不可用,因此不会与新级别上的保存点冲突。当函数或触发器终止时,它创建的任何保存点都将被释放并恢复先前的保存点级别。
4. LOCK INSTANCE FOR BACKUP 和 UNLOCK INSTANCE 语句
1 | LOCK INSTANCE FOR BACKUP |
LOCK INSTANCE FOR BACKUP获取实例级备份锁,允许在线备份期间进行 DML,同时防止可能导致不一致快照的操作。
执行LOCK INSTANCE FOR BACKUP 语句需要BACKUP_ADMIN 特权。从早期版本执行就地升级到 MySQL 8.0 时,该BACKUP_ADMIN 权限会自动授予具有该RELOAD权限的用户 。
多个会话可以同时持有一个备份锁。
UNLOCK INSTANCE释放当前会话持有的备份锁。如果会话终止,会话持有的备份锁也会被释放。
LOCK INSTANCE FOR BACKUP允许仅影响用户创建的临时表的 DDL 操作。实际上,在持有备份锁时,可以创建、重命名或删除属于用户创建的临时表的文件。也允许创建二进制日志文件。
获取的备份锁LOCK INSTANCE FOR BACKUP独立于事务锁和获取的锁 ,并且允许以下语句序列: FLUSH TABLES *tbl_name* [, *tbl_name*] ... WITH READ LOCK
1 | LOCK INSTANCE FOR BACKUP; |
1 | FLUSH TABLES tbl_name [, tbl_name] ... WITH READ LOCK; |
5. LOCK TABLES 和 UNLOCK TABLES 语句
1 | LOCK TABLES |
MySQL 使客户端会话能够明确获取表锁,以便与其他会话合作访问表,或者在会话需要独占访问表时防止其他会话修改表。会话只能为自己获取或释放锁。一个会话不能为另一个会话获取锁或释放另一个会话持有的锁。
UNLOCK TABLES显式释放当前会话持有的任何表锁。LOCK TABLES 在获取新锁之前隐式释放当前会话持有的任何表锁。
6. SET TRANSACTION 语句
1 | SET [GLOBAL | SESSION] TRANSACTION |
此语句指定 事务 特性。它需要一个由逗号分隔的一个或多个特征值的列表。每个特征值设置事务隔离级别或访问模式。隔离级别用于对InnoDB表进行操作。访问模式指定事务是以读/写还是只读模式运行。
7. XA 事务
存储引擎 支持XA事务。
XA 支持分布式事务,即允许多个独立的事务资源参与一个全局事务的能力。事务资源通常是 RDBMS,但也可能是其他类型的资源。
全局事务涉及多个本身是事务性的操作,但所有操作都必须作为一个组成功完成,或者作为一个组全部回滚。本质上,这将 ACID 属性“上一层”扩展,以便多个 ACID 事务可以作为具有 ACID 属性的全局操作的组件一起执行。(与非分布式事务一样,SERIALIZABLE如果您的应用程序对读取现象敏感,则 可能是首选。 REPEATABLE READ对于分布式事务可能不够。)
分布式事务的一些示例:
- 应用程序可以充当将消息传递服务与 RDBMS 相结合的集成工具。该应用程序确保处理消息发送、检索和处理的事务(也涉及事务数据库)都发生在全局事务中。您可以将其视为 “交易电子邮件”。”
- 应用程序执行涉及不同数据库服务器的操作,例如 MySQL 服务器和 Oracle 服务器(或多个 MySQL 服务器),其中涉及多个服务器的操作必须作为全局事务的一部分发生,而不是作为每个服务器本地的单独事务发生.
- 银行将帐户信息保存在 RDBMS 中,并通过自动柜员机 (ATM) 分配和接收资金。有必要确保 ATM 操作正确反映在帐户中,但这不能仅通过 RDBMS 来完成。全局交易管理器集成了 ATM 和数据库资源,以确保金融交易的整体一致性。
使用全局事务的应用程序涉及一个或多个资源管理器和一个事务管理器:
- 资源管理器 (RM) 提供对事务资源的访问。数据库服务器是一种资源管理器。必须可以提交或回滚 RM 管理的事务。
- 事务管理器 (TM) 协调作为全局事务一部分的事务。它与处理每个事务的 RM 进行通信。全局事务中的单个事务是全局事务的 “分支”。全局事务及其分支由稍后描述的命名方案标识。
XA 的 MySQL 实现使 MySQL 服务器能够充当处理全局事务中的 XA 事务的资源管理器。连接到 MySQL 服务器的客户端程序充当事务管理器。
要执行全局事务,需要知道涉及哪些组件,并将每个组件带到可以提交或回滚的点。根据每个组件报告其成功能力的内容,它们必须全部作为原子组提交或回滚。也就是说,要么所有组件都必须提交,要么所有组件都必须回滚。要管理全局事务,必须考虑到任何组件或连接网络都可能出现故障。
执行全局事务的过程使用两阶段提交 (2PC)。这发生在全局事务的分支执行的操作已经执行之后。
- 在第一阶段,所有分支都准备好了。也就是说,TM 告诉他们准备提交。通常,这意味着管理分支的每个 RM 都会在稳定存储中记录分支的操作。分支表明他们是否能够做到这一点,这些结果用于第二阶段。
- 在第二阶段,TM 告诉 RM 是提交还是回滚。如果所有分支在准备好时都表示可以提交,则所有分支都会被告知提交。如果任何分支在准备时表示它无法提交,则所有分支都会被告知回滚。
在某些情况下,全局事务可能使用单阶段提交 (1PC)。例如,当事务管理器发现全局事务仅包含一个事务资源(即单个分支)时,可以告知该资源同时准备和提交。
XA 事务SQL语句
要在 MySQL 中执行 XA 事务,请使用以下语句:
1 | XA {START|BEGIN} xid [JOIN|RESUME] |
对于XA START,JOINand RESUME子句被识别但无效。
因为XA END该SUSPEND [FOR MIGRATE] 条款被承认但没有效力。
每个 XA 语句都以XA关键字开头,并且大多数都需要一个*xid* 值。An*xid是 XA 事务标识符。它指示该语句适用于哪个事务。xid*值由客户端提供,或由 MySQL 服务器生成。一个 *xid*值由一到三个部分组成:
1 | xid: gtrid [, bqual [, formatID ]] |
*gtrid是全局事务标识符,bqual是分支限定符,并且formatID是标识gtrid*和 *bqual值使用的格式的数字 。如语法所示,bqual*和 *formatID*是可选的。 如果未给出,则为默认 bqual值''。formatID 如果未给出,则默认值为 1。
XA 事务状态
XA 事务通过以下状态进行:
- 使用
XA START启动一个XA事务,并把它的ACTIVE状态。 - 对于
ACTIVEXA 事务,发出构成事务的 SQL 语句,然后发出XA END语句。XA END将交易置于IDLE状态。 - 对于
IDLEXA 事务,您可以发出XA PREPARE语句或XA COMMIT ... ONE PHASE语句:XA PREPARE将交易置于PREPARED状态。此时的XA RECOVER语句在*xid*其输出中包含交易的值,因为XA RECOVER列出了处于该PREPARED状态的所有 XA 交易。XA COMMIT ... ONE PHASE准备并提交事务。由于事务终止,该 *xid*值未列出XA RECOVER。
- 对于
PREPAREDXA 事务,您可以发出XA COMMIT语句来提交和终止事务,或者XA ROLLBACK回滚和终止事务。
这是一个简单的 XA 事务,它作为全局事务的一部分将一行插入到表中:
1 | mysql> XA START 'xatest'; |
在给定客户端连接的上下文中,XA 事务和本地(非 XA)事务是互斥的。例如,如果XA START已发出开始 XA 事务,则在 XA 事务提交或回滚之前无法启动本地事务。相反,如果本地事务已经以 启动 START TRANSACTION,则在事务提交或回滚之前不能使用 XA 语句。
如果 XA 事务处于该ACTIVE状态,则不能发出任何导致隐式提交的语句。这将违反 XA 合同,因为您无法回滚 XA 事务。尝试执行这样的语句会引发以下错误:
1 | ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed |
对 XA 交易的限制
XA 事务支持仅限于 InnoDB存储引擎。
使用 XA 事务存在以下限制:
XA 事务不能完全适应二进制日志的意外停止。如果服务器在执行
XA PREPARE,XA COMMIT,XA ROLLBACK, 或XA COMMIT ... ONE PHASE语句的过程中意外停止,则服务器可能无法恢复到正确状态,从而使服务器和二进制日志处于不一致状态。在这种情况下,二进制日志可能包含未应用的额外 XA 事务,或错过应用的 XA 事务。此外,如果启用了 GTID,则在恢复后@@GLOBAL.GTID_EXECUTED可能无法正确描述已应用的事务。请注意,如果在 之前XA PREPARE、之间XA PREPARE和XA COMMIT(或XA ROLLBACK)或之后XA COMMIT(或XA ROLLBACK)发生意外停止,服务器和二进制日志将被正确恢复并进入一致状态。不支持将复制过滤器或二进制日志过滤器与 XA 事务结合使用。表过滤可能导致副本上的 XA 事务为空,并且不支持空 XA 事务。此外,由于副本的连接元数据存储库和应用程序元数据存储库存储在
InnoDB表中(在 MySQL 8.0 中成为默认设置),数据引擎事务的内部状态在过滤的 XA 事务之后发生更改,并且可能与复制事务上下文状态不一致.ER_XA_REPLICATION_FILTERS每当 XA 事务受到复制过滤器的影响时,就会记录 该错误 ,无论该事务是否因此为空。如果事务不为空,副本可以继续运行,但您应该采取措施停止对 XA 事务使用复制过滤器,以避免潜在问题。如果事务为空,则副本停止。在这种情况下,副本可能处于不确定状态,在这种状态下,复制过程的一致性可能会受到影响。特别是,gtid_executed在副本的副本上设置可能与源上的设置不一致。要解决这种情况,请隔离源并停止所有复制,然后检查复制拓扑中的 GTID 一致性。撤消生成错误消息的 XA 事务,然后重新启动复制。XA 事务对于基于语句的复制被认为是不安全的。如果在源上并行提交的两个 XA 事务以相反的顺序在副本上准备,则会出现无法安全解决的锁定依赖关系,并且复制可能会因副本上的死锁而失败。对于单线程或多线程副本,可能会发生这种情况。当
binlog_format=STATEMENT设置,则会发出警告XA事务的内部DML语句。当binlog_format=MIXED或binlog_format=ROW设置,XA事务内的DML语句使用基于行的复制记录,以及潜在的问题是不存在的。
12.5 复制语句
1. 控制源服务器的 SQL 语句
PURGE BINARY LOGS 语句
1 | PURGE { BINARY | MASTER } LOGS { |
二进制日志是一组文件,其中包含有关 MySQL 服务器所做的数据修改的信息。日志由一组二进制日志文件和一个索引文件组成
该PURGE BINARY LOGS语句删除指定日志文件名或日期之前的日志索引文件中列出的所有二进制日志文件。 BINARY和MASTER是同义词。删除的日志文件也会从索引文件中记录的列表中删除,以便给定的日志文件成为列表中的第一个。
RESET MASTER 语句
对于启用了二进制日志 ( log_binis ON)的服务器,RESET MASTER删除所有现有的二进制日志文件并重置二进制日志索引文件,将服务器重置为启动二进制日志之前的状态。创建一个新的空二进制日志文件,以便可以重新启动二进制日志记录。
对于使用 GTID ( gtid_modeis ON)的服务器,发出RESET MASTER重置 GTID 执行历史记录。
SET sql_log_bin 语句
该sql_log_bin变量控制是否为当前会话启用日志记录到二进制日志(假设二进制日志本身已启用)。默认值为ON。要为当前会话禁用或启用二进制日志记录,请将会话sql_log_bin变量设置为 OFF或ON。
2. 控制副本服务器的 SQL 语句
CHANGE MASTER TO 语句
CHANGE MASTER TO更改副本服务器用于连接到源和从源读取数据的参数。它还更新复制元数据存储库的内容
CHANGE MASTER TO根据复制 SQL 线程和复制 I/O(接收器)线程的状态, 您可以在不先停止的情况下对正在运行的副本发出语句。
CHANGE REPLICATION FILTER 语句
CHANGE REPLICATION FILTER在副本上设置一个或多个复制过滤规则,其方式与使用复制过滤选项(如 或 ) 启动副本mysqld相同。与服务器选项的情况不同,此语句不需要重新启动服务器即可生效,只需先停止使用复制 SQL 线程 (然后再重新启动 )。需要 特权(或已弃用的 特权)。
CHANGE REPLICATION SOURCE TO 语句
CHANGE REPLICATION SOURCE TO 更改副本服务器用于连接到源和从源读取数据的参数。它还更新复制元数据存储库的内容
3. 控制组复制的 SQL 语句
START GROUP_REPLICATION 语句
开始组复制。这些凭据用于group_replication_recovery通道上的分布式恢复 。当您在 上指定用户凭据时START GROUP_REPLICATION,凭据仅保存在内存中,并通过STOP GROUP_REPLICATION语句或服务器关闭删除。您必须发出START GROUP_REPLICATION 声明以再次提供凭据。因此,此方法与group_replication_start_on_boot 系统变量指定的服务器启动时自动启动 Group Replication 不兼容 。
STOP GROUP_REPLICATION 语句
停止组复制
配置Group Replication Primary的功能
以下函数使您可以配置单主复制组的哪个成员为主。
group_replication_set_as_primary()指定组中的特定成员作为新的主要成员,覆盖任何选举过程。传中 *
member_uuid*这是server_uuid要成为新的主成员。必须在以单主模式运行的复制组的成员上发出。
12.6 复合语句语法
本节介绍BEGIN ... END 复合语句和其他可用于存储程序主体的语句的语法 :存储过程和函数、触发器和事件。这些对象是根据存储在服务器上供以后调用的 SQL 代码定义的
复合语句是一个可以包含其他块的块;变量、条件处理程序和游标的声明;和流程控制结构,例如循环和条件测试。
1. BEGIN … END 复合语句
1 | [begin_label:] BEGIN |
BEGIN ... END 语法用于编写复合语句,这些语句可以出现在存储程序(存储过程和函数、触发器和事件)中。一个复合语句可以包含多个语句,用BEGIN和 END关键字括起来。 *statement_list*表示一个或多个语句的列表,每个语句以分号 ( ;) 语句分隔符结尾。在 *statement_list*本身是可选的,所以空复合语句(BEGIN END)是合法的。
2. 声明标签
1 | [begin_label:] BEGIN |
标签被允许 BEGIN ... END 块和对LOOP, REPEAT和 WHILE语句。这些语句的标签使用遵循以下规则:
begin_label后面必须跟一个冒号。- *
begin_label*可以不给 *end_label*。如果 *end_label存在,它必须与 相同begin_label*。 - *
end_label*不能没有begin_label. - 同一嵌套级别的标签必须是不同的。
- 标签最长可达 16 个字符。
要引用标记结构中的标签,请使用 ITERATEor LEAVE语句。以下示例使用这些语句继续迭代或终止循环:
1 | CREATE PROCEDURE doiterate(p1 INT) |
块标签的范围不包括块内声明的处理程序的代码。
3. 存储程序中的变量
系统变量和用户定义的变量可以在存储程序中使用,就像它们可以在存储程序上下文之外使用一样。此外,存储程序可以DECLARE用来定义局部变量,并且可以声明存储例程(过程和函数)以获取在例程与其调用者之间传递值的参数。
局部变量 DECLARE 语句
1 | DECLARE var_name [, var_name] ... type [DEFAULT value] |
此语句声明存储程序中的局部变量。要为变量提供默认值,请包含一个 DEFAULT子句。该值可以指定为表达式;它不必是一个常数。如果DEFAULT缺少该 子句,则初始值为NULL。
就数据类型和溢出检查而言,局部变量被视为存储的例程参数。
4. 流控制语句
MySQL的支持IF, CASE, ITERATE, , ,和 用于存储程序中流程控制结构。它还支持 存储功能。 LEAVE LOOPWHILEREPEATRETURN
其中许多结构包含其他语句,如以下各节中的语法规范所示。这样的构造可以嵌套。例如,一个 IF语句可能包含一个 WHILE循环,而循环本身又包含一个 CASE语句。
MySQL 不支持FOR循环。
CASE 声明
1 | CASE case_value |
或者:
1 | CASE |
CASE存储程序 的语句实现了一个复杂的条件结构。
对于第一种语法,*case_value是一个表达式。该值与when_value每个WHEN子句中的表达式进行 比较, 直到它们中的一个相等。当when_value*找到一个相等时,相应的THEN子句就会 *statement_list*执行。如果 no *when_value*相等,则 执行ELSE子句 *statement_list*(如果有)。
如果没有*when_value*或 *search_condition*匹配测试的值并且CASE语句不包含ELSE子句,则会导致CASE 语句错误的Case not found。
每个*statement_list由一个或多个 SQL 语句组成;statement_list*不允许为空 。
要处理任何WHEN子句都没有匹配值的情况 ,请使用ELSE 包含空 BEGIN ... END块的 ,如本例所示。(该ELSE条款中使用的缩进只是为了清楚起见,没有其他意义。)
1 | DELIMITER | |
IF 语句
1 | IF search_condition THEN statement_list |
IF存储程序 的语句实现了一个基本的条件结构。
如果给定的*search_condition*计算结果为真,则执行相应的THENor ELSEIF子句 *statement_list*。如果没有 *search_condition*匹配项,则执行该 ELSE子句 *statement_list*。
每个*statement_list由一个或多个 SQL 语句组成;statement_list*不允许为空 。
一个IF ... END IF块,与存储程序中使用的所有其他流控制块一样,必须以分号终止,如下例所示:
1 | DELIMITER // |
与其他流控制构造一样,IF ... END IF块可以嵌套在其他流控制构造中,包括其他IF 语句。每个都IF必须以它自己的END IF后跟分号结束。您可以使用缩进使嵌套的流控制块更容易被人类读取(尽管 MySQL 不需要),如下所示:
1 | DELIMITER // |
在此示例中,IF仅当n不等于 时才计算内部m。
ITERATE 语句
1 | ITERATE label |
ITERATE只能出现 LOOP, REPEAT和 WHILE语句。 ITERATE意思是“再次开始循环。”
LEAVE 语句
1 | LEAVE label |
此语句用于退出具有给定标签的流控制构造。如果标签用于最外层存储的程序块,则LEAVE退出程序。
LEAVE可用于 BEGIN ... END或 循环结构 ( LOOP, REPEAT, WHILE)。
LOOP 语句
1 | [begin_label:] LOOP |
LOOP实现一个简单的循环结构,允许重复执行语句列表,该列表由一个或多个语句组成,每个语句以分号 ( ;) 语句分隔符终止。循环中的语句会重复执行,直到循环终止。通常,这是通过LEAVE语句完成的 。在存储的函数中,RETURN也可以使用,它完全退出函数。
忽略包含循环终止语句会导致无限循环。
一个LOOP语句可以被标记。有关标签使用的规则,请参阅 第 13.6.2 节,“声明标签”。
例子:
1 | CREATE PROCEDURE doiterate(p1 INT) |
REPEAT 语句
1 | [begin_label:] REPEAT |
语句中的语句列表 REPEAT会重复,直到*search_condition*表达式为真。因此, aREPEAT总是至少进入循环一次。 *statement_list*由一个或多个语句组成,每个语句以分号 ( ;) 语句分隔符结束。
例子:
1 | mysql> delimiter // |
RETURN 语句
1 | RETURN expr |
该RETURN语句终止存储函数的执行并将值返回 *expr*给函数调用者。RETURN 存储函数中必须至少有一个语句。如果函数有多个退出点,则可能不止一个。
此语句不用于存储过程、触发器或事件。该LEAVE语句可用于退出这些类型的存储程序。
WHILE 语句
1 | [begin_label:] WHILE search_condition DO |
WHILE 只要*search_condition*表达式为真 ,语句中的语句列表就会重复 。 *statement_list*由一个或多个 SQL 语句组成,每个语句以分号 ( ;) 语句分隔符终止。
一个WHILE语句可以被标记。
例子:
1 | CREATE PROCEDURE dowhile() |
5. 光标
MySQL 支持存储程序中的游标。语法与嵌入式 SQL 中的一样。游标具有以下属性:
- 敏感:服务器可能会也可能不会复制其结果表
- 只读:不可更新
- 不可滚动:只能在一个方向上遍历,不能跳过行
游标声明必须出现在处理程序声明之前以及变量和条件声明之后。
例子:
1 | CREATE PROCEDURE curdemo() |
光标 CLOSE 语句
1 | CLOSE cursor_name |
此语句关闭先前打开的游标。
游标 DECLARE 语句
1 | DECLARE cursor_name CURSOR FOR select_statement |
此语句声明一个游标并将其与SELECT检索游标要遍历的行的语句相关联 。要稍后获取行,请使用FETCH语句。SELECT语句检索的列数 必须与语句中指定的输出变量数相匹配 FETCH。
该SELECT语句不能有一个INTO条款。
游标声明必须出现在处理程序声明之前以及变量和条件声明之后。
游标 FETCH 语句
1 | FETCH [[NEXT] FROM] cursor_name INTO var_name [, var_name] ... |
此语句获取SELECT与指定游标(必须打开)关联的语句的下一行 ,并推进游标指针。如果存在一行,则获取的列存储在命名变量中。SELECT语句检索的列数 必须与语句中指定的输出变量数相匹配 FETCH。
如果没有更多行可用,则会出现 SQLSTATE value 的 No Data 条件'02000'。要检测此条件,您可以为其(或NOT FOUND条件)设置处理程序 。
游标 OPEN 语句
1 | OPEN cursor_name |
此语句打开一个先前声明的游标。
对服务器端游标的限制
服务器端游标是使用该mysql_stmt_attr_set()函数在 C API 中实现的 。相同的实现用于存储例程中的游标。服务器端游标允许在服务器端生成结果集,但除了客户端请求的那些行之外,不会传输到客户端。例如,如果客户端执行查询但只对第一行感兴趣,则不会传输其余行。
在 MySQL 中,服务器端游标具体化为内部临时表。最初,这是一个MEMORY 表,但是MyISAM当它的大小超过max_heap_table_size和 tmp_table_size系统变量的最小值时转换为表 。相同的限制适用于为保存游标的结果集而创建的内部临时表,以及内部临时表的其他用途。
游标是只读的;您不能使用游标来更新行。
UPDATE WHERE CURRENT OF并且DELETE WHERE CURRENT OF未实现,因为不支持可更新游标。
游标是不可保留的(提交后不保持打开状态)。
光标不敏感。
光标不可滚动。
游标没有命名。语句处理程序充当游标 ID。
每个准备好的语句只能打开一个游标。如果需要多个游标,则必须准备多个语句。
如果语句在准备模式下不受支持,则不能将游标用于生成结果集的语句。
6. 条件处理
在存储程序执行期间可能会出现需要特殊处理的条件,例如退出当前程序块或继续执行。可以为一般情况(如警告或异常)或特定情况(如特定错误代码)定义处理程序。可以为特定条件分配名称并在处理程序中以这种方式引用。
要命名条件,请使用该 DECLARE ... CONDITION语句。要声明处理程序,请使用该 DECLARE ... HANDLER语句。
DECLARE … CONDITION 语句
1 | DECLARE condition_name CONDITION FOR condition_value |
该DECLARE ... CONDITION语句声明了一个命名错误条件,将名称与需要特定处理的条件相关联。该名称可以在后续DECLARE ... HANDLER语句中引用。
DECLARE … HANDLER 语句
1 | DECLARE handler_action HANDLER |
该DECLARE ... HANDLER语句指定处理一个或多个条件的处理程序。如果出现这些条件之一,则*statement*执行指定的。 *statement*可以是简单的语句,例如,也可以是使用and 编写的复合语句
12.7 数据库管理语句
1. 账户管理报表
ALTER USER 语句
该ALTER USER语句修改 MySQL 帐户。它允许为现有帐户修改身份验证、角色、SSL/TLS、资源限制、密码管理、注释和属性属性。它还可用于锁定和解锁帐户。
示例:更改帐户密码并使其过期。因此,用户必须使用命名密码进行连接,并在下一次连接时选择一个新密码:
1 | ALTER USER 'jeffrey'@'localhost' |
CREATE ROLE 语句
1 | CREATE ROLE [IF NOT EXISTS] role [, role ] ... |
CREATE ROLE创建一个或多个角色,这些角色被命名为特权集合。
CREATE USER 语句
该CREATE USER语句创建新的 MySQL 帐户。它支持为新帐户建立身份验证、角色、SSL/TLS、资源限制、密码管理、注释和属性属性。它还控制帐户最初是锁定还是解锁。
GRANT 语句
该GRANT语句为 MySQL 用户帐户和角色分配权限和角色。
REVOKE 语句
该REVOKE语句使系统管理员可以撤销权限和角色,这些权限和角色可以从用户帐户和角色中撤销。
SET PASSWORD 语句
1 | SET PASSWORD [FOR user] auth_option |
该SET PASSWORD语句为 MySQL 用户帐户分配密码。密码可以在语句中明确指定,也可以由 MySQL 随机生成。该语句还可能包括一个密码验证子句,用于指定要替换的帐户当前密码,以及一个管理帐户是否具有辅助密码的子句。 并且 各自表示明文(未加密)的密码。
2. 资源组管理语句
MySQL 支持资源组的创建和管理,并允许将服务器内运行的线程分配给特定的组,以便线程根据组可用的资源执行。本节介绍可用于资源组管理的 SQL 语句。
ALTER RESOURCE GROUP 语句
1 | ALTER RESOURCE GROUP group_name |
ALTER RESOURCE GROUP用于资源组管理。
例子:
更改组 CPU 关联性:
1
ALTER RESOURCE GROUP rg1 VCPU = 0-63;
更改组线程优先级:
1
ALTER RESOURCE GROUP rg2 THREAD_PRIORITY = 5;
禁用组,将分配给它的任何线程移动到默认组:
1
ALTER RESOURCE GROUP rg3 DISABLE FORCE;
资源组管理在它发生的服务器上是本地的。ALTER RESOURCE GROUP 语句不会写入二进制日志,也不会被复制。
CREATE RESOURCE GROUP 语句
1 | CREATE RESOURCE GROUP group_name |
CREATE RESOURCE GROUP用于资源组管理(参见 第 5.1.16 节,“资源组”)。此语句创建一个新的资源组并分配其初始属性值。它需要RESOURCE_GROUP_ADMIN 特权。
例子:
创建具有单个 CPU 和最低优先级的已启用用户组:
1
2
3
4CREATE RESOURCE GROUP rg1
TYPE = USER
VCPU = 0
THREAD_PRIORITY = 19;创建一个没有 CPU 亲和性(可以使用所有 CPU)和最高优先级的禁用系统组:
1
2
3
4CREATE RESOURCE GROUP rg2
TYPE = SYSTEM
THREAD_PRIORITY = -20
DISABLE;
SET RESOURCE GROUP 语句
1 | SET RESOURCE GROUP group_name |
SET RESOURCE GROUP用于资源组管理
例子:
将当前会话线程分配给一个组:
1
SET RESOURCE GROUP rg1;
将命名线程分配给一个组:
1
SET RESOURCE GROUP rg2 FOR 14, 78, 4;
3. 表维护语句
ANALYZE TABLE 语句
1 | ANALYZE [NO_WRITE_TO_BINLOG | LOCAL] |
ANALYZE TABLE 生成表统计信息:
ANALYZE TABLEwithout anyHISTOGRAM子句执行键分布分析并存储命名表的分布。对于MyISAM表,ANALYZE TABLE对于键分布分析相当于使用 myisamchk –analyze。ANALYZE TABLEwithUPDATE HISTOGRAM子句为命名的表列生成直方图统计信息并将它们存储在数据字典中。此语法只允许使用一个表名。ANALYZE TABLEwithDROP HISTOGRAM子句从数据字典中删除指定表列的直方图统计信息。此语法只允许使用一个表名。
ANALYZE TABLE有工作 InnoDB,NDB和 MyISAM表。它不适用于视图。
在分析过程中,该表已被锁定与读锁 InnoDB和MyISAM。
ANALYZE TABLE从表定义缓存中删除表,这需要刷新锁。如果有长时间运行的语句或事务仍在使用该表,则后续语句和事务必须等待这些操作完成才能释放刷新锁。因为ANALYZE TABLE它本身通常很快完成,所以延迟的事务或涉及同一个表的语句可能并不明显是由于剩余的刷新锁造成的。
分析表输出
ANALYZE TABLE 返回包含下表中显示的列的结果集。
| 柱子 | 价值 |
|---|---|
Table |
表名 |
Op |
analyze 或者 histogram |
Msg_type |
status, error, info, note, 或 warning |
Msg_text |
信息性消息 |
密钥分布分析
ANALYZE TABLEwithout any HISTOGRAM子句执行键分布分析并存储表的分布。任何现有的直方图统计数据不受影响。
如果表自上次键分布分析以来没有改变,则不会再次分析该表。
MySQL 使用存储的键分布来决定表的连接顺序,以便连接到常量以外的其他东西。此外,在决定将哪些索引用于查询中的特定表时,可以使用键分布。
直方图统计分析
ANALYZE TABLEwith HISTOGRAM子句可以管理表列值的直方图统计信息。有关直方图统计信息的信息,请参阅 第 8.9.6 节,“优化器统计信息”。
这些直方图操作可用:
ANALYZE TABLEwithUPDATE HISTOGRAM子句为命名的表列生成直方图统计信息并将它们存储在数据字典中。此语法只允许使用一个表名。可选子句指定直方图的桶数。的值 必须是1到1024之间的整数。如果省略该子句,则桶数为100。
WITH *N* BUCKETSNANALYZE TABLEwith aDROP HISTOGRAM子句从数据字典中删除指定表列的直方图统计信息。此语法只允许使用一个表名。
CHECK TABLE 语句
1 | CHECK TABLE tbl_name [, tbl_name] ... [option] ... |
CHECK TABLE检查一个或多个表是否有错误。CHECK TABLE 还可以检查视图是否存在问题,例如视图定义中引用的表不再存在。
CHECKSUM TABLE 语句
1 | CHECKSUM TABLE tbl_name [, tbl_name] ... [QUICK | EXTENDED] |
CHECKSUM TABLE报告 表内容的校验和。您可以使用此语句来验证备份、回滚或其他旨在将数据恢复到已知状态的操作前后的内容是否相同。
优化表语句
1 | OPTIMIZE [NO_WRITE_TO_BINLOG | LOCAL] |
OPTIMIZE TABLE重新组织表数据和关联索引数据的物理存储,以减少访问表时的存储空间并提高I/O效率。对每个表所做的确切更改取决于该表使用的 存储引擎。
OPTIMIZE TABLE在这些情况下 使用,具体取决于表的类型:
- 在对
InnoDB具有自己的.ibd 文件的表 执行大量插入、更新或删除操作之后,因为它是在innodb_file_per_table启用该选项的情况下创建的 。重新组织表和索引,可以回收磁盘空间以供操作系统使用。 - 在对
FULLTEXT作为InnoDB表中索引一部分的列执行大量插入、更新或删除操作之后。首先设置配置选项innodb_optimize_fulltext_only=1。要将索引维护期保持在合理的时间,请设置innodb_ft_num_word_optimize选项以指定要在搜索索引中更新多少字,并运行一系列OPTIMIZE TABLE语句,直到搜索索引完全更新。 - 删除的大部分后
MyISAM或ARCHIVE表,或者使许多变化为MyISAM或ARCHIVE具有可变长度的行表(表具有VARCHAR,VARBINARY,BLOB,或TEXT列)。删除的行保存在链表中,后续INSERT操作会重用旧的行位置。您可以使用OPTIMIZE TABLE来回收未使用的空间并对数据文件进行碎片整理。在对表进行大量更改后,此语句还可以提高使用该表的语句的性能,有时会显着提高。
REPAIR TABLE 语句
1 | REPAIR [NO_WRITE_TO_BINLOG | LOCAL] |
REPAIR TABLE 修复可能损坏的表,仅适用于某些存储引擎。
尽管通常您永远不必运行 REPAIR TABLE,但如果发生灾难,此语句很可能会从MyISAM表中取回所有数据。
4. CLONE 语句
1 | CLONE clone_action |
该CLONE语句用于在本地或从远程 MySQL 服务器实例克隆数据。要使用 CLONE语法,必须安装克隆插件。
5. 缓存索引语句
1 | CACHE INDEX { |
该CACHE INDEX语句将表索引分配给特定的键缓存。它仅适用于 MyISAM表,包括分区 MyISAM表。分配索引后,如果需要,可以将它们预加载到缓存中 LOAD INDEX INTO CACHE。
6. FLUSH 语句
该FLUSH语句有多种变体形式,可以清除或重新加载各种内部缓存、刷新表或获取锁。
12.8 实用语句
DESCRIBE 语句
该DESCRIBE和 EXPLAIN语句是同义词,或者用于获取有关表结构或查询执行计划的信息。有关
EXPLAIN 语句
该DESCRIBE和 EXPLAIN语句是同义词。在实践中,DESCRIBE关键字更多地用于获取有关表结构的信息,而EXPLAIN用于获取查询执行计划(即 MySQL 如何执行查询的解释)。
DESCRIBE 提供有关表中列的信息:
1 | mysql> DESCRIBE City; |
DESCRIBE是 的快捷方式 SHOW COLUMNS。这些语句还显示视图信息。
使用 EXPLAIN ANALYZE 获取信息
MySQL 8.0.18 引入了EXPLAIN ANALYZE,它运行一个语句并产生 EXPLAIN输出以及时间和额外的、基于迭代器的关于优化器的期望如何与实际执行相匹配的信息。对于每个迭代器,提供以下信息:
估计执行成本
(一些迭代器没有被成本模型考虑在内,因此不包括在估计中。)
估计的返回行数
返回第一行的时间
返回所有行的时间(实际成本),以毫秒为单位
(当有多个循环时,此图显示了每个循环的平均时间。)
迭代器返回的行数
循环次数
查询执行信息使用TREE输出格式显示 ,其中节点表示迭代器。EXPLAIN ANALYZE始终使用 TREE输出格式。在 MySQL 8.0.21 及更高版本中,可以选择使用 FORMAT=TREE; 格式以外的其他格式 TREE仍然不受支持。
EXPLAIN ANALYZE可以与SELECT语句一起使用 ,也可以与多表UPDATE和 DELETE语句一起使用。从 MySQL 8.0.19 开始,它还可以与TABLE语句一起使用 。
从 MySQL 8.0.20 开始,您可以使用KILL QUERY 或CTRL-C终止此语句。
EXPLAIN ANALYZE不能与 FOR CONNECTION.
示例输出:
1 | mysql> EXPLAIN ANALYZE SELECT * FROM t1 JOIN t2 ON (t1.c1 = t2.c2)\G |
actual time此语句输出中 显示的值以毫秒表示。
帮助语句
1 | HELP 'search_string' |
该HELP语句返回 MySQL 参考手册中的在线信息。其正确操作要求mysql 使用帮助主题信息初始化数据库中的帮助表(请参阅 第 5.1.17 节,“服务器端帮助支持”)。
该HELP语句在帮助表中搜索给定的搜索字符串并显示搜索结果。搜索字符串不区分大小写。
搜索字符串可以包含通配符 %和_。这些与使用LIKE运算符执行的模式匹配操作具有相同的含义 。例如, HELP 'rep%'返回以 开头的主题列表rep。
USE 语句
1 | USE db_name |
该USE语句告诉 MySQL 使用命名数据库作为后续语句的默认(当前)数据库。此语句需要对数据库或其中的某个对象的某些特权。
此时创建读视图,up_limit_id = 21, low_limit_id = 23 活跃事务列表为(21,22)


