Mysql 回表、SQL优化、四种隔离级别、三大日志binlog、redo log、undo log
MySQL innodb的主键索引是簇集索引,也就是索引的叶子节点存的是整个单条记录的所有字段值,不是主键索引的就是非簇集索引,非簇集索引的叶子节点存的是主键字段的值。回表是什么意思?就是你执行一条sql语句,需要从两个b+索引中去取数据。
举个例子:、
bl有a,b,c三个字段,其中a是主键,b上建了索引, 然后编写sql语句SELECT * FROM tbl WHERE a=1这样不会产生回表,因为所有的数据在a的索引树中均能找到
SELECT * FROM tbl WHERE b=1这样就会产生回表,因为where条件是b字段,那么会去b的索引树里查找数据,但b的索引里面只有a,b两个字段的值,没有c,那么这个查询为了取到c字段,就要取出主键a的值,然后去a的索引树去找c字段的数据。查了两个索引树,这就叫回表。
索引覆盖就是查这个索引能查到你所需要的所有数据,不需要去另外的数据结构去查。其实就是不用回表。
怎么避免?不是必须的字段就不要出现在SELECT里面。或者b,c建联合索引。但具体情况要具体分析,索引字段多了,存储和插入数据时的消耗会更大。这是个平衡问题
如何避免回表?
使用覆盖索引,所谓覆盖索引就是指索引中包含了查询中的所有字段,这种情况下就不需要再进行回表查询了。
覆盖索引
就是把单列的非主键 索引 修改为 多字段 的联合索引, 在一棵索引数上。 就找到了想要的数据, 不需要去主键索引树上,再检索一遍 这个现象,称之为 索引覆盖.
覆盖索引(covering index ,或称为索引覆盖)即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能。
Mysql性能优化的9中方法
1、选择合适的字段属性
Mysql是一种关系型数据库,可以很好地支持大数据量的存储,但是一般来说,数据库中的表越小,在它上面执行的查询也就越快。因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度舍得尽可能小。
例如:在定义邮政编码这个字段时,如果将其设置为char(255),显然给数据库增加了不必要的空间,甚至使用varchar这种类型也是多余的,因为char(6)就可以很好地完成了任务。同样的如果可以的话,我们应该是用MEDIUMINT而不是BIGINT来定义整形字段。
2、尽量把字段设置为NOT NULL
在可能的情况下,尽量把字段设置为NOT NULL,这样在将来执行查询的时候,数据库不用去比较NULL值。
对于某些文本字段来说,例如“省份”或者“性别”,我们可以将他们定义为ENUM(枚举)类型。因为在MySQL中,ENUM类型被当做数值型数据来处理,而数值型数据被处理起来的速度要比文本类型要快得多。这样我们又可以提高数据库的性能。
3、使用连接(JOIN)来代替子查询(Sub-Queries)
连接(JOIN)之所以更有效率一些,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上 需要两个步骤的查询工作。
4、使用联合(UNION)来代替手动创建的临时表
当我们可以确认不可能出现重复结果集或者不在乎重复结果集的时候尽量使用union all而不是union,因为union和union all的差异主要是前者需要将两个或者多个结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,增大资源消耗及延迟。
8、使用索引
索引是提高数据库性能的常用方法,他可以令数据库服务器比没有索引快得多的速度检索特定的行,尤其是在查询语句当中包含有MAX(),MIN()和ORDERBY这些命令的时候,性能提高更为明显。
那该对那些字段进行索引呢?
一般来说,索引应该建立在那些将用于join,where判断和orderby排序的字段上。尽量不要对数据库中某个含有大量重复的值的字段建立索引,对于一个ENUM类型的字段来说,出现大量重复值是很有可能的情况。在这样的字段上建立索引将不会有什么帮助;相反,还有可能降低数据库的性能
9、优化查询语句
1 、不使用子查询
例:SELECT * FROM t1 WHERE id (SELECT id FROM t2 WHERE name='hechunyang');
子查询在MySQL5.5版本里,内部执行计划器是这样执行的:先查外表再匹配内表,而不是先查内表t2,当外表的数据很大时,查询速度会非常慢。
在MariaDB10/MySQL5.6版本里,采用join关联方式对其进行了优化,这条SQL会自动转换为
SELECT t1.* FROM t1 JOIN t2 ON t1.id = t2.id;
但请注意的是:优化只针对SELECT有效,对UPDATE/DELETE子查询无效,固生产环境应避免使用子查询
2、 避免函数索引
例:
SELECT * FROM t WHERE YEAR(d) >= 2016;
由于MySQL不像Oracle那样支持函数索引,即使d字段有索引,也会直接全表扫描。
应改为—–>
SELECT * FROM t WHERE d >= '2016-01-01';
3 、用IN来替换OR
低效查询
SELECT * FROM t WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30;
—–> 高效查询
SELECT * FROM t WHERE LOC_IN IN (10,20,30);
4.调整Where字句中的连接顺序
MySQL采用从左往右,自上而下的顺序解析where子句。根据这个原理,应将过滤数据多的条件往前放,最快速度缩小结果集。
5. 使用truncate代替delete
当删除全表中记录时,使用delete语句的操作会被记录到undo块中,删除记录也记录binlog,当确认需要删除全表时,会产生很大量的binlog并占用大量的undo数据块,此时既没有很好的效率也占用了大量的资源。
使用truncate替代,不会记录可恢复的信息,数据不能被恢复。也因此使用truncate操作有其极少的资源占用与极快的时间。另外,使用truncate可以回收表的水位,使自增字段值归零。
7、LIKE前缀%号、双百分号、_下划线查询非索引列或*无法使用到索引,如果查询的是索引列则可以
8、读取适当的记录LIMIT M,N,而不要读多余的记录
9、避免数据类型不一致
10、分组统计可以禁止排序sort,总和查询可以禁止排重用union all
11、避免随机取记录
12、禁止不必要的ORDER BY排序
13、批量INSERT插入
14、不要使用NOT等负向查询条件
15、尽量不用select *
16、区分in和exists
Mysql 中 exists 和 in 的区别
下面将主查询的表称为外表;子查询的表称为内表。exists 与 in 的区别如下:
子查询使用 exists,会先进行主查询,将查询到的每行数据循环带入子查询校验是否存在,过滤出整体的返回数据;子查询使用 in,会先进行子查询获取结果集,然后主查询匹配子查询的结果集,返回数据 外表内表相对大小情况不一样时,查询效率不一样:两表大小相当,in 和 exists 差别不大;内表大,用 exists 效率较高;内表小,用 in 效率较高。 不管外表与内表的大小,not exists 的效率一般要高于 not in,跟子查询的索引访问类型有关。
Mysql整个查询的过程
-
客户端向MySQL服务器发送一条查询请求
-
服务器首先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段
-
服务器进行SQL解析、预处理、再由优化器生成对应的执行计划
-
MySQL根据执行计划,调用存储引擎的API来执行查询
-
将结果返回给客户端,同时缓存查询结果
比如InnoDB就是使用的B+Tree。B+Tree中的B是指balance,意为平衡。需要注意的是,B+树索引并不能找到一个给定键值的具体行,它找到的只是被查找数据行所在的页,接着数据库会把页读入到内存,再在内存中进行查找,最后得到要查找的数据。
Mysql的四种隔离级别
read-uncommitted 读未提交:
在该级别,所有的事务都可以看到其他未提交事务的执行结果,本隔离级别很少用于实际应用,因为它的性能不比其他级别好多少。读取未提交的数据,也称之为脏读。
read-committed 读提交内容:
这是大多数数据库系统的默认隔离级别(但不是MYSQL默认的),它满足了隔离的简单定义:一个事务只能看见已提交事务所做的改变。也支持所谓的不可重复读。
repeatable-read 可重读:
是MYSQL默认的,确保统一事务的多个实例在并发读取数据时,会看到同样的数据行。
serializable 可串行化:
这是最高的隔离级别,他通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简而言之,他是在每个读的数据行上加上共享锁。在这个级别可能导致大量的超时现象和锁竞争。
低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
MySQL8 之前使用如下命令查看 MySQL 隔离级别:
SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
MySQL8 开始,通过如下命令查看 MySQL 默认隔离级别:
SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读提交(read-committed) | 否 | 是 | 是 |
可重读(repeatable-read) | 否 | 否 | 是 |
可串行化(serializable) | 否 | 否 | 否 |
1,脏读: 又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改但是还未提交,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改并提交,这就导致了T2所读取到的数据是无效的,值得注意的是,脏读一般是针对于update操作的。
2、不可重复读: 是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据并修改数据。那么,在第一个事务的两次读数据之间。由于另一个事务的修改,那么第一个事务两次读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。
3,幻读: 事务A 按照一定条件进行数据读取, 期间事务B 插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B 新插入的数据称为幻读。
不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
MYSQL三大日志
1、binlog
binlog记录了数据库表结构和表数据变更,以二进制的形式保存在磁盘中;比如update/delete/insert/truncate/create操作。 binlog 是通过追加的方式进行写入的,可以通过max_binlog_size参数设置每个binlog文件的大小,又分为逻辑日志、物理日志,任何存储引擎的 mysql 数据库都会记录binlog日志。
逻辑日志:可以简单理解为记录的就是sql语句 物理日志:mysql 数据最终是保存在数据页的,物理日志记录的就是数据页变更。 主要有两个作用:复制和恢复数据 MySQL在公司使用的时候往往都是一主多从结构的,从服务器需要与主服务器的数据保持一致,这就是通过binlog来实现的 数据库的数据被干掉了,我们可以通过binlog来对数据进行恢复因为binlog记录了数据库表的变更,所以我们可以用binlog进行复制(主从复制)和恢复数据
binlog 记录过程及刷盘时机
binlog 大致记录过程是将所有未提交(uncommitted)的二进制日志写入到 binlog buffer中,等该事务提交(committed)时,然后通过刷盘时机,控制刷入 OS Buffer,控制 fsync() 进行写入 Binlog File 日记文件磁盘的过程。对于 binlog, MYSQL 是通过参数 sync_binlog 参数来控制刷盘时机,取值范围是 0-N:
0: 不去强制要求,由系统自行判断何时写入磁盘
1: 每次事务提交(committed)的时候都要将 binlog 写入磁盘
N: 每提交 N 个事务,才会将 binlog 写入磁盘
binlog在MySQL的server层产生,不属于任何引擎,主要记录用户对数据库操作的SQL语句(除了查询语句)。之所以将binlog称为归档日志,是因为binlog不会像redo log一样擦掉之前的记录循环写,而是一直记录(超过有效期才会被清理),如果超过单日志的最大值(默认1G,可以通过变量 max_binlog_size 设置),则会新起一个文件继续记录。 正是由于binlog有归档的作用,所以binlog主要用作主从同步和数据库基于时间点的还原,或者一些其他功能推送下游系统(如解析binlog推送待办,累计风险保额)。不会记录select,但是update没变化也会记录。 事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。事务提交的时候,执行器把 binlog cache 里的完整事务写入到 binlog 中,并清空 binlog cache。每个线程有自己 binlog cache,但是共用同一份 binlog 文件。 - binlog日志格式
binlog日志有三种格式,分别为STATMENT、ROW和MIXED。
在 MySQL 5.7.7之前,默认的格式是STATEMENT,
MySQL 5.7.7之后,默认值是ROW。
-----------------------------------------------------------------------------------
日志格式通过binlog-format指定。
STATMENT
基于SQL语句的复制(statement-based replication, SBR)。
每一条会修改数据的sql语句会记录到binlog中。
优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO, 从而提高了性能;
缺点:在某些情况下会导致主从数据不一致,比如执行sysdate()、slepp()等。
-----------------------------------------------------------------------------------
ROW
基于行的复制(row-based replication, RBR)。
不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了。
优点:不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题;
缺点:会产生大量的日志,尤其是alter table的时候会让日志暴涨
-----------------------------------------------------------------------------------
MIXED
基于STATMENT和ROW两种模式的混合复制(mixed-based replication, MBR)。
一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog。
2、redo log
InnoDB 和 ACID 模型 事务有 ACID 四个属性, InnoDB 是支持事务的,它实现 ACID 的机制如下:
原子性:innodb的原子性主要是通过提供的事务机制实现,与原子性相关的特性有:Autocommit 设置。 COMMIT 和 ROLLBACK 语句(通过 Undo Log实现)。
一致性:innodb的一致性主要是指保护数据不受系统崩溃影响
隔离性:innodb的隔离性也是主要通过事务机制实现,特别是为事务提供的多种隔离级别
持久性:也就是事务提交成功,那么对数据库做的修改就被永久保存下来,不可能因为任何原因再回到原来的状态 (通过 redo
Log实现)
Redo log 是重做日志,属于 InnoDB储存引擎的日志。是物理日志,日志记录的内容是数据页的更改,这个页"做了什么改动"。
假设我们有一条sql语句:
update user set name='张三' where id = '3'
MySQL执行这条SQL语句,肯定是先把id=3的这条记录查出来,然后将name字段给改掉。这没问题吧?
实际上Mysql的基本存储结构是页(记录都存在页里边),所以MySQL是先把这条记录所在的页找到,然后把该页加载到内存中,将对应记录进行修改。
现在就可能存在一个问题:如果在内存中把数据改了,还没来得及落磁盘,而此时的数据库挂了怎么办?显然这次更改就丢了。
MySQL引入了redo log,内存写完了,然后会写一份redo log,这份redo log记载着这次在某个页上做了什么修改。
其实写redo log的时候,也会有buffer,是先写buffer,再真正落到磁盘中的。至于从buffer什么时候落磁盘,会有配置供我们配置。
写redo log也是需要写磁盘的,但它的好处就是顺序IO(我们都知道顺序IO比随机IO快非常多)。
所以,redo log的存在为了:当我们修改的时候,写完内存了,但数据还没真正写到磁盘的时候。此时我们的数据库挂了,我们可以根据redo log来对数据进行恢复。因为redo log是顺序IO,所以写入的速度很快,并且redo log记载的是物理变化(xxxx页做了xxx修改),文件的体积很小,恢复速度很快。
为什么需要redo log?
事务的四大特性里面有一个是持久性,具体来说就是只要事务提交成功,那么对数据库做的修改就被永久保存下来了,
不可能因为任何原因再回到原来的状态。
那么mysql是如何保证一致性的呢?最简单的做法是在每次事务提交的时候,
将该事务涉及修改的数据页全部刷新到磁盘中。但是这么做会有严重的性能问题,
主要体现在两个方面:
1. 因为Innodb是以页为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,
这个时候将完整的数据页刷到磁盘的话,太浪费资源了!
2. 一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机IO写入性能太差!
因此mysql设计了redo log,
具体来说就是只记录事务对数据页做了哪些修改,
这样就能完美地解决性能问题了(相对而言文件更小并且是顺序IO)。
undo log
undo log主要有两个作用:回滚和多版本控制(MVCC)
在数据修改的时候,不仅记录了redo log,还记录undo log,如果因为某些原因导致事务失败或回滚了,可以用undo log进行回滚
undo log主要存储的也是逻辑日志,比如我们要insert一条数据了,那undo log会记录的一条对应的delete日志。我们要update一条记录时,它会记录一条对应相反的update记录。
这也应该容易理解,毕竟回滚嘛,跟需要修改的操作相反就好,这样就能达到回滚的目的。因为支持回滚操作,所以我们就能保证:“一个事务包含多个操作,这些操作要么全部执行,要么全都不执行”。【原子性】
因为undo log存储着修改之前的数据,相当于一个前版本,MVCC实现的是读写不阻塞,读的时候只要返回前一个版本的数据就行了。
binlog和redo log的区别
存储本质特点
binlog记载的是update/delete/insert这样的SQL语句,而redo log记载的是物理修改的内容(xxxx页修改了xxx)。
所以在搜索资料的时候会有这样的说法:redo log 记录的是数据的物理变化,binlog 记录的是数据的逻辑变化
功能比较
redo log的作用是为持久化而生的。写完内存,如果数据库挂了,那我们可以通过redo log来恢复内存还没来得及刷到磁盘的数据,将redo log加载到内存里边,那内存就能恢复到挂掉之前的数据了。
binlog的作用是复制和恢复而生的。
主从服务器需要保持数据的一致性,通过binlog来同步数据。
如果整个数据库的数据都被删除了,binlog存储着所有的数据变更情况,那么可以通过binlog来对数据进行恢复。
redo log事务开始的时候,就开始记录每次的变更信息,而binlog是在事务提交的时候才记录。
如果写redo log和binlog都成功了,那这次事务算是成功了。
但是如果我写其中的某一个log,失败了,那会怎么办?现在我们的前提是先写redo log,再写binlog,我们来看看:
如果写redo log失败了,那我们就会回滚redo log不再写binlog。
如果写redo log成功了,写binlog,写binlog写一半了,但失败了怎么办?那我们会将无效的binlog给删除(因为binlog会影响从库的数据,所以需要做删除操作)
简单来说:MySQL需要保证redo log和binlog的数据是一致的,如果不一致,那就乱套了。
如果redo log写失败了,而binlog写成功了。那假设内存的数据还没来得及落磁盘,机器就挂掉了。那主从服务器的数据就不一致了。(从服务器通过binlog得到最新的数据,而主服务器由于redo log没有记载,没法恢复数据)
如果redo log写成功了,而binlog写失败了。那从服务器就拿不到最新的数据了。那么如何解决这个问题呢?
MySQL通过两阶段提交来保证redo log和binlog的数据是一致的。
阶段1:InnoDB redo log 写盘,InnoDB 事务进入 prepare (准备)状态 阶段2:binlog 写盘,InooDB 事务进入 commit 状态