基于MySQL 5.6.16
一次内存访问、SSD 硬盘访问和SATA 硬盘随机访问的时间分别约是_______。 html
A 几微秒,几毫秒,几十毫秒mysql
B 几微秒,几毫秒,几十毫秒git
C 几十纳秒,几十微秒,几十毫秒程序员
D 几十纳秒,几十微秒,十几毫秒
以上腾讯2017实习生题目,答案为C。github
当前互联网时代,性能尤其重要,性能差即意味着不可用。既然内存性能最好,是否能够将数据所有加载在内存中?算法
2018年12月,“美光旗下品牌英睿达(Crucial)宣布已经开始出货自家容量最高、速度最快的服务器级内存,128GBDDR4-4266LRDIMM,一条就要3999美圆,约合人民币2.65万元。”sql
大数据时代,数据随随便便就上T,基本成本、容量等方面考虑,没法将数据所有加载入内存。因为没法所有装入内存,则必然依赖磁盘存储。而内存的读写速度是磁盘的成千上万倍(与具体实现有关),所以,存储的核心问题是“如何减小磁盘读写次数”。数据库
Hash table,也叫散列表, 是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它经过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫作散列函数,存放记录的数组叫作散列表。哈希表最大的优势,就是把数据的存储和查找消耗的时间大大下降,几乎能够当作是常数时间(不考虑hash冲突的状况下,时间复杂度为O(1))。segmentfault
因为hash索引的上述缺点,因此实际使用hash索引的状况不多,MySQL的Memory存储引擎和NDB分布式存储引擎使用了hash结构索引。
当一个数据结构,在支持第一点(顺序存储)的状况下,对第二点(部分匹配)有一个自然的加强:全部前缀同样的值都是按顺序存储在一块儿的,当咱们使用左前缀查询时,从第一个符合前缀条件的值开始扫描,扫到第一个不符合规则的值便可中止,不用扫描所有内容。数组
BST、AVL、RBT很好的将读写次数从O(n)优化到O(log2(n));其中,AVL和RBT都比BST多了自平衡的功能,将读写次数降到最大O(log2(n))。
假设使用自增主键,则主键自己是有序的,树结构的读写次数可以优化到树高(无序的数据会致使插入位置先后的节点移动),树的高度越低读写次数越少;自平衡保证了树结构的稳定。若是想进一步优化,能够引入B/B+树。
示例图:
若是抛开维护操做,那么B树就像一棵“m叉搜索树”(m是子树的最大个数),时间复杂度为O(logm(n))。然而,B树设计了一种高效简单的维护操做,使B树的深度维持在约log(ceil(m/2))(n)~logm(n)之间,大大下降树高。与单纯的算法不一样,磁盘IO次数才是更大的影响因素。B树与AVL的时间复杂度是相同的,但因为B树的层数少,磁盘IO次数少,实践中B树的性能要优于AVL等二叉树,例如上图中查找“10”数据,只须要通过三次磁盘I/O便可。
另外,B树对局部性原理很是友好:
预读:对于每一个文件的第一个读请求,系统读入所请求的页面并读入紧随其后的少数几个页面(X86的Linux中一个标准页面大小是4KB),这时的预读称为同步预读。对于第二次读请求,若是所读页面不在 Cache 中,即不在前次预读的页中,则代表文件访问不是顺序访问,系统继续采用同步预读;若是所读页面在 Cache 中,则代表前次预读命中,操做系统把预读页的大小扩大一倍,此时预读过程是异步的,应用程序能够不等预读完成便可返回,只要后台慢慢读页面便可,这时的预读称为异步预读。任何接下来的读请求都会处于两种状况之一:
数据库系统的设计者巧妙利用了操做系统以上特性,将一个节点的大小设为等于一个页(MySQL InnoDB的页为16KB),新建节点时,InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块(磁盘块为512B)来达到页的大小16KB,保证一个节点物理上也存储在一个页里。在把磁盘数据读入到磁盘时会以页为基本单位,这样每一个节点只须要一次I/O就能够彻底载入。
MySQL 在执行读操做时,会先从数据库的缓冲区中读取,若是不存在与缓冲区中就会尝试从内存中加载页面,若是前面的两个步骤都失败了,最后就只能执行 I/O 从磁盘中获取对应的数据页。
非叶子节点不存储数据,且是叶子节点的索引(稀疏索引)。
下图展现了“叶子节点是否存储数据”状况下,两颗树的差异,为了显示的更直观点,假设每一个节点只够存储两条数据。
能够看到上面那颗树最多须要三次磁盘I/O,而下面的那棵树须要两次。
例子:
新闻文章的内容一般都很长,因此内容字段类型通常都是大文本类型。平时更多的是显示文章列表,若是显示的列表中仅须要展现标题/副标题,而不须要展现内容片断的话,能够将内容字段单独抽出一张表。剥离内容字段后的文章表记录相对小了不少,这样在获取文章列表时也会有比较少的I/O。
为何like只能左匹配?
由于MySQL InnoDB使用原数据格式进行存储(函数索引存的是表中的数据应用函数后获得的数据),加上B+树的有序特性,以下简略图:
若是sql执行“ like '林%' ”,能够在查找到第一个“林”以后,向后一直取得数据,直到遇到第一个非“林”开头到数据,“ like '%1%' ”则不行。
注:
以上列出了了种种B/B+树作索引的优势,不表明B/B+树就是最适合作索引,还有其余适合作索引的数据结构/存储引擎,好比LSM树,二者各有优缺点,适用不一样的场景。
索引能够分为主键索引(聚簇索引),非主键索引(非聚簇索引)。
主键索引:每一个表都有一个索引是存储了全部数据的,这个索引既主键索引,通常创建在主键上,若是表没有主键,则为自增键,或隐藏的Rowid列。
非主键索引:不存储数据,仅存储索引数据和主键值。非惟一索引以“索引信息+主键”来保证键的惟一性。
当sql执行“ where name = 'n' ”(name为非主键索引)时:
索引包含查询中所须要的所有数据列,为覆盖索引。
例如:对于“ SELECT username, age FROM users WHERE username='林' ”, (username, age) 就是该查询的一个覆盖索引。
覆盖索引可以避免“回表”查询。
创建覆盖索引,查询速度能够提高数十倍,甚至上千倍,为何回表查询这么耗时?
HDD
磁盘读取时间
顺序读写和随机读写对于机械硬盘来讲为何性能差别巨大?
SSD
固态驱动器(solid state drives SSDs)没有旋转磁盘设备,所有都是采用闪存。SSD内部维护了一张映射表(Mapping Table),HOST每写入一个Host Page,就会产生一个新的映射关系,这个映射关系会加入(第一次写)或者更改Mapping Table;当读取某个Host Page时, SSD首先查找MappingTable中该Host Page对应的Physical Page,而后再访问Flash读取相应的Host数据。与传统的机械磁盘相比,省去了寻道时间和旋转时间。
SSD内部通常使用NAND Flash来做为存储介质,其逻辑结构以下:
SSD中通常有多个NAND Flash,每一个NAND Flash包含多个Block,每一个Block包含多个Page。因为NAND的特性,其存取都必须以page为单位,即每次读写至少是一个page,一般地,每一个page的大小为4k或者8k。另外,NAND还有一个特性是,其只能是读或写单个page,但不能覆盖写入某个page,必须先要清空里面的内容,再写入。因为清空内容的电压较高,必须是以block为单位。所以,没有空闲的page时,必需要找到没有有效内容的block,先擦写,而后再选择空闲的page写入。
Block中的数据变老或者无效,是指没有任何映射关系指向它们,用户不会访问到这些FLASH空间,它们被新的映射关系所取代。好比有一个Host Page A,开始它存储在FLASH空间的X,映射关系为A->X。后来,HOST重写了该Host Page,因为FLASH不能覆盖写,SSD内部必须寻找一个没有写过的位置写入新的数据,假设为Y,这个时候新的映射关系创建:A->Y,以前的映射关系解除,位置X上的数据变老失效,咱们把这些数据叫垃圾数据。
随着HOST的持续写入,FLASH存储空间慢慢变小,直到耗尽。若是不及时清除这些垃圾数据,HOST就没法写入。SSD内部都有垃圾回收机制,它的基本原理是把几个Block中的有效数据(非垃圾数据,上图中的绿色小方块表示的)集中搬到一个新的Block上面去,而后再把这几个Block擦除掉,这样就产生新的可用Block了。
上图中,Block x上面有效数据为A,B,C,Block y上面有效数据为D,E,F,G,红色方块为无效数据。垃圾回收机制就是先找一个未写过的可用Block z,而后把Block x和Block y的有效数据搬移到Block z上面去,这样Block x和Block y上面就没有任何有效数据,能够擦除变成两个可用的Block。
写:写相同数据量的状况下:
select * from user where name like '林%'
假设按照name索引过滤剩下600条数据。则该索引总共会产生1次随机访问(查找第一个匹配的节点),和599次顺序访问(按着第一个匹配的节点顺序往下查找匹配)。由于该索引中的列并不能知足须要,因此会作“回表”查询,每个索引行都会产生一次随机访问。以上查询总共有601次随机访问和599次顺序访问。
注:MySQL会对特定的查询作优化,如MySQL5.6以后引入MMR,以上假设为未被优化状况。
为何分辨度不高的列(如性别)不适合建索引?
每一次回表都是耗时都随机访问,索引查找加上回表的性能并不会优于全表扫描。MySQL查询优化器也会自动优化成全表扫描。
案例
SELECT * FROM t_terminal WHERE token LIKE 'hw%' LIMIT 1006000, 5
设备表t_terminal数量4800W,token字段为索引字段,符合筛选条件的数据为370W。MySQL版本5.6.29-log。
以上SQL查询耗时3.5秒:
若是优化成:
SELECT * FROM t_terminal t1 INNER JOIN ( SELECT id FROM t_terminal WHERE token LIKE 'hw%' LIMIT 1006000, 5 ) t2 USING (id);
查询耗时0.5秒:子查询中会先筛选出5条limit结果,也就是只有5条数据作回表查询。
有时候排序也会成为性能杀手,例如查出来的结果集很大,对结果集作排序也会耗去很多时间。
按照索引顺序扫描得出的结果天然是有序的,将排序字段加入到索引组中,以免对结果重排序,减小磁盘I/O和内存的使用。
SELECT id, name FROM user WHERE name = '林' ORDER BY age ASC
以上SQL创建组合索引(name, age),利用索引树已经排好序的特性,查询结果无需再次排序。
思考:
SELECT id, name FROM user WHERE city = '深圳' AND name LIKE '林%' ORDER BY age ASC
A(city, name, age)和 B(city, age, name)以上哪一种组合索引更合适?
一个 SQL 查询中同时拥有范围谓词和 ORDER BY 时,咱们可以作的就是在这二者之间作出选择。近几年排序速度已经提高不少,大多数状况下A和B同样快,甚至A比B更快。
可是若是:
那么B会比A快不少(很大的可能,除非符合的数据都排在后面),由于A须要扫描出全部符合name的条件,再按age排序,以后才limit,而B直接找到前100个符合条件的便可。
group by操做在没有合适的索引可用的时候,一般先扫描整个表提取数据并建立一个临时表,而后按照group by指定的列进行排序。在这个临时表里面,对于每个group的数据行来讲是连续在一块儿的。完成排序以后,就能够发现全部的groups,并能够执行汇集函数(aggregate function)。在没有使用索引的时候,须要建立临时表和排序,因此制约group by性能的问题,就是临时表+排序,尽可能减小磁盘排序,减小磁盘临时表的建立,是比较有用的处理办法。在执行计划中一般能够看到“Using temporary; Using filesort”。
CREATE TABLE `t1` ( `c1` int(11) DEFAULT NULL, `c2` int(11) DEFAULT NULL, `c3` int(11) DEFAULT NULL, `c4` int(11) DEFAULT NULL, KEY `idx_g` (`c1`,`c2`,`c3`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; mysql> explain extended select c1,c2 from t1 group by c2 \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: index possible_keys: idx_g key: idx_g key_len: 15 ref: NULL rows: 15441 filtered: 100.00 Extra: Using index; Using temporary; Using filesort
松散索引扫描不须要连续的扫描索引中得每个元组,扫描时仅考虑索引中得一部分。当查询中没有where条件的时候,松散索引扫描读取的索引元组的个数和groups的数量相同。若是where条件包含范围预测,松散索引扫描查找每一个group中第一个知足范围条件,而后再读取最少可能数的keys。
若是查询可以使用松散索引扫描,那么执行计划中Etra中提示“ using index for group-by”。
mysql> explain select c1, min(c2) from t1 group by c1 \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: range possible_keys: idx_g key: idx_g key_len: 10 ref: NULL rows: 15442 Extra: Using index for group-by
松散索引扫描只须要读取不多量的数据就能够完成group by操做,于是执行效率很是高。
松散索引条件:
自从5.5开始,松散索引扫描能够做用于在select list中其它形式的汇集函数,除了min()和max()以外,还支持:
紧凑索引扫描实现 GROUP BY 和松散索引扫描的区别主要在于:紧凑索引扫描须要在扫描索引的时候,读取全部知足条件的索引键(注意,是索引健,不包含索引树内没有的数据),而后再根据读取出的数据来完成 GROUP BY 操做获得相应结果。
若是紧凑索引扫描起做用,那么必须知足:在查询中存在常量相等where条件字段(索引中的字段),且该字段在group by指定的字段的前面或者中间。
紧凑索引扫描一样能够避免额外的排序操做,可是效率低于松散索引。使用紧凑索引扫描,执行计划Extra通常显示“using index”,至关于使用了覆盖索引。
mysql> explain extended select c1,c2 from t1 where c1=2 group by c2 \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ref possible_keys: idx_g key: idx_g key_len: 5 ref: const rows: 5 filtered: 100.00 Extra: Using where; Using index
在MySQL中,MySQL Query Optimizer首先会选择尝试经过松散索引扫描来实现GROUP BY操做,当发现某些状况没法知足松散索引扫描实现GROUP BY的要求以后,才会尝试经过紧凑索引扫描来实现。当MySQL Query Optimizer发现仅仅经过索引扫描并不能直接获得GROUP BY的结果以后,他就不得不选择经过使用临时表而后再排序的方式来实现GROUP BY了。当没法使用索引完成GROUP BY的时候,因为要使用到临时表且须要filesort,因此咱们必需要有足够的sort_buffer_size来供MySQL排序的时候使用,并且尽可能不要进行大结果集的GROUP BY操做,由于若是超出系统设置的临时表大小的时候会出现将临时表数据copy到磁盘上面再进行操做,这时候的排序分组操做性能将是成数量级的降低。此外,GROUP BY若是在没法利用到索引的状况下想避免filesort操做,能够在整个语句最后添加一个以null排序(ORDER BY null)。
DISTINCT实际上和GROUP BY的操做很是类似,只不过是在GROUP BY以后的每组中只取出一条记录而已。因此,DISTINCT的实现和GROUP BY的实现也基本差很少,没有太大的区别。一样能够经过松散索引扫描或者是紧凑索引扫描来实现,固然,在没法仅仅使用索引即能完成DISTINCT的时候,MySQL只能经过临时表来完成。可是和GROUP BY有一点差异的是,DISTINCT并不须要进行排序。也就是说,在仅仅只是DISTINCT操做的Query若是没法仅仅利用索引完成操做的时候,MySQL会利用临时表来作一次数据的“缓存”,可是不会对临时表中的数据进行filesort操做。固然,若是咱们在进行DISTINCT的时候还使用了GROUP BY并进行了分组,并使用了相似于MAX之类的聚合函数操做,就没法避免filesort了。
explain并无真的去执行sql语句从而得出行数,而是进行了某种预估。
增长增长采样数目,必定程度上缓解了有偏的问题,可是不许确仍是存在的。
参考:
《浅谈Mysql的B树索引与索引优化》
《数据库中的索引》
《程序员须要知道的SSD基本原理》
《InnoDB一棵B+树能够存放多少行数据?》
《MySQL松散索引扫描与紧凑索引扫描》
《mysql 原理:explain》
《MySQL order by,group by和distinct原理》