索引下推(index condition pushdown )简称ICP,在Mysql5.6的版本上推出,用于优化查询。
需求: 查询users表中 "名字第一个字是张,年龄为10岁的所有记录"。
SELECT * FROM users WHERE user_name LIKE '张%' AND user_age = 10;
根据最左前缀法则,该语句在搜索索引树的时候,只能匹配到名字第一个字是‘张’的记录,接下来是怎么处理的呢?当然就是从该记录开始,逐个回表,到主键索引上找出相应的记录,再比对 `age` 这个字段的值是否符合。
在 (name,age) 索引里面特意去掉了 age 的值,这个过程 InnoDB 并不会去看 age 的值,只是按顺序把“name 第一个字是’张’”的记录一条条取出来回表。因此,需要回表 4 次。
MySQL 5.6引入了索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,减少回表次数。
InnoDB 在 (name,age) 索引内部就判断了 age 是否等于 10,对于不等于 10 的记录,直接判断并跳过,减少回表次数.
总结
如果没有索引下推优化(或称ICP优化),当进行索引查询时,首先根据索引来查找记录,然后再根据where条件来过滤记录;
在支持ICP优化后,MySQL会在取出索引的同时,判断是否可以进行where条件过滤再进行索引查询,也就是说提前执行where的部分过滤操作,在某些场景下,可以大大减少回表次数,从而提升整体性能。
自适应Hash索引(Adatptive Hash Index,内部简称AHI)是InnoDB的三大特性之一,还有两个是 Buffer Pool简称BP、双写缓冲区(Doublewrite Buffer)。
1、自适应即我们不需要自己处理,当InnoDB引擎根据查询统计发现某一查询满足hash索引的数据结构特点,就会给其建立一个hash索引;
2、hash索引底层的数据结构是散列表(Hash表),其数据特点就是比较适合在内存中使用,自适应Hash索引存在于InnoDB架构中的缓存中(不存在于磁盘架构中),见下面的InnoDB架构图。
3、自适应hash索引只适合搜索等值的查询,如select * from table where index_col='xxx',而对于其他查找类型,如范围查找,是不能使用的;
当我们在进行等值查询的时候,他会在下面维护一张Hash表。和B+树的叶子节点进行一个关联,这样我们在进行范围查找的时候就会比较快。
InnoDB的自适应Hash索引是默认开启的,可以通过配置下面的参数设置进行关闭。
innodb_adaptive_hash_index = off
like查询为范围查询,%出现在左边,则索引失效。%出现在右边索引未失效.
场景1: 两边都有% 或者 字段左边有%,索引都会失效。
EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom%';
EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom';
场景2: 字段右边有%,索引生效
EXPLAIN SELECT * FROM users WHERE user_name LIKE 'tom%';
解决%出现在左边索引失效的方法,使用覆盖索引
EXPLAIN SELECT user_name FROM users WHERE user_name LIKE '%jack%';
EXPLAIN SELECT user_name,user_age,user_level FROM users WHERE user_name LIKE '%jack%';
对比场景1可以知道, 通过使用覆盖索引 `type = index`,并且 `extra = Using index`,从全表扫描变成了全索引扫描.
总结like 失效的原因:
1. %号在右: 由于B+树的索引顺序,是按照首字母的大小进行排序,%号在右的匹配首字母。所以可以在B+树上进行有序的查找,查找首字母符合要求的数据。
2. %号在左: 是匹配字符串尾部的数据,我们上面说了排序规则,尾部的字母是没有顺序的,所以不能按照索引顺序查询,就用不到索引.
3. 两个%%号: 这个是查询任意位置的字母满足条件即可,只有首字母是进行索引排序的,其他位置的字母都是相对无序的,所以查找任意位置的字母是用不上索引的.
auto_increment的优点:
1. 字段长度较uuid小很多,可以是bigint甚至是int类型,这对检索的性能会有所影响。
2. 在写的方面,因为是自增的,所以主键是趋势自增的,也就是说新增的数据永远在后面,这点对于性能有很大的提升。
3. 数据库自动编号,速度快,而且是增量增长,按顺序存放,对于检索非常有利。
4. 数字型,占用空间小,易排序,在程序中传递也方便。
auto_increment的缺点:
1. 由于是自增,很容易通过网络爬虫知晓当前系统的业务量。
2. 高并发的情况下,竞争自增锁会降低数据库的吞吐能力。
3. 数据迁移或分库分表场景下,自增方式不再适用。
UUID的优点:
1. 不会冲突。进行数据拆分、合并存储的时候,能保证主键全局的唯一性
2. 可以在应用层生成,提高数据库吞吐能力
UUID的缺点:
1. 影响插入速度, 并且造成硬盘使用率低。与自增相比,最大的缺陷就是随机io,下面我们会去具体解释
2. 字符串类型相比整数类型肯定更消耗空间,而且会比整数类型操作慢。
使用自增 id 的内部结构:
自增的主键的值是顺序的,所以 InnoDB 把每一条记录都存储在一条记录的后面。
* 当达到页面的最大填充因子时候(InnoDB 默认的最大填充因子是页大小的 15/16,会留出 1/16 的空间留作以后的修改)。
* 下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费。
* 新插入的行一定会在原有的最大数据行下一行,MySQL 定位和寻址很快,不会为计算新行的位置而做出额外的消耗。减少了页分裂和碎片的产生。
使用 uuid 的索引内部结构
插入UUID: 新的记录可能会插入之前记录的中间,因此需要移动之前的记录
因为 uuid 相对顺序的自增 id 来说是毫无规律可言的,新行的值不一定要比之前的主键的值要大,所以 innodb 无法做到总是把新行插入到索引的最后,而是需要为新行寻找新的合适的位置从而来分配新的空间。
这个过程需要做很多额外的操作,数据的毫无顺序会导致数据分布散乱,将会导致以下的问题:
1. 写入的目标页很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,innodb 在插入之前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机 IO。
2. 因为写入是乱序的,innodb 不得不频繁的做页分裂操作,以便为新的行分配空间,页分裂导致移动大量的数据,一次插入最少需要修改三个页以上。
3. 由于频繁的页分裂,页会变得稀疏并被不规则的填充,最终会导致数据会有碎片。
4. 在把随机值(uuid 和雪花 id)载入到聚簇索引(InnoDB 默认的索引类型)以后,有时候会需要做一次 OPTIMEIZE TABLE 来重建表并优化页的填充,这将又需要一定的时间消耗。
结论:使用 InnoDB 应该尽可能的按主键的自增顺序插入,并且尽可能使用单调的增加的聚簇键的值来插入新行。如果是分库分表场景下,分布式主键ID的生成方案 优先选择雪花算法生成全局唯一主键(雪花算法生成的主键在一定程度上是有序的)。
InnoDB和MyISAM是使用MySQL时最常用的两种引擎类型,我们重点来看下两者区别。
* 事务和外键
InnoDB支持事务和外键,具有安全性和完整性,适合大量insert或update操作
MyISAM不支持事务和外键,它提供高速存储和检索,适合大量的select查询操作
* 锁机制
InnoDB支持行级锁,锁定指定记录。基于索引来加锁实现。
MyISAM支持表级锁,锁定整张表。
* 索引结构
InnoDB使用聚簇索引,索引和记录在一起存储,既缓存索引,也缓存记录。
MyISAM使用非聚簇索引,索引和记录分开。
* 并发处理能力
MyISAM使用表锁,会导致写操作并发率低,读之间并不阻塞,读写阻塞。
InnoDB读写阻塞可以与隔离级别有关,可以采用多版本并发控制(MVCC)来支持高并发
* 存储文件
InnoDB表对应两个文件,一个.frm表结构文件,一个.ibd数据文件。InnoDB表最大支持64TB;
MyISAM表对应三个文件,一个.frm表结构文件,一个MYD表数据文件,一个.MYI索引文件。从MySQL5.0开始默认限制是256TB。
MyISAM 适用场景:
* 不需要事务支持(不支持)
* 并发相对较低(锁定机制问题)
* 数据修改相对较少,以读为主
* 数据一致性要求不高
InnoDB 适用场景
* 需要事务支持(具有较好的事务特性)
* 行级锁定对高并发有很好的适应能力
* 数据更新较为频繁的场景
* 数据一致性要求较高
* 硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,减少磁盘IO
两种引擎该如何选择?
* 是否需要事务?有,InnoDB
* 是否存在并发修改?有,InnoDB
* 是否追求快速查询,且数据修改少?是,MyISAM
* 在绝大多数情况下,推荐使用InnoDB
使用缓存的好处:当MySQL接收到客户端的查询SQL之后,仅仅只需要对其进行相应的权限验证之后,就会通过Query Cache来查找结果,甚至都不需要经过Optimizer模块进行执行计划的分析优化,更不需要发生任何存储引擎的交互.
mysql5.7支持内部缓存,8.0之后已废弃
mysql缓存的限制
1. mysql基本没有手段灵活的管理缓存失效和生效,尤其对于频繁更新的表
2. SQL必须完全一致才会导致cache命中
3. 为了节省内存空间,太大的result set不会被cache (< query_cache_limit);
4. MySQL缓存在分库分表环境下是不起作用的;
5. 执行SQL里有触发器,自定义函数时,MySQL缓存也是不起作用的;
6. 在表的结构或数据发生改变时,基于该表相关cache立即全部失效。
替代方案
* 应用层组织缓存,最简单的是使用redis,ehcached等
在线修改大表的可能影响
* 在线修改大表的表结构执行时间往往不可预估,一般时间较长。
* 由于修改表结构是表级锁,因此在修改表结构时,影响表写入操作。
* 如果长时间的修改表结构,中途修改失败,由于修改表结构是一个事务,因此失败后会还原表结构,在这个过程中表都是锁着不可写入。
* 修改大表结构容易导致数据库CPU、IO等性能消耗,使MySQL服务器性能降低。
* 在线修改大表结构容易导致主从延时,从而影响业务读取。
修改方式:
1. 对表加锁(表此时只读)
2. 复制原表物理结构
3. 修改表的物理结构
4. 把原表数据导入中间表中 ,数据同步完后,锁定中间表,并删除原表
5. rename中间表为原表
6. 刷新数据字典,并释放锁
使用工具: pt-online-schema-change ,是percona推出的一个针对mysql在线ddl的工具。percona是一个mysql分支维护公司,专门提供mysql技术服务的。
binlog日志有三种模式
1)ROW(row-based replication, RBR):日志中会记录每一行数据被修改的情况,然后在slave端对相同的数据进行修改。
* 优点:能清楚记录每一个行数据的修改细节,能完全实现主从数据同步和数据的恢复。而且不会出现某些特定情况下存储过程或function无法被正确复制的问题。
* 缺点:批量操作,会产生大量的日志,尤其是alter table会让日志量暴涨。
2)STATMENT(statement-based replication, SBR):记录每一条修改数据的SQL语句(批量修改时,记录的不是单条SQL语句,而是批量修改的SQL语句事件), slave在复制的时候SQL进程会解析成和原来master端执行过的相同的SQL再次执行。简称SQL语句复制。
* 优点:日志量小,减少磁盘IO,提升存储和恢复速度
* 缺点:在某些情况下会导致主从数据不一致,比如last_insert_id()、now()等函数。
3)MIXED(mixed-based replication, MBR):以上两种模式的混合使用,一般会使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog,MySQL会根据执行的SQL语句选择写入模式。
企业场景如何选择binlog的模式
1. 如果生产中使用MySQL的特殊功能相对少(存储过程、触发器、函数)。选择默认的语句模式,Statement。
2. 如果生产中使用MySQL的特殊功能较多的,可以选择Mixed模式。
3. 如果生产中使用MySQL的特殊功能较多,又希望数据最大化一致,此时最好Row 模式;但是要注意,该模式的binlog日志量增长非常快.
1)redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
2)redo log是物理日志,记录的是“在XXX数据页上做了XXX修改”;binlog是逻辑日志,记录的是原始逻辑,其记录是对应的SQL语句。
3)redo log是循环写的,空间一定会用完,需要write pos和check point搭配;binlog是追加写,写到一定大小会切换到下一个,并不会覆盖以前的日志。
* Redo Log 文件内容是以顺序循环的方式写入文件,写满时则回溯到第一个文件,进行覆盖写。
4)Redo Log作为服务器异常宕机后 事务数据自动恢复使用,Binlog可以作为主从复制和数据恢复使用。Binlog没有自动crash-safe能力
CrashSafe指MySQL服务器宕机重启后,能够保证:
* 所有已经提交的事务的数据仍然存在。
* 所有没有提交的事务的数据自动回滚。
1、主从复制的用途
- 实时灾备,用于故障切换
- 读写分离,提供查询服务
- 备份,避免影响业务
2、主从部署必要条件
- 主库开启binlog日志(设置log-bin参数)
- 主从server-id不同
- 从库服务器能连通主库
3、主从复制的原理
- Mysql 中有一种日志叫做 binlog 日志(二进制日志)。这个日志会记录下所有修改了数据库的SQL 语句(insert,update,delete,create/alter/drop table, grant 等等)。
- 主从复制的原理其实就是把主服务器上的 binlog日志复制到从服务器上执行一遍,这样从服务器上的数据就和主服务器上的数据相同了。
1. 主库db的更新事件(update、insert、delete)被写到binlog。
2. 主库创建一个binlog dump thread,把binlog的内容发送到从库。
3. 从库启动并发起连接,连接到主库。
4. 从库启动之后,创建一个I/O线程,读取主库传过来的binlog内容并写入到relay log。
5. 从库启动之后,创建一个SQL线程,从relay log里面读取内容,执行读取到的更新事件,将更新内容写入到slave的db。
缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统缓冲区( OS Buffer )。因此, redo log buffer 写入 redo logfile 实际上是先写入 OS Buffer,然后再通过系统调用 fsync() 将其刷到 redo log file.
Redo Buffer 持久化到 redo log 的策略,可通过 `Innodb_flush_log_at_trx_commit` 设置:
0 (延迟写) :事务提交时不会将 `redo log buffer`中日志写入到 `os buffer`, 而是每秒写入 `os buffer`并调用 `fsync()`写入到 `redo log file`中。 也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。
1 (实时写,实时刷):事务每次提交都会将 `redo log buffer`中的日志写入 `os buffer`并 调用 `fsync()`刷到 `redo log file`中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。
2 (实时写, 延时刷) :每次提交都仅写入到 `os buffer`,然后是每秒调用 `fsync()`将 `os buffer`中的日志写入到 `redo log file`。
一般建议选择取值2,因为 MySQL 挂了数据没有损失,整个服务器挂了才会损失1秒的事务提交数据。
MySQL中的Undo Log严格的讲不是Log,而是数据,因此他的管理和落盘都跟数据是一样的。