【1】数据库调优思路

当我们遇到数据库调优问题的时候,该如何思考呢?这里把思考的流程整理成下面这张图。

整个流程划分成了观察(show status) 和行动(Action)两个部分。字母S的部分代表观察(会使用相应的分析工具),字母A代表的部分是行动(对应分析可以采取的行动)。

mysql比较数据是否一致 mysql数据比对怎么做_字段

我们可以通过观察了解数据库整体的运行状态, 通过性能分析工具可以让我们了解执行慢的SQL都有哪些,查看具体的SQL执行计划,甚至是SQL执行中的每一步的成长代价,这样才能定位问题所在。找到了问题,再采取相应的行动。

下面我们详细解释一下这张图。

首先在S1部分,我们需要观察服务器的状态是否存在周期性的波动。如果存在周期性的波动,有可能是周期性节点的原因,比如双十一、促销活动等。这样的话,我们可以通过A1这一步骤解决,也就是加缓存,或者更改缓存失效策略。

如果缓存策略没有解决,或者不是周期性波动的原因,我们就需要进一步分析查询延迟和卡顿的原因。接下来进入到S2这一步,我们需要开启慢查询。慢查询可以帮我们定位执行慢的SQL语句。我们可以通过设置 long_query_time 参数定义“慢”的阈值,如果SQL执行时间超过了 long_query_time ,则认为是慢查询。当手机上来这些慢查询之后,我们就可以通过分析工具对慢查询日志进行分析。

在S3这一步骤中,我们就知道了执行慢的SQL,这样就可以针对性的用explain 查看对应的SQL语句的执行计划,或者使用 show profile 查看SQL中每一个步骤的时间成本。这样我们就可以了解SQL查询慢是因为执行时间长还是等待时间长。

如果是SQL等待时间长,我们进入A2步骤。这一步骤中,我们可以调优服务器的参数,比如适当增加数据库缓冲池等。 如果是SQL执行时间长,就进入A3步骤,这一步中我们需要考虑是索引设计的问题,还是查询关联的数据表过多?还是因为数据表的字段设计问题导致了这一现象。然后在这些维度上进行对应的调整。

如果A2和A3都不能解决问题,我们需要考虑数据库自身的SQL查询性能是否已经达到了瓶颈。如果确认没有达到性能瓶颈,就需要重新检查,重复以上的步骤。如果已经达到了性能瓶颈,进入A4阶段,需要考虑增加服务器,采用读写分离的架构,或者考虑对数据库进行分库分表,比如垂直分库、垂直分表和水平分表等。

以上就是数据库调优的思路。如果我们发现执行SQL时存在不规则延迟或卡顿的时候,就可以采用分析工具帮我们定位有问题的SQL,这三种分析工具你可以立即是SQL调优的三个步骤:慢查询、explain和show profiling 。

小结:

mysql比较数据是否一致 mysql数据比对怎么做_1024程序员节_02

我们可以使用explain分析SQL执行计划

关于explain与慢查询日志,其实通常是观察生产的运行情况,开启慢查询日志设置阈值将慢SQL抓取出来,然后使用explain分析优化。

【2】查看系统性能参数

在MySQL中,可以使用 show status 语句查询一些MySQL数据库服务器的性能参数、执行频率。

show status语句语法如下:

show [global|session] status like '%参数%'

一些常用的性能参数如下:

• Connections:连接MySQL服务器的次数;
• Uptime:MySQL服务器的上线时间;
• Slow_queries:慢查询的次数;
• Innodb_rows_read:select查询返回的行数;
• Innodb_rows_inserted:执行insert操作插入的行数
• Innodb_rows_updated:执行update操作更新的行数
• Innodb_rows_deleted:执行delete操作删除的行数
• Com_select:查询操作的次数;
• Com_insert:插入操作的次数。对于批量插入的insert操作,只累加一次。
• Com_update:更新操作的次数。
• Com_delete:删除操作的次数。

【3】统计SQL的查询成本

一条SQL查询语句在执行前需要确定查询执行计划,如果存在多种执行计划的话,MySQL会计算每个执行计划所需要的成本,从中选择成本最小的一个座位最终执行的执行计划。

如果我们想要查看某条SQL语句的查询成本,可以在执行完这条SQL语句之后,通过查看当前会话中的 last_query_cost 变量值来得到当前查询的成本。它通常也是我们评价一个查询的执行效率的一个常用指标。这个查询成本对应的是 SQL语句所需要读取的页的数量。

如下说明从用户表查询id=1的数据,然后查看查询成本:

SELECT* from tb_sys_user where id=1;

show status like 'last_query_cost'

运行结果(一条记录,运行时间为0.001s), 查询成本结果为1,也就是实际上我们只需要检索一页即可。

mysql比较数据是否一致 mysql数据比对怎么做_mysql比较数据是否一致_03

那如果我们查询 id > 1的数据 呢?如下图所示,这时查询成本为3.427182(运行时间0.002s)。说明数据来自四个数据页中。

mysql比较数据是否一致 mysql数据比对怎么做_mysql比较数据是否一致_04

这里能看到查询效率并没有明显的变化。实际上这两个SQL查询的时间基本上一页,就是因为采用了顺序读取的方式将页面一次性加载到缓冲池中,然后再进行查找。虽然页数据量(last_query_cost)增加了不少,但是通过缓冲池的机制,并没有增加多少查询时间。

使用场景: 它对于比较开销是有用的,特别是我们有好几种查询方式可选的时候。

SQL查询是一个动态的过程,从页加载的角度来看,我们可以得到以下两点结论:

  • 位置决定效率。如果页就在数据库缓冲池中,那么效率是最高的,否则还需要从内测或者磁盘中进行读取。当然针对单个页的读取来说,如果页存在于内存中,会比在磁盘中读取效率高很多。
  • 批量决定效率。 如果我们从磁盘中对单一页进行随机度,那么效率是很低的。而采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提升很多,甚至要快于单个页面在内存中的随机读取。

【4】几种常见的优化场景

① 优化查询过程中的数据访问

访问数据太多导致查询性能下降,确定应用程序是否在检索大量超过需要的数据,可能是太多行或列。

确认MySQL服务器是否在分析大量不必要的数据行避免犯如下SQL语句错误:

  • 查询不需要的数据。解决办法:使用limit解决
  • 多表关联返回全部列。解决办法:指定列名
  • 总是返回全部列。解决办法:避免使用SELECT *
  • 重复查询相同的数据。解决办法:可以缓存数据,下次直接读取缓存

是否在扫描额外的记录

解决办法:使用explain进行分析,如果发现查询需要扫描大量的数据,但只返回少数的行,可以通过如下技巧去优化:

  • 使用索引覆盖扫描,把所有的列都放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果。
  • 改变数据库和表的结构,修改数据表范式
  • 重写SQL语句,让优化器可以以更优的方式执行查询。

② 优化长难的查询语句

要考虑是一个复杂查询还是多个简单查询。MySQL内部每秒能扫描内存中上百万行数据,相比之下,响应数据给客户端就要慢得多。建议使用尽可能小的查询,有时将一个大的查询分解为多个小的查询是很有必要的。

另外还可以切分查询将一个大的查询分为多个小的相同的查询。

一次性删除1000万的数据要比一次删除1万,暂停一会的方案更加损耗服务器开销。

如果是关联查询,可以考虑分解关联查询,让缓存的效率更高。

③ 优化关联查询

确定ON或者USING子句中是否有索引。确保GROUP BY和ORDER BY只有一个表中的列,这样MySQL才有可能使用索引。

如果应用了union,那么考虑换成union all。UNION ALL的效率高于UNION。

④ 优化子查询

可以从如下几种方面考虑:

  • 用关联查询替代。
  • 优化GROUP BY和DISTINCT
    这两种查询可以使用索引来优化,是有效的优化方法
  • 关联查询中,使用标识列分组的效率更高
  • 如果不需要ORDER BY,进行GROUP BY时加ORDER BY NULL,MySQL不会再进行文件排序。
  • WITH ROLLUP超级聚合,可以挪到应用程序处理

⑤ 优化LIMIT分页

LIMIT偏移量大的时候,查询效率较低。可以记录上次查询的 大ID,下次查询时直接根据该ID来查询。

超大分页怎么处理? 超大的分页一般从两个方向上来解决:数据库层面和需求层面。

数据库层面

这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age> 20 limit 1000000,10这种查询其实也是有可以优化的余地的。 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢。

我们可以修改为select * from table where id in(select id from table where age > 20 limit 1000000,10)。这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快。

同时如果ID连续的话,我们还可以select* from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种, 但是核心思想都一样,就是减少load的数据.

从需求的角度减少这种请求

主要是不做类似的需求(直接跳转到几百万页之后的具体某一页,只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.

解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.在阿里巴巴《Java开发手册》中,对超大分页的解决办法是类似于上面提到的第一种。

【推荐】利用延迟关联或者子查询优化超多分页场景。

说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。

正例:先快速定位需要获取的id段,然后再关联:

SELECT a.* FROM 表1 a,(select id from 表1 where 条件 LIMIT 100000,20) b w here
a.id=b.id

其实本质就是利用表的覆盖索引来加速分页查询。我们都知道,利用了索引查询的语句中如果只包含了那个索引列(覆盖索引),那么这种情况会查询很快。因为利用索引查找有优化算法,且数据就在查询索引上面,不用再去找相关的数据地址了,这样节省了很多时间。另外Mysql中也有相关的索引缓存,在并发高的时候利用缓存就效果更好了。

⑥ 优化WHERE子句

其实这个问题要从上面几个方面综合考虑。先定位低效SQL语句,然后根据SQL语句可能低效的原因做排查,先从索引着手,如果索引没有问题,考虑以上几个方面,数据访问的问题,长难查询句的问题还是一些特定类型优化的问题。

这里总结一下SQL语句优化的一些方法。

对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

应尽量避免在 where 子句中对字段进行 null 值判断,否则可能将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null 

#可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num=0

应尽量避免在 where 子句中使用!=或<> 操作符,否则引擎可能放弃使用索引而进行全表扫描。

MySQL中BTree算法只对以下操作符才使用索引:

<,<=,=,>,>=,between,in,以及某些时候的like(不以通配符%或_开头的情形)

Hash索引只能用于对等比较,例如=,<=>(相当于=)操作符。

应尽量避免在 where 子句中使用or 来连接条件,否则可能将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or num=20 
-- 可以这样查询:
select id from t where num=10 union all select id from t where num=20

in 和 not in 也要慎用,否则可能会导致全表扫描,如:

select id from t where num in(1,2,3) 

-- 对于连续的数值,能用 between 就不要用 in 了:

select id from t where num between 1 and 3

谨慎使用like,如果通配符%不出现在开头,则可以用到索引。下面的查询也可能将导致全表扫描:select id from t where name like '%李% 。若要提高效率,可以考虑全文检索。不过select id from t where name like '李%时如果name有索引,将会引用到索引。

如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时,它必须在编译时进行选择。

然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num=@num 

--可以改为强制查询使用索引:

select id from t with (index(索引名)) where num=@num

应尽量避免在 where 子句中对字段进行表达式操作,这可能将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2=100 

-- 应改为:

select id from t where num=100*2

应尽量避免在where子句中对字段进行函数操作,这可能将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3)='abc'

-- name以abc开头的id应改为: 

select id from t where name like 'abc%'

不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

谨慎使用select *,其通常会导致应用不到索引,可以考虑索引覆盖,只返回需要的字段。

⑦ 数据结构的优化

一个好的数据库设计方案对于数据库的性能往往会起到事半功倍的效果。需要考虑数据冗余、查询和更新的速度、字段的数据类型是否合理等多方面的内容。

数据结构的优化可以考虑如下几个方面。

将字段很多的表分解成多个表对于字段较多的表。如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。

增加中间表对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,将需要通过联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的
查询。

增加冗余字段设计数据表时应尽量遵循范式理论的规约,尽可能的减少冗余字段,让数据库设计看起来精致、优雅。但是,合理的加入冗余字段可以提高查询速度。

表的规范化程度越高,表和表之间的关系越多,需要连接查询的情况也就越多,性能也就越差。

注意:冗余字段的值在一个表中修改了,就要想办法在其他表中更新,否则就会导致数据不一致的问题

【5】关于字段和查询SQL的使用总结

① 字段

  • 尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED
  • VARCHAR的长度只分配真正需要的空间
  • 使用枚举或整数代替字符串类型
  • 尽量使用TIMESTAMP而非DATETIME,
  • 单表不要有太多字段,建议在20以内
  • 避免使用NULL字段,很难查询优化且占用额外索引空间
  • 用整型来存IP

② 查询SQL

  • 可通过开启慢查询日志来找出较慢的SQL
  • 不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
  • sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库
  • 不用SELECT *
  • OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内
  • 不用函数和触发器,在应用程序实现
  • 避免%xxx式查询
  • 少用JOIN
  • 使用同类型进行比较,比如用'123'和'123'比123和123比
  • 尽量避免在WHERE子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描
  • 对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
  • 列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大

③ 关于between 和 in

前面我们提到过 对于连续数值,使用BETWEEN不用IN

explain  select SQL_NO_CACHE * from book b 
where bookid  between 1 and 3;


explain  select SQL_NO_CACHE * from book b 
where bookid  in(1,2,3);

有的同学可能就验证了,类似如下两个SQL的查询时间可能 后者比前者还要快一点。

select SQL_NO_CACHE * from book b 
where bookid  between 1 and 3;

select SQL_NO_CACHE * from book b 
where bookid  in(1,2,3);

单纯从时间上看,没有说between…and 一定比 in要快,我们是出于整体成本考虑的。我们修改查询计划如下:

explain format=json select SQL_NO_CACHE * from book b 
where bookid  between 1 and 3;


explain format=json select SQL_NO_CACHE * from book b 
where bookid  in(1,2,3);

将得到的查询计划拷贝出来格式化进行对比可知,between..andread_cost也就是读取数据的成本是小于 in 的!

mysql比较数据是否一致 mysql数据比对怎么做_SQL_05

④ 多使用commit

只要有可能,在程序中尽量多食用commit,这样程序的性能得到提高,需求也会因为commit所释放的资源而减少。

commit锁释放的资源:

  • 回滚段上用于恢复数据的信息
  • 被程序语句获得的锁
  • redo / undo log buffer中的空间
  • 管理上述3种资源中的内部花费

【6】 为什么不让用select *

  • 数据库需要知道 * 等于什么 = 查数据字典会增大开销(记录数据库和应用程序元数据的目录)。也就是说MySQL在解析过程中,会通过查询数据字典将 * 按序转换成所有列名,这会大大的耗费资源和时间。
  • 多出一些不用的列,这些列可能正好不在索引的范围之内(索引的好处不多说)select * 杜绝了索引覆盖的可能性,而索引覆盖又是速度极快,效率极高,业界极为推荐的查询方式。
  • 不需要的字段会增加数据传输的时间,即使 mysql 服务器和客户端是在同一台机器上,使用的协议还是 tcp,通信也是需要额外的时间。
  • 大字段,例如很长的 varchar,blob,text。准确来说,长度超过 728 字节的时候,会把超出的数据放到另外一个地方,因此读取这条记录会增加一次 io 操作。----mysql innodb
  • 影响数据库自动重写优化SQL(类似 Java 中编译 class 时的编译器自动优化) 。----Oracle
  • select * 数据库需要解析更多的 对象,字段,权限,属性相关,在 SQL 语句复杂,硬解析较多的情况下,会对数据库造成沉重的负担
  • 额外的 io,内存和 cpu 的消耗,因为多取了不必要的列。
  • SELECT * 需谨慎,因为一旦列的个数或顺序更改,就有可能程序执行失败。

【7】调优的维度和步骤总结

① 选择适合的DBMS

如果对事务性处理以及安全性要求高的话,可以选择商业的数据库产品。这些数据库在事务处理和查询性能上都比较强,比如采用SQL Server、Oracle,那么单表存储上亿条数据是没有问题的。如果数据表设计得好,即使不采用分库分表的方式,查询效率也不差。

除此以为,还可以采用开源的MySQL进行存储,它有很多存储引擎可以选择。如果进行事务处理的话可以选择InnoDB,非事务处理可以选择MyISAM。

NoSQL阵营包括键值型数据库、文档型数据库、搜索引擎、列式存储和图形数据库。这些数据库的优缺点和使用场景各有不同。比如列式存储数据库可以大幅度降低系统的IO,适合于分布式文件系统。但如果数据需要频繁地增删改,那么列式存储就不太适用了。

② 优化表设计

选择了DBMS之后,我们就需要进行表设计了。而数据表的设计方式也直接影响了后续的SQL查询语句。RDBMS中,每个对象都可以定义为一张表,表与表之间的关系代表了对象之间的关系。如果用的是MySQL,我们还可以根据不同表的使用需求,选择不同的存储引擎。除此以为,还有一些优化的原则可以参考:

  • 表结构要尽量遵循三范式的原则。这样可以让数据结构更加清晰规范,减少冗余字段,同时也减少了在更新,插入和删除数据时等异常情况的发生。
  • 如果查询应用比较多,尤其是需要进行多表联查的时候,可以采用反范式进行优化。反范式采用空间换时间的方式,通过增加冗余字段提高查询的效率。
  • 表字段的数据类型选择,关系到了查询效率的高低以及存储空间的大小。一般来说,如果字段可以采用数值类型,就不要采用字符类型。针对字符类型来说,当确定字符长度固定时,就可以采用CHAR类型;当长度不固定时,通常采用VARCHAR类型,字符长度要尽可能设计得短一些。

③ 优化逻辑查询

SQL查询优化,可以分为逻辑查询优化和物理查询优化。逻辑查询优化就是通过改变SQL语句的内容让SQL执行效率更高效,采用的方式是对SQL语句进行等价变换,对查询进行重写。

SQL的查询重写包括了子查询优化、等价谓词重写、视图重写、条件简化、连接消除和嵌套连接消除等。

比如exists子查询和in子查询的时候,会根据小表驱动大表的原则选择适合的子查询。在where子句中尽量避免对字段进行函数运算,它们会让字段的索引失效。

举例:查询评论内容开头为abc的内容有哪些,如果在where子句中使用了函数,语句就会写成下面这样:

select comment_id,comment_text,comment_time from product_comment
where substring(comment_text,1,3)='abc';

采用查询重写的方式进行等价替换:

select comment_id,comment_text,comment_time from product_comment
where comment_text like 'abc%'

④ 优化物理查询

物理查询优化是在确定了逻辑查询优化之后,采用物理优化技术(比如索引等),通过计算代价模型对各种可能的访问路径进行估算,从而找到执行方式中代价最小的作为执行计划。在这个部分中,我们需要掌握的重点是对索引的创建和使用。

SQL查询时需要对不同的数据表进行查询,因此在物理查询优化阶段也需要确定这些查询所采用的路径,具体的情况包括:

  • 单表扫描:对于单表扫描来说,我们可以全表扫描所有的数据,也可以局部扫描
  • 两张表的连接:常用的连接方式包括了嵌套循环连接、HASH连接和合并连接
  • 多张表的连接:多张数据表进行连接的时候,顺序很重要。因为不同的连接路径查询的效率不同,搜索空间也会不同。我们在进行多表连接的时候,搜索空间可能会达到很高的数据量级。巨大的搜索空间显然会占用更多的资源,因此我们需要通过调整连接顺序,将搜索空间调整在一个可接受的范围内。

⑤ 使用Redis或Memcached作为缓存

除了可以对SQL本身进行优化以外,我们还可以通过使用缓存提升查询的效率。

因为数据都是存放在数据库总,我们需要从数据库层中取出数据放到内存中进行业务逻辑的操作。当用户量增大的时候,如果频繁地进行数据查询,会消耗数据库的很多资源。如果我们将常用的数据直接放到内存中,就会大幅提升查询的效率。

键值存储数据库可以帮我们解决这个问题。常用的有Redis和Memcached,它们都可以将数据存放到内存中。

从可靠性来讲,Redis支持持久化,可以让我们的数据保存到硬盘上。不过这样一来,性能消耗也会比较大。而Memcached仅仅是内存存储,不支持持久化。

从支持的数据类型来说,Redis比Memcached更多,它不仅支持key-value类型的数据,还支持List、Set、Hash等数据结构。当我们有持久化需要或者是更高级的数据处理需求的时候,就可以使用Redis。

通常我们对于查询响应要求高的场景(响应实际短,吞吐量大),可以考虑内存数据库来提升性能,减轻MySQL的压力。

⑥ 库级优化

库级优化是站在数据库的维度上进行的优化策略。通过主从架构优化我们的读写策略,通过对数据库进行垂直或水平切分,突破单一数据库或数据表的访问限制,提升查询的性能。

1.读写分离

为了提升系统的性能,优化用户体验,我们可以采用读写分离的方式降低主数据库的负载。比如用主数据库(master)完成写操作,用从数据库(slave)完成读操作。

mysql比较数据是否一致 mysql数据比对怎么做_字段_06


mysql比较数据是否一致 mysql数据比对怎么做_mysql比较数据是否一致_07

2.数据分片

当数据量级达到千万级以上时,有时候我们需要把一个数据库切成多份,放到不同的数据库服务器上,减少对单一数据库服务器的访问压力。如果使用的是MySQL,就可以使用MySQL自带的分区功能。当然也可以考虑自己做垂直拆分(分库),水平拆分(分表),垂直+水平拆分(分库分表)。

垂直拆分(分库)

如我们按照功能模块把订单表和日志表放到两个库(甚至在两个机器)中。

垂直拆分(分表)

其实就是把一个宽表拆成两个窄表,如客户表和客户详情表、客户地址表。

水平拆分(分表)

水平拆分就是分表,将数据按照不同维度拆分到多个表。如Hash、取模以及时间维度等。

mysql比较数据是否一致 mysql数据比对怎么做_MySQL优化_08

【8】优化数据库结构

一个好的数据库设计方案对于数据库的性能常常会起到事半功倍的效果。合理的数据库结构不仅可以使数据库占用更小的磁盘空间,而且能够使查询速度更快。数据库结构的设计需要考虑数据冗余、查询和更新的速度、字段的数据类型是否合理等多方面的内容。

① 拆分表:冷热数据分离

拆分表的思路是,把1个包含很多字段的表拆分成2个或者多个相对较小的表。这样做的原因是,这些表中某些字段的操作频率很高(热数据),经常要进行查询或者更新操作。而另外一些字段的使用频率却很低(冷数据),冷热数据分离,可以减少表的宽度。如果放在一个表里面,每次查询都要读取大记录,会消耗较多的资源。

MySQL限制每个表最多存储4096列,并且每一行数据的大小不能超过65535字节。表越宽,把表装载进内存缓冲池所占用的内存也就越大,也会消耗更多的IO。

冷热数据分离的目的是:

  • 减少磁盘IO,保证热数据的内存缓存命中率
  • 更有效的利用缓存,避免读入无用的冷数据

② 增加中间表

对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,把需要经常联合查询的数据插入中间表中,然后将原来的联合查询改为对中间表的查询,以此来提高查询的效率。

③ 增加冗余字段

设计数据库表时应尽量遵循范式理论的规约,尽可能减少冗余字段,让数据库设计看起来精致、优雅。但是合理地加入冗余字段可以提高查询速度。

表的规范化程度越高,表与表之间的关系就越多,需要连接查询的情况也就越多。尤其在数据量大,而且需要频繁进行连接的时候,为了提升效率,我们也可以考虑增加冗余字段来减少连接。

④ 优化数据类型

改进表的设计时,可以考虑优化字段的数据类型,优先选择符合存储需要的最小的数据类型。列的字段越大,建立索引时所需要的空间也就越大,这样一页中所能存储的索引节点的数量也就越少,在遍历时所需要的IO次数也就越多,索引的性能也就越差。

1.对整数类型数据进行优化

遇到整数类型的字段可以用int型。int型数据有足够大的取值范围,不用担心数据超出取值范围的问题。对于非负型的数据(如自增ID、整型IP)来说,要优先使用无符合整型unsigned来存储。因为无符合相对于有符合,同样的字节数,存储的数值范围更大。

2.既可以使用文本类型也可以使用整数类型的字段,要选择使用整数类型

跟文本类型数据相比,大整数往往占用更少的存储空间,因此在存取和比对的时候,可以占用更少的内存空间。所以在二者皆可用的情况下,尽量使用整数类型,这样可以提高查询的效率,如将IP地址转换为整型数据。

3.避免使用text、blob数据类型

MySQL内存临时表不支持text、blob这样的大数据类型。如果查询中包含这样的数据,在排序等操作时,就不能使用内存临时表,必须使用磁盘临时表进行。并且对于这种数据,MySQL还要进行二次查询,会使SQL性能变得很差。

如果一定要使用,建议把blob或者text列分离到单独的扩展中,查询时一定不要使用select * ,而只需要取出必要的列,不需要text列的数据时不要对该列进行查询。

4.避免使用ENUM类型

修改ENUM值需要使用alter语句,ENUM类型的order by 操作效率低,需要额外操作。使用TINYINT来代替ENUM类型。

5.使用TIMESTAMP存储时间

timestamp存储的时间范围1970-01-01 00:00:01 ~ 2038-01-19 03:14:07。timestamp使用4字节,datetime使用8个字节,同时timestamp具有自动赋值以及自动更新的特性。

6.用decimal代替float和double存储精确浮点数

  • 非精确浮点数:float,double
  • 精确浮点数:decimal

decimal类型为精准浮点数,在计算时不会丢失精度,尤其是财务相关的金融类数据。占用空间由定义的宽度决定,每4个字节可以存储9位数字,并且小数点要占用一个字节。可用于存储比bigint更大的整型数据。

⑤ 优化插入记录的速度

插入记录时,影响插入速度的主要是索引、唯一性校验、一次性插入记录条数等。根据这些情况可以分别进行优化,这里我们分为myisam和InnoDB存储引擎来说明

① MyISAM引擎的表

1.禁用索引

对于非空表,插入记录时MySQL会根据表的索引对插入的记录建立索引。如果插入大量数据,建立索引就会降低插入记录的速度。为了解决这种情况,可以在插入记录之前禁用索引,数据插入完毕后再开启索引。

禁用索引的语句如下:

alter table tab_name disable keys;

重新开启索引的语句如下:

alter table tab_name enable keys;

若对于空表批量导入数据,则不需要进行此操作,因为MyISAM引擎的表时在导入数据之后才建立索引的。

2.禁用唯一性检查

插入数据时,MySQL会对插入的记录进行唯一性校验。这种唯一性校验会降低插入记录的速度。为了降低这种情况对查询速度的影响,可以在插入记录之前禁用唯一性检查,等到记录插入完毕后再开启。

禁用唯一性检查的语句如下:

set unique_checks=0;

开启唯一性检查的语句如下:

set unique_checks=1;
3.使用批量插入

插入多条记录时,可以使用一条insert语句插入一条记录,也可以使用一条insert语句插入多条记录。而后者的插入速度要比前者快。

如果能用 load data infile语句就尽量使用,因为load data infile 语句导入数据的速度比 insert语句快。

Load data infile 文件所在路径 into table 表名[(字段列表)] fields 字段处理 lines 行处理。

//怎么导出去的就怎么还原(fields,lines)

Load data infile 'D:/temDirectory/backup.txt'  into table p_user 
FIELDS
ENCLOSED by '"'-- 数据使用双引号包裹;
TERMINATED by '|' -- 使用竖线分隔字段数据;
-- 行处理
LINES
STARTING by 'START:'

② InnoDB引擎的表

1.禁用唯一性检查

插入数据之前执行set unique_checks=0 来禁止对唯一性索引的检查,数据导入完成之后再运行 set unique_checks=1 。这个和MyISAM引擎的使用方法一样。

2.禁用外键检查

插入数据之前执行禁止对外键的检查,数据插入完成之后再恢复对外键的检查。

禁用外键检查的语句如下:

set foreign_key_checks=0;

恢复对外键的检查语句如下:

set foreign_key_checks=1;
3.禁止自动提交

插入数据之前禁止事务的自动提交,数据导入完成之后,执行恢复自动提交操作。

禁止自动提交的语句如下:

set autocommit=0;

恢复自动提交的语句如下:

set autocommit=1;

⑥ 使用非空约束

在设计字段的时候,如果业务运行,建议尽量使用非空约束。这样做的好处是:

  • 进行比较和计算时,省去要对null值得字段判断是否为空的开销,提高存储效率。
  • 非空字段也容易创建索引。因为索引NULL列需要额外的空间来保存,所以要占用更多的空间。使用非空约束,就可以节省存储空间(每个字段1一个bit)。

【9】其他调优策略

① 服务器语句超时处理

在MySQL8.0中可以设置服务器语句超时的限制,单位可以达到毫秒级别。当中断的执行语句超时设置的毫秒数后,服务器将终止查询影响不大的事务或连接,然后将错误报给客户端。

设置服务器语句超时的限制,可以通过设置系统变量 MAX_EXECUTION_TIME 来实现。默认情况下,MAX_EXECUTION_TIME 的值为0,代表没有时间限制。

set global MAX_EXECUTION_TIME =2000;

② 创建全局通用表空间

MySQL8.0使用 create tablespace 语句来创建一个全局通用表空间。全局表空间可以被所有的数据库的表共享,而且相比于独享表空间,使用手动创建共享表空间可以节约元数据方面的内存。 可以在创建表的时候,指定数据哪个表空间,也可以对已有表进行表空间修改等。

如下创建名为 janus 的共享表空间,SQL语句如下:

create tablespace janus add datafile 'janus.ibd' file_block_size=16K;

指定表空间,SQL语句如下:

create table test(id int,name varchar(10)) engine=innodb default charset utf8mb4 tablespace janus;

也可以通过alter table语句指定表空间,SQL语句如下:

alter table test tablespace janus;

如何删除创建的共享表空间?因为是共享表空间,所以不能直接通过drop table table_name 删除,这样操作并不能回收空间。当确定共享表空间的数据都没用,并且依赖该表空间的表均已经删除时,可以通过 drop tablespace 删除共享表空间来释放空间。如果依赖该共享表空间的表存在,就会删除失败。