0
点赞
收藏
分享

微信扫一扫

数据库大数据量的优化方案

在系统开发的初期以及使用的初期,一般不会太过于在意数据库的设计以及sql语句的优化,这就会导致系统有可能在日积月累的海量数据下越来越慢直至崩溃,所以以后在系统 数据库设计之初完备的数据库模型的设计是必须的。

优化数据库方案

对于数据库的的优化此处给出三种优化方案:

  • 1.优化现有mysql数据库
    优点:不影响现有业务,源程序不需要修改代码,成本最低
    缺点:有优化瓶颈,数据量过亿就无法继续支撑相应的业务
  • 2.升级数据库类型,换一种100%兼容mysql的数据库
    优点:不影响现有业务,源程序不需要修改代码,你几乎不需要做任何操作就能提升数据库性能
    缺点:多花钱
  • 3.一步到位,大数据解决方案,更换newsql/nosql数据库
    优点:扩展性强,成本低,没有数据容量瓶颈
    缺点:需要修改源程序代码

1.方案一:优化现有数据库

  • 1.数据库设计和表创建时就要考虑性能
  • 2.sql的编写需要注意优化
  • 3.分区
  • 4.分表
  • 5.分库

1.1 数据库设计和表创建时就要考虑性能

mysql数据库本身高度灵活,造成性能不足,严重依赖开发人员能力。也就是说开发人员能力高,则mysql性能高。这也是众多关系型数据库的通病

所以我们在设计表时应当注意:

  • 1.表字段避免null值出现,null值很难查询优化且占用额外的索引空间,推荐默认数字0代替null。
  • 2.尽量使用INT而非BIGINT,如果非负则加上UNSIGNED(这样数值容量会扩大一倍),当然能使用TINYINT、SMALLINT、MEDIUM_INT更好。
  • 3.使用枚举或整数代替字符串类型
  • 4.尽量使用TIMESTAMP而非DATETIME
  • 5.单表不要有太多字段,建议在20以内
  • 6.用整型来存IP
  • 7.数据行的长度不要超过8020字节,如果超过这个长度的话在物理页中这条数据会占用两行从而造成存储碎片,降低查询效率。
  • 8.能够用数字类型的字段尽量选择数字类型而不用字符串类型的(电话号码),这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接回逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
  • 9.对于不可变字符类型char和可变字符类型varchar 都是8000字节,char查询快,但是耗存储空间,varchar查询相对慢一些但是节省存储空间。在设计字段的时候可以灵活选择,例如用户名、密码等长度变化不大的字段可以选择CHAR,对于评论等长度变化大的字段可以选择VARCHAR。
  • 10.字段的长度在最大限度的满足可能的需要的前提下,应该尽可能的设得短一些,这样可以提高查询的效率,而且在建立索引的时候也可以减少资源的消耗。

1.2 查询优化

  • 1.保证在实现功能的基础上,尽量减少对数据库的访问次数;
  • 2.通过搜索参数,尽量减少对表的访问行数,最小化结果集,从而减轻网络负担;
  • 能够分开的操作尽量分开处理,提高每次的响应速度;
  • 3.在数据窗口使用SQL时,尽量把使用的索引放在选择的首列;
  • 4.算法的结构尽量简单;
  • 5.在查询时,不要过多地使用通配符如SELECT * FROM T1语句,要用到几列就选择几列
  • 6.在可能的情况下尽量限制尽量结果集行数
  • 7.在查询时,不要过多地使用通配符如SELECT * FROM T1语句,要用到几列就选择几列;
    在没有建索引的情况下,数据库查找某一条数据,就必须进行全表扫描了,对所有数据进行一次遍历,查找出符合条件的记录。在数据量比较小的情况下,也许看不出明显的差别,但是当数据量大的情况下,这种情况就是极为糟糕的了。
  • 8.尽量使语句符合查询优化器的规则避免全表扫描而使用索引查询

下面我重点讲解一下第8点

接下来通过查询优化规则接续看看在具体编写sql语句的时候应该需要注意哪些内容 :


查询语句中对于where的优化:

  • 9.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
    如:select id from t where num is null
    可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=0

  • 10.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。优化器将无法通过索引来确定将要命中的行数,因此需要搜索该表的所有行。

  • 11.应尽量避免在 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

  • 12.in 和 not in 也要慎用,因为IN会使系统无法使用索引,而只能直接搜索表中的数据。
    如:select id from t where num in(1,2,3)
    对于连续的数值,能用 between 就不要用 in 了
    select id from t where num between 1 and 3

  • 13.尽量避免在索引过的字符数据中,使用非打头字母搜索。这也使得引擎无法利用索引。
    见如下例子:
    SELECT * FROM T1 WHERE NAME LIKE ‘%L%’
    SELECT * FROM T1 WHERE SUBSTING(NAME,2,1)=’L’
    SELECT * FROM T1 WHERE NAME LIKE ‘L%’
    即使NAME字段建有索引,前两个查询依然无法利用索引完成加快操作,引擎不得不对全表所有数据逐条操作来完成任务。而第三个查询能够使用索引来加快操作。

  • 14.必要时强制查询优化器使用某个索引,如在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。
    如下面语句将进行全表扫描:
    select id from t where num=@num
    可以改为强制查询使用索引:
    select id from t with(index(索引名)) where num=@num

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

    • SELECT * FROM T1 WHERE F1/2=100
      应改为:SELECT * FROM T1 WHERE F1=100*2
    • SELECT * FROM RECORD WHERE SUBSTRING(CARD_NO,1,4)=’5378’
      应改为:SELECT * FROM RECORD WHERE CARD_NO LIKE ‘5378%’
    • SELECT member_number, first_name, last_name FROM members WHERE DATEDIFF(yy,datofbirth,GETDATE()) > 21
      应改为:SELECT member_number, first_name, last_name FROM members WHERE dateofbirth < DATEADD(yy,-21,GETDATE())

    即:任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。

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

  • 17.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

  • 18.很多时候用 exists是一个好的选择:
    elect num from a where num in(select num from b)
    用下面的语句替换:select num from a where exists(select 1 from b where num=a.num)

😗 where的优化就这么多啦


接下来看看临时表的优化

临时表的优化

    1. 尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
  • 20.避免频繁创建和删除临时表,以减少系统表资源的消耗。
  • 21.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
  • 22.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;
    如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
  • 23.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

  • 24.尽量避免大事务操作,提高系统并发能力

  • 25.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

  • 26.避免使用不兼容的数据类型。例如float和int、char和varchar、binary和varbinary是不兼容的。
    数据类型的不兼容可能使优化器无法执行一些本来可以进行的优化操作。

  • 27.充分利用连接条件,在某种情况下,两个表之间可能不只一个的连接条件,这时在 WHERE 子句中将连接条件完整的写上,有可能大大提高查询速度。
    例如 :

  • 28.使用视图加速查询
    把表的一个子集进行排序并创建视图,有时能加速查询。它有助于避免多重排序 操作,而且在其他方面还能简化优化器的工作。
    例如:

    如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个视图中,并按客户的名字进行排序:

    然后以下面的方式在视图中查询:

    视图中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘I/O,所以查询工作量可以得到大幅减少。

  • 29.能用DISTINCT的就不用GROUP BY

    可改为:

  • 30.能用UNION ALL就不要用UNION
    UNION ALL不执行SELECT DISTINCT函数,这样就会减少很多不必要的资源

  • 31.尽量不要用SELECT INTO语句。
    SELECT INTO 语句会导致表锁定,阻止其他用户访问该表。

以上就是常见的一些查询优化方案,但是在我们实际开发中如果数据量比较少,此时是比较不出来的,这时可以用查看执行计划,即:

1.3 算法优化

尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
游标提供了对特定集合中逐行扫描的手段,一般使用游标逐行遍历数据,根据取出的数据不同条件进行不同的操作。尤其对多表和大表定义的游标(大的数据集合)循环很容易使程序进入一个漫长的等特甚至死机。
在有些场合,有时也非得使用游标,此时也可考虑将符合条件的数据行转入临时表中,再对临时表定义游标进行操作,可时性能得到明显提高。

1.4 建立高效的索引

创建索引一般有以下两个目的:

  • 维护被索引列的唯一性
  • 提供快速访问表中数据的策略

大型数据库一般有两种索引:

  • 聚集索引
  • 非聚集索引

一个没有簇索引的表是按堆结构存储数据,所有的数据均添加在表的尾部
建立了簇索引的表,其数据在物理上会按照簇索引键的顺序存储,一个表只允许有一个簇索引,因此,根据B树结构,可以理解添加任何一种索引均能提高按索引列查询的速度,但会降低插入、更新、删除操作的性能,尤其是当填充因子(Fill Factor)较大时。
所以对索引较多的表进行频繁的插入、更新、删除操作,建表和索引时因设置较小的填充因子,以便在各数据页中留下较多的自由空间,减少页分割及重新组织的工作。
同时通过上面的定义我们也可以看出索引并不是越多越好

聚集索引和非聚集的区别

在需要了解两者的区别之前我们先弄清楚这两个索引都是干嘛的
实际上,可以把索引理解为一种特殊的目录

  • 聚集索引
  • 非聚集索引

于是我们做以下总结:

  • 聚集索引一个表只能有一个
  • 非聚集索引一个表可以存在多个

  • 聚集索引存储记录是物理上连续存在
  • 非聚集索引是逻辑上的连续,物理存储并不连续

  • 聚集索引:物理存储按照索引排序;聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序。
  • 非聚集索引:物理存储不按照索引排序;非聚集索引则就是普通索引了,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序。

  • 索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。
  • 非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。

聚集索引的优势与缺点:

对于以上内容我们再继续深入总结一下:

何时使用聚集索引或非聚集索引

在这里插入图片描述

事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。
如:返回某范围内的数据一项。

索引的使用误区

  • 1、主键就是聚集索引
    这种想法是极端错误的,是对聚集索引的一种浪费。
    虽然SQL SERVER默认是在主键上建立聚集索引的。
    通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但这样做意义不大。
    显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。
    从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。

  • 2、只要建立索引就能显著提高查询速度
    事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。
    从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。

  • 3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度
    上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。
    很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?
    如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);
    而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。
    如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。

“水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片
所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。

1.5.引擎

目前广泛使用的是MyISAM和InnoDB两种引擎:

MyISAM

MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:

  • 1.不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁
  • 2.不支持事务
  • 3.不支持外键
  • 4.不支持崩溃后的安全恢复
  • 5.在表有读取查询的同时,支持往表中插入新纪录
  • 6.支持BLOB和TEXT的前500个字符索引,支持全文索引
  • 7.支持延迟更新索引,极大提升写入性能
  • 8.对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用

InnoDB

InnoDB在MySQL 5.5后成为默认索引,它的特点是:

  • 1.支持行锁,采用MVCC来支持高并发
  • 2.支持事务
  • 3.支持外键
  • 4.支持崩溃后的安全恢复
  • 5.不支持全文索引

总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表

1.6 分区

MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码。

对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引。

用户的SQL语句是需要针对分区表做优化,SQL条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,可以通过EXPLAIN PARTITIONS来查看某条SQL语句会落在那些分区上,从而进行SQL优化,我测试,查询时不带分区条件的列,也会提高速度,故该措施值得一试。

分区的好处是:

  • 1.可以让单表存储更多的数据
  • 2.分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作
  • 3.部分查询能够从查询条件确定只落在少数分区上,速度会很快
  • 4.分区表的数据还可以分布在不同的物理设备上,从而搞笑利用多个硬件设备
  • 5.可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争
  • 6.可以备份和恢复单个分区

分区的限制和缺点:

  • 1.一个表最多只能有1024个分区
  • 2.如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来
  • 3.分区表无法使用外键约束
  • 4.NULL值会使分区过滤无效
  • 5.所有分区必须使用相同的存储引擎

分区的类型:

  • 1.RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区
  • 2.LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择
  • 3.HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式
  • 4.KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值

1.7 分表

分表就是把一张大表,按照如上过程都优化了,还是查询卡死,那就把这个表分成多张表,把一次查询分成多次查询,然后把结果组合返回给用户。

分表分为垂直拆分和水平拆分,通常以某个字段做拆分项。比如以id字段拆分为100张表: 表名为 tableName_id%100

但:分表需要修改源程序代码,会给开发带来大量工作,极大的增加了开发成本,故:只适合在开发初期就考虑到了大量数据存在,做好了分表处理,不适合应用上线了再做修改,成本太高!!!

1.8 分库

如果是已经设计好的只有一个数据库需要分成多个,也会带来大量的开发成本,得不偿失!如果是分布式环境下可以按照模块以及业务的划分在设计之初进行分库,同时建议做个读写分离。

方案2:升级数据库,换一个100%兼容mysql的数据库

mysql性能不行,那就换个。为保证源程序代码不修改,保证现有业务平稳迁移,故需要换一个100%兼容mysql的数据库。

开源选择:

1.tiDB https://github.com/pingcap/tidb

2.Cubrid https://www.cubrid.org/

开源数据库会带来大量的运维成本且其工业品质和MySQL尚有差距,有很多坑要踩,如果你公司要求必须自建数据库,那么选择该类型产品。

云数据选择

1.阿里云POLARDB

2.阿里云HybridDB for MySQL (原PetaData)

方案三:去掉mysql,换大数据引擎处理数据

数据量过亿了,没得选了,只能上大数据了。

开源解决方案

hadoop家族。hbase/hive怼上就是了。但是有很高的运维成本,一般公司是玩不起的,没十万投入是不会有很好的产出的!

云解决方案

这个就比较多了,也是一种未来趋势,大数据由专业的公司提供专业的服务,小公司或个人购买服务,大数据就像水/电等公共设施一样,存在于社会的方方面面。

国内做的最好的当属阿里云。

MaxCompute可以理解为开源的Hive,提供sql/mapreduce/ai算法/python脚本/shell脚本等方式操作数据,数据以表格的形式展现,以分布式方式存储,采用定时任务和批处理的方式处理数据。
DataWorks提供了一种工作流的方式管理你的数据处理任务和调度监控。

举报

相关推荐

0 条评论