一、存储引擎
在mysql客户端中,使用以下命令可以查看MySQL支持的引擎。
show engines;
几个常用存储引擎的特点
下面我们重点介绍几种常用的存储引擎并对比各个存储引擎之间的区别和推荐使用方式。
特点 | Myisam | BDB | Memory | InnoDB | Archive |
存储限制 | 没有 | 没有 | 有 | 64TB | 没有 |
事务安全 | | 支持 | | 支持 | |
锁机制 | 表锁 | 页锁 | 表锁 | 行锁 | 行锁 |
B树索引 | 支持 | 支持 | 支持 | 支持 | |
哈希索引 | | | 支持 | 支持 | |
全文索引 | 支持 | | | | |
集群索引 | | | | 支持 | |
数据缓存 | | | 支持 | 支持 | |
索引缓存 | 支持 | | 支持 | 支持 | |
数据可压缩 | 支持 | | | | 支持 |
空间使用 | 低 | 低 | N/A | 高 | 非常低 |
内存使用 | 低 | 低 | 中等 | 高 | 低 |
批量插入的速度 | 高 | 高 | 高 | 低 | 非常高 |
支持外键 | | | | 支持 | |
最常使用的2种存储引擎:
- Myisam是Mysql的默认存储引擎。当create创建新表时,未指定新表的存储引擎时,默认使用Myisam。每个MyISAM在磁盘上存储成三个文件。文件名都和表名相同,扩展名分别是.frm(存储表定义)、.MYD (MYData,存储数据)、.MYI (MYIndex,存储索引)。数据文件和索引文件可以放置在不同的目录,平均分布io,获得更快的速度。
- InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比Myisam的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。
如何选择合适的存储引擎
选择标准可以分为: (1)是否需要支持事务; (2)是否需要使用热备; (3)崩溃恢复:能否接受崩溃; (4)是否需要外键支持;
根据应用特点选择合适的存储引擎,对于复杂的应用系统可以根据实际情况选择多种存储引擎进行组合。 下面是常用存储引擎的适用环境:
- MyISAM:默认的MySQL插件式存储引擎,它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一;拥有较高的插入,查询速度,但不支持事务。
- InnoDB:用于事务处理应用程序;5.5版本后Mysql的默认数据库,事务型数据库的首选引擎,支持ACID事务,支持行级锁定。
- Memory:所有数据置于内存的存储引擎,拥有极高的插入,更新和查询效率。但是会占用和数据量成正比的内存空间。并且其内容会在Mysql重新启动时丢失。
- Merge:允许MySQL DBA或开发人员将一系列等同的MyISAM表以逻辑方式组合在一起,并作为1个对象引用它们。对于诸如数据仓储等VLDB环境十分适合,且在超大规模数据存储时很有用。
- BDB:源自Berkeley DB,事务型数据库的另一种选择,支持COMMIT和ROLLBACK等其他事务特性。
- Archive :非常适合存储大量的独立的,作为历史记录的数据。因为它们不经常被读取。Archive拥有高效的插入速度,但其对查询的支持相对较差。
- Federated: 将不同的Mysql服务器联合起来,逻辑上组成一个完整的数据库。非常适合分布式应用
- Cluster/NDB :高冗余的存储引擎,用多台数据机器联合提供服务以提高整体性能和安全性。适合数据量大,安全和性能要求高的应用。
- CSV: 逻辑上由逗号分割数据的存储引擎。它会在数据库子目录里为每个数据表创建一个.CSV文件。这是一种普通文本文件,每个数据行占用一个文本行。CSV存储引擎不支持索引。
- BlackHole :黑洞引擎,写入的任何数据都会消失,一般用于记录binlog做复制的中继。
二、mysql常用配置参数
基本配置:
datadir:指定mysql的数据目录位置,用于存放mysql数据库文件、日志文件等。
配置示例:datadir=D:/wamp/mysqldata/Data
default-character-set:mysql服务器默认字符集设置。
配置示例:default-character-set=utf8
skip-grant-tables:当忘记mysql用户密码的时候,可以在mysql配置文件中配置该参数,跳过权限表验证,不需要密码即可登录mysql。
日志相关:
log-error:指定错误日志文件名称,用于记录当mysqld启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。
配置示例:log-error="WJT-PC.err"(默认在mysql数据目录下)
log-bin:指定二进制日志文件名称,用于记录对数据造成更改的所有查询语句。
配置示例:log-bin="WJT-PC-bin.log"(默认在mysql数据目录下)
binlog-do-db:指定将更新记录到二进制日志的数据库,其他所有没有显式指定的数据库更新将被忽略,不记录在日志中。
配置示例:binlog-do-db=db_name
binlog-ignore-db:指定不将更新记录到二进制日志的数据库,其他没有显式忽略的数据库都将进行记录。
配置示例:binlog-ignore-db=db_name
如果想记录或忽略多个数据库,可以对上面两个选项分别使用多次。
sync-binlog:指定多少次写日志后同步磁盘。
配置示例:sync-binlog=N
general-log:是否开启查询日志记录。
配置示例:general-log=1
general_log_file:指定查询日志文件名,用于记录所有的查询语句。
配置示例:general_log_file="WJT-PC.log"(默认在mysql数据目录下)
slow-query-log:是否开启慢查询日志记录。
配置示例:slow-query-log=1
slow_query_log_file:指定慢查询日志文件名称,用于记录消耗时间较长的查询语句。
配置示例:slow_query_log_file="WJT-PC-slow.log"(默认在mysql数据目录下)
long_query_time:设置慢查询的时间,超过这个时间的查询语句才记录日志。
配置示例:long_query_time=10(单位:秒)
log-slow-admin-statements:是否将慢管理语句(例如OPTIMIZE TABLE、ANALYZE TABLE和ALTER TABLE)写入慢查询日志。
存储引擎相关:
default-table-type:设置mysql的默认存储引擎。
innodb_data_home_dir:InnoDB引擎的共享表空间数据文件根目录。若没有设置,则使用mysql的datadir目录作为缺省目录。
innodb_data_file_path:单独指定共享表空间数据文件的路径与大小。数据文件的完整路径由innodb_data_home_dir与这里配置的值组合起来,文件大小以MB单位指定。
配置示例:innodb_data_home_dir=
innodb_data_file_path=ibdata1:12M;/data/mysql/mysql3306/data1/ibdata2:12M:autoextend
如果想为innodb表空间指定不同目录下的文件,必须指定 innodb_data_home_dir =。这个例子中会在datadir下建立ibdata1,在/data/MySQL/mysql3306/data1/目录下创建ibdata2。
innodb_file_per_table:是否开启独立表空间,若开启,InnoDB将使用独立的.idb文件创建新表而不是在共享表空间中创建。
配置示例:innodb_file_per_table=1
innodb_autoinc_lock_mode:配置在向有着auto_increment列的表插入数据时,相关锁的行为。该参数有3个取值,0:tradition传统,所有的insert语 句开始的时候得到一个表级的auto_inc锁,在语句结束的时候才能释放 这个锁,影响了并发的插入。1:consecutive连续,mysql可以一次生成 几个连续的auto_inc的值,auto_inc不需要一直保持到语句结束,只要 语句得到了相应的值后就可以提前释放锁(这也是mysql的默认模式)。 2:interleaved交错,这个模式下已经没有了auto_inc锁,所以性能是最好的,但是对于同一个语句来说它得到的auto_inc的值可能不是连续的。
配置示例:innodb_autoinc_lock_mode=1
low_priority_updates:在myisam引擎锁使用中,默认情况下写请求优先于读请求,可以通过将该参数设置为1来使myisam引擎给予读请求优先权限, 所有的insert、update、delete和lock table write语句将等待直到受影响 的表没有挂起的select或lock table read。
配置示例:low_priority_updates=0(默认配置)
max_write_lock_count:当一个myisam表的写锁定达到这个值后,mysql就暂时 将写请求优先级降低,给部分读请求获得锁的机会。
innodb_lock_wait_timeout:InnoDB锁等待超时参数,若事务在该时间内没有获 得需要的锁,则发生回滚。
配置示例:innodb_lock_wait_timeout=50(默认50秒)
max_heap_table_size:设置memory表的最大空间大小,该变量可以用来计算 memory表的max_rows值。在已有memory表上设置该参数是没有效果 的,除非重建表。
查询相关:
max_sort_length:配置对blob或text类型的列进行排序时使用的字节数(只对配置的前max_sort_length个字节进行排序,其他的被忽略)
max_length_for_sort:mysql有两种排序算法,两次传输排序和单次传输排序。当查询需要所有列的总长度不超过max_length_for_sort时,mysql使用 单次传输排序,否则使用两次传输排序。
optimizer_search_depth:在关联查询中,当需要关联的表数量超过optimizer_search_depth的时候,优化器会使用“贪婪”搜索的方式查找“最优”的关联顺序。
补充
热备:国内大部分采用的是双机热备的方式
组成双机热备的方案主要的三种方式分别为:基于共享存储(磁盘阵列)的方式,全冗余方式和复制方式。
基于共享存储(磁盘阵列)的方式
共享存储方式主要通过磁盘阵列提供切换后,对数据完整性和连续性的保障。用户数据一般会放在磁盘阵列上,当主机宕机后,备机继续从磁盘阵列上取得原有数据。如下图所示传统的单存储方式这种方式因为使用一台存储设备,往往被业内人士称为磁盘单点故障。但一般来讲存储的安全性较高。所以如果忽略存储设备故障的情况下,这种方式也是业内采用最多的热备方式。
全冗余方式
全冗余方式就是双机双存储,基于单台存储的传统双机热备方式,确实存在存储单点故障的情况,为实现存储冗余,存储高可用也已经越来越多的被用户接受。我们从理解上可以看出,双机热备最早是为解决服务器的计划性停机与非计划性宕机的解决方案,但是我们无法实现存储的计划性停机与非计划性宕机带来的服务器停机,而存储作为双机热备中唯一存储数据的设备,它一旦发生故障往往会造成双机热备系统全面崩溃。存储热备方式随着科技的进步,云存储,云计算发展,对于存储热备已经进入了成熟及快速发展阶段,双机热备也随着技术的进步,进入到了没有单点故障的全冗余双机热备方式。
这种方式的特点在于:
1、存储之间的数据复制不经过网络,而是由存储之间进行复制。
2、两个存储之间的复制是完全实时的,不存在任何时间延时。
3、主备存储之间的切换时间小于500ms,以确保系统存储时不产生延时。
4、硬盘盘符及分区不因为主备存储之间的切换而改变。
5、服务器的切换,不影响存储之间的初始化,增量同步及数据复制。
6、某一存储设备的计划性停机,不影响整个服务器双机热备系统的工作。
7、存储设备之间使用重复数据删除技术,完成增量同步工作。
8、真正的7X24小时或切换的全冗余方案。
复制方式
这种方式主要利用数据的同步方式,保证主备服务器的数据一致性。
基于数据复制的方式有多种方法,其性能和安全也不尽相同,其主要方法有以下几种:
A、单纯的文件方式的拷贝不适用于数据库等应用,因为打开的文件是不能被复制的,如果要复制必须将数据库关闭,这显然是不可以的。以文件方式的复制主要适用于WEB页的更新,FTP上传应用,对主备机数据完整性,连续性要数据同步方式求不高的情况下使用。
B、利用数据库所带有复制功能,比如SQLServer2000或2005所带的定阅复制,这种方式用户要根据自己的应用小心使用,原因主要是:
(1)SQLServer的定阅复制会在用户表上增加字段,对那些应用软件编程要求较高,如果在应用软件端书写时未明确指定字段的用户,而使用此功能会造成应用程序无法正常工作。
(2)数据滞留,这个限制怕也是最要命的,因为SQLServer在数据传输过程中数据并非实时的到达主备机,而是数据先写到主机,再写到备机,如此一来,备机的数据往往来不及更新,此时如果发生切换,备机的数据将不完整,也不连续,如果用户发现已写入的数据在备机找不到,重新写入的话,则主机修复后,就会发生主备机数据严重冲突,数据库会乱掉。
(3)复杂应用切莫使用定阅复制来做双机热备,包括数据结构中存储过程的处理,触发器和序列,一旦发生冲突,修改起来非常麻烦。
(4)服务器性能降低,对于大一点的数据库,SQLServer2000或2005所带的定阅复制会造成服务器数据库运行缓慢。
总之SQLServer2000或2005所带的定阅复制主要还是应用于数据快照服务,切莫用他来做双机热备中的数据同步。
C:硬盘数据拦截,目前国际国内,比较成熟的双机热备软件通常会使用硬盘数据拦截的技术,通常称为镜像软件即Mirror软件,这种技术当前已非常成熟,拦截的方式也不尽相同。
(1)分区拦截技术,以Pluswell热备份产品为例,他采用的是一种分区硬盘扇区拦截的技术,通过驱动级的拦截方式,将数据写往硬盘的数据提取,并首先写到备用服务器,以保证备用服务器的数据最新,然后再将数据回写到主机硬盘。这种方式将绝对保证,主备机数据库的数据完全一致,无论发生哪种切换,都能保证数据库的完整性与连续性。由于采用分区拦截技术,所以用户可以根据需要在一块硬盘上划分适合大小的分区来完成数据同步工作。
(2)硬盘拦截技术,以Symantec的Co-Standby为例,也是一种有效的硬盘拦截软件,他的拦截主要基于一整块硬盘,往往在硬盘初始化时需要消耗大量的时间。
三、优化目标
1.减少 IO 次数
IO永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,减少 IO 次数是 SQL 优化中需要第一优先考虑,当然,也是收效最明显的优化手段。
2.降低 CPU 计算
除了 IO 瓶颈之外,SQL优化中需要考虑的就是 CPU 运算量的优化了。order by, group by,distinct … 都是消耗 CPU 的大户(这些操作基本上都是 CPU 处理内存中的数据比较运算)。当我们的 IO 优化做到一定阶段之后,降低 CPU 计算也就成为了我们 SQL 优化的重要目标
优化方法
改变 SQL 执行计划
明确了优化目标之后,我们需要确定达到我们目标的方法。对于 SQL 语句来说,达到上述2个目标的方法其实只有一个,那就是改变 SQL 的执行计划,让他尽量“少走弯路”,尽量通过各种“捷径”来找到我们需要的数据,以达到 “减少 IO 次数” 和 “降低 CPU 计算” 的目标
常见误区
1.count(1)和count(primary_key) 优于 count(*)
很多人为了统计记录条数,就使用 count(1) 和 count(primary_key) 而不是 count(*) ,他们认为这样性能更好,其实这是一个误区。对于有些场景,这样做可能性能会更差,应为数据库对 count(*) 计数操作做了一些特别的优化。
2.count(column) 和 count(*) 是一样的
这个误区甚至在很多的资深工程师或者是 DBA 中都普遍存在,很多人都会认为这是理所当然的。实际上,count(column) 和 count(*) 是一个完全不一样的操作,所代表的意义也完全不一样。
count(column) 是表示结果集中有多少个column字段不为空的记录
count(*) 是表示整个结果集有多少条记录
3.select a,b from … 比 select a,b,c from … 可以让数据库访问更少的数据量
这个误区主要存在于大量的开发人员中,主要原因是对数据库的存储原理不是太了解。
实际上,大多数关系型数据库都是按照行(row)的方式存储,而数据存取操作都是以一个固定大小的IO单元(被称作 block 或者 page)为单位,一般为4KB,8KB… 大多数时候,每个IO单元中存储了多行,每行都是存储了该行的所有字段(lob等特殊类型字段除外)。
所以,我们是取一个字段还是多个字段,实际上数据库在表中需要访问的数据量其实是一样的。
当然,也有例外情况,那就是我们的这个查询在索引中就可以完成,也就是说当只取 a,b两个字段的时候,不需要回表,而c这个字段不在使用的索引中,需要回表取得其数据。在这样的情况下,二者的IO量会有较大差异。
4.order by 一定需要排序操作
我们知道索引数据实际上是有序的,如果我们的需要的数据和某个索引的顺序一致,而且我们的查询又通过这个索引来执行,那么数据库一般会省略排序操作,而直接将数据返回,因为数据库知道数据已经满足我们的排序需求了。
实际上,利用索引来优化有排序需求的 SQL,是一个非常重要的优化手段
延伸阅读:MySQL ORDER BY 的实现分析,MySQL 中 GROUP BY 基本实现原理以及 MySQL DISTINCT 的基本实现原理这3篇文章中有更为深入的分析,尤其是第一篇
5.执行计划中有 filesort 就会进行磁盘文件排序
有这个误区其实并不能怪我们,而是因为 MySQL 开发者在用词方面的问题。filesort 是我们在使用 explain 命令查看一条 SQL 的执行计划的时候可能会看到在 “Extra” 一列显示的信息。
实际上,只要一条 SQL 语句需要进行排序操作,都会显示“Using filesort”,这并不表示就会有文件排序操作。
基本原则
1.尽量少 join
MySQL 的优势在于简单,但这在某些方面其实也是其劣势。MySQL 优化器效率高,但是由于其统计信息的量有限,优化器工作过程出现偏差的可能性也就更多。对于复杂的多表 Join,一方面由于其优化器受限,再者在 Join 这方面所下的功夫还不够,所以性能表现离 Oracle 等关系型数据库前辈还是有一定距离。但如果是简单的单表查询,这一差距就会极小甚至在有些场景下要优于这些数据库前辈。
2.尽量少排序
排序操作会消耗较多的 CPU 资源,所以减少排序可以在缓存命中率高等 IO 能力足够的场景下会较大影响 SQL 的响应时间。
对于MySQL来说,减少排序有多种办法,比如:
上面误区中提到的通过利用索引来排序的方式进行优化
减少参与排序的记录条数
非必要不对数据进行排序
…
3.尽量避免 select *
很多人看到这一点后觉得比较难理解,上面不是在误区中刚刚说 select 子句中字段的多少并不会影响到读取的数据吗?
是的,大多数时候并不会影响到 IO 量,但是当我们还存在 order by 操作的时候,select 子句中的字段多少会在很大程度上影响到我们的排序效率,这一点可以通过我之前一篇介绍 MySQL ORDER BY 的实现分析的文章中有较为详细的介绍。
此外,上面误区中不是也说了,只是大多数时候是不会影响到 IO 量,当我们的查询结果仅仅只需要在索引中就能找到的时候,还是会极大减少 IO 量的。
4.尽量用 join 代替子查询
虽然 Join 性能并不佳,但是和 MySQL 的子查询比起来还是有非常大的性能优势。MySQL 的子查询执行计划一直存在较大的问题,虽然这个问题已经存在多年,但是到目前已经发布的所有稳定版本中都普遍存在,一直没有太大改善。虽然官方也在很早就承认这一问题,并且承诺尽快解决,但是至少到目前为止我们还没有看到哪一个版本较好的解决了这一问题。
5.尽量少 or
当 where 子句中存在多个条件以“或”并存的时候,MySQL 的优化器并没有很好的解决其执行计划优化问题,再加上 MySQL 特有的 SQL 与 Storage 分层架构方式,造成了其性能比较低下,很多时候使用 union all 或者是union(必要的时候)的方式来代替“or”会得到更好的效果。
6.尽量用 union all 代替 union
union 和 union all 的差异主要是前者需要将两个(或者多个)结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的 CPU 运算,加大资源消耗及延迟。所以当我们可以确认不可能出现重复结果集或者不在乎重复结果集的时候,尽量使用 union all 而不是 union。
7.尽量早过滤
这一优化策略其实最常见于索引的优化设计中(将过滤性更好的字段放得更靠前)。
在 SQL 编写中同样可以使用这一原则来优化一些 Join 的 SQL。比如我们在多个表进行分页数据查询的时候,我们最好是能够在一个表上先过滤好数据分好页,然后再用分好页的结果集与另外的表 Join,这样可以尽可能多的减少不必要的 IO 操作,大大节省 IO 操作所消耗的时间。
8.避免类型转换
这里所说的“类型转换”是指 where 子句中出现 column 字段的类型和传入的参数类型不一致的时候发生的类型转换:
人为在column_name 上通过转换函数进行转换
直接导致 MySQL(实际上其他数据库也会有同样的问题)无法使用索引,如果非要转换,应该在传入的参数上进行转换
由数据库自己进行转换
如果我们传入的数据类型和字段类型不一致,同时我们又没有做任何类型转换处理,MySQL 可能会自己对我们的数据进行类型转换操作,也可能不进行处理而交由存储引擎去处理,这样一来,就会出现索引无法使用的情况而造成执行计划问题。
9.优先优化高并发的 SQL,而不是执行频率低某些“大”SQL
对于破坏性来说,高并发的 SQL 总是会比低频率的来得大,因为高并发的 SQL 一旦出现问题,甚至不会给我们任何喘息的机会就会将系统压跨。而对于一些虽然需要消耗大量 IO 而且响应很慢的 SQL,由于频率低,即使遇到,最多就是让整个系统响应慢一点,但至少可能撑一会儿,让我们有缓冲的机会。
10.从全局出发优化,而不是片面调整
SQL 优化不能是单独针对某一个进行,而应充分考虑系统中所有的 SQL,尤其是在通过调整索引优化 SQL 的执行计划的时候,千万不能顾此失彼,因小失大。
11.尽可能对每一条运行在数据库中的SQL进行 explain
优化 SQL,需要做到心中有数,知道 SQL 的执行计划才能判断是否有优化余地,才能判断是否存在执行计划问题。在对数据库中运行的 SQL 进行了一段时间的优化之后,很明显的问题 SQL 可能已经很少了,大多都需要去发掘,这时候就需要进行大量的 explain 操作收集执行计划,并判断是否需要进行优化。