更新次数 | 更新时间 |
---|---|
首发 | 2021.10.25 |
第一次更新 | 2021.10.26 1.删除了书中大量不必要的存储引擎类型 2.摘要完毕Mysql架构与历史部分 |
第二次更新 | 2021.10.29 1.摘要基准测试内容 2.删除了大量对概念的举例. |
第三次更新 | 2021.11.4 1.简化服务器性能剖析 |
第四次更新 | 2021.11.18 摘要完毕Schema与数据类型优化 |
第五次更新 | 2022.8.30 索引篇以及性能优化高级篇部分 |
第六次更新 | 2022.8.31 索引篇以及性能优化高级篇部分内容优化 |
第七次更新 | 2022.9.2 性能优化高级篇新增order by之后语句优化 |
第八次更新 | 2022.9.4 1.锁机制更新 2.对引用的’尚硅谷MySQL数据库高级’原理内容研判 3.更新事务内容 4. 重构锁内容 |
第九次更新 | 2022.11.16 1.更新为什么mysql选择b+树 2.b+树数据结构内容优化 |
第十次更新 | 2022.11.18 1.索引底层原理:如为什么要遵循最左前缀原则 |
第十一次更新 | 2022.11.22 1.发现网上大量其他变种b+树拿来讲mysql底层. 强调mysql的b+树叶子节点之间是双向循环链表 |
第十二次更新 | 2023.1.24 1.补充b+树3-4层的数据量 2.mysql8.0新特性跳跃扫描对最左前缀查询的影响 |
第十三次更新 | 2023.5.30 1 !=和<>到底走不走索引? 2.!=和<>对主键有没有影响? |
第十四次更新 | 2023.5.31 sql分析工具optimizer trace |
第十五次更新 | 2023.7.20 不连续模糊查询怎么解决 |
作者刚刚研读学习了Mysql的高性能优化和架构设计.写这篇文章为了日后复习巩固和帮助后来者提炼mysql精华! 还请各位读者及时勘误联系作者.本章节将持续更新!阅读本文章,需要有一定的数据库基础.为了避免重复造轮子,本章筛选了各位领域大佬的文章并注明了出处.目的只有两个复习和总结知识共同学习. 笔者主要参考文献为mysql官方文档,<
mysql8.0 比mysql5.0性能提高10~100+倍
1.影响性能考虑点包括:【这里可以从访问顺序来作为链路进行性能调优】
数据库、应用程序、中间件( tomact、gateway、Nginx、)、网络(带宽)和操作系统等方面
2.首先考虑自己的应用属于CPU密集型还是Io密集型,根据jvisualvm查看应用健康情况
CPU:计算、排序、过滤、整合【集群】
IO:网络、磁盘、数据库、redis【内存+缓存+固态+提高网卡的传输效率】
3.监控执行时中间件CPU、内存使用量
docker stats:查看cpu占用、内存使用量、
主流两大存储引擎的文件结构 | innodb | MyISAM |
---|---|---|
共享表空间 ibdata1(管理innodb所有表空间) | .MYI,存储索引 | |
日志文件组 redo日志 | .MYD,存储数据 | |
表数据结构 .frm | .frm,存储表定义 | |
独占表空间文件 .ibd (数据索引) |
提示:以下是本篇文章正文内容,下面案例可供参考
MySQL的最重要,最与众不同的特性:存储引擎架构.
这种架构的设计将查询处理及其他系统任务和数据的存储/提取相分离.这种架构模式我们可以在不同环境,不同需求下自由选择数据存储的方式.数据的隔离性由锁来体现
脏写
的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务相继对一条记录做改动时,需要让它们排队执行,这个排队的过程其实是通过锁来实现的。脏读、不可重复读、幻读
的问题。REPEATABLE READ
隔离级别上就已经解决了脏读、不可重复读、幻读
问题。脏读、不可重复读、幻读
这些问题呢?其实有两种可选的解决方案:需要注意的是对于InnoDB引擎来说,读锁和写锁可以加在表上,也可以加在行上。
#对读取的记录加S锁:
SELECT ... LOCK IN SHARE MODE
#或
SELECT ... FOR SHARE;#(8.B新增语法)
#对读取的记录加X锁:
SELECT ... FOR UPDATE;
平常所用到的写操作无非是DELETE、UPDATE、INSERT这三种:
delete mark
操作。我们也可以把这个定位待删除记录在B+树中位置的过程看成是一个获取X锁的锁定读。隐式锁
的结构来保护这条新插入的记录在本事务提交前不被别的事务访问。1.表锁是MySQL中最基本的锁策略,并且是开销最小的策略,并发能力低。类似于读写锁.
2.存储引擎可以管理自己的锁,但MySQL服务器层还是会使用各种有效的表锁来实现不同的目的。
由于表级锁一次会将整个表锁定,所以可以很好的避免死锁问题。当然,锁的粒度大所带来最大的负面影响就是出现锁资源争用的概率也会最高,导致并发率大打折扣。
使用innodb存储引擎的话,它支持行锁.因此我们更多使用粒度更小的行级锁.
# InnoDB存储擎会对表t加表级别的S锁。
LOCK TABLES t READ;
# InnoDB存储引擎会对表t加表级别的X锁.
LOCK TABLES t WRITE;
# 意向共享锁
--事务要获取某些行的S锁,必须先获得表的IS锁。
SELECT column FROM table ... LOCK IN SHARE MODE;
# 意向排他锁
-- 事务要获取某些行的X锁,必须先获得表的IX锁。
SELECT column FROM table ... FOR UPDATE;
在使用MySQL过程中,我们可以为表的某个列添加AUTO_INCREMENT属性。举例:
CREATE TABLE teacher(
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255)NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_Unicode_ci;
innodb_autoinc_lock_mode有三种取值,分别对应与不同锁定模式:
innodb_autoinc_1ock_mode=0 #(“传统"锁定模式)
innodb_autoinc_1ock_mode=1 #(“连续"锁定模式)
innodb_autoinc_1ock_mode=2 #(“交错"锁定模式)
1.行级锁可以最大程度地支持并发处理(同时也带来了最大的锁开销,加锁会比较慢,容易出现死锁情况).
2. 行级锁只在存储引擎层实现,而MySQL服务器层没有实现。innodb实现.
3. 行级锁基于索引实现,必须要走索引才会触发行级锁
1.性能问题
2.死锁问题:对多条数据加锁时,加锁顺序要—致
3库存操作要统一: select … for update 普涌select
#对读取的记录加S锁:
SELECT ... LOCK IN SHARE MODE
#或
SELECT ... FOR SHARE;#(8.B新增语法)
#对读取的记录加X锁:
SELECT ... FOR UPDATE;
页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。
每个层级的锁数量是有限制的,因为锁会占用内存空间,锁空间的大小是有限的。当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级。锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如IoDB中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了。
数据的一致性,原子性,持久性由事务体现
事务内的语句,要么全部执行成功,要么全部执行失败。这是为了保证数据信息的安全性.
一个运行良好的事务处理系统, 必须具备这些标准特征:
原子性 | 一个事务必须被视为一个不可分割的最小工作单元,整个事务 中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。 |
一致性 | 数据库总是从一个一致性的状态转换到另外一个一致性的状态。 |
隔离性 | 一个事务所做的修改在最终提交以前,对其他事务是不可见的。 |
持久性 | 一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。 |
需要强调的是InnoDB和XtraDB存储引 擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)在REPEATABLE READ(可重复读)
解决了幻读
的问题。稍后在多版本并发控制(MVCC)中详细讨论.
详见这位博主 什么是脏读、不可重复读(提交读),幻读
1.如果凑巧,两个事务都执行了第一条UPDATE 语句,会出现互相锁定,然后两个事务都等待对方释放锁, 同时又持有对方需要的锁,则陷入死循环。(除非有外部因素介入才可能 解除死锁。)
2.为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,比如InnoDB存储引擎,越能检测到死锁的循环依赖,并立即返回一个错误。这种解决方式很有效,否则死锁会导致出现非常慢的查询。InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚
(这是相对比较简单的死锁回滚算法)。
3.死锁的原因:(1)真正的数据冲突 (2)存储引擎的实现方式导致的.
- 而事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证。
- REDO LOG称为重做日志
,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。
- UND0LOG称为回滚日志
,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。
redo log:是存储引擎层innodb)生成的日志,记录的是物理级别
上的页修改操作,比如页号xxx、偏移量yyy写入了’zzz`数据。主要为了保证数据的可靠性;
undo log:是存储引擎层(innodb)生成的日志,记录的是逻辑操作
日志,比如对某一行数据进行了INSERT语句操作,那么undo log就记录一条与之相反的DELETE操作。主要用于事务的回滚
(undo log记录的是每个修改操作的逆操作)和一致性非锁定读
(undo log回滚行记录到某种特定的版本MVCC,即多版本并发控制。
InnoDB存储引擎是以页为单位来管理存储空间的。在真正访问页面之前,需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问。所有的变更都必须先更新缓冲池中的数据,然后缓冲池中的脏页会以一定的频率被刷入磁盘(checkPoint机制),通过缓冲池来优化CPU和磁盘之间的鸿沟,这样就可以保证整体的性能不会下降太快。
一方面,缓冲池可以帮助我们消除CPU和磁盘之间的鸿沟,checkpointi机制可以保证数据的最终落盘,然而由于checkpoint并不是每次变更的时候就触发
的,而是master线程隔一段时间去处理的。所以最坏的情况就是事务提交后,刚写完缓冲池,数据库宕机了,那么这段数据就是丢失的,无法恢复。
另一方面,事务包含持久性的特性,就是说对于一个已经提交的事务,在事务提交后即使系统发生了崩溃,这个事务对数据库中所做的更改也不能丢失。那么如何保证这个持久性呢?一个简单的做法:在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘,但是这个简单粗暴的做法有些问题:
另一个思路redo:
它只记录修改的数据.事务日志可以帮助提高事务的效率。事务日志采用的是追加写
的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O.我们通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。如果数据的修改已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。具体的恢复方式则视存储引擎而定。InnoDB引擎的事务采用了WAL技术(Write-Ahead Logging),这种技术的思想就是先写日志,再刷盘
,只有日志写入成功,才算事务提交成功,这里的日志就是redo log。当发生宕机目数据未刷到磁盘的时候,可以通过redo log来恢复,保证ACID中的D,这就是redo log的作用。
redo log可以简单分为以下两个部分:
1.重做日志的缓冲(redo log buffer),保存在内存中,是易失的
在服务器启动时就向操作系统申请了一大片称之为redo log buffert的连续内存空间
,翻译成中文就是redo日志缓冲区。这片内存空间被划分成若干个连续的redo log block
。一个redo log block占用512字节
大小。
MySQL把对底层页面中的一次原子访问的过程称之为一个Mini-Transaction
,简称mtr
,比如,向某个索引对应的B+树中插入一条记录的过程就是一个Mini-Transaction。一个所谓的mtr可以包含一组redo日本,在进行崩溃恢复时这一组redo日志作为一个不可分割的整体。
redo log是事务持久性的保证,undo log是务原子性的保证
。在事务中修改数据
的前置操作
其实是要先写入一个undo log。
事务需要保证原子性,也就是事务中的操作要么全部完成,要么什么也不做。但有时候事务执行到一半会出现一些情况,比如:
每当我们要对一条记录做改动时(这里的改动可以指INSERT、DELETE、UPDATE),都需要"留一手"一一把回滚时所需的东西记下来。比如:
undo 日志的作用:
作用1:回滚数据
用户对undo日志可能有误解:undo用于将数据库物理地恢复到执行语句或事务之前的样子。但事实并非如此。undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同
。这是因为在多用户并发系统中,可能会有数十、数百甚至数干个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。
作用2:MVCC
undo的另一个作用是MVCC,即在InnoDB存储引擎中MVCC的实现是通过undo来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。 MVCC将在后续章节详细介绍.
MySQL提供了两种事务型的存储引擎:InnoDB和NDB Cluster。
值为 0 和值为 OFF:关闭事务自动提交。如果关闭自动提交,用户将会一直处于某个事务中,只有提交或回滚后才会结束当前事务,重新开始一个新事务。
值为 1 和值为 ON:开启事务自动提交。如果开启自动提交,则每执行一条 SQL 语句,事务都会提交一次。
-- 修改提交模式
set session autocommit=0; //会话的事务提交模式
show session variables like 'autocommit';
set global autocommit=0; //全局的事务提交模式
show global variables like 'autocommit';
ROLLBACK;
COMMIT;
//隔离级别操作
//mysql5.7及之后版本
show variables like 'transaction_isolation';或者select @@transaction_isolation;
//mysql5.7之前版本
show variables like 'tx_isolation';或者select @@tx_isolation;
//设置read uncommitted级别:
set session transaction isolation level read uncommitted;
//设置read committed级别:
set session transaction isolation level read committed;
//设置repeatable read级别:
set session transaction isolation level repeatable read;
//设置serializable级别:
set session transaction isolation level serializable;
MySQL服务器层不管理事务,事务是由下层的存储引擎实现的。 所以在同一个事务中,使用多种存储引擎是不可靠的。事务需要回滚,非事务型的表上的变更就无法撤销,这会 导致数据库处于不一致的状态,这种情况很难修复,事务的最终结果将 无法确定。所以,为每张表选择合适的存储引擎非常重要。
InnoDB采用的是两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有在执行COMMIT 或者ROLLBACK 的时候才会释放,并且所有的锁是在同一时刻被释放。InnoDB会根据隔离级别在需要的时候自动加锁。
LOCK TABLES 和事务之间相互影响的话,情况会变得非常复杂,在某些MySQL版 本中甚至会产生无法预料的结果。除了事务中禁用了AUTOCOMMIT ,可以使用LOCK TABLES 之外,其他任何时候都不要显式地执行LOCK TABLES ,不管使用的是什么存储引擎.
大多数数据库的大多数事务型存储引擎实现的都不是简单的行级锁。基 于提升并发性能的考虑,它们一般都同时实现了多版本并发控制 (MVCC)。MVCC是行级锁的一个变种,大都实现了非阻塞的读操作,写操作也只锁定必要的行,因此开销更低。
MVCC的实现,是通过保存数据在某个时间点的快照来实现的。不同存储引擎的MVCC实现是不同的.InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列
来实现 的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或 删除时间)。当然存储的并不是实际的时间值,而是系统版本号.
下面看一下在REPEATABLE READ 隔离 级别下,MVCC具体是如何操作的。
SELECT InnoDB会根据以下两个条件检查每行记录:
a. InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在 的,要么是事务自身插入或者修改过的。
b. 行的删除版本要么未定义,要么大于当前事务版本号。这 可以确保事务读取到的行,在事务开始之前未被删除。
只有符合上述两个条件的记录,才能返回作为查询结果。
INSERT
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE
InnoDB为插入一行新记录,保存当前系统版本号作为行版本 号,同时保存当前系统版本号到原来的行作为行删除标识
保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这 样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作.
MVCC在mysql下只在REPEATABLE READ 和READ COMMITTED 两个隔离级别下工作.其他两个隔离级别都和MVCC不兼容 ,因为READ UNCOMMITTED 总是读取最新的数据行,而不是符合当前事务版本的数据行.而SERIALIZABLE 则会对所有读取的行都加锁.
关于存储引擎我将大量引用mysql高性能一书的原文,它太过于重要和基础.
不同的存储引擎保存数据和索引的方式是不 同的,但表的定义则是在MySQL服务层统一处理的。这样屏蔽了不同平台的语言差异.
//查询表结构
SHOW TABLE STATUS LIKE 'members'
mysql8.0与mysql5.7的区别
InnoDB是MySQL的默认事务型引擎,也是最重要、使用最广泛的存储引擎。
它被设计用来处理大量的短期(short-lived)事务,短期事 务大部分情况是正常提交的,很少会被回滚。InnoDB的性能和自动崩 溃恢复特性,使得它在非事务型存储的需求中也很流行。除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。值得钻研.
InnoDB的数据存储在表空间(tablespace)中,表空间是由InnoDB 管理的一个黑盒子,由一系列的数据文件组成。在MySQL 4.1以后的版 本中,InnoDB可以将每个表的数据和索引存放在单独的文件中。 InnoDB也可以使用裸设备作为表空间的存储介质,但现代的文件系统 使得裸设备不再是必要的选择。
InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离级 别。其默认级别是REPEATABLE READ (可重复读),并且通过间隙锁 (next-key locking)策略防止幻读的出现。间隙锁使得InnoDB不仅仅锁 定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插 入。
InnoDB表是基于聚簇索引建立的,我们会在后面的章节详细讨论聚簇索引。InnoDB的索引结构和MySQL的其他存储引擎有很大的不 同,聚簇索引对主键查询有很高的性能。不过它的二级索引(secondary index,非主键索引)中必须包含主键列,所以如果主键列很大的话, 其他的所有索引都会很大。因此,若表上的索引较多的话,主键应当尽 可能的小。InnoDB的存储格式是平台独立的,也就是说可以将数据和 索引文件从Intel平台复制到PowerPC或者Sun SPARC平台。
InnoDB内部做了很多优化,包括从磁盘读取数据时采用的可预测 性预读,能够自动在内存中创建hash索引以加速读操作的自适应哈希索引(adaptive hash index),以及能够加速插入操作的插入缓冲区(insert buffer)等。本书后面将更详细地讨论这些内容。
InnoDB的行为是非常 复杂的,不容易理解。 如果使用了InnoDB引擎,笔者强烈建议阅读官 方手册中的“InnoDB事务模型和锁”一节。如果应用程序基于InnoDB构 建,则事先了解一下InnoDB的MVCC架构带来的一些微妙和细节之处是 非常有必要的。存储引擎要为所有用户甚至包括修改数据的用户维持一 致性的视图,是非常复杂的工作。
作为事务型的存储引擎,InnoDB通过一些机制和工具支持真正的 热备份,Oracle提供的MySQL Enterprise Backup、Percona提供的开源的 XtraBackup都可以做到这一点。MySQL的其他存储引擎不支持热备份, 要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
在MySQL 5.1及之前的版本,MyISAM是默认的存储引擎。 MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)
等,但MyISAM不支持事务和行级锁,而且有一个毫无疑问的缺陷就是 崩溃后无法安全恢复。正是由于MyISAM引擎的缘故,即使MySQL支持 事务已经很长时间了,在很多人的概念中MySQL还是非事务型的数据 库。尽管MyISAM引擎不支持事务、不支持崩溃后的安全恢复,但它绝 不是一无是处的。对于只读的数据,或者表比较小、可以忍受修复 (repair)操作,则依然可以继续使用MyISAM(但请不要默认使用 MyISAM,而是应当默认使用InnoDB)。
存储
MyISAM会将表存储在两个文件中:数据文件和索引文件,分别 以.MYD 和.MYI 为扩展名。MyISAM表可以包含动态或者静态(长度固 定)行
。MySQL会根据表的定义来决定采用何种行格式。MyISAM表可 以存储的行记录数,一般受限于可用的磁盘空间,或者操作系统中单个 文件的最大尺寸。 在MySQL 5.0中,MyISAM表如果是变长行,则默认配置只能处理 256TB的数据,因为指向数据记录的指针长度是6个字节。而在更早的 版本中,指针长度默认是4字节,所以只能处理4GB的数据。而所有的 MySQL版本都支持8字节的指针。要改变MyISAM表指针的长度(调高 或者调低),可以通过修改表的MAX_ROWS 和AVG_ROW_LENGTH 选项的值 来实现,两者相乘就是表可能达到的最大大小。修改这两个参数会导致 重建整个表和表的所有索引,这可能需要很长的时间才能完成。
作为MySQL最早的存储引擎之一,MyISAM有一些已经开发出来很 多年的特性,可以满足用户的实际需求。
加锁与并发
MyISAM对整张表加锁,而不是针对行。读取时会对需要读到 的所有表加共享锁,写入时则对表加排他锁。但是在表有读取查询 的同时,也可以往表中插入新的记录(这被称为并发插入, CONCURRENT INSERT)。
修复
对于MyISAM表,MySQL可以手工或者自动执行检查和修复操 作,但这里说的修复和事务恢复以及崩溃恢复是不同的概念。执行 表的修复可能导致一些数据丢失,而且修复操作是非常慢的。可以 通过CHECK TABLE mytable 检查表的错误,如果有错误可以通过执 行REPAIR TABLE mytable 进行修复。另外,如果MySQL服务器已 经关闭,也可以通过myisamchk 命令行工具进行检查和修复操作。
索引特性
对于MyISAM表,即使是BLOB和TEXT等长字段,也可以基于其前500个字符创建索引。MyISAM也支持全文索引,这是一种基 于分词创建的索引,可以支持复杂的查询。
延迟更新索引键(Delayed Key Write)
创建MyISAM表的时候,如果指定了DELAY_KEY_WRITE 选项,
在每次修改执行完成时,不会立刻将修改的索引数据写入磁盘,而 是会写到内存中的键缓冲区(in-memory key buffer),只有在清理 键缓冲区或者关闭表的时候才会将对应的索引块写入到磁盘。这种 方式可以极大地提升写入性能,但是在数据库或者主机崩溃时会造 成索引损坏,需要执行修复操作。延迟更新索引键的特性,可以在 全局设置,也可以为单个表设置。
如果表在创建并导入数据以后,不会再进行修改操作,那么这样的 表或许适合采用MyISAM压缩表。 可以使用myisampack 对MyISAM表进行压缩(也叫打包pack)。压 缩表是不能进行修改的(除非先将表解除压缩,修改数据,然后再次压 缩)。压缩表可以极大地减少磁盘空间占用,因此也可以减少磁盘 I/O,从而提升查询性能。压缩表也支持索引,但索引也是只读的。 以现在的硬件能力,对大多数应用场景,读取压缩表数据时的解压 带来的开销影响并不大,而减少I/O带来的好处则要大得多。压缩时表 中的记录是独立压缩的,所以读取单行的时候不需要去解压整个表(甚 至也不解压行所在的整个页面)。
MyISAM引擎设计简单,数据以紧密格式存储,所以在某些场景下 的性能很好。MyISAM有一些服务器级别的性能扩展限制,比如对索引键缓冲区(key cache)的Mutex锁,MariaDB基于段(segment)的索引 键缓冲区机制来避免该问题。但MyISAM最典型的性能问题还是表锁的 问题,如果你发现所有的查询都长期处于“Locked”状态,那么毫无疑问表锁就是罪魁祸首。
1.Archive存储引擎只支持INSERT 和SELECT 操作.
2.Archive引擎会缓存所有的写并利用zlib对插入的行进行压缩,所以 比MyISAM表的磁盘I/O更少。但是每次SELECT查询都需要执行全表扫 描。所以Archive表适合日志和数据采集类应用.
3.Archive引擎支持行级锁和专用的缓冲区,所以可以实现高并发的插入。
4.Archive引擎是一个针对高速插入和压缩做了优化的简单引擎。
1.可以将普通的CSV文件(逗号分割值的文件)作为MySQL 的表来处理,但这种表不支持索引。
2.可以将Excel等电子表格软件中的数据存储为CSV文件.
3.CSV引擎可以作为一种数据交 换的机制,非常有用.
1.如果需要快速地访问数据,并且这些数据不会被修改,重启以后丢 失也没有关系,那么使用Memory表(以前也叫做HEAP表)是非常有用 的。Memory表至少比MyISAM表要快一个数量级,因为所有的数据都 保存在内存中,不需要进行磁盘I/O。Memory表的结构在重启以后还会 保留,但数据会丢失。
2.Memory表支持Hash索引,因此查找操作非常快。
3.Memroy表是表级 锁,因此并发写入的性能较低。它不支持BLOB 或TEXT 类型的列,并且 每行的长度是固定的.
Memroy表在很多场景可以发挥好的作用:
用于查找(lookup)或者映射(mapping)表,例如将邮编和州名 映射的表。
用于缓存周期性聚合数据(periodically aggregated data)的结果。
用于保存数据分析中产生的中间数据.
NDB集群存储引擎,作为SQL和NDB原生协议之间的接口。MySQL服务器、NDB集群存储引擎,以及分布式的、share-nothing 的、容灾的、高可用的NDB数据库的组合,被称为MySQL集群 (MySQL Cluster)。后续会有章节专门来讨论MySQL集群。
XtraDB可以作为InnoDB的一个完全的替代 产品,甚至可以兼容地读写InnoDB的数据文件,并支持InnoDB的所有查询。
它的改进点主要集中在性能、可测量性和操作灵活性方面。
PBXT存储引擎.它支持引擎级别的复制、外键约束,并且以一 种比较复杂的架构对固态存储(SSD)提供了适当的支持,还对较大的 值类型如BLOB也做了优化。支持 ACID事务和MVCC。PBXT是一款社区支持的存储引擎, MariaDB包含了该引擎。
MySQL默认是面向行的,每一行的数据是一起存储的,服务器的 查询也是以行为单位处理的。而在大数据量处理时,面向列的方式可能 效率更高。如果不需要整行的数据,面向列的方式可以传输更少的数 据。如果每一列都单独存储,那么压缩的效率也会更高。
Infobright是最有名的面向列的存储引擎。在非常大的数据量(数十TB)时,该引擎工作良好。
数据高度压缩,按照块进行排序,每个块都对应有一组元数据.在处理查询时,访问元数据可决定跳过该块,甚至可能只需要元数据即 可满足查询的需求。但该引擎不支持索引.
除非 需要用到某些InnoDB不具备的特性,并且没有其他办法可以替代,否 则都应该优先选择InnoDB引擎!
例如,如果要用到全文索引,建议优 先考虑InnoDB加上Sphinx的组合,而不是使用支持全文索引的 MyISAM。当然,如果不需要用到InnoDB的特性,同时其他引擎的特性 能够更好地满足需求,也可以考虑一下其他存储引擎。举个例子,如果 不在乎可扩展能力和并发能力,也不在乎崩溃后的数据丢失问题,却对 InnoDB的空间占用过多比较敏感,这种场合下选择MyISAM就比较合适。
不建议混合使用多种存储引擎!!
应用需要不同的存储引擎需要考虑以下因素:
事务
特有的特性
你不需要现在就做决定。接下来会提供很多关于各种存储引擎优缺点的详细描述,也会讨论一些架构设计的技巧。
日志型应用: MyISAM或者Archive存储引擎对这类应用比较合适,因为它们 开销低,而且插入速度非常快。
只读或者大部分情况下只读的表 如果不介意 MyISAM的崩溃恢复问题,选用MyISAM引擎是合适的。不要低估崩溃恢复问题的重要性.(一个值得推荐的方式,是在性能测试环境模拟真实的环境,运行应用,然后拔下电 源模拟崩溃测试。对崩溃恢复的第一手测试经验是无价之宝,可以避免真的碰到崩溃时手足无措)
订单处理 InnoDB是订单处理类应用的最佳选择.
电子公告牌和主题讨论论坛
当设计上述类型的应用时,建议采用InnoDB。MyISAM引擎在一开 始可能没有任何问题,但随着应用压力的上升,则可能迅速恶化。各种 锁争用、崩溃后的数据丢失等问题都会随之而来。
大数据量 如果数据量继续增长到10TB以上的级别,可能就需要建立数据仓 库。Infobright是MySQL数据仓库最成功的解决方案。也有一些大数据 库不适合Infobright,却可能适合TokuDB。
创建与查询(CREATE和SELECT)
该转换的技术综合了ALTER TABLE语法的高效和导出与导入方法的安全.不需要导出整个表的数据,而是先创建一个新的存储引擎的表,然后利 用INSERT…SELECT 语法来导数据:
CREATE TABLE innodb_table LIKE myisam_table;
ALTER TABLE innodb_table ENGINE=InnoDB;
INSERT INTO innodb_table SELECT * FROM myisam_table;
数据量不大的话,这样做工作得很好。如果数据量很大,则可以考 虑做分批处理,针对每一段数据执行事务提交操作,以避免大事务产生 过多的undo。假设有主键字段id ,重复运行以下语句(最小值x 和最大 值y 进行相应的替换)将数据导入到新表:
START TRANSACTION;
INSERT INTO innodb_table SELECT * FROM myisam_table
-> WHERE id BETWEEN x AND y;
COMMIT;
这样操作完成以后,新表是原表的一个全量复制,原表还在,如果 需要可以删除原表。如果有必要,可以在执行的过程中对原表加锁,以 确保新表和原表的数据一致。 Percona Toolkit提供了一个pt-online-schema-change 的工具(基于 Facebook的在线schema变更技术),可以比较简单、方便地执行上述过 程,避免手工操作可能导致的失误和烦琐。
Mysql白皮书,版本更新历程
MySQL拥有分层的架构。上层是服务器层的服务和查询执行引擎,下层则是存储引擎。虽然有很多不同作用的插件API,但存储引擎 API还是最重要的。如果能理解MySQL在存储引擎和服务层之间处理查 询时如何通过API来回交互,就能抓住MySQL的核心基础架构的精髓。
MySQL最初基于ISAM构建(后来被MyISAM取代),其后陆续添 加了更多的存储引擎和事务支持。MySQL有一些怪异的行为是由于历 史遗留导致的。例如,在执行ALTER TABLE 时,MySQL提交事务的方式 是由于存储引擎的架构直接导致的,并且数据字典也保存在.frm 文件中 (这并不是说InnoDB会导致ALTER变成非事务型的。对于InnoDB来 说,所有的操作都是事务)。
当然,存储引擎API的架构也有一些缺点。有时候选择多并非好 事,而在MySQL 5.0和MySQL 5.1中有太多的存储引擎可以选择。 InnoDB对于95%以上的用户来说都是最佳选择,所以其他的存储引擎 可能只是让事情变得复杂难搞,当然也不可否认某些情况下某些存储引 擎能更好地满足需求。
Oracle一开始收购了InnoDB,之后又收购了MySQL,在同一个屋檐 下对于两者都是有利的。InnoDB和MySQL服务器之间可以更快地协同 发展。MySQL依然基于GPL协议开放全部源代码,社区和客户都可以获 得坚固而稳定的数据库,MySQL正在变得越来越可扩展和有用。
基准测试(benchmark)是MySQL新手和专家都需要掌握的一项基本技能。基准测试是针对系统设计的一种压力测试。
本章将讨论MySQL和基于MySQL的应 用的基准测试的重要性,策略和工具。
基准测试可以完成以下工作,或者更多:
1.验证基于系统的一些假设,确认这些假设是否符合实际情况。
2.重现系统中的某些异常行为,以解决这些异常。
3.测试系统当前的运行情况。如果不清楚系统当前的性能,就无法确认某些优化的效果如何。也可以利用历史的基准测试结果来分析诊断一些无法预测的问题。
4.模拟比当前系统更高的负载,以找出系统随着压力增加而可能遇到 的扩展性瓶颈。
5. 规划未来的业务增长。基准测试可以评估在项目未来的负载下,需要什么样的硬件,需要多大容量的网络,以及其他相关资源。这有
6.助于降低系统升级和重大变更的风险。
7.测试应用适应可变环境的能力。例如,通过基准测试,可以发现系统在随机的并发峰值下的性能表现,或者是不同配置的服务器之间 的性能表现。基准测试也可以测试系统对不同数据分布的处理能力。
8.测试不同的硬件、软件和操作系统配置。比如RAID 5还是RAID 10 更适合当前的系统?如果系统从ATA硬盘升级到SAN存储,对于随机写性能有什么帮助?Linux 2.4系列的内核会比2.6系列的可扩展性 更好吗?升级MySQL的版本能改善性能吗?为当前的数据采用不同的存储引擎会有什么效果?所有这类问题都可以通过专门的基准 测试来获得答案。
9.证明新采购的设备是否配置正确。笔者曾经无数次地通过基准测试来对新系统进行压测,发现了很多错误的配置,以及硬件组件的失 效等问题。因此在新系统正式上线到生产环境之前进行基准测试是一个好习惯,永远不要相信主机提供商或者硬件供应商的所谓系统 已经安装好,并且能运行多快的说法。如果可能,执行实际的基准测试永远是一个好主意。
注意:
基准测试还可以用于其他目的,比如为应用创建单元测试套件。
基准测试的一个主要问题在于其不是真实压力的测试。
大部分压力测试工具不支持对压力进行复杂的控制。务必要记住,测试工具自身的局限也会影响到结果的有效性。
结论就是,我们只能进行大概的测试,来确定系统大致的余量有多少。
基准测试有两种主要的策略:
1.针对整个系统的整体测试.
2.单独测试MySQL.
这两种策略也被称为集成式(full-stack)以及单组 件式(single-component)基准测试。
针对整个系统做集成式测试,而不 是单独测试MySQL的原因主要有以下几点:
1.测试整个应用系统,包括Web服务器、应用代码、网络和数据库是 非常有用的,因为用户关注的并不仅仅是MySQL本身的性能,而是应用整体的性能。
2.MySQL并非总是应用的瓶颈,通过整体的测试可以揭示这一点。
3.只有对应用做整体测试,才能发现各部分之间的缓存带来的影响。
4.整体应用的集成式测试更能揭示应用的真实表现,而单独组件的测 试很难做到这一点。
另外一方面,应用的整体基准测试很难建立,甚至很难正确设置。
基于以下情况,可以选择只测试 MySQL:
需要比较不同的schema或查询的性能。
针对应用中某个具体问题的测试。
为了避免漫长的基准测试,可以通过一个短期的基准测试,做快速的“周期循环”,来检测出某些调整后的效果。
请考虑以下指标,看看如何满足测试的需求。
吞吐量
吞吐量指的是单位时间内的事务处理数。这一直是经典的数据 库应用测试指标。一些标准的基准测试被广泛地引用,如TPC-C(参考http://www.tpc.org )。这类基准测试主要针对在线事务处理(OLTP)的吞吐量,非常适用于多用户的交互式应用。常用的测 试单位是每秒事务数(TPS),有些也采用每分钟事务数 (TPM)
响应时间或者延迟
这个指标用于测试任务所需的整体时间。通常可以使用百分比响应时间 (percentile response time)来替代最大响应时间。
并发性
并发性指同一时刻用户向服务器发送的请求量.并发性测试通常不是为了 测试应用能达到的并发度,而是为了测试应用在不同并发下的性
能。当然,数据库的并发性还是需要测量的。可以通过sysbench 指 定32、64或者128个线程的测试,然后在测试期间记录MySQL数据库的Threads_running 状态值。在第11章将讨论这个指标对容量规 划的影响。
可扩展性
在系统的业务压力可能发生变化的情况下,测试可扩展性就非常必要了。可扩展性指的是,给系统增加一倍的工作,在理想情况下就能获得两倍的结果(即吞吐量增加一倍)。
归根结底,应该测试那些对用户来说最重要的指标。因此应该尽可 能地去收集一些需求,比如,什么样的响应时间是可以接受的,期待多 少的并发性,等等。然后基于这些需求来设计基准测试,避免目光短浅 地只关注部分指标,而忽略其他指标.
使用真实数据的子集而不是全集。例如应用需要处理几百GB的数 据,但测试只有1GB数据;或者只使用当前数据进行测试,却希望
模拟未来业务大幅度增长后的情况。
使用错误的数据分布。例如使用均匀分布的数据测试,而系统的真 实数据有很多热点区域(随机生成的测试数据通常无法模拟真实的 数据分布)。
使用不真实的分布参数,例如假定所有用户的个人信息(profile) 都会被平均地读取.
在多用户场景中,只做单用户的测试。
在单服务器上测试分布式应用。
与真实用户行为不匹配。例如Web页面中的“思考时间”。真实用户 在请求到一个页面后会阅读一段时间,而不是不停顿地一个接一个 点击相关链接。
反复执行同一个查询。真实的查询是不尽相同的,这可能会导致缓 存命中率降低。而反复执行同一个查询在某种程度上,会全部或者 部分缓存结果。
没有检查错误。如果测试的结果无法得到合理的解释,比如一个本 应该很慢的查询突然变快了,就应该检查是否有错误产生。否则可 能只是测试了MySQL检测语法错误的速度了。基准测试完成后, 一定要检查一下错误日志,这应当是基本的要求。
忽略了系统预热(warm up)的过程。例如系统重启后马上进行测 试。有时候需要了解系统重启后需要多长时间才能达到正常的性能 容量,要特别留意预热的时长。反过来说,如果要想分析正常的性 能,需要注意,若基准测试在重启以后马上启动,则缓存是冷的、 还没有数据,这时即使测试的压力相同,得到的结果也和缓存已经装满数据时是不同的。
使用默认的服务器配置。第3章将详细地讨论服务器的优化配置。
测试时间太短。基准测试需要持续一定的时间。后面会继续讨论这个话题。
1.规划基准测试的第一步是提出问题并明确目标。
2.然后决定是采用标 准的基准测试,还是设计专用的测试。
如果采用标准的基准测试,应该确认选择了合适的测试方案。
如果是设计专用的基准测试:
1.首先 需要获得生产数据集的快照,并且该快照很容易还原,以便进行后续的测试。
2.然后,针对数据运行查询。可以建立一个单元测试集作为初步的测 试,并运行多遍。
3.详细地写下测试规划也是必需的。测试可能要多次反复运行,因此需要精确地重现测试过程。
4.应该建立将参数和结果文档化的规范,每一轮测试都必须进行详细记录。
基准测试应该运行足够长的时间,这一点很重要。有时候无法确认测试需要运行多长的时间才足够。如果是这样,可 以让测试一直运行,持续观察直到确认系统已经稳定。
在执行基准测试时,需要尽可能多地收集被测试系统的信息。最好 为基准测试建立一个目录,并且每执行一轮测试都创建单独的子目录, 将测试结果、配置文件、测试指标、脚本和其他相关说明都保存在其 中。即使有些结果不是目前需要的,也应该先保存下来。多余一些数据 总比缺乏重要的数据要好,而且多余的数据以后也许会用得着。需要记 录的数据包括系统状态和性能指标,诸如CPU使用率、磁盘I/O、网络 流量统计、SHOW GLOBAL STATUS 计数器等。
1.获得准确测试结果的最好办法,是回答一些关于基准测试的基本问 题:是否选择了正确的基准测试?是否为问题收集了相关的数据?是否 采用了错误的测试标准?例如,是否对一个I/O密集型(I/O-bound)的 应用,采用了CPU密集型(CPU-bound)的测试标准来评估性能?
接着,确认测试结果是否可重复。
2.如果测试的过程会修改数据或者schema,那么每次测试前,需要利用快照还原数据。
3.要注意很多因素,包括外部的压力、性能分析和监控系统、详细的 日志记录、周期性作业,以及其他一些因素,都会影响到测试结果。
4.每次测试中,修改的参数应该尽量少。
5.一般情况下,都是通过迭代逐步地修改基准测试的参数,而不是每 次运行时都做大量的修改。
6.来做预测系统迁移后的性能的通常需要重新设计MySQL的schema和查询.
7.基于MySQL的默认配置的测试没有什么意义,因为默认配置是基于消耗很少内存的极小应用的。
8.如果测试中出现异常结果,不要轻易当作坏数据点而丢弃。 应该认真研究并找到产生这种结果的原因。
1.自动化测试
通常来说,自动化基准测试是个好主意。要尽可能地使 所有测试过程都自动化,包括装载数据、系统预热、执行测试、记录结果等.
(一旦设置了正确的自动化操作,基准测试将成为一步式操作。如果只是针对某些应用做一次性的快速验证测试,可能就没必要做自动化。但只要未来可能会引用到测试结果,建议都尽量地自动化。否则到时候可能就搞不清楚是如何获得这个结果的,也不记得采用了什么 参数,这样就很难再通过测试重现结果了。)
2.基准测试通常需要运行多次。
3.如何从数据中抽象出有意义的结果,依赖于如何收集数据。。通常需 要写一些脚本来分析数据,这不仅能减轻分析的工作量,而且和自动化 基准测试一样可以重复运行,并易于文档化。。假设MySQL数 据正在遭受“疯狂刷新(furious flushing)”的问题,在刷新落后于检查 点时会阻塞所有的活动,从而导致吞吐量严重下跌。95%的响应时间和 平均响应时间指标都无法发现这个问题,也就是说这两个指标掩盖了问 题。
或许你会坚持看 测试工具打印出来的平均值或其他汇总过的信息,但平均值有时候是没有用的,它会掩盖掉一些真实情况。
详情此处
详情此处
数据采集
#!/bin/sh
INTERVAL=5
PREFIX=$INTERVAL-sec-status
RUNFILE=/home/benchmarks/running
mysql -e 'SHOW GLOBAL VARIABLES' >> mysql-variables
while test -e $RUNFILE; do
file=$(date +%F_%I)
sleep=$(date +%s.%N | awk "{print $INTERVAL - (\$1 % $INTERVAL)}")
sleep $sleep
ts="$(date +"TS %s.%N %F %T")"
loadavg="$(uptime)"
echo "$ts $loadavg" >> $PREFIX-${file}-status
mysql -e 'SHOW GLOBAL STATUS' >> $PREFIX-${file}-status &
echo "$ts $loadavg" >> $PREFIX-${file}-innodbstatus
mysql -e 'SHOW ENGINE INNODB STATUS\G' >> $PREFIX-${file}-innodbstatus &
echo "$ts $loadavg" >> $PREFIX-${file}-processlist
mysql -e 'SHOW FULL PROCESSLIST\G' >> $PREFIX-${file}-processlist &
echo $ts
done
echo Exiting because $RUNFILE does not exist
将采集的信息,分析其时间维度
#!/bin/sh
# This script converts SHOW GLOBAL STATUS into a tabulated format, one line
# per sample in the input, with the metrics divided by the time elapsed
# between samples.
awk 'BEGIN {
printf "#ts date time load QPS";
fmt = " %.2f";
}
/^TS/ { # The timestamp lines begin with TS.
ts = substr($2, 1, index($2, ".") - 1);
load = NF - 2;
diff = ts - prev_ts;
prev_ts = ts;
printf "\n%s %s %s %s", ts, $3, $4, substr($load, 1, length($load)-1); }
/Queries/ {
printf fmt, ($2-Queries)/diff;
Queries=$2 }' "$@"
每个MySQL的使用者都应该了解一些基准测试的知识。基准测试 不仅仅是用来解决业务问题的一种实践行动,也是一种很好的学习方 法。学习如何将问题分解成可以通过基准测试来获得答案的方法,就和在数学课上从文字题目中推导出方程式一样。首先正确地描述问题,之 后选择合适的基准测试来回答问题,设置基准测试的持续时间和参数, 运行测试,收集数据,分析结果数据,这一系列的训练可以帮助你成为 更好的MySQL用户。
如果你还没有做过基准测试,那么建议至少要熟悉sysbench 。可以 先学习如何使用oltp 和fileio 测试。oltp 基准测试可以很方便地比较 不同系统的性能。另一方面,文件系统和磁盘基准测试,则可以在系统 出现问题时有效地诊断和隔离异常的组件。通过这样的基准测试,我们 多次发现了一些数据库管理员的说法存在问题,比如SAN存储真的出现 了一块坏盘,或者RAID控制器的缓存策略的配置并不是像工具中显示 的那样。通过对单块磁盘进行基准测试,如果发现每秒可以执行14000 次随机读,那要么是碰到了严重的错误,要么是配置出现了问题。
如果经常执行基准测试,那么制定一些原则是很有必要的。选择一 些合适的测试工具并深入地学习。可以建立一个脚本库,用于配置基准 测试,收集输出结果、系统性能和状态信息,以及分析结果。使用一种 熟练的绘图工具如gnuplot 或者R (不用浪费时间使用电子表格,它们既 笨重,速度又慢)。尽量早和多地使用绘图的方式,来发现基准测试和 系统中的问题和错误。你的眼睛是比任何脚本和自动化工具都更有效的 发现问题的工具。
我们将性能定义为完成某件任务所需要的时间度量,换句话说,性能即响应时间,这是一个非常重要的原则。
数据库服务 器的性能用查询的响应时间来度量,单位是每个查询花费的时间。
假如你认为性能优化是降低CPU利用率,那么 可以减少对资源的使用。但这是一个陷阱,资源是用来消耗并用来工作的,所以有时候消耗更多的资源能够加快查询速度。查询的响应时间则更能体现升级后的性能是不是变得更好。
如果把性能优化仅仅看成是提升每秒查询量,这其实只是吞 吐量优化。吞吐量的提升可以看作性能优化的副产品 (3) 。对查询的优 化可以让服务器每秒执行更多的查询,因为每条查询执行的时间更短了.
如果目标是降低响应时间,那么就需要理解为什么服务器执行 查询需要这么多时间,然后去减少或者消除那些对获得查询结果来说不必要的工作。
测量是一项很有挑战性的工作,并且分析结果也同样有 挑战性,测出时间花在哪里,和知道为什么花在那里,是两码事。需要合适的测量范围.有两种比较常见的情况会导致不合适的测量:
在错误的时间启动和停止测量。
测量的是聚合后的信息,而不是目标活动本身.
完成一项任务所需要的时间可以分成两部分:执行时间和等待时间。如果要优化任务的执行时间,最好的办法是通过测量定位不同的子 任务花费的时间,然后优化去掉一些子任务、降低子任务的执行频率或 者提升子任务的效率。而优化任务的等待时间则相对要复杂一些,因为 等待有可能是由其他系统间接影响导致,任务之间也可能由于争用磁盘或者CPU资源而相互影响。
性能剖析一般 有两个步骤:(1)测量任务所花费的时间;(2)然后对结果进行统计和排序,将 重要的任务排到前面。
我们将实际地讨论两种类型的性能剖析:基于执行时间的分析和基 于等待的分析。基于执行时间的分析研究的是什么任务的执行时间最 长,而基于等待的分析则是判断任务在什么地方被阻塞的时间最长。
不幸的是,尽管性能剖析输出了排名、总计和平均值,但还是有 很多需要的信息是缺失的,如下所示。
值得优化的查询(worthwhile query)
性能剖析不会自动给出哪些查询值得花时间去优化。
异常情况
某些任务即使没有出现在性能剖析输出的前面也需要优化。
未知的未知
一款好的性能剖析工具会显示可能的“丢失的时间”。丢失的时 间指的是任务的总时间和实际测量到的时间之间的差。
被掩藏的细节
性能剖析无法显示所有响应时间的分布。只相信平均值是非常危险的,它会隐藏很多信息,而且无法表达全部情况。
还有一个重要的缺失,就是无法在更 高层次的堆栈中进行交互式的分析。也有一些办法可以解决这个 问题,比如给查询加上特殊的注释作为标签,可以标明其来源并据此做聚合,也可以在应用层面增加更多的测量点,这是下一节的主题。
虽然性能问题大多数情况下都和数据库有关, 但应用导致的性能问题也不少。性能瓶颈可能有很多影响因素:
外部资源,比如调用了外部的Web服务或者搜索引擎。
应用需要处理大量的数据,比如分析一个超大的XML文件。
在循环中执行昂贵的操作,比如滥用正则表达式。
使用了低效的算法,比如使用暴力搜索算法(naïve search algorithm)来查找列表中的项。
建议在所有的新项目中都考虑包含性能剖析的代码。往已有的项目中加入性能剖析代码也许很困难,新项目就简单一些。
MySQL的企业监控器(Enterprise Monitor)也是值得考虑的工 具之一。这是Oracle提供的MySQL商业服务支持中的一部分。它可以 捕获发送给服务器的查询,要么是通过应用程序连接MySQL的库文件 实现,要么是在代理层实现(我们并不太建议使用代理层)。该工具 有设计良好的用户界面,可以直观地显示查询的剖析结果,并且可以 根据时间段进行缩放,例如可以选择某个异常的性能尖峰时间来查看状态图。也可以查看EXPLAIN出来的执行计划,这在故障诊断时非常有用。
服务器端的剖析很有价值,因为在服务器端可以有效地审计效率低 下的查询。定位和优化“坏”查询能够显著地提升应用的性能,也能解决 某些特定的难题。还可以降低服务器的整体压力,这样所有的查询都将 因为减少了对共享资源的争用而受益(“间接的好处”)。降低服务器的 负载也可以推迟或者避免升级更昂贵硬件的需求,还可以发现和定位糟 糕的用户体验,比如某些极端情况。
捕获MySQL的查询到日志文件中
1.在MySQL 5.1及更新的版本中,慢日志的功能已经被加强,可以通过设置long_query_time 为0来捕获所有的查询,而且查询 的响应时间单位已经可以做到微秒级。
在MySQL的当前版本中,慢查询日志是开销最低、精度最高的测 量查询时间的工具。如果还在担心开启慢查询日志会带来额外的I/O开 销,那大可以放心。我们在I/O密集型场景做过基准测试,慢查询日志 带来的开销可以忽略不计(实际上在CPU密集型场景的影响还稍微大一 些)。更需要担心的是日志可能消耗大量的磁盘空间。如果长期开启慢查询日志,注意要部署日志轮转(log rotation)工具。或者不要长期启 用慢查询日志,只在需要收集负载样本的期间开启即可。
2.MySQL还有另外一种查询日志,被称之为“通用日志”,但很少用于 分析和剖析服务器性能。通用日志在查询请求到服务器时进行记录,所 以不包含响应时间和执行计划等重要信息。
Percona Server的慢查询日志比MySQL官方版本记录了更多细节且 有价值的信息,如查询执行计划、锁、I/O活动等。
有时因为某些原因如权限不足等,无法在服务器上记录查询。这样 的限制我们也常常碰到,所以我们开发了两种替代的技术,都集成到了 Percona Toolkit中的pt-query-digest 中。第一种是通过–processlist 选项不 断查看SHOW FULL PROCESSLIST 的输出,记录查询第一次出现的时间和 消失的时间。某些情况下这样的精度也足够发现问题,但却无法捕获所 有的查询。一些执行较快的查询可能在两次执行的间隙就执行完成了, 从而无法捕获到。
第二种技术是通过抓取TCP网络包,然后根据MySQL的客户端/服 务端通信协议进行解析。可以先通过tcpdump 将网络包数据保存到磁 盘,然后使用pt-query-digest 的–type=tcpdump 选项来解析并分析查询。 此方法的精度比较高,并且可以捕获所有查询。还可以解析更高级的协 议特性,比如可以解析二进制协议,从而创建并执行服务端预解析的语 句(prepared statement)及压缩协议。另外还有一种方法,就是通过 MySQL Proxy代理层的脚本来记录所有查询,但在实践中我们很少这样做.
分析查询日志
强烈建议大家从现在起就利用慢查询日志捕获服务器上的所有查 询,并且进行分析。可以在一些典型的时间窗口如业务高峰期的一个小 时内记录查询。如果业务趋势比较均衡,那么一分钟甚至更短的时间内 捕获需要优化的低效查询也是可行的。 不要直接打开整个慢查询日志进行分析,这样做只会浪费时间和金 钱。首先应该生成一个剖析报告,如果需要,则可以再查看日志中需要 特别关注的部分。自顶向下是比较好的方式,否则有可能像前面提到的,反而导致业务的逆优化。
间歇性的问题比如系统偶尔停顿或者慢查询,很难诊断。有些幻影 问题只在没有注意到的时候才发生,而且无法确认如何重现,诊断这样 的问题往往要花费很多时间,有时候甚至需要好几个月。在这个过程 中,有些人会尝试以不断试错的方式来诊断,有时候甚至会想要通过随机地改变一些服务器的设置来侥幸地找到问题。尽量不要使用试错的方式来解决问题。这种方式有很大的风险,因为结果可能变得更坏。
下面列举了我们认为已经解决的一些间歇性数据库性能问题的实际案例:
应用通过curl 从一个运行得很慢的外部服务来获取汇率报价的数据。
memcached 缓存中的一些重要条目过期,导致大量请求落到 MySQL以重新生成缓存条目。
DNS查询偶尔会有超时现象。
可能是由于互斥锁争用,或者内部删除查询缓存的算法效率太低的缘故,MySQL的查询缓存有时候会导致服务有短暂的停顿。
当并发度超过某个阈值时,InnoDB的扩展性限制导致查询计划的优化需要很长的时间。
在开始之前,需要搞清楚两件事:
诊断触发器
触发器非常重要。这是在问题出现时能够捕获数据的基础。有两个常见的问题可能导致无法达到预期的结果:误报(false positive)或者漏检(false negative)。误报是指收集了很多诊断数据,但期间其实没有发生问题,这可能浪费时间,而且令人沮丧。而漏检则指在问题出现 时没有捕获到数据,错失了机会,一样地浪费时间。所以在开始收集数据前多花一点时间来确认触发器能够真正地识别问题是划算的。
那么好的触发器的标准是什么呢?在出现问题时会比较敏感,而没有问题时则比较平稳。
需要收集什么样的数据
收集所有的数据听起来工作量很大。或许读者之前已经做过类似的 事情,但我们提供的工具可以提供一些帮助。这个工具名为pt-collect , 也是Percona Toolkit中的一员。pt-collect 一般通过pt-stalk 来调用。因为 涉及很多重要数据的收集,所以需要用root 权限来运行。默认情况下, 启动后会收集30秒的数据,然后退出。
在服务器正常运行时捕获一些样本数据也很重要,而不只是在有问题时捕获数据。 这样可以帮助对比确认是否某些样本,或者样本中的某部分数据有异常。例如,在查看进程列 表(process list)中查询的状态时,可以回答一些诸如“大量查询处于正在排序结果的状态是不是正常的”的问题。
我们已经演示了很多剖析MySQL、操作系统及查询的方法。我们 也演示了那些我们觉得很有用的案例。当然,通过本书,我们还会展示 更多工具和技术来检查和测量系统。但是等一下,本章还有更多工具没介绍呢。
详见此处
本章给出了一些基本的思路和技术,有助于你成功地进行性能优 化。正确的思维方式是开启系统的全部潜力和应用本书其他章节提供的 知识的关键。下面是我们试图演示的一些基本知识点:
@我们认为定义性能最有效的方法是响应时间。
@如果无法测量就无法有效地优化,所以性能优化工作需要基于高质量、全方位及完整的响应时间测量。
@测量的最佳开始点是应用程序,而不是数据库。即使问题出在底层 的数据库,借助良好的测量也可以很容易地发现问题。
@大多数系统无法完整地测量,测量有时候也会有错误的结果。但也 可以想办法绕过一些限制,并得到好的结果(但是要能意识到所使 用的方法的缺陷和不确定性在哪里)。
@完整的测量会产生大量需要分析的数据,所以需要用到剖析器。这 是最佳的工具,可以帮助将重要的问题冒泡到前面,这样就可以决 定从哪里开始分析会比较好。
@剖析报告是一种汇总信息,掩盖和丢弃了太多细节。而且它不会告 诉你缺少了什么,所以完全依赖剖析报告也是不明智的。
@有两种消耗时间的操作:工作或者等待。大多数剖析器只能测量因 为工作而消耗的时间,所以等待分析有时候是很有用的补充,尤其 是当CPU利用率很低但工作却一直无法完成的时候。
@优化和提升是两回事。当继续提升的成本超过收益的时候,应当停止优化。
@ 注意你的直觉,但应该只根据直觉来指导解决问题的思路,而不是 用于确定系统的问题。决策应当尽量基于数据而不是感觉。
本章和聚焦在索引优化的下一章,覆盖了MySQL特有的schema设 计方面的主题。这一章关注的是MySQL数据库的设计,主要介绍的是MySQL数据库设 计与其他关系型数据库管理系统的区别。
本章内容是为接下来的两个章节做铺垫。在这三章中,我们将讨论 逻辑设计、物理设计和查询执行,以及它们之间的相互作用。这既需要 关注全局,也需要专注细节。还需要理解整个系统以便弄清楚各个部分 如何相互影响。如果在阅读完索引和查询优化章节后再回头来看这一 章,也许会发现本章很有用,很多讨论的议题不能孤立地考虑。
MySQL支持的数据类型非常多,选择正确的数据类型对于获得高性能至关重要。不管存储哪种类型的数据,下面几个简单的原则都有助于做出更好的选择。
更小的通常更好。
一般情况下,应该尽量使用可以正确存储数据的最小数据类型 。更小的数据类型通常更快,因为它们占用更少的磁盘、内存和 CPU缓存,并且处理时需要的CPU周期也更少。 但是要确保没有低估需要存储的值的范围,因为在schema中的 多个地方增加数据类型的范围是一个非常耗时和痛苦的操作。如果 无法确定哪个数据类型是最好的,就选择你认为不会超过范围的最小类型。
简单就好
简单数据类型的操作通常需要更少的CPU周期。例如,整型比 字符操作代价更低,因为字符集和校对规则(排序规则)使字符比较比整型比较更复杂。
尽量避免NULL
很多表都包含可为NULL (空值)的列,即使应用程序并不需 要保存NULL 也是如此,这是因为可为NULL 是列的默认属性。通常情况下最好指定列为NOT NULL ,除非真的需要存储NULL值。如果查询中包含可为NULL 的列,对MySQL来说更难优化,因 为可为NULL 的列使得索引、索引统计和值比较都更复杂。可 为NULL 的列会使用更多的存储空间,在MySQL里也需要特殊处 理。当可为NULL 的列被索引时,每个索引记录需要一个额外的字 节,在MyISAM里甚至还可能导致固定大小的索引(例如只有一个 整数列的索引)变成可变大小的索引。 通常把可为NULL 的列改为NOT NULL 带来的性能提升比较小, 所以(调优时)没有必要首先在现有schema中查找并修改掉这种情 况,除非确定这会导致问题。但是,如果计划在列上建索引,就应 该尽量避免设计成可为NULL 的列。 当然也有例外,例如值得一提的是,InnoDB使用单独的位 (bit)存储NULL 值,所以对于稀疏数据 (4) 有很好的空间效率。但这一点不适用于MyISAM。
有两种类型的数字:整数(whole number)和实数(real number)。如果存储整数,可以使用这几种整数类型:TINYINT, SMALLINT,MEDIUMINT,INT,BIGINT 。分别使用8,16,24,32,64位 存储空间。 整数类型有可选的UNSIGNED 属性,表示不允许负值,这大致可以使正数的上限提高一倍。例如TINYINT UNSIGNED 可以存储的范围是0~ 255,而TINYINT的存储范围是−128~127。
因为需要额外的空间和计算开销,所以应该尽量只在对小数进行精确计算时才使用DECIMAL ——例如存储财务数据。但在数据量比较大的 时候,可以考虑使用BIGINT代替DECIMAL ,将需要存储的货币单位根据小数的位数乘以相应的倍数即可。假设要存储财务数据精确到万分之一 分,则可以把所有金额乘以一百万,然后将结果存储在BIGINT 里,这 样可以同时避免浮点存储计算不精确和DECIMAL 精确计算代价高的问
题。
有多种方法可以指定浮点列所需要的精度,这会使得MySQL悄悄 选择不同的数据类型,或者在存储时对值进行取舍。这些精度定义是非标准的,所以我们建议只指定数据类型,不指定精度。
1.VARCHAR 类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型更节省空间,因为它仅使用必要的空间(例 如,越短的字符串使用越少的空间)。有一种情况例外,如果 MySQL表使用ROW_FORMAT=FIXED 创建的话,每一行都会使用定长 存储,这会很浪费空间。
2.VARCHAR 节省了存储空间,所以对性能也有帮助。但是,由于 行是变长的,在UPDATE 时可能使行变得比原来更长,这就导致需 要做额外的工作。如果一个行占用的空间增长,并且在页内没有更 多的空间可以存储,在这种情况下,不同的存储引擎的处理方式是 不一样的。例如,MyISAM会将行拆成不同的片段存储,InnoDB则 需要分裂页来使行可以放进页内。其他一些存储引擎也许从不在原 数据位置更新数据。
3.下面这些情况下使用VARCHAR 是合适的:字符串列的最大长度 比平均长度大很多;列的更新很少,所以碎片不是问题;使用了像 UTF-8这样复杂的字符集,每个字符都使用不同的字节数进行存储.InnoDB则更灵活,它可以把过长的VARCHAR 存储为BLOB
1.CHAR 适合存储很短的字符串,或者所有值都接近同一个长度。
2.对于经 常变更的数据,CHAR 也比VARCHAR 更好,因为定长的CHAR 类型不容易 产生碎片。
3.对于非常短的列,CHAR 比VARCHAR 在存储空间上也更有效率。
MySQL提供两种相似的日期类型:DATETIME 和TIMESTAMP 。
精度为秒。 它把日期和时间封装到格式为YYYYMMDDHHMMSS的整数中, 与时区无关。使用8个字节的存储空间。
除了特殊行为之外,通常也应该尽量使用TIMESTAMP ,因为它比 DATETIME空间效率更高。
如果需要存储比秒更小粒度的日期和时间值:可以使用BIGINT 类型存储微秒级别的时间截,或者使用DOUBLE 存储秒之后的小数部分。
一般而言,大部分ALTER TABLE 操作将导致MySQL服务中断。对常见的场景,能使用的技巧只有两种:一种是先在一台不提供服务的机器上执行ALTER TABLE 操作,然后和提供服务的主库进行切换; 另外一种技巧是“影子拷贝”。影子拷贝的技巧是用要求的表结构创建一 张和源表无关的新表,然后通过重命名和删表操作交换两张表。
不是所有的ALTER TABLE 操作都会引起表重建。例如,有两种方法 可以改变或者删除一个列的默认值(一种方法很快,另外一种则很 慢)。假如要修改电影的默认租赁期限,从三天改到五天。下面是很慢的方式:
mysql> ALTER TABLE sakila.film -> MODIFY COLUMN rental_duration TINYINT(3) NOT NULL DEFAUL
SHOW STATUS 显示这个语句做了1000次读和1000次插入操作。换句 话说,它拷贝了整张表到一张新表,甚至列的类型、大小和可否为NULL 属性都没改变。
理论上,MySQL可以跳过创建新表的步骤。列的默认值实际上存 在表的.frm 文件中,所以可以直接修改这个文件而不需要改动表本身。 然而MySQL还没有采用这种优化的方法,所有的MODIFY COLUMN 操作都 将导致表重建。 另外一种方法是通过ALTER COLUMN (16) 操作来改变列的默认值:
mysql> ALTER TABLE sakila.film -> ALTER COLUMN rental_duration SET DEFAULT 5;
这个语句会直接修改.frm 文件而不涉及表数据。所以,这个操作是非常快的。
从上面的例子我们看到修改表的.frm 文件是很快的,但MySQL有时 候会在没有必要的时候也重建表。如果愿意冒一些风险,可以让MySQL做一些其他类型的修改而不用重建表。
下面这些操作是有可能不需要重建表的:
移除(不是增加)一个列的AUTO_INCREMENT 属性。
增加、移除,或更改ENUM 和SET 常量。如果移除的是已经有行数据 用到其值的常量,查询将会返回一个空字串值。
基本的技术是为想要的表结构创建一个新的.frm 文件,然后用它替 换掉已经存在的那张表的.frm 文件,像下面这样:
索引定义:索引(Index)是帮助MySQL高效获取数据的数据结构
索引会影响到where后面的查找和order by后面的排序
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引b+tree. 实质上B+Tree是一颗平衡树,如果对这颗树新增、修改、删除的话,会破坏它的原有结构;我们在做数据新增、修改、删除的时候,需要花额外的时间去维护索引;正因为这些额外的开销,导致索引会降低新增、修改、删除的速度。B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页 到根的距离相同。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘.
select * from user where username='张三';
# 创建索引
create index idx_user_username on user(username); # 单值索引
alter table user add index `idx_user_username`(username) # 单值索引
select * from user where username='张三' and password= '123456';
create index idx_user_usernamePassword on user(username, password); # 复合索引
alter table user add index `idx_user_usernamePassword`(username, password); # 复合索引
# 1.PRIMARY KEY(主键索引)
ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
# 2.UNIQUE(唯一索引)
ALTER TABLE `table_name` ADD UNIQUE (`column` )
# 3.INDEX(普通索引)
ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
# 4.FULLTEXT(全文索引)
ALTER TABLE `table_name` ADD FULLTEXT ( `column` )
# 5.多列索引
ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
# 删除索引
drop index idx_user_usernamePassword on user; # 删除user表的idx_user_usernamePassword 索引
# 查看索引
show index from user; # 查看user表的所有索引
叶子节点是双向循环链表
我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。 其中聚集索引,次要索,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,像B+树这种类型的索引之外,还有哈希(hash index)索引
mysq的b+树页分裂上面描述的仅是最简单的情况.更多相关内容,树的各种数据结构详情姜承尧的<
SHOW GLOBAL STATUES LIKE 'Innodb_page_size';
# 页大小默认16384
innodb引擎中,默认一个连续的节点(页)为16KB.
如上图除用户记录和空闲空间,页目录外共128B.
innodb默认指针大小为6KB.
设一条用户记录的主键为BIGINT类型,即8字节.
综上得: (16384-128)/(6+8) =1161 条索引数据.
因而16k的页大小,大约可存1161条索引数据
设叶子节点的数据为1KB(如果是非聚簇索引,只存BIGINT主键只需要8B.这里选聚簇索引数据结构的保守最大数据量).则一个页具备的数据条数为(16384-128)B/1000 B=16 条数据
因而可以推算出一个根页具备3层树深.就可以存(满叶子节点数) 1161条 * 1161条 * 16条 = 2156 6736 条
B+Tree对索引列是顺序组织存储的,所以很适合查找范围数据。
1.非叶子节点只存储键值信息。
2.所有叶子节点之间都有一个链指针。(顺序访问,提高范围访问速度)
3.数据记录都存放在叶子节点中。
4.通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是大的.如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
二叉树,红黑树,b-tree,b+tree树逻辑讲解
如上图,当存入有序数时,由于二叉树的特性,他退化成了链表.没有起到减少比较次数的作用
在满足二叉树的基础上,任意两个节点的两个子树的高度差不能超过1
添加操作: 需要回溯改变节点的深度.查询较慢
(1)维护平衡过程的成本代价很高,因为每次删除一个节点或者增加一个节点的话,需要一次或者多次的左旋,右旋等去维护“平衡”状态
(2)然后是查询的效率不稳定,有序性结构发生改变
(3)然后是如果节点很多的话,那么这个AVL树的高度还是会很高的,那么查询效率还是会很低
数据结构可视化网站
B+Tree索引适用于全键值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。 前面所述的索引对如下类型的查询有效。
当构建一颗联合索引的b+树时候.第一字段递增排完,然后是第二个字段递增排序....如此...受到先前字段的约束,后面的字段可能是无序的.
(那么当你跳过前面的字段,他是无序的,相当于全表扫描了)
全值匹配是查询条件
要和索引字段完全匹配
,覆盖索引是查询字段
必须在设定的索引字段范围内
因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的ORDER BY操作(按顺序查找)。一般来说,如果B-+Tree可以按照某种方式查找到值,那么也可以按照这种方式用于排序。 所以,如果ORDER BY子句满足前面列出的几种查询类型,则这个索引也可以满足对应的排序需求。
到这里读者应该可以明白,前面提到的索引列的顺序是多么的重要:这些限制都和索引列的顺序有关。在优化性能的时候,可能需要使 用相同的列但顺序不同的索引来满足不同类型的查询需求。 也有些限制并不是B-Tree本身导致的,而是MySQL优化器和存储引擎使用索引的方式导致的,这部分限制在未来的版本中可能就不再是限制了.
(and并列.mysql中存在查询优化器.会自动调整顺序比对索引.但还是建议按照顺序写查询条件)
where之后 order by之前
避免索引失效口诀:
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE%写最右,覆盖索引不写*;
要想LIKE左右%,覆盖索引要记牢
;
不等空值还有or,索引失效要少用;
字符引号不能少,隐式转化泪千行.
注意大多情况遵循以上条件.实际深究这些都不可靠.走不走索引和数据倾斜度(数据量)有很大关系.将在最后提出比explain执行计划更精确详细的optimizer trace分析工具
在mysql8.0中有跳跃扫描的新特性.在一定条件下可以不遵循最左前缀原则
!=和<>到底走不走索引?
在mysql8.0中!=会走索引,但是是针对主键说的. 二级索引看数据量而生效.
在mysql8.0中 先说结论: 和数据量有关,当 !=和<>指明的索引条件占比大于数据量大概20%就能生效.但注意.主键索引不受这个约束.会进行范围索引搜索
测试结果如下
CREATE TABLE `user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`age` tinyint unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
InnoDB引擎有一个特殊的功能叫做“自适应哈希索引(adaptive hash index)”。当InnoDB注意到某些索引值被使用得非常频繁时,它会在内 存中基于B-Tree索引之上再创建一个哈希索引,这样就让B-Tree索引也 具有哈希索引的一些优点,比如快速的哈希查找。这是一个完全自动 的、内部的行为,用户无法控制或者配置,不过如果有必要,完全可以关闭该功能。真正支持哈希索引的除了Memory引擎外,NDB集群引擎也支持唯一哈希索引.
hash的特点
MyISAM表支持空间索引,可以用作地理数据存储。和B-Tree索引 不同,这类索引无须前缀查询。空间索引会从所有维度来索引数据。查 询时,可以有效地使用任意维度来组合查询。必须使用MySQL的GIS相关函数如MBRCONTAINS()等来维护数据。MySQL的GIS支持并不完 善,所以大部分人都不会使用这个特性。开源关系数据库系统中对GIS 的解决方案做得比较好的是PostgreSQL的PostGIS。
全文索引是一种特殊类型的索引,它查找的是文本中的关键词,而 不是直接比较索引中的值。全文搜索和其他几类索引的匹配方式完全不 一样。它有许多需要注意的细节,如停用词、词干和复数、布尔搜索 等。全文索引更类似于搜索引擎做的事情,而不是简单的WHERE 条件匹 配。在相同的列上同时创建全文索引和基于值的B-Tree索引不会有冲 突,全文索引适用于MATCH AGAINST 操作,而不是普通的WHERE 条件操作。
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。术语“聚簇”表示数据行和相邻的键值紧凑地存储在一起.因为无法同时把数据行存放在两 个不同的地方,所以一个表只能有一个聚簇索引.
因为是存储引擎负责实现索引,因此不是所有的存储引擎都支持聚簇索引(MyISAM是非聚簇索引)。但InnoDB的聚簇索引实际上在同 一个结构中保存了B-Tree索引和数据行。一些数据库服务器允许选择哪个索引作为聚簇索引,但直到2022.8.30,还没有任何一个MySQL内建的存储引擎支持这一点。InnoDB 将通过主键聚集数据
,也就是说主键索引就是聚集索引
,它包含了完整的数据记录
其余的都叫非聚集索引,一个索引构建一颗B+树,除了聚集索引(一级),其余全是非聚集(二级索引).非聚集索引只包含主键id是为了减少数据大量重复.因此回表找聚集索引对应的值是有必要的
如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。
聚集的数据有一些重要的优点:
同时,聚簇索引也有一些缺点:
上述基本概念提到如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引.
你小子不建索引还让mysql帮你维护!
推荐使用整型的自增主键,整型比大小比字符类型快,自增为了充分利用自适应哈希和b+树叶子节点的双向链表.也避免了页频繁分裂(Hash仅能满足等值查找和嵌套查找)
1.主键自动建立唯一索引
2.频繁作为查询条件的字段应该创建索引
3.查询中与其它表关联的字段,外键关系建立索引
4.频繁更新的字段不适合创建索引(因为每次更新不单单是更新了记录还会更新索引记录)
5.Where条件里用不到的字段不创建索引.
6.单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
7.查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
8.查询中统计或者分组字段(group by的前提是排序)
1.表记录太少(这种情况下,简单的全表扫描更高效)
2.经常增删改的字段(提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。
因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件)
3.数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
- 优势
提高数据检索速度,减少io时间
降低排序和临时表,减少cpu消耗
将随机io变为顺序io
- 劣势
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的.
索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息.
索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句
我们现在大部分采用逻辑删除,原因如下: 1. 为了数据分析 2.为了索引有效
重建索引的原因主要包括:
1、删除的空间没有重用,导致索引出现碎片(频繁的delete和update操作)
2、删除大量的表数据后,空间没有重用,导致索引"虚高”(大量的delete操作)
3、索引的clustering facto和表不一致(即修改操作,内部还进行索引指针修改.clustering_factor是用来比较索引的顺序程度与表的杂乱排序程度的一个度量)
select * from user where username='张三';
# 创建索引
create index idx_user_username on user(username); # 单值索引
alter table user add index `idx_user_username`(username) # 单值索引
select * from user where username='张三' and password= '123456';
create index idx_user_usernamePassword on user(username, password); # 复合索引
alter table user add index `idx_user_usernamePassword`(username, password); # 复合索引
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈.
执行计划包含的信息
system:系统表,少量数据,往往不需要进行磁盘IO
const:常量连接
eq_ref:主键索引(primary key)或者非空唯一索引(unique not null)等值扫描
ref:非主键非唯一索引等值扫描
range:范围扫描
index:索引树扫描(全索引扫描)
ALL:全表扫描(full table scan) (没加索引就是all)
固态硬盘类似: 在固态硬盘里面,闪存颗粒则替代了机械磁盘成为了存储单元。 闪存(Flash Memory)本质上是一种长寿命的非易失性(在断电情况下仍能保持所存储的数据信息)的存储器,数据删除不是以单个的字节为单位而是以固定的区块为单位。
4.possible_keys 和key字段
possible_keys: 显示可能应用在这张表中的索引,一个或多个查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
key: 被优化器实际应用的索引,如果为null则没使用索引.查询中若使用了覆盖索引,则该索引仅出现在key列表中.(查询字段顺序和个数等于建立索引)
5.key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好.
key_Ien显示的值为索引字段的最大可能长度
,并非实际使用长度,即key_len是根据表定义计算而得
,不是通过表内检索出的.
UTF-8一个(字符)占用(3)个字节,英文占用(1)个字节。GDK: GDK一个(字符)占用(2)个字节,英文占用(1)个字节。
6. ref
显示了之前的表在key列记录的索引中查找值所用的列或常量
7. rows
rows 为了找到所需的行而需要读取的行数,估算值,不精确。通过把所有rows列值相乘,可粗略估算整个查询会检查的行数
8.extra
字段 | 解释 |
---|---|
using filesort: | 排序没用到索引 (索引跟排序是有关系的.详见本文第五章,哪些字段需要创建索引) 弱鸡必须优化 |
using temporary: | 产生内部临时表 更弱鸡必须优化 |
using index: | 很好的查询方式.如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查询动作。 |
Covering Index: | 覆盖索引,就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select*,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。 |
using where: | 使用了where过滤 |
using join buffer: | 使用了连接缓存 |
impossible where: | where子句的值总是false,不能用来获取任何元组 |
select tables optimized away: | 在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyTSAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计景,查询执行计划生成的阶段即完成优化。 |
distinct: | 优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作 |
优化动作详见本文第五章,b+tree索引条件和限制条件
小总结 explain 的表字段中 id,type,key,rows,extra是最重要的字段属性
左连接: 右表加索引(左边一定会做全表扫描,而右边做匹配.合表左边做不做索引都不影响)
右连接: 左表加索引(这是因为RIGHT JOIN条件用于确定如何从左表搜索行,右边一定都有,所以左边是我们的关键点,一定需要建立素引。)
索引生效条件,请查看本文第五章 索引生效的条件和限制
回表就是先通过数据库索引扫描出数据所在的行,再通过行主键id取出索引中未提供的数据,即基于非主键索引的查询需要多扫描一棵索引树.因此,可以通过索引先查询出id字段,再通过主键id字段,查询行中的字段数据,即通过再次查询提供MySQL查询速度.
引自什么是 MySQL 的“回表”?
可以使用覆盖索引解决.
在mysql8.0中引入跳跃扫描.在特定条件下可以不遵循最左前缀原则
覆盖索引走的全索引扫描不是全表扫描.如下文: 查询优化器做了优化走全索引扫描.
mysql查询优化器优化:将会尝试先调整条件顺序,使得条件和索引相匹配.
mysql表关联常见两种算法
优化分析过程:
----------------分析----------------
1 观察,至少跑1天,看看生产的慢SQL情况。
2 开启慢查询日志,设置阈值,比如超过5秒钟的就是慢SQL,并将它抓取出来。
3 explain-+慢SQL分析
4 show profile
5 运维经理or DBA,进行SQL数据库服务器的参数调优
EXISTS
语法:
SELECT 字段 FROM table WHERE EXISTS (subquery);
参数:
subquery是一个受限的SELECT语句(不允许有COMPUTE子句和INTO关键字)
示例:
SELECT * FROM A WHERE EXISTS (SELECT 1 FROM B WHERE B.id = A.id);
EXISTS执行顺序:
1、首先执行一次外部查询,并缓存结果集,如 SELECT * FROM A
2、遍历外部查询结果集的每一行记录R,代入子查询中作为条件进行查询,如 SELECT 1 FROM B WHERE B.id = A.id
3、如果子查询有返回结果,则EXISTS子句返回TRUE,这一行R可作为外部查询的结果行,否则不能作为结果
--知乎 图南(https://zhuanlan.zhihu.com/p/351922940)
关注点:会不会产生FileSort
双路排序:
mysql4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据.
单路排序:
从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。
默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。
慢
查询日志支持将日志记录写入文件.
# 使用set global slow_query_.log=1开启了慢查询日志只对当前数据库生效,
# 如果MySQL重启后则会失效。
# 默认 SHOW VARIABLES LIKE '%slow_query_log%';
# 开启 set global show_query_log=1;
# 如果要永久生效,就必须修改配置文件my.cnf
# 记录运行时间大于long_query_time的sql
SHOW VARIABLES LIKE 'long_query_time%';
# 设置阈值时间
set global long_query_time=3;
# 为什么设置后看不出变化?
# 需要重新连接或新开一个会话才能看到修改值。
SHOW VARIABLES LIKE 'long_query_time%'
# 查看慢查询日志
# docker 在数据卷相应位置寻找
# show variables like "slow_query_log_file"; 表示慢查询日志位置
# show global status like 'Slow_queries%'; 查看有当前有多少慢查询记录
同样不要在生产环境开启
查看慢sql语句:
# 查看记录的慢sql.默认关闭,并只记录前15条
Show variables like 'profiling%';
set profling=on;
# 查看结果
show profiles;
# 具体查看其中一条信息
show profile cpu,block io for query query_id;
#在mysqlE的my.cnf中,设置如下
#开启
general_log=1
#记录日志文件的路径
general_log_file=/path/logfile
#输出格式
log_output=FILE
#命令启用方式
set global general_log=1;
set global log_output='TABLE';
使用样例:
show variables like '%optimizer_trace%';
SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on;
SET optimizer_trace_offset=-30, optimizer_trace_limit=20;
SELECT * FROM `user` WHERE uname='于震南' AND age>49 AND is_delete=0;
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
SELECT * FROM `user` WHERE uname='于震南' AND age>=49 AND is_delete=0;
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
SET optimizer_trace="enabled=off";
使用参数详解这里->MySQL OPTIMIZER TRACE 详解
对于不连续的模糊查询.哈哈我第一个想到的是str.replacce(“”,“%”)做模糊查询.
但是呢mysql5.7以后的全文索引支持中文分词.
【MySQL8】中文分词支持全文索引
当然我们也可以用类似es这种带分词器.使用编辑距离算法的来做这个需求
over!