1、不稳定列问题:
磁盘驱动器的负载和DML语句的性能需求决定了表上索引数目的上限。只有当更新频率多余10次/秒的情况下,不稳定列才可能成为问题。(具体数值还应该参考机器配置)
2、系统化的索引设计
2.1、找到由于不合适而导致运行太慢的查询语句
2.2、设计索引,使所有查询语句都运行的足够快。
3、多余的索引设计
完全多余的索引
近乎多余的索引
可能多余的索引
4、新增索引的代价
响应时间
当数据量非常庞大,新增一个索引的代价就是带来表的插入、更新和删除操作缓慢。
原理:当数据库管理系统向表中添加一行,必须在每一个索引上添加相应的行。而在每一个索引上添加一行,插入操作花费的时间至少增加10ms.因为必须从磁盘上读取一个叶子页,当一个事务向一张有10个索引的表里插入1行数据,索引维护使响应时间增加10ms*10=100ms。但是如果插入20行数据则可能181次随机读取,耗费1.8s。因为新的索引行会把表上其中一个索引(一直增大的键值上的索引)添加到同一个叶子页上,从而把其余9个索引添加到20个不同的叶子页上。所以如果表上有10个索引,不建议每秒多余10行的插入操作。
磁盘负载
被修改的叶子页,会异步写入磁盘,虽然不会影响事务的响应时间,但是会增加磁盘负载。每一次磁盘的写入会耗费12ms大约。(一次寻道4ms,两次旋转2*4ms)因此,向磁盘写一个被修改的页所带来的磁盘繁忙度增加24ms.
插入和删除带来的负载相同,更新操作只会影响列值被修改了的索引。
理想情况下,平均磁盘负载不应该超过25%。
在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
B-Tree通常意味着所有的值都是按顺序存储的,井且每一个叶子页到根的距离相同。下图展示了B-Tree索引的抽象表示,大致反映了InnoDB索引是如何工作的。
InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。
这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键,如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。这里以英文字符的ASCII码作为比较准则。聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助,
不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。
用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。
高性能的索引策略
正确的创建和使用索引是实现高性能查询的基础。
索引的字段应该尽可能的小
因为每次查索引本身也是一次IO,较小的索引可以有更好的性能。
索引限制
使用索引的操作符 <,<=,=,>,>=,between, IN
like:‘name%’使用索引,’%name%’不适用索引
扫描的记录数超过30%会进行全表扫描
独立的列
如果查询中的列不是独立的,则mysql就不会使用索引。“独立的列”是指索引列不能是表达式的一部分,也不能是函数的参数。
示例一:索引列不能是表达式的一部分
SELECT item_code FROM cc_item WHERE item_code + 1 = ‘282005’;
示例二:索引列不能是函数的参数
SELECT … WHERE TO_DAYS(CURRENT_DATE()) - TO_DAYS(date_col) <= ‘10’;
前缀索引和索引选择性
有时候需要索引很长的字符列,这会让索引变的大且慢,一个策略是模拟哈希索引。
通常还可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率,但这样也会降低索引的选择性。索引的选择性越高则查询效率越高,因为选择性高的索引可以让mysql在查找时过滤掉更多的行。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
一般情况下某个列前缀的选择性也是足够高的,足以满足查询性能。对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为mysql不允许索引这些列的完整长度。
诀窍在于要选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节约空间)。
计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。
mysql> SELECT COUNT(DISTINCT last_name)/COUNT(*) FROM people;
+------------------------------------+
| COUNT(DISTINCT last_name)/COUNT(*) |
+------------------------------------+
| 0.8462 |
+------------------------------------+
通常来说,示例中如果前缀的选择性能够接近于0.846,基本上就可用了。可以在一个查询中针对不同前缀长度进行计算,这对于大表非常有用。
mysql> SELECT COUNT(DISTINCT LEFT(last_name, 2))/COUNT(*) AS sel2,
-> COUNT(DISTINCT LEFT(last_name, 3))/COUNT(*) AS sel3,
-> COUNT(DISTINCT LEFT(last_name, 4))/COUNT(*) AS sel4,
-> COUNT(DISTINCT LEFT(last_name, 5))/COUNT(*) AS sel5,
-> COUNT(DISTINCT LEFT(last_name, 6))/COUNT(*) AS sel6,
-> COUNT(DISTINCT LEFT(last_name, 7))/COUNT(*) AS sel7,
-> COUNT(DISTINCT LEFT(last_name, 8))/COUNT(*) AS sel8,
-> COUNT(DISTINCT LEFT(last_name, 9))/COUNT(*) AS sel9
-> FROM people;
+--------+--------+--------+--------+--------+--------+--------+--------+
| sel2 | sel3 | sel4 | sel5 | sel6 | sel7 | sel8 | sel9 |
+--------+--------+--------+--------+--------+--------+--------+--------+
| 0.4615 | 0.4615 | 0.7692 | 0.7692 | 0.7692 | 0.7692 | 0.7692 | 0.8462 |
+--------+--------+--------+--------+--------+--------+--------+--------+
只看平均选择性是不够的,也有例外的情况,需要考虑最坏情况下的选择性。如果数据分布很不均匀,可能就会有陷阱。
前缀索引是一种能使索引更小、更快的有效办法,但另一方面也有其缺点:mysql无法使用前缀索引做GROUP BY和ORDER BY,也无法使用前缀索引做覆盖扫描。
多列索引
一个常见的错误是,为每个列创建独立的索引,或者按照错误的顺序创建多列索引。为每个列创建独立索引的策略,一般是听从“把WHERE条件里面的列都建上索引”这种错误建议。
在多个列上建立独立的索引大部分情况下并不能提高mysql的查询性能。mysql 5.0和更新版本引入了一种叫索引合并(index merge)的策略,一定程度上可以使用表上的多个单列索引来定位指定的行,查询能够同时使用这两个单列索引进行扫描,并将结果进行合并,这种算法有三个变种:
OR条件的联合(union)
AND条件的相交(intersection)
组合前两种情况的联合及相交
例如,字段last_name、first_name上各有一个单列索引:
mysql> EXPLAIN SELECT last_name,first_name FROM people WHERE last_name = 'yanzuojing' OR first_name = 'h'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: people
type: ALL
possible_keys: last_name,first_name
key: NULL
key_len: NULL
ref: NULL
rows: 13
Extra: Using where
索引合并策略有时候是一种优化的结果,但实际上更多时候说明了表上的索引建的很糟糕:
当有多个AND条件,通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引
当有多个OR条件,通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上
更重要的是,优化器不会把这些计算到查询成本中,优化器只关心随机页面读取。这会使得查询的成本被低估,导致该执行计划还不如直接走全表扫描
选择合适的索引顺序
正确的索引顺序依赖于使用该索引的查询,并且同时需要考虑如何更好的满足排序和分组的需要。
在一个多列B-Tree索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列,等等。所以,索引可以按照升序或者降序进行扫描,以满足精确符合列顺序的ORDER BY、GROUP BY和DISTINCT等子句的查询需求。
至于如何选择索引的列顺序有一个经验法则:将选择性最高的列放到索引最前列。当不需要考虑排序和分组时,将选择性最高的列放在前面通常是很好的,这时候索引的作用只是用于优化WHERE条件的查找。然而,性能不只是依赖于所有索引列的选择性(整体基数),也和查询条件的具体值有关,也就是和值的分布有关。
SELECT * FROM people WHERE last_name = ‘yanzuojing’ AND first_name = ‘h’;
上述示例是创建一个(last_name, first_name)索引还是应该颠倒顺序?
根据前述经验法则,应该将last_name放到前面,因为对应值的last_name数量更小,但这样查询的结果非常依赖于选定的具体值。如果没有类似的具体查询来运行,最好还是按经验法则来做,因为经验法则考虑的是全局基数和选择性,而不是某个具体查询:
mysql> SELECT COUNT(DISTINCT last_name)/COUNT(*) AS l_select,
-> COUNT(DISTINCT first_name)/COUNT(*) AS f_select,
-> COUNT(*)
-> FROM people;
+----------+----------+----------+
| l_select | f_select | COUNT(*) |
+----------+----------+----------+
| 0.8462 | 0.9231 | 13 |
+----------+----------+----------+
聚簇索引
当表有聚簇索引时,它的数据行实际上存放在索引的叶子叶中。“聚簇”表示数据行和相邻的键值紧凑的存储在一起。
InnoDB通过主键聚集数据,即上图中被索引的列就是主键列。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。
聚集的数据的优点:
可以把相关数据保存在一起
数据访问更快。聚簇索引将索引和数据保存在同一个B-Tree中,因此从聚簇索引中获取数据通常比在非聚簇索引中查找要快
聚簇主键可能对性能有帮助,但也可能导致严重的性能问题。所以需要仔细的考虑聚簇索引,尤其是将表的存储引擎从InnoDB改成其它引擎时。
聚簇索引的缺点:
聚簇数据最大限度的提高了I/O密集型应用的性能,但如果数据全部都放在内存中,则访问的顺序也就没那么重要了,聚簇索引也就没什么优势了
插入速度严重依赖于插入顺序
更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置
基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行时,可能面临页分裂(page split)的问题
聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候
二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列
二级索引访问需要两次索引查找,而不是一次
二级索引叶子结点保存的不是指向行的物理位置的指针,而是行的主键值。这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子结点获得对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了重复的工作:两次B-Tree查找而不是一次,对于InnoDB,自适应哈希索引能够减少这样的重复工作。
聚簇索引的每一个
二级索引叶子结点保存的不是指向行的物理位置的指针,而是行的主键值。这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子结点获得对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了重复的工作:两次B-Tree查找而不是一次,对于InnoDB,自适应哈希索引能够减少这样的重复工作。
聚簇索引的每一个叶子结点都包含了主键值、事务ID、用于事务和多版本控制(MVVC)的回滚指针以及所有的剩余列。
InnoDB按主键顺序插入
如果正在使用InnoDB表并且没有什么数据需要聚集,那么可以定义一个代理键作为主键,这种主键的数据应该和应用无关,最简单的方法是使用AUTO_INCREMENT自增列。这样可以保证数据行是按顺序写入,对于根据主键做关联操作的性能也会更好。
最好避免随机的(不连续且值的分布范围非常大)聚簇索引,特别是对于I/O密集型的应用,它使得聚簇索引的插入变的完全随机,使数据没有任何聚集特性。使用InnoDB时应该尽可能的按主键顺序插入数据,并且尽可能的使用单调增加的聚簇键的值来插入新行。
覆盖索引
如果一个索引包含(或者覆盖)所有需要查询的字段的值,则称之为覆盖索引,优点如下:
索引条目通常远小于数据行大小,所以如果只需要读取索引,mysql就会极大的减少数据访问量
因为索引是按照列值顺序存储(至少在单个页内是如此),所以对于I/O密集型的范围查询会比随机从磁盘读取每一行数据的I/O要少的多
由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别有用。InnoDB的二级索引在叶子结点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询
不是所有类型的索引都可以成为覆盖索引,覆盖索引必须要存储索引列的值,
而哈希索引、空间索引和全文索引等都不存储索引列的值,所以mysql只能使用B-Tree索引做覆盖索引。
InnoDB的二级索引的叶子结点都包含了主键的值,这意味着InnoDB的二级索引可以有效的利用这些“额外”的主键列来覆盖查询。
使用索引扫描做排序
mysql有两种方式可以生成有序的结果:通过排序操作,或者按索引顺序扫描。如果EXPLAIN中的type列的值为index,则表明mysql使用了索引扫描来做排序。
扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就都回表查询一次对应的行。这基本上都是随机I/O,因此按索引顺序读取的速度要比顺序的全表扫描慢。
有一种情况下ORDER BY子句可以不满足索引的最左前缀的要求,就是前导列为常量的时候,示例如下,
KEY last_name (first_name,last_name) USING BTREE:
SELECT dob,address FROM people WHERE first_name = ‘m’ ORDER BY last_name\G
冗余索引
如果创建了索引(A, B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。
mysql的唯一限制和主键限制都是通过索引实现。
大多数情况下都不需要冗余索引,应该尽量扩展已有的索引而不是创建新索引。但也有时候出于性能方面的考虑需要冗余索引,因为扩展已有的索引会导致其变的太大,从而影响其他使用该索引的查询的性能。
一般来说,增加新索引将会导致INSERT、UPDATE、DELETE等操作的速度变慢,特别是当新增索引后导致达到了内存瓶颈的时候。
索引和锁
索引可以让查询锁定更少的行。如果查询从不访问那些不需要的行,那么就会锁定更少的行,从两个方面来看这对性能都有好处:
首先,虽然InnoDB的行锁效率很高,内存使用也很少,但是锁定行的时候仍然会带来额外开销
其次,锁定超过需要的行会增加锁争用并减少并发性
在mysql 5.1和更新的版本中,InnoDB可以在服务器端过滤掉行后就释放锁。
InnoDB在二级索引上使用共享锁(读锁),但访问主键索引需要排他锁(写锁)。这消除了使用覆盖索引的可能性,并且使得SELECT … FOR UPDATE 比 LOCK IN SHARE MODE或非锁定查询要慢的多。
什么是三星索引?
实例:
select A,B,C,D from user where A=“xx” and B = “xx” order by C;
比如A的选择性为 0.01%
B的选择性为 0.1%
最佳的索引是建复合索引 (A, B, C, D) ,这是一个三星索引。
第一颗星:
过滤尽可能多的行,这意味着把选择性高的索引放在前面A、B。
减少索引片的大小以减少需要扫描的数据行
第二颗星:
也就是说,当经过了A,B的筛选之后,筛选出来的行本身就是已C排序的。
避免排序,减少磁盘 IO 和内存的使用;
第三颗星:
通过宽索引实现索引覆盖。
避免每一个索引对应的数据行都需要进行一次随机 IO 从聚集索引中读取剩余的数据;
SELECT id, name, age FROM users
WHERE age BETWEEN 18 AND 21
AND city = “Beijing”
ORDER BY name;
在上述查询中,我们总可以通过增加索引中的列以获得第三颗星,但是如果我们想要获得第一颗星就需要最小化索引片的大小,这时索引的前缀必须为 (city, age),在这时再想获得第三颗星就不可能了,哪怕在 age 的后面添加索引列 name,也会因为 name 在范围索引列 age 后面必须进行一次排序操作,最终得到的索引就是 (city, age, name, id):如果我们需要在内存中避免排序的话,就需要交换 age 和 name 的位置了,在这时就可以得到索引 (city, name, age, id),当一个 SQL 查询中同时拥有范围谓词和 ORDER BY 时,无论如何我们都是没有办法获得一个三星索引的,我们能够做的就是在这两者之间做出选择,是牺牲第一颗星还是第二颗星。
实际上大多数时候,我们更偏爱第一颗星。我们希望减少需要扫描的数据行。
查询的消耗到底在什么地方
只有搞清上面的问题就可以更好的分析我们的索引。
索引常被加载到mysql缓存池 或者 磁盘的读取缓存中。所以查索引的时候,一般都是在内存中查,这个速度是比较快的,大概10ms~20ms。
如果索引没有被载到内存里,也是我们接下来的QUBE(快速上限估算法中考虑的情况)。那么我们就要考虑查询索引时的 随机IO以及顺序IO
如果没有索引覆盖,就得再算上查询表的 随机IO以及顺序IO
以及fetch数据时的时间消耗,
每次随机I/O大概耗时 10ms
每次顺序I/O只有 0.01ms
如果数据页大小为4kb,每fetch一页,大概耗时0.1ms (4kb/40M)
优化查询和索引
这样一来,就清楚我们的目标了。
1、减小扫描的数据量,最好就是实现索引覆盖,不用再去聚簇索引里查了。
这样可以减少 fetch的耗时,以及扫描时的I/O消耗
2、尽量减少随机I/O
每次随机I/O的消耗是比较大的。
这也是聚簇索引性能好的原因,当结果集是连续的时候,基本只需要顺序I/O。或者是只在跳跃的时候需要随机I/O。
但是像Myisam这种非聚簇,索引的叶节点存的是指针,对表的访问就都会变成随机I/O。
参考:
MySQL 索引性能分析概要
《数据库索引设计与优化》
顺序读取的术语包括----顺序预读,多块I/O,多重顺序前读。
自动跳跃式顺序读:
如果一系列不连续的行被按照同一个方向扫描,那么访问模式是跳跃式顺序的。(由于顺序或者几乎顺序访问索引或表页,则DBMS就可以动态预读提前读取几页)
比如列表预读。下图中由于需要访问满足条件的索引行,然后按照表页的顺序对其进行排序后再访问表行。而左边则代表了操作顺序,从109-》103-》101
数据块预读
当表和索引行的访问顺序不一致时候,oracle会使用数据块预读这一特性。DBMS会先从索引片上收集指针,然后进行多重随机I/O来并行读取表行。
辅助式顺序读
当扫描大表时候,优化器可能会选择开启并行机制。将一个游标拆分为多个用范围谓词限定的游标,每一个游标扫描一个索引片。
注意:对于select语句,在解析查询之前,服务器会先检查查询缓存,如果能够在其中找到对应的查询,服务器就不再执行查询解析,优化和执行的整个过程,而是直接返回查询缓存中的结果集。
query优化的核心是:
query必须配合索引使用,其实mysql无论是什么样的优化手段,核心就是减少磁盘的IO,尤其是减少随机IO。
在能查出正确的结果的前提下,我们的query要尽可能地扫描更少的行,尽可能地顺序扫描。
利用explain进行query优化
如果是oracle数据库的话,则需要使用explain plan或者show plan或者explain来监控执行计划。
Explain 是在优化Query 时最直接有效的验证我们想法的工具。
一个利用索引覆盖进行优化的例子:
Explain具体示例:
使用Profiling进行query优化
MySQL 的Query Profiler 是一个使用非常方便的Query 诊断分析工具,通过该工具可以获取一条 Query 在整个执行过程中多种资源的消耗情况,如CPU,IO,IPC,SWAP 等,以及发生的PAGE FAULTS, CONTEXT SWITCHE 等等,同时还能得到该Query 执行过程中MySQL 所调用的各个函数在源文件中的位 置。
下面我们看看Query Profiler 的具体用法。
query优化的其他技巧
索引设计依赖谓词表达式(条件表达式或者真值表达式----where后面的语句)
尽量避免Join
数据适当的冗余
水平切分
比如按照某个字段取余。
不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;
应用程序端整体架构改动相对较少;
事务处理相对简单;
只要切分规则能够定义好,较难遇到扩展性限制;
max_connections:
是指MySQL服务实例能够同时接受的的最大并发连接数。MySQL实际上支持最大连接数加一的算法,保障当连接数用完的时候,超级管理员依然可以和服务端建立连接,进行管理。
max_user_connections:
设置指定账号的最大并发连接数。
max_connect_errors:
当某台非法主机恶意连接MySQL服务端,遭到的错误达到设置值后,MySQL会解决来自该主机的所有连接。但执行flush hosts后会清零。
查看mysql连接状态
Connection_errors_max_connections
当MySQL的最大并发数大于系统变量(show variables)中max_connections的最大并发数,因此而被拒绝的次数,将会记录在这个变量里。如果Connection_error_max_connections值比较大,则说明当前系统并发比较高,要考虑调大max_connections的值。
Connections
表示MySQL从启动至今,成功建立连接的连接数,这个值是不断累加的。
Max_used_connections
表示MySQL从启动至今,同一时刻并发的连接数,取得是最大值。如果这个值大于 max_connections则表明系统经常处于高并发的状态,应该考虑调大最大并发连接数。
连接线程参数
thread_cache_size
设置连接线程缓存的数目。这个缓存相当于MySQL线程的缓存池(thread cache pool),将空闲的连接线程放入连接池中缓存起来,而非立即销毁。当有新的连接请求时,如果连接池中有空闲的连接,则直接使用。否则要重新创建线程。创建线程是一个不小的系统开销。MySQL的这部分线程处理和Nginx 的线程处理有异曲同工之妙,以后介绍Nginx的线程处理时,会拿来做对比。
thread_handling
默认值是: one-thread-per-connection 表示为每个连接提供或者创建一个线程来处理请求,直至请求完毕,连接销毁或者存入缓存池。当值是no-threads 时,表示在始终只提供一个线程来处理连接,一般是单机做测试使用的。
thread_stack
stack 是堆的意思,进程和线程都是有唯一的ID的,进程的ID系统会维护线程的ID, 则由具体的线程库区维护,当进程或者线程休眠的时候,进程的上下文信息要在内存中开辟出一块区域,保存进程的上下文信息,以便于迅速唤醒程序。默认为MySQL的每个线程设置的堆栈大小为:262144/1024=256k
Thread_cached
当前线程池的线程数
Thread_connected
当前的连接数
Thread_cached:
当前连接线程创建数, 如果这个值过高,可以调整threadcachesize 也就是调整线程缓存池的大小。
Thred_runnint:
当前活跃的线程数。
连接请求堆栈
MySQL在很短的时间内,突然收到很多的连接请求时,MySQL会将不能来得及处理的连接请求保存在堆栈中,以便MySQL后续处理。back_log参数设置了堆栈的大小,可以通过如下命令查看:
Aborted_clients
MySQL 客户机被异常关闭的次数。
Aborted_connects
试图连接到MySQL服务器而失败的连接次数。
mysql连接优化
对于mysql服务器最大连接数值的设置范围比较理想的是:
Max_used_connections / max_connections 在10%以上
如果在10%以下,说明mysql服务器的max_connections设置过高open-files-limit
ini/cnf 参数: open-files-limit
mysql 变量: open_files_limit
全局变量,不可动态调整,取值范围 0到65535。
open_files_limit指mysql能够打开的文件句柄数。该值不足时,会引发 Too many open files错误。具体需要多少个文件句柄,还需要根据 max_connections 和 table_open_cache来计算。
open-files-limit可能受到操作系统的限制, 值不能大于 ulimit -n
上面的配置,是OS限制各个用户能够打开的文件描述符限制(hard soft区别参看man ulimit),新增上面两行,表示mysql用户能够打开65535个文件描述符。
max_connections设置过高有什么坏处?
需要考虑使用的平台所支持的线程库数量(windows只能支持到2048)、服务器的配置(特别是内存大小)、每个连接占用资源(内存和负载)的多少
配置MySQL时该如何估算内存的消耗。那么该使用什么公式来计算呢?
关心内存怎么使用的原因是可以理解的。如果配置MySQL服务器使用太少的内存会导致性能不是最优的;如果配置了太多的内存则会导致崩溃,无法执行查询或者导致交换操作严重变慢。在现在的32位平台下,仍有可能把所有的地址空间都用完了,因此需要监视着。
话虽如此,但我并不觉得找到什么可以计算内存使用的秘诀公式就能很好地解决这个问题。原因有 – 如今这个公式已经很复杂了,更重要的是,通过它计算得到的值只是“理论可能”并不是真正消耗的值。事实上,有8GB内存的常规服务器经常能运行到最大的理论值 – 100GB甚至更高。此外,你轻易不会使用到“超额因素” – 它实际上依赖于应用以及配置。一些应用可能需要理论内存的 10% 而有些仅需 1%。
那么,我们可以做什么呢?首先,来看看那些在启动时就需要分配并且总是存在的全局缓冲 – key_buffer_size,
innodb_buffer_pool_size,
innodb_additional_memory_pool_size,
innodb_log_buffer_size,
query_cache_size。
如果你大量地使用MyISAM表,那么你也可以增加操作系统的缓存空间使得MySQL也能用得着。把这些也都加到操作系统和应用程序所需的内存值之中,可能需要增加32MB甚至更多的内存给MySQL服务器代码以及各种不同的小静态缓冲。这些就是你需要考虑的在MySQL服务器启动时所需的内存。其他剩下的内存用于连接。例如有8GB内存的服务器,可能监听所有的服务就用了6GB的内存,剩下的2GB内存则留下来给线程使用。
每个连接到MySQL服务器的线程都需要有自己的缓冲。大概需要立刻分配256K,甚至在线程空闲时 – 它们使用默认的线程堆栈,网络缓存等。事务开始之后,则需要增加更多的空间。运行较小的查询可能仅给指定的线程增加少量的内存消耗,然而如果对数据表做复杂的操作例如扫描、排序或者需要临时表,则需分配大约 read_buffer_size, sort_buffer_size, read_rnd_buffer_size, tmp_table_size 大小的内存空间。不过它们只是在需要的时候才分配,并且在那些操作做完之后就释放了。有的是立刻分配成单独的组块,例如 tmp_table_size 可能高达MySQL所能分配给这个操作的最大内存空间了。注意,这里需要考虑的不只有一点 – 可能会分配多个同一种类型的缓存,例如用来处理子查询。一些特殊的查询的内存使用量可能更大 – 如果在MyISAM表上做成批的插入时需要分配 bulk_insert_buffer_size 大小的内存。执行 ALTER TABLE, OPTIMIZE TABLE, REPAIR TABLE 命令时需要分配 myisam_sort_buffer_size 大小的内存。
只有简单查询OLTP应用的内存消耗经常是使用默认缓冲的每个线程小于1MB,除非需要使用复杂的查询否则无需增加每个线程的缓冲大小。使用1MB的缓冲来对10行记录进行排序和用16MB的缓冲基本是一样快的(实际上16MB可能会更慢,不过这是其他方面的事了)。
另外,就是找出MySQL服务器内存消耗的峰值。这很容易就能计算出操作系统所需的内存、文件缓存以及其他应用。在32位环境下,还需要考虑到32位的限制,限制 “mysqld” 的值大约为2.5G(实际上还要考虑到很多其他因素)。现在运行 “ps aux” 命令来查看 VSZ 的值 – MySQL 进程分配的虚拟内存。也可以查看 “Resident Memory” 的值,不过我想它可能没多大用处,因为它会由于交换而变小 – 这并不是你想看到的。监视着内存变化的值,就能知道是需要增加/减少当前的内存值了
可能有的人想说,我们想要让服务器能保证100%不会耗尽内存,不管决定用什么样的查询、什么样的用户。很不幸,这其实很不明智也不可能,因为:
以下是很少考虑的MySQL服务器内存需求
每个线程可能会不止一次需要分配缓冲。 考虑到例如子查询 – 每层都需要有自己的
read_buffer,
sort_buffer,
tmp_table_size 等。
在每个连接中很多变量都可能需要重新设置。 如果开发者想设定自己的变量值来运行某些查询就不能继续使用全局值。
可能有多个索引缓存。 为了配合执行查询可能会创建多个索引缓存。
解析查询和优化都需要内存。 这些内存通常比较小,可以忽略,不过如果是某些查询在这个步骤中则需要大量内存,尤其是那些设计的比较特别的查询。
存储过程。 复杂的存储过程可能会需要大量内存。
准备查询语句以及游标。 单次链接可能会有很多的准备好的语句以及游标。它们的数量最后可以限定,但是仍然会消耗大量的内存。
Innodb表缓存。 Innnodb表有自己的缓存,它保存了从一开始访问每个表的元数据。它们从未被清除过,如果有很多Innodb表的话,那么这个量就很大了。这也就意味着拥有 CREATE TABLE 权限的用户就可能把MySQL服务器的内存耗尽。
MyISAM缓冲。 MyISAM表可能会分配一个足以装下指定表最大记录的缓冲,而且这个缓冲直到表关闭了才释放。
Blobs可能需要3倍的内存。 这在处理很大(max_allowed_packet 的值较大)的Blobs数据时很重要,如果处理256MB的数据可能需要768MB的内存。
存储引擎。 通常情况下,存储引擎会设置自己的每个线程的全局分配内存,它通常不能像缓存一样可以调节。现在应该通过各种方式来特别关注MySQL释放出来的存储引擎。
我想这还不是完成的列表,相反地,我觉得还是漏掉了一些(如果你知道,请给我回复加上)。但主要的原因是 – 找到每次内存消耗峰值是不切实际的,因此我的这些建议可以用来衡量一下你实际修改一些变量值产生的反应。例如,把 sort_buffer_size 从1MB增加到4MB并且在 max_connections 为 1000 的情况下,内存消耗增长峰值并不是你所计算的3000MB而是30MB。
小结:
每个连接到MySQL服务器的线程都需要有自己的缓冲。大概需要立刻分配256K,甚至在线程空闲时 – 它们使用默认的线程堆栈,网络缓存等。事务开始之后,则需要增加更多的空间。运行较小的查询可能仅给指定的线程增加少量的内存消耗,然而如果对数据表做复杂的操作例如扫描、排序或者需要临时表,则需分配大约 read_buffer_size, sort_buffer_size, read_rnd_buffer_size, tmp_table_size 大小的内存空间。不过它们只是在需要的时候才分配,并且在那些操作做完之后就释放了。有的是立刻分配成单独的组块,例如 tmp_table_size 可能高达MySQL所能分配给这个操作的最大内存空间了。注意,这里需要考虑的不只有一点 – 可能会分配多个同一种类型的缓存,例如用来处理子查询。一些特殊的查询的内存使用量可能更大 – 如果在MyISAM表上做成批的插入时需要分配 bulk_insert_buffer_size 大小的内存。执行 ALTER TABLE, OPTIMIZE TABLE, REPAIR TABLE 命令时需要分配 myisam_sort_buffer_size 大小的内存。
Query cache 作用于整个 MySQL,主要用来缓存 MySQL 中的查询结果集,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。
优化查询缓存配置
通过以下命令查看缓存相关变量
show variables like ‘%query_cache%’;
参数解释:
have_query_cache :
表示此版本mysql是否支持缓存
query_cache_limit :
缓存最大值
query_cache_size :
缓存大小
query_cache_type :off 表示不缓存,on表示缓存所有结果
开启缓存方式:在my.ini 文件底部添加:
query_cache_type = 1
如果query_cache_size 值为0 ,也需要在my.ini中设置为需要的值。
通过以下命令查看查询缓存状态
show status like ‘%Qcache%’;
Qcache_free_blocks:
目前还处于空闲状态的 Query Cache 中内存 Block 数目
Qcache_free_memory:
目前还处于空闲状态的 Query Cache 内存总量
Qcache_hits:
Query Cache 命中次数
Qcache_inserts:
向 Query Cache 中插入新的 Query Cache的次数,也就是没有命中的次数
Qcache_lowmem_prunes:
当 Query Cache 内存容量不够,需要从中删除老的 Query Cache以给新的 Cache 对象使用的次数
Qcache_not_cached:
没有被 Cache 的 SQL 数,包括无法被 Cache 的 SQL 以及由于query_cache_type 设置的不会被 Cache 的 SQL
Qcache_queries_in_cache:
目前在 Query Cache 中的 SQL 数量
Qcache_total_blocks:
Query Cache 中总的 Block 数量
检查换成命中情况
查询缓存命中率的计算公式是
Qcache_hits/(Qcache_hits+Com_select)。
查询缓存命中率
(Qcache_hits - Qcache_inserts) / Qcache_hits * 100%
由于日志的记录带来的直接性能损耗就是数据库系统中最为昂贵的IO资源。
MySQL的日志包括错误日志(ErrorLog),二进制日志(Binlog),查询日志(QueryLog),慢查询日志(SlowQueryLog)等。
在默认情况下,系统仅仅打开错误日志,关闭了其他所有日志,以达到尽可能减少IO损耗提高系统性能的目的。但是在一般稍微重要一点的实际应用场景中,都至少需要打开二进制日志,因为这是MySQL很多存储引擎进行增量备份的基础,也是MySQL实现复制的基本条件。
binlog日志性能优化
“binlog_cache_size":
在事务过程中容纳二进制日志SQL语句的缓存大小。每个Client都可以分配设置大小的binlogcache空间。如果系统中存在很大较大的事务,可以尝试增加该值的大小,以获得更有的性能。
通过MySQL的以下两个状态变量来判断当前的binlog_cache_size的状况:Binlog_cache_use和Binlog_cache_disk_use。
“max_binlog_cache_size”:
和"binlog_cache_size"相对应,但是所代表的是binlog能够使用的最大cache内存大小。当我们执行多语句事务的时候,max_binlog_cache_size如果不够大的话,系统可能会报出“Multi-statementtransactionrequiredmorethan’max_binlog_cache_size’bytesofstorage”的错误。
“max_binlog_size”:
Binlog日志最大值,一般来说设置为512M或者1G,但不能超过1G。该大小并不能非常严格控制Binlog大小,尤其是当到达Binlog比较靠近尾部而又遇到一个较大事务的时候,系统为了保证事务的完整性,不可能做切换日志的动作,只能将该事务的所有SQL都记录进入当前日志,直到该事务结束。
“sync_binlog”:
这个参数是对于MySQL系统来说是至关重要的,他不仅影响到Binlog对MySQL所带来的性能损耗,而且还影响到MySQL中数据的完整性。
sync_binlog=0: 当事务提交之后,MySQL不做fsync之类的磁盘同步指令刷新binlog_cache中的信息到磁盘,而让Filesystem自行决定什么时候来做同步,或者cache满了之后才同步到磁盘。
sync_binlog=n,当每进行n次事务提交之后,MySQL将进行一次fsync之类的磁盘同步指令来将binlog_cache中的数据强制写入磁盘。
在MySQL中系统默认的设置是sync_binlog=0,也就是不做任何强制性的磁盘刷新指令,这时候的性能是最好的,但是风险也是最大的。因为一旦系统Crash,在binlog_cache中的所有binlog信息都会被丢失。
而当设置为“1”的时候,是最安全但是性能损耗最大的设置。因为当设置为1的时候,即使系统Crash,也最多丢失binlog_cache中未完成的一个事务,对实际数据没有任何实质性影响。从以往经验和相关测试来看,对于高并发事务的系统来说,“sync_binlog”设置为0和设置为1的系统写入性能差距可能高达5倍甚至更多。
大家都知道,MySQL的复制(Replication),实际上就是通过将Master端的Binlog通过利用IO线程通过网络复制到Slave端,然后再通过SQL线程解析Binlog中的日志再应用到数据库中来实现的。所以,Binlog量的大小对IO线程以及Msater和Slave端之间的网络都会产生直接的影响。
MySQL中Binlog的产生量是没办法改变的,只要我们的Query改变了数据库中的数据,那么就必须将该Query所对应的Event记录到Binlog中。那我们是不是就没有办法优化复制了呢?当然不是,在MySQL复制环境中,实际上是是有8个参数可以让我们控制需要复制或者需要忽略而不进行复制的DB或者Table的,分别为:
Binlog_Do_DB:设定哪些数据库(Schema)需要记录Binlog;
Binlog_Ignore_DB:设定哪些数据库(Schema)不要记录Binlog;
Replicate_Do_DB:设定需要复制的数据库(Schema),多个DB用逗号(“,”)分隔;
Replicate_Ignore_DB:设定可以忽略的数据库(Schema);
Replicate_Do_Table:设定需要复制的Table;
Replicate_Ignore_Table:设定可以忽略的Table;
Replicate_Wild_Do_Table:功能同Replicate_Do_Table,但可以带通配符来进行设置;
Replicate_Wild_Ignore_Table:功能同Replicate_Ignore_Table,可带通配符设置;
通过上面这八个参数,我们就可以非常方便按照实际需求,控制从Master端到Slave端的Binlog量尽可能的少,从而减小Master端到Slave端的网络流量,减少IO线程的IO量,还能减少SQL线程的解析与应用SQL的数量,最终达到改善Slave上的数据延时问题。
实际上,上面这八个参数中的前面两个是设置在Master端的,而后面六个参数则是设置在Slave端的。虽然前面两个参数和后面六个参数在功能上并没有非常直接的关系,但是对于优化MySQL的Replication来说都可以启到相似的功能。当然也有一定的区别,其主要区别如下:
如果在Master端设置前面两个参数,不仅仅会让Master端的Binlog记录所带来的IO量减少,还会让Master端的IO线程就可以减少Binlog的读取量,传递给Slave端的IO线程的Binlog量自然就会较少。这样做的好处是可以减少网络IO,减少Slave端IO线程的IO量,减少Slave端的SQL线程的工作量,从而最大幅度的优化复制性能。
而如果是在Slave端设置后面的六个参数,在性能优化方面可能比在Master端要稍微逊色一点,因为不管是需要还是不需要复制的Event都被会被IO线程读取到Slave端,这样不仅仅增加了网络IO量,也给Slave端的IO线程增加了RelayLog的写入量。但是仍然可以减少Slave的SQL线程在Slave端的日志应用量。
虽然性能方面稍有逊色,但是在Slave端设置复制过滤机制,可以保证不会出现因为默认Schema的问题而造成Slave和Master数据不一致或者复制出错的问题。
Slow Query Log 相关参数及使用建议
有些时候,我们为了定位系统中效率比较地下的Query语句,则需要打开慢查询日志,也就是SlowQueryLog。
mysql> show variables like 'log_slow%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| log_slow_queries | ON |
+------------------+-------+
1 row in set (0.00 sec)
mysql> show variables like 'long_query%';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| long_query_time | 1 |
+-----------------+-------+
1 row in set (0.01 sec)
“log_slow_queries”
参数显示了系统是否已经打开SlowQueryLog功能,而“long_query_time”参数则告诉我们当前系统设置的SlowQuery记录执行时间超过多长的Query。
打开SlowQueryLog功能对系统性能的整体影响没有Binlog那么大,毕竟SlowQueryLog的数据量比较小,带来的IO损耗也就较小,但是,系统需要计算每一条Query的执行时间,所以消耗总是会有一些的,主要是CPU方面的消耗。如果大家的系统在CPU资源足够丰富的时候,可以不必在乎这一点点损耗,毕竟他可能会给我们带来更大性能优化的收获。但如果我们的CPU资源也比较紧张的时候,也完全可以在大部分时候关闭该功能,而只需要间断性的打开SlowQueryLog功能来定位可能存在的慢查询。
MySQL的其他日志由于使用很少(QueryLog)或者性能影响很少,我们就不在此过多分析了,至于各个存储引擎相关的日志,我们留在后面“常用存储引擎优化”部分再做相应的分析。
InnoDB 存储引擎和 MyISAM 存储引擎最大区别主要有 4点:
缓存机制;
事务支持;
锁定实现;
数据存储方式差异;
InnoDB 缓存相关优化
innodb_buffer_pool_size 的合适设置
InnoDB 不仅仅缓存索引,同时还会缓存实际的数据。所以, InnoDB 存储引擎可以使用更多的内存来缓存数据库的相关信息。
innodb_buffer_pool_size 参数用来设置 InnoDB 最主要的 Buffer(InnoDB Buffer Pool)的大小,缓存用户表及索引数据的最主要缓存空间,对 InnoDB 整体性能影响也最大。
生产环境 InnoDB 存储引擎哟啊根据 Buffer Pool 实时信息作出进一步分析。
show status like "Innodb_buffer_pool_%";
上图可看出 总共有 8191 个page, 有 7303 个是 Free 状态, 有 788个 page 有数据, read 请求 31569次,其中有 710次请求使用物理磁盘获取
InnoDB Buffer Pool 的 Read 命中率大概: (31569-710)/ 31569 * 100% = 97.75%
Innodb_buffer_pool_read_ahead_rnd:记录进行随机读的时候产生的预读次数;
Innodb_buffer_pool_read_ahead_seq:记录连续读的时候产生的预读次数;
预读 – 在一些高端存储才会有,简单来说,就是通过分析数据请求的特点来自动判断客户端在请求当前数据块之后可能会继续请求的数据块。通过该自动判断,存储引擎可能会一次性将当前请求的数据库和后面可能请求的下一个(或者几个)数据库全部读出,以期望通过这种方式减少磁盘 IO 次数,提高 IO 性能 。
innodb_log_buffer_size 参数的使用
设置 InnoDB 的 Log Buffer 大小,系统默认 1MB。 Log Buffer 的主要作用就是 缓冲 Log 数据,提高写 Log 的 IO 性能。一般来说,如果系统不是写负载非常高而且以大事务居多的话, 8MB 以内大小足够。
也可以通过 系统状态 参数提供的性能统计数据来分析 Log 的使用情况
innodb_additional_mem_pool_size 参数
设置用户存放 InnoDB 的字典信息和其他内部结构所需要的内存空间。所以, InnoDB 表越多,需要的空间自然也就越大,系统默认 1MB。
一个常规的 几百个 InnoDB 表的 MySQL ,如果不是每个表都是上百个字段, 20MB 内存已经足够了。
该参数对系统整体性能并无太大影响,设置超过实际所需存储,只是浪费内存而已。
Double Write Buffer
Double Write Buffer 是 InnoDB 所使用的一种较为独特的文件 Flush 实现技术,主要作用是在减少文件同步次数提高 IO 性能的情况下,提高系统崩溃(Crash)或断电情况下的安全性,避免写入的数据不完整。
Adaptive Hash Index
Adaptive Hash index 的目的并不是为了改善磁盘 IO 性能,而是为了提高 Buffer Pool 中的数据访问效率,也就是给 Buffer Pool中的数据做索引。
InnoDB事务优化
脏读:指当一个事物正在访问数据,并且对数据进行修改,而这种修改没有提交到数据库中,这时,另外一个事物也访问这个数据,然后使用了这个数据。
不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据不一样,因此成为不可重复读。
幻读:指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改了这个表中的数据,如想表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还没有修改的数据行,就好像发生了幻觉一样。
对于高并发应用来说,为了尽可能保证数据的一致性,避免并发可能带来的数据不一致,自然是事务隔离级别越高越好。但是,对于 InnoDB 来说,所使用的事务隔离级别越高,实现复杂度自然就会更高,所要做的事情也会更高,整体性能也就更差。虽然 InnoDB 存储引擎默认的事务隔离级别是 REPEATABLE READ,但实际上在大部分应用中,只需要 READ COMMITED 的事务隔离级别就可以满足需求了。(不太赞同,因为并发是很容易出现的场景)。
事务与 IO 的关系及优化
InnoDB 修改数据操作,实际上修改的是Buffer Pool中的数据。
为了保障数据的安全稳定,不丢失数据。InnoDB并不是一个事务提交后就将 Buffer Pool 中被修改的数据同步到磁盘上,而是要先记录到redo log日志中,以防崩溃之后可以恢复。
最后再从Buffer Pool 中把脏页连续写入磁盘。
而且记录到redo log日志中也不是直接写磁盘,而是先写到redo log 缓冲区
控制 InnoDB 事务日志刷新方式参数:innodb_flush_log_at_trx_commit :
innodb_flush_log_at_trx_commit = 0, InnoDB 中的 Log Thread 每隔 1秒将 log buffer 中的数据写入文件,同时还会通知文件系统进行与文件同步的 flush操作,保证数据确实已经写入磁盘。
innodb_flush_log_at_trx_commit = 1, InnoDB 默认设置。每次事务的结束都会出发 Log Thread 将 Log Buffer 中的数据写入文件、并通知文件系统同步文件。这个设置最安全,能够保证不论是 MySQL 崩溃、OS崩溃还是主机断电都不会丢失任何已经提交的数据。
innodb_flush_log_at_trx_commit = 2, 每次事务结束的时候将数据写入事务日志,仅仅是调用了文件系统的文件写入操作。而文件系统都是有缓存机制的,所以 Log Thread 的写入并不能保证内容已经写入到物理磁盘完成持久化的动作。文件系统什么时候会将缓存中的数据同步到物理磁盘、文件, Log Thread 就完全不知道,所以,当设置 2 的时候, MySQL 崩溃并不会造成数据的丢失,但是 OS 崩溃或主机断电后可能丢失的数据量就完全控制在文件上了。
分析:
设置1 最安全,由于 IO 同步操作多,所以,性能最低。
设置 0 ,则每秒一次同步,性能相对高一下。
如果设置 2 ,性能可能是三种最好的。但是也可能出现故障后丢失数据最多的一种。如果 OS 足够稳定,主键硬件设备足够好,且主机供电系统足够安全,可将设置为2 ,让系统整体性能尽可能高。【建议设置为 2】
InnoDB数据存储优化
查看 InnoDB 表空间的使用情况:
show table status like "tablename"\G;