压缩索引

MyISAM使用前缀压缩来减少索引的大小,从而让更多的索引可以放入内存中,这在某些情况下能极大地提高性能。默认只压缩字符串,但通过参数设置也可以对整数做压缩。

MyISAM压缩每个索引块的方法是,先完全保存索引块中的第一个值,然后将其他值和第一个值进行比较得到相同前缀的字节数和剩余的不同后缀部分,把这部分存储起来即可。例如,索引块中的第一个值是“perform”,第二个值是“performance”,那么第二个值的前缀压缩后存储的是类似“7,ance”这样的形式。MyISAM对行指针也采用类似的前缀压缩方式。

压缩块使用更少的空间,代价是某些操作可能更慢。因为每个值的压缩前缀都依赖前面的值,所以MyISAM查找时无法在索引块使用二分查找而只能从头开始扫描。正序的扫描速度还不错,但是如果是倒序扫描——例如ORDER BY DESC——就不是很好了。所有在块中查找某一行的操作平均都需要扫描半个索引块。

测试表明,对于CPU密集型应用,因为扫描需要随机查找,压缩索引使得MyISAM在索引查找上要慢好几倍。压缩索引的倒序扫描就更慢了。压缩索引需要在CPU内存资源与磁盘之间做权衡。压缩索引可能只需要十分之一大小的磁盘空间,如果是I/O密集型应用,对某些查询带来的好处会比成本多很多。

可以在CREATE TABLE语句中指定PACK_KEYS参数来控制索引压缩的方式。

冗余和重复索引

MySQL允许在相同列上创建多个索引,无论是有意还是无意的。MySQL需要单独维护重复的索引,并且优化器在优化查询的是时候也需要逐个地进行考虑,这会影响性能。重复索引是指的在相同的列上按照相同的顺序创建的相同类型的索引,应该避免这样创建重复索引,发现以后也应该立即删除。

有时会在不经意间创建了重复索引,例如下面的代码:

CREATE TABLE test(
ID INT NOT NULL PRIMARY KEY,
A INT NOT NULL,
B INT NOT NULL,
UNIQUE(ID),
INDEX(ID),
) ENGINE=InnoDB;

  

一个经验不足的用户可能是想创建一个主键,先加上唯一限制,然后再加上索引以供查询使用。事实上,MySQL的唯一限制和主键限制都是通过索引实现的,因此,上面的写法实际上在相同的列上创建了三个重复的索引。通常并没有理由这样做,除非是在同一列上创建不同类型的索引来满足不同的查询需求。

冗余索引和重复索引有一些不同。如果创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。因此索引(A,B)也可以当做索引(A)来使用(这种冗余只是对B-Tree索引来说的)。但是如果再创建索引(B,A),则不是冗余索引,索引(B)也不是,因为B不是索引(A,B)的最左前缀。另外,其他不同类型的索引(例如哈希索引或者全文索引)也不会是B-Tree索引的冗余索引,而无论覆盖的索引列是什么。

冗余索引通常发生在为表添加新索引的时候。例如,有人可能会增加一个新的索引(A,B)而不是扩展已有的索引(A)。还有一种情况是将一个索引扩展为(A,ID),其中ID是主键,对于InnoDB来说主键列已经包含在二级索引中了,所以这也是冗余的。

大多数情况下都不需要冗余索引,应该尽量扩展已有的索引而不是创建新索引。但也有时候出于性能方面的考虑需要冗余索引,因为扩展已有的索引会导致其变得太大,从而影响其他使用该索引的查询的性能。

例如,如果在整数列上有一个索引,现在需要额外增加一个很长的VARCHAR列来扩展该索引,那性能可能会急剧下降。特别是有查询把这个索引当做覆盖索引,或者这是MyISAM表并且有很多范围查询(由于MyISAM的前缀压缩)的时候。

有一个userinfo表,这个表有1000 000行,对每个state_id值大概有20 000条记录。在state_id列有一个索引对下面的查询有用,假设查询名为Q1:

SELECT count(*) FROM userinfo WHERE state_Id=5;

  

一个简单的测试表明该查询的执行速度大概是每秒115次(QPS)。还有一个相关查询需要检索几个列的值,而不是只统计行数,假设名为Q2:

SELECT state_id,city,address FROM userinfo WHERE state_id=5;

  

对于这个查询,测试结果QPS小于10。提升该查询性能的最简单办法就是扩展索引为(state_id,city,address),让索引能覆盖查询:

ALTER TABLE userinfo DROP KEY state_id, ADD KEY state_id_2(state_id,city,address);

  

索引扩展后,Q2运行得更快了,但是Q1却变慢了。如果我们想让两个查询都变得更快,就需要两个索引,尽管这样一来原来的单列索引是冗余的了。表1-3显示这两个查询在不同索引策略下的详细结果,分别使用MyISAM和InnoDB存储引擎。注意到只有state_id_2索引时,InnoDB引擎上的查询Q1的性能下降并不明显,这是因为InnoDB没有使用索引压缩。

表1-3   使用不同索引策略的SELECT查询的QPS测试结果
  只有state_id 只state_id_2 同时有state_id和state_d_2
MyISAM, Q1 114.96 25.40 112.19
MylSAM, Q2 9.97 16.34 16.37
InnoDB, Q1 108.55 100.33 107.97
InnoDB, Q2 12.12 28.04 28.06

有两个索引的缺点是索引成本更高。表1-4显示了想表中插入100万行数据所需要的时间。

表1-4   在使用不同索引策略时插入100万行数据的速度
  只有state_id 同时有state_id和state_ id_2
InnoDB,对两个索引都有足够的内容 80秒 136秒
MylSAM,只有一个索引有足够的内容 72秒 470秒

可以看到,表中的索引越多插入速度越慢。一般来说,增加新索引将会导致INSERT、UPDATE、DELETE等操作的速度变慢,特别是当新增索引后导致达到了内存瓶颈的时候。

解决冗余索引和重复索引的方法很简单,删除这些索引就可以,但首先要做的是找出这样的索引。可以通过写一些复杂的访问INFORMATION_SCHEMA表的查询来找,不过还有两个更简单的方法。可使用Shlomi Noach的common_schema中的一些视图来定位,common_schema是一系列可以安装到服务器上的常用的存储和视图。这笔自己编写查询要快而且简单。另外也可以使用Percona Toolkit中的pt-duplicate-key-checker,该工具通过分析表结构来找出冗余和重复的索引。对于大型服务器来说,使用外部的工具可能更合适些;如果服务器上有大量的数据或者大量的表,查询INFORMATION_SCHEMA表可能会导致性能问题。

在决定哪些索引可以被删除的时候要非常小心。回忆一下,在前面的InnoDB的示例表中,因为二级索引的叶子节点包含了主键值,所以在列(A)上的索引就相当于在(A,ID)上的索引。如果有像WHERE A = 5 ORDER BY ID这样的查询,这个索引会很有作用。但如果将索引扩展为(A,B),则实际上就变成了(A,B,ID),那么上面查询的ORDER BY子句就无法使用该索引做排序,而只能用文件排序了。所以,建议使用Percona工具箱中的pt-upgrade工具来仔细检查计划中的索引变更。

未使用的索引

除了冗余索引和重复索引,可能还会有一些服务器永远不使用的索引,这样的索引完全是累赘,建议考虑删除,有两个工具可以帮助定位未使用的索引:

  • 在Percona Server或者Mariadb中先打开userstates服务器变量(默认是关闭的),然后让服务器运行一段时间,再通过查询INFORMATION_SCHEMA.INDEX_STATISTICS就能查到每个索引的使用频率。
  • 使用Percona Toolkit中的pt-index-usage工具,该工具可以读取查询日志,并对日志中的每个查询进行EXPLAIN操作,然后打印出关于索引和查询的报告。这个工具不仅可以找出哪些索引是未使用的,还可以了解查询的执行计划——例如在某些情况下有些类似的查询的执行方式不一样,这可以帮助定位到那些偶尔服务器质量差的查询,该工具也可以将结果写入到MySQL的表中,方便查询结果。

索引和锁

索引可以让查询锁定更少的行,如果查询从不访问那些不需要的行,那么就会锁定更少的行,从两个方面来看这对性能都有好处。首先,虽然InnoDB的行锁效率很高,内存使用也很少,但是锁定行的时候仍然会带来额外开销;其次,锁定超过需要的行会增加锁争用并减少并发性。

InnoDB只有在访问行的时候才会对其加锁,而索引能够减少InnoDB访问的行数,从而减少锁的数量。但这只有当InnoDB在存储引擎层能够过滤掉所有不需要的行时才有效。如果索引无法过滤掉无效的行,那么在InnoDB检索到数据并返回给服务器层以后,MySQL服务器才能应用 WHERE子句。这时已经无法避免锁定行了:InnoDB已经锁住这些行,到适当的时候才释放。在MySQL5.1及更新的版本中,InnoDB可以在服务器端过滤掉行后就释放锁。

通过下面的例子再次使用数据库Sakila很好的解释这些情况:

mysql> SET AUTOCOMMIT = 0;
Query OK, 0 rows affected (0.00 sec) mysql> SELECT actor_id FROM actor WHERE actor_id < 5 AND actor_id <> 1 FOR UPDATE;
+----------+
| actor_id |
+----------+
| 2 |
| 3 |
| 4 |
+----------+
3 rows in set (0.01 sec)

  

这条查询只返回2~4行数据,实际上获取1~4行排他锁。InnoDB锁住第1行,因为MySQL为该查询选择的执行计划是索引范围扫描:

mysql> EXPLAIN SELECT actor_id FROM actor WHERE actor_id < 5 AND actor_id <> 1 FOR UPDATE\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: actor
partitions: NULL
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 2
ref: NULL
rows: 4
filtered: 100.00
Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)

  

换句话说,底层存储引擎的操作是“从索引的开头获取满足条件 actor_id < 5 的记录”,服务器并没有告诉InnoDB可以过滤第1行的WHERE 条件。注意到EXPLAIN的Extra出现“Using Where”表示MySQL服务器将存储引擎返回行以后再应用WHERE 过滤条件。

第二个查询就能证明第一行确实已经被锁定(重新开启一个MySQL的控制台),尽管第一个查询的结果并没有这个第一行。保持第一个连接的打开,然后开启第二个连接并执行如下查询:

mysql> SET AUTOCOMMIT = 0;
Query OK, 0 rows affected (0.00 sec) mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec) mysql> SELECT actor_id FROM actor WHERE actor_id = 1 FOR UPDATE;

  

这个查询将会挂起,直到第一个事物释放第一行的锁。这个行为对于基于语句的复制的正常运行来说是必要的。就像这个例子显示的,即使使用了索引,InnoDB可能也会锁住一些不需要的数据。如果不能使用索引查找和锁定行的话问题可能会很糟糕,MySQL会做全表扫描并锁定所有的行,而不管是不是需要。关于InnoDB,索引和锁有一些很少有人知道的细节:InnoDB在二级索引上使用共享锁(读锁),但访问主键索引需要排他锁(写),这消除了使用覆盖索引的可能性,并且使得SELECT FOR UPDATE 比LOCK IN SHARE MODE  或非锁定查询要慢得多。

MySQL之索引(四)的更多相关文章

  1. MySQL基础(四)——索引

    MySQL基础(四)--索引

  2. MYSQL的全表扫描,主键索引(聚集索引、第一索引),非主键索引(非聚集索引、第二索引),覆盖索引四种不同查询的分析

    文章出处:http://inter12.iteye.com/blog/1430144 MYSQL的全表扫描,主键索引(聚集索引.第一索引),非主键索引(非聚集索引.第二索引),覆盖索引四种不同查询的分 ...

  3. 【转】MYSQL数据库四种索引类型的简单使用--MYSQL组合索引“最左前缀”原则

    MYSQL数据库索引类型包括普通索引,唯一索引,主键索引与组合索引,这里对这些索引的做一些简单描述: (1)普通索引 这是最基本的MySQL数据库索引,它没有任何限制.它有以下几种创建方式: 创建索引 ...

  4. MySQL中索引和优化的用法总结

    1.什么是数据库中的索引?索引有什么作用? 引入索引的目的是为了加快查询速度.如果数据量很大,大的查询要从硬盘加载数据到内存当中. 2.InnoDB中的索引原理是怎么样的? InnoDB是Mysql的 ...

  5. mysql高性能索引策略

    转载说明:http://www.nyankosama.com/2014/12/19/high-performance-index/ 1. 引言 随着互联网时代地到来,各种各样的基于互联网的应用和服务进 ...

  6. SQL Server中的联合主键、聚集索引、非聚集索引、mysql 联合索引

    我们都知道在一个表中当需要2列以上才能确定记录的唯一性的时候,就需要用到联合主键,当建立联合主键以后,在查询数据的时候性能就会有很大的提升,不过并不是对联合主键的任何列单独查询的时候性能都会提升,但我 ...

  7. MySQL建立索引的注意事项

    对于大数据量的表格,尤其是百万行以上的数据表,一定要对其建立索引,否则查询速度极慢.(参考后面的测试结果)建立索引时需注意: MySQL的索引有两种:单列索引(即在某一列上建索引).多列组合索引(即在 ...

  8. 手把手教你mysql(十)索引

    手把手教你mysql(十)索引 一:索引的引入 索引定义:索引是由数据库表中一列或者多列组合而成,其作用是提高对表中数据的查询速度. 类似于图书的目录,方便快速定位,寻找指定的内容,如一本1000页的 ...

  9. MySQL(3)-索引

    一.索引类型 在MySQL中,存储引擎使用索引,首先在索引中找到对应值,然后根据匹配的索引记录中找到对应的行. 无论是多么复杂的ORM工具,在精妙和复杂的索引面前都是"浮云".这里 ...

  10. MySQL数据库索引简介

    一.索引的含义和特点     索引是一个单独的.存储在磁盘上的数据库结构,他们包含着对数据表里所有记录的引用指针.使用索引用于快速找出某个或多个列中有一特点值的行,所用MySQL列类型都可以被索引,对 ...

随机推荐

  1. servlet和jsp存值和取值的方式

    在servlet和jsp中存值和取值的方式由两种 1种是setAttribute和getAttribute 2种是c:forEach

  2. agc015E - Mr.Aoki Incubator(dp)

    题意 题目链接 平面上有$n$个点,每个点都有一个位置$x_i$,和向右的速度$v_i$ 现在要求你对其中的一些点进行染色,当一个点被染色后,在无限距离内与它相遇的点也会被染色 问在可能的$2^n$种 ...

  3. mockjs模拟数据请求

    一般项目的方法 <html> <head> <script> <script src="http://requirejs.org/docs/rele ...

  4. Tomcat中配置JAVA_HOME

    一般来说我们在使用Tomcat时配置JAVA_HOME都是采用的系统环境变量直接配置,下面我提供一种新的配置思路. 这里我们使用的是免安装版的apache-tomcat-7.0.35,首先我们安装好j ...

  5. 画报表框架——Echarts.js

    官网:http://echarts.baidu.com/index.html ————————————————————————————————— 先看看我做的第一个柱状图形报表 ——————————— ...

  6. Extjs4.1+desktop+SSH2 定义程序入口

    app.js定义程序入口: MainController.js: 加载控制器: 外部组件引用入口loader.js 时间组件 静态变量组件: 引入comm.js index.jsp 验证打印 comm ...

  7. app之间的跳转和传参问题

    app 之间跳转和传参: 首先 创建2个app   formApp (需要跳转到另外app的项目)     toApp(被跳转的项目) 一:在toApp 项目中的操作: 1:创建URLSchemes ...

  8. CentOS创建不需要输入密码的sudo用户

     linux 怎么让普通用户使用sudo权限不需要输入密码.    在网上看到很多资料,都只说到了在/etc/sudoers添加下列行即可:    username  ALL=(ALL)       ...

  9. iOS中View的创建过程

    ios应用中控制器view的创建方式有三种:storyboard.xib和代码,当APP启动后View的具体加载过程如图(苹果官方): 假设我使用的是WYSViewController控制器 应用启动 ...

  10. ES6 Proxy拦截器详解

    Proxy 拦截器 如有错误,麻烦指正,共同学习 Proxy的原意是"拦截",可以理解为对目标对象的访问和操作之前进行一次拦截.提供了这种机制,所以可以对目标对象进行修改和过滤的操 ...