MySQL 对于千万级的大表要怎么优化?

作者:互联网编程
链接:https://www.zhihu.com/question/19719997/answer/549041957
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

修改回答,老有人说我是发广告,好吧,知乎不适合我这种玩

  • 第一我不是阿里,腾讯员工,而且我也没必要帮他们发广告
  • 第二,阿里云国内第一,世界第三,不需要我这种小白发广告,本身人家就是牛逼
  • 第三,我只是记录我解决问题的过程,并不是回答如何优化!我的标准是,最低成本!不要花企业的钱
  • 第四,评论你们一个表多少亿数据的,请公布你的硬件配置,或者你的优化建议。
  • 第五,这次维护的表是六年前的,毕业生项目,硬件配置8核2G云服务

以下是原来答案


问题概述

使用阿里云rds for MySQL数据库(就是MySQL5.6版本),有个用户上网记录表6个月的数据量近2000万,保留最近一年的数据量达到4000万,查询速度极慢,日常卡死。严重影响业务。

问题前提:老系统,当时设计系统的人大概是大学没毕业,表设计和sql语句写的不仅仅是垃圾,简直无法直视。原开发人员都已离职,到我来维护,这就是传说中的维护不了就跑路,然后我就是掉坑的那个!!!

我尝试解决该问题,so,有个这个日志。

方案概述

  • 方案一:优化现有mysql数据库。优点:不影响现有业务,源程序不需要修改代码,成本最低。缺点:有优化瓶颈,数据量过亿就玩完了。
  • 方案二:升级数据库类型,换一种100%兼容mysql的数据库。优点:不影响现有业务,源程序不需要修改代码,你几乎不需要做任何操作就能提升数据库性能,缺点:多花钱
  • 方案三:一步到位,大数据解决方案,更换newsql/nosql数据库。优点:没有数据容量瓶颈,缺点:需要修改源程序代码,影响业务,总成本最高。

 

以上三种方案,按顺序使用即可,数据量在亿级别一下的没必要换nosql,开发成本太高。三种方案我都试了一遍,而且都形成了落地解决方案。该过程心中慰问跑路的那几个开发者一万遍 :)

方案一详细说明:优化现有mysql数据库

跟阿里云数据库大佬电话沟通 and Google解决方案 and 问群里大佬,总结如下(都是精华):

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

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

mysql数据库本身高度灵活,造成性能不足,严重依赖开发人员能力。也就是说开发人员能力高,则mysql性能高。这也是很多关系型数据库的通病,所以公司的dba通常工资巨高。

设计表时要注意:

  • 表字段避免null值出现,null值很难查询优化且占用额外的索引空间,推荐默认数字0代替null。
  • 尽量使用INT而非BIGINT,如果非负则加上UNSIGNED(这样数值容量会扩大一倍),当然能使用TINYINT、SMALLINT、MEDIUM_INT更好。
  • 使用枚举或整数代替字符串类型
  • 尽量使用TIMESTAMP而非DATETIME
  • 单表不要有太多字段,建议在20以内
  • 用整型来存IP

索引

  • 索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描
  • 应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描
  • 值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段
  • 字符字段只建前缀索引
  • 字符字段最好不要做主键
  • 不用外键,由程序保证约束
  • 尽量不用UNIQUE,由程序保证约束
  • 使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引

简言之就是使用合适的数据类型,选择合适的索引

1.选择合适的数据类型

  • (1)使用可存下数据的最小的数据类型,整型 < date,time < char,varchar < blob
  • (2)使用简单的数据类型,整型比字符处理开销更小,因为字符串的比较更复杂。如,int类型存储时间类型,bigint类型转ip函数
  • (3)使用合理的字段属性长度,固定长度的表会更快。使用enum、char而不是varchar
  • (4)尽可能使用not null定义字段
  • (5)尽量少用text,非用不可最好分表

2.选择合适的索引列

  • (1)查询频繁的列,在where,group by,order by,on从句中出现的列
  • (2)where条件中<,<=,=,>,>=,between,in,以及like 字符串+通配符(%)出现的列
  • (3)长度小的列,索引字段越小越好,因为数据库的存储单位是页,一页中能存下的数据越多越好
  • (4)离散度大(不同的值多)的列,放在联合索引前面。查看离散度,通过统计不同的列值来实现,count越大,离散程度越高:
原开发人员已经跑路,该表早已建立,我无法修改,故:该措辞无法执行,放弃!

2.sql的编写需要注意优化

  • 使用limit对查询结果的记录进行限定
  • 避免select *,将需要查找的字段列出来
  • 使用连接(join)来代替子查询
  • 拆分大的delete或insert语句
  • 可通过开启慢查询日志来找出较慢的SQL
  • 不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
  • sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库
  • 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来分页,每页数量也不要太大
原开发人员已经跑路,程序已经完成上线,我无法修改sql,故:该措辞无法执行,放弃!

引擎

引擎

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

1. MyISAM

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

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

2. InnoDB

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

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

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

MyISAM速度可能超快,占用存储空间也小,但是程序要求事务支持,故InnoDB是必须的,故该方案无法执行,放弃!

3.分区

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

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

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

分区的好处是:

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

分区的限制和缺点:

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

分区的类型:

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

具体关于mysql分区的概念请自行google或查询官方文档,我这里只是抛砖引玉了。

我首先根据月份把上网记录表RANGE分区了12份,查询效率提高6倍左右,效果不明显,故:换id为HASH分区,分了64个分区,查询速度提升显著。问题解决!
结果如下:PARTITION BY HASH (id)PARTITIONS 64

select count(*) from readroom_website; --11901336行记录

/* 受影响行数: 0 已找到记录: 1 警告: 0 持续时间 1 查询: 5.734 sec. */

select * from readroom_website where month(accesstime) =11 limit 10;

/* 受影响行数: 0 已找到记录: 10 警告: 0 持续时间 1 查询: 0.719 sec. */

4.分表

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

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

但:分表需要修改源程序代码,会给开发带来大量工作,极大的增加了开发成本,故:只适合在开发初期就考虑到了大量数据存在,做好了分表处理,不适合应用上线了再做修改,成本太高!!!而且选择这个方案,都不如选择我提供的第二第三个方案的成本低!故不建议采用。

5.分库

把一个数据库分成多个,建议做个读写分离就行了,真正的做分库也会带来大量的开发成本,得不偿失!不推荐使用。

方案二详细说明:升级数据库,换一个100%兼容mysql的数据库

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

1. 开源选择

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

2. 云数据选择

  • 阿里云POLARDB

云数据库POLARDB_高吞吐在线事务处理_关系型云数据库_价格_购买 - 阿里云

官方介绍语:POLARDB 是阿里云自研的下一代关系型分布式云原生数据库,100%兼容MySQL,存储容量最高可达 100T,性能最高提升至 MySQL 的 6 倍。POLARDB 既融合了商业数据库稳定、可靠、高性能的特征,又具有开源数据库简单、可扩展、持续迭代的优势,而成本只需商用数据库的 1/10。

我开通测试了一下,支持免费mysql的数据迁移,无操作成本,性能提升在10倍左右,价格跟rds相差不多,是个很好的备选解决方案!

  • 阿里云OcenanBase

淘宝使用的,扛得住双十一,性能卓著,但是在公测中,我无法尝试,但值得期待

  • 阿里云HybridDB for MySQL (原PetaData)

云数据库HybridDB for MySQL_产品详情_阿里云

官方介绍:云数据库HybridDB for MySQL (原名PetaData)是同时支持海量数据在线事务(OLTP)和在线分析(OLAP)的HTAP(Hybrid Transaction/Analytical Processing)关系型数据库。

我也测试了一下,是一个olap和oltp兼容的解决方案,但是价格太高,每小时高达10块钱,用来做存储太浪费了,适合存储和分析一起用的业务。

  • 腾讯云DCDB

分布式数据库 - 腾讯云

官方介绍:DCDB又名TDSQL,一种兼容MySQL协议和语法,支持自动水平拆分的高性能分布式数据库——即业务显示为完整的逻辑表,数据却均匀的拆分到多个分片中;每个分片默认采用主备架构,提供灾备、恢复、监控、不停机扩容等全套解决方案,适用于TB或PB级的海量数据场景。

腾讯的我不喜欢用,不多说。原因是出了问题找不到人,线上问题无法解决头疼!但是他价格便宜,适合超小公司,玩玩。

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

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

1. 开源解决方案

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

2.云解决方案

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

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

我选择了阿里云的MaxCompute配合DataWorks,使用超级舒服,按量付费,成本极低。

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

当然你也可以选择阿里云hbase等其他产品,我这里主要是离线处理,故选择MaxCompute,基本都是图形界面操作,大概写了300行sql,费用不超过100块钱就解决了数据处理问题。

编辑于 2019-01-14

​赞同 1939​​127 条评论

​分享

​收藏​喜欢收起​

更多回答

zhuqz

zhuqz

一个来自遥远的德州的攻城尸,专攻咋哇之城的城池...

2,311 人赞同了该回答

很多人第一反应是各种切分;我给的顺序是:
第一优化你的sql和索引;

第二加缓存,memcached,redis;

第三以上都做了后,还是慢,就做主从复制或主主复制,读写分离,可以在应用层做,效率高,也可以用三方工具,第三方工具推荐360的atlas,其它的要么效率不高,要么没人维护;

第四如果以上都做了还是慢,不要想着去做切分,mysql自带分区表,先试试这个,对你的应用是透明的,无需更改代码,但是sql语句是需要针对分区表做优化的,sql条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,另外分区表还有一些坑,在这里就不多说了;

第五如果以上都做了,那就先做垂直拆分,其实就是根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;

第六才是水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key,为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;

mysql数据库一般都是按照这个步骤去演化的,成本也是由低到高;

有人也许要说第一步优化sql和索引这还用说吗?的确,大家都知道,但是很多情况下,这一步做的并不到位,甚至有的只做了根据sql去建索引,根本没对sql优化(中枪了没?),除了最简单的增删改查外,想实现一个查询,可以写出很多种查询语句,不同的语句,根据你选择的引擎、表中数据的分布情况、索引情况、数据库优化策略、查询中的锁策略等因素,最终查询的效率相差很大;优化要从整体去考虑,有时你优化一条语句后,其它查询反而效率被降低了,所以要取一个平衡点;即使精通mysql的话,除了纯技术面优化,还要根据业务面去优化sql语句,这样才能达到最优效果;你敢说你的sql和索引已经是最优了吗?

再说一下不同引擎的优化,myisam读的效果好,写的效率差,这和它数据存储格式,索引的指针和锁的策略有关的,它的数据是顺序存储的(innodb数据存储方式是聚簇索引),他的索引btree上的节点是一个指向数据物理位置的指针,所以查找起来很快,(innodb索引节点存的则是数据的主键,所以需要根据主键二次查找);myisam锁是表锁,只有读读之间是并发的,写写之间和读写之间(读和插入之间是可以并发的,去设置concurrent_insert参数,定期执行表优化操作,更新操作就没有办法了)是串行的,所以写起来慢,并且默认的写优先级比读优先级高,高到写操作来了后,可以马上插入到读操作前面去,如果批量写,会导致读请求饿死,所以要设置读写优先级或设置多少写操作后执行读操作的策略;myisam不要使用查询时间太长的sql,如果策略使用不当,也会导致写饿死,所以尽量去拆分查询效率低的sql,

innodb一般都是行锁,这个一般指的是sql用到索引的时候,行锁是加在索引上的,不是加在数据记录上的,如果sql没有用到索引,仍然会锁定表,mysql的读写之间是可以并发的,普通的select是不需要锁的,当查询的记录遇到锁时,用的是一致性的非锁定快照读,也就是根据数据库隔离级别策略,会去读被锁定行的快照,其它更新或加锁读语句用的是当前读,读取原始行;因为普通读与写不冲突,所以innodb不会出现读写饿死的情况,又因为在使用索引的时候用的是行锁,锁的粒度小,竞争相同锁的情况就少,就增加了并发处理,所以并发读写的效率还是很优秀的,问题在于索引查询后的根据主键的二次查找导致效率低;

ps:很奇怪,为什innodb的索引叶子节点存的是主键而不是像mysism一样存数据的物理地址指针吗?如果存的是物理地址指针不就不需要二次查找了吗,这也是我开始的疑惑,根据mysism和innodb数据存储方式的差异去想,你就会明白了,我就不费口舌了!

所以innodb为了避免二次查找可以使用索引覆盖技术,无法使用索引覆盖的,再延伸一下就是基于索引覆盖实现延迟关联;不知道什么是索引覆盖的,建议你无论如何都要弄清楚它是怎么回事!

尽你所能去优化你的sql吧!说它成本低,却又是一项费时费力的活,需要在技术与业务都熟悉的情况下,用心去优化才能做到最优,优化后的效果也是立竿见影的!

编辑于 2016-01-19

​赞同 2311​​102 条评论

​分享

​收藏​喜欢收起​

jeanron100

jeanron100

竞技世界(北京)网络技术有限公司 资深DBA

680 人赞同了该回答

千万级大表如何优化,这是一个很有技术含量的问题,通常我们的直觉思维都会跳转到拆分或者数据分区,在此我想做一些补充和梳理,想和大家做一些这方面的经验总结,也欢迎大家提出建议。

 

从一开始脑海里开始也是火光四现,到不断的自我批评,后来也参考了一些团队的经验,我整理了下面的大纲内容。

 

 

既然要吃透这个问题,我们势必要回到本源,我把这个问题分为三部分:

“千万级”,“大表”,“优化”,

也分别对应我们在图中标识的

“数据量”,“对象”和“目标”。

 

我来逐步展开说明一下,从而给出一系列的解决方案。

1.数据量:千万级

千万级其实只是一个感官的数字,就是我们印象中的数据量大。 这里我们需要把这个概念细化,因为随着业务和时间的变化,数据量也会有变化,我们应该是带着一种动态思维来审视这个指标,从而对于不同的场景我们应该有不同的处理策略。

1) 数据量为千万级,可能达到亿级或者更高

通常是一些数据流水,日志记录的业务,里面的数据随着时间的增长会逐步增多,超过千万门槛是很容易的一件事情。

2) 数据量为千万级,是一个相对稳定的数据量

如果数据量相对稳定,通常是在一些偏向于状态的数据,比如有1000万用户,那么这些用户的信息在表中都有相应的一行数据记录,随着业务的增长,这个量级相对是比较稳定的。

 

3) 数据量为千万级,不应该有这么多的数据

这种情况是我们被动发现的居多,通常发现的时候已经晚了,比如你看到一个配置表,数据量上千万;或者说一些表里的数据已经存储了很久,99%的数据都属于过期数据或者垃圾数据。

 

数据量是一个整体的认识,我们需要对数据做更近一层的理解,这就可以引出第二个部分的内容。

2.对象:数据表

数据操作的过程就好比数据库中存在着多条管道,这些管道中都流淌着要处理的数据,这些数据的用处和归属是不一样的。

一般根据业务类型把数据分为三种:

(1)流水型数据

流水型数据是无状态的,多笔业务之间没有关联,每次业务过来的时候都会产生新的单据,比如交易流水、支付流水,只要能插入新单据就能完成业务,特点是后面的数据不依赖前面的数据,所有的数据按时间流水进入数据库。

(2)状态型数据

状态型数据是有状态的,多笔业务之间依赖于有状态的数据,而且要保证该数据的准确性,比如充值时必须要拿到原来的余额,才能支付成功。

(3)配置型数据

此类型数据数据量较小,而且结构简单,一般为静态数据,变化频率很低。

至此,我们可以对整体的背景有一个认识了,如果要做优化,其实要面对的是这样的3*3的矩阵,如果要考虑表的读写比例(读多写少,读少写多...),那么就会是3*3*4=24种,显然做穷举是不显示的,而且也完全没有必要,可以针对不同的数据存储特性和业务特点来指定不同的业务策略。

对此我们采取抓住重点的方式,把常见的一些优化思路梳理出来,尤其是里面的核心思想,也是我们整个优化设计的一把尺子,而难度决定了我们做这件事情的动力和风险。

而对于优化方案,我想采用面向业务的维度来进行阐述。

 

3.目标:优化

在这个阶段,我们要说优化的方案了,总结的有点多,相对来说是比较全了。

整体分为五个部分:

 

 

其实我们通常所说的分库分表等方案只是其中的一小部分,如果展开之后就比较丰富了。

 

 

其实不难理解,我们要支撑的表数据量是千万级别,相对来说是比较大了,DBA要维护的表肯定不止一张,如何能够更好的管理,同时在业务发展中能够支撑扩展,同时保证性能,这是摆在我们面前的几座大山。

我们分别来说一下这五类改进方案:

优化设计方案1.规范设计

在此我们先提到的是规范设计,而不是其他高大上的设计方案。

黑格尔说:秩序是自由的第一条件。在分工协作的工作场景中尤其重要,否则团队之间互相牵制太多,问题多多。

规范设计我想提到如下的几个规范,其实只是属于开发规范的一部分内容,可以作为参考。

 

 

规范的本质不是解决问题,而是有效杜绝一些潜在问题,对于千万级大表要遵守的规范,我梳理了如下的一些细则,基本可以涵盖我们常见的一些设计和使用问题,比如表的字段设计不管三七二十一,都是varchar(500),其实是很不规范的一种实现方式,我们来展开说一下这几个规范。

 

1)配置规范

(1)MySQL数据库默认使用InnoDB存储引擎。

(2)保证字符集设置统一,MySQL数据库相关系统、数据库、表的字符集使都用UTF8,应用程序连接、展示等可以设置字符集的地方也都统一设置为UTF8字符集。

注:UTF8格式是存储不了表情类数据,需要使用UTF8MB4,可在MySQL字符集里面设置。在8.0中已经默认为UTF8MB4,可以根据公司的业务情况进行统一或者定制化设置。

(3)MySQL数据库的事务隔离级别默认为RR(Repeatable-Read),建议初始化时统一设置为RC(Read-Committed),对于OLTP业务更适合。

(4)数据库中的表要合理规划,控制单表数据量,对于MySQL数据库来说,建议单表记录数控制在2000W以内。

(5)MySQL实例下,数据库、表数量尽可能少;数据库一般不超过50个,每个数据库下,数据表数量一般不超过500个(包括分区表)。

 

2)建表规范

(1)InnoDB禁止使用外键约束,可以通过程序层面保证。

(2)存储精确浮点数必须使用DECIMAL替代FLOAT和DOUBLE。

(3)整型定义中无需定义显示宽度,比如:使用INT,而不是INT(4)。

(4)不建议使用ENUM类型,可使用TINYINT来代替。

(5)尽可能不使用TEXT、BLOB类型,如果必须使用,建议将过大字段或是不常用的描述型较大字段拆分到其他表中;另外,禁止用数据库存储图片或文件。

(6)存储年时使用YEAR(4),不使用YEAR(2)。

(7)建议字段定义为NOT NULL。

(8)建议DBA提供SQL审核工具,建表规范性需要通过审核工具审核后

 

3)命名规范

(1)库、表、字段全部采用小写。

(2)库名、表名、字段名、索引名称均使用小写字母,并以“_”分割。

(3)库名、表名、字段名建议不超过12个字符。(库名、表名、字段名支持最多64个字符,但为了统一规范、易于辨识以及减少传输量,统一不超过12字符)

(4)库名、表名、字段名见名知意,不需要添加注释。

对于对象命名规范的一个简要总结如下表4-1所示,供参考。

4)索引规范

(1)索引建议命名规则:idx_col1_col2[_colN]、uniq_col1_col2[_colN](如果字段过长建议采用缩写)。

(2)索引中的字段数建议不超过5个。

(3)单张表的索引个数控制在5个以内。

(4)InnoDB表一般都建议有主键列,尤其在高可用集群方案中是作为必须项的。

(5)建立复合索引时,优先将选择性高的字段放在前面。

(6)UPDATE、DELETE语句需要根据WHERE条件添加索引。

(7)不建议使用%前缀模糊查询,例如LIKE “%weibo”,无法用到索引,会导致全表扫描。

(8)合理利用覆盖索引,例如:

(9)SELECT email,uid FROM user_email WHERE uid=xx,如果uid不是主键,可以创建覆盖索引idx_uid_email(uid,email)来提高查询效率。

(10)避免在索引字段上使用函数,否则会导致查询时索引失效。

(11)确认索引是否需要变更时要联系DBA。

 

5)应用规范

(1)避免使用存储过程、触发器、自定义函数等,容易将业务逻辑和DB耦合在一起,后期做分布式方案时会成为瓶颈。

(2)考虑使用UNION ALL,减少使用UNION,因为UNION ALL不去重,而少了排序操作,速度相对比UNION要快,如果没有去重的需求,优先使用UNION ALL。

(3)考虑使用limit N,少用limit M,N,特别是大表或M比较大的时候。

(4)减少或避免排序,如:group by语句中如果不需要排序,可以增加order by null。

(5)统计表中记录数时使用COUNT(*),而不是COUNT(primary_key)和COUNT(1);InnoDB表避免使用COUNT(*)操作,计数统计实时要求较强可以使用Memcache或者Redis,非实时统计可以使用单独统计表,定时更新。

(6)做字段变更操作(modify column/change column)的时候必须加上原有的注释属性,否则修改后,注释会丢失。

(7)使用prepared statement可以提高性能并且避免SQL注入。

(8)SQL语句中IN包含的值不应过多。

(9)UPDATE、DELETE语句一定要有明确的WHERE条件。

(10)WHERE条件中的字段值需要符合该字段的数据类型,避免MySQL进行隐式类型转化。

(11)SELECT、INSERT语句必须显式的指明字段名称,禁止使用SELECT * 或是INSERT INTO table_name values()。

(12)INSERT语句使用batch提交(INSERT INTO table_name VALUES(),(),()……),values的个数不应过多。

 

优化设计方案2:业务层优化

业务层优化应该是收益最高的优化方式了,而且对于业务层完全可见,主要有业务拆分,数据拆分和两类常见的优化场景(读多写少,读少写多)

 

 

1)业务拆分

ü 将混合业务拆分为独立业务

ü 将状态和历史数据分离

业务拆分其实是把一个混合的业务剥离成为更加清晰的独立业务,这样业务1,业务2。。。独立的业务使得业务总量依旧很大,但是每个部分都是相对独立的,可靠性依然有保证。

对于状态和历史数据分离,我可以举一个例子来说明。

例如:我们有一张表Account,假设用户余额为100。

 

 

我们需要在发生数据变更后,能够追溯数据变更的历史信息,如果对账户更新状态数据,增加100的余额,这样余额为200。

这个过程可能对应一条update语句,一条insert语句。

对此我们可以改造为两个不同的数据源,account和account_hist

在account_hist中就会是两条insert记录,如下:

 

 

而在account中则是一条update语句,如下:

 

 

这也是一种很基础的冷热分离,可以大大减少维护的复杂度,提高业务响应效率。

2)数据拆分

2.1 按照日期拆分,这种使用方式比较普遍,尤其是按照日期维度的拆分,其实在程序层面的改动很小,但是扩展性方面的收益很大。

  • 数据按照日期维度拆分,如test_20191021
  • 数据按照周月为维度拆分,如test_201910
  • 数据按照季度,年维度拆分,如test_2019

2.2 采用分区模式,分区模式也是常见的使用方式,采用hash,range等方式会多一些,在MySQL中我是不大建议使用分区表的使用方式,因为随着存储容量的增长,数据虽然做了垂直拆分,但是归根结底,数据其实难以实现水平扩展,在MySQL中是有更好的扩展方式。

2.3 读多写少优化场景

采用缓存,采用Redis技术,将读请求打在缓存层面,这样可以大大降低MySQL层面的热点数据查询压力。

2.4 读少写多优化场景,可以采用三步走:

1) 采用异步提交模式,异步对于应用层来说最直观的就是性能的提升,产生最少的同步等待。

2) 使用队列技术,大量的写请求可以通过队列的方式来进行扩展,实现批量的数据写入。

3) 降低写入频率,这个比较难理解,我举个例子

对于业务数据,比如积分类,相比于金额来说业务优先级略低的场景,如果数据的更新过于频繁,可以适度调整数据更新的范围(比如从原来的每分钟调整为10分钟)来减少更新的频率。

例如:更新状态数据,积分为200,如下图所示

 

 

可以改造为,如下图所示。

 

 

如果业务数据在短时间内更新过于频繁,比如1分钟更新100次,积分从100到10000,则可以根据时间频率批量提交。

例如:更新状态数据,积分为100,如下图所示。

 

 

无需生成100个事务(200条SQL语句)可以改造为2条SQL语句,如下图所示。

 

 

对于业务指标,比如更新频率细节信息,可以根据具体业务场景来讨论决定。

 

优化设计方案3:架构层优化

架构层优化其实就是我们认为的那种技术含量很高的工作,我们需要根据业务场景在架构层面引入一些新的花样来。

 

 

 

3.1.系统水平扩展场景

3.1.1采用中间件技术,可以实现数据路由,水平扩展,常见的中间件有MyCAT,ShardingSphere,ProxySQL等

 

 

3.1.2 采用读写分离技术,这是针对读需求的扩展,更侧重于状态表,在允许一定延迟的情况下,可以采用多副本的模式实现读需求的水平扩展,也可以采用中间件来实现,如MyCAT,ProxySQL,MaxScale,MySQL Router等

 

 

3.1.3 采用负载均衡技术,常见的有LVS技术或者基于域名服务的Consul技术等

 

3.2.兼顾OLTP+OLAP的业务场景,可以采用NewSQL,优先兼容MySQL协议的HTAP技术栈,如TiDB

3.3.离线统计的业务场景,有几类方案可供选择。

3.3.1 采用NoSQL体系,主要有两类,一类是适合兼容MySQL协议的数据仓库体系,常见的有Infobright或者ColumnStore,另外一类是基于列式存储,属于异构方向,如HBase技术

3.3.2 采用数仓体系,基于MPP架构,如使用Greenplum统计,如T+1统计

 

优化设计方案4:数据库优化

 

数据库优化,其实可打的牌也不少,但是相对来说空间没有那么大了,我们来逐个说一下。

 

 

 

4.1 事务优化

根据业务场景选择事务模型,是否是强事务依赖

对于事务降维策略,我们来举出几个小例子来。

4.1.1 降维策略1:存储过程调用转换为透明的SQL调用

对于新业务而言,使用存储过程显然不是一个好主意,MySQL的存储过程和其他商业数据库相比,功能和性能都有待验证,而且在目前轻量化的业务处理中,存储过程的处理方式太“重”了。

有些应用架构看起来是按照分布式部署的,但在数据库层的调用方式是基于存储过程,因为存储过程封装了大量的逻辑,难以调试,而且移植性不高,这样业务逻辑和性能压力都在数据库层面了,使得数据库层很容易成为瓶颈,而且难以实现真正的分布式。

所以有一个明确的改进方向就是对于存储过程的改造,把它改造为SQL调用的方式,可以极大地提高业务的处理效率,在数据库的接口调用上足够简单而且清晰可控。

 

4.1.2 降维策略2:DDL操作转换为DML操作

有些业务经常会有一种紧急需求,总是需要给一个表添加字段,搞得DBA和业务同学都挺累,可以想象一个表有上百个字段,而且基本都是name1,name2……name100,这种设计本身就是有问题的,更不用考虑性能了。究其原因,是因为业务的需求动态变化,比如一个游戏装备有20个属性,可能过了一个月之后就增加到了40个属性,这样一来,所有的装备都有40个属性,不管用没用到,而且这种方式也存在诸多的冗余。

我们在设计规范里面也提到了一些设计的基本要素,在这些基础上需要补充的是,保持有限的字段,如果要实现这些功能的扩展,其实完全可以通过配置化的方式来实现,比如把一些动态添加的字段转换为一些配置信息。配置信息可以通过DML的方式进行修改和补充,对于数据入口也可以更加动态、易扩展。

4.1.3 降维策略3:Delete操作转换为高效操作

有些业务需要定期来清理一些周期性数据,比如表里的数据只保留一个月,那么超出时间范围的数据就要清理掉了,而如果表的量级比较大的情况下,这种Delete操作的代价实在太高,我们可以有两类解决方案来把Delete操作转换为更为高效的方式。

第一种是根据业务建立周期表,比如按照月表、周表、日表等维度来设计,这样数据的清理就是一个相对可控而且高效的方式了。

第二种方案是使用MySQL rename的操作方式,比如一张2千万的大表要清理99%的数据,那么需要保留的1%的数据我们可以很快根据条件过滤补录,实现“移形换位”。

 

4.2 SQL优化

其实相对来说需要的极简的设计,很多点都在规范设计里面了,如果遵守规范,八九不离十的问题都会杜绝掉,在此补充几点:

4.2.1 SQL语句简化,简化是SQL优化的一大利器,因为简单,所以优越。

4.2.2 尽可能避免或者杜绝多表复杂关联,大表关联是大表处理的噩梦,一旦打开了这个口子,越来越多的需求需要关联,性能优化就没有回头路了,更何况大表关联是MySQL的弱项,尽管Hash Join才推出,不要像掌握了绝对大杀器一样,在商业数据库中早就存在,问题照样层出不穷。

4.2.3 SQL中尽可能避免反连接,避免半连接,这是优化器做得薄弱的一方面,什么是反连接,半连接?其实比较好理解,举个例子,not in ,not exists就是反连接,in,exists就是半连接,在千万级大表中出现这种问题,性能是几个数量级的差异。

 

4.3 索引优化

应该是大表优化中需要把握的一个度。

4.3.1 首先必须有主键,规范设计中第一条就是,此处不接收反驳。

4.3.2 其次,SQL查询基于索引或者唯一性索引,使得查询模型尽可能简单。

4.3.3 最后,尽可能杜绝范围数据的查询,范围扫描在千万级大表情况下还是尽可能减少。

优化设计方案4:管理优化

这部分应该是在所有的解决方案中最容易被忽视的部分了,我放在最后,在此也向运维同事致敬,总是为很多认为本应该正常的问题尽职尽责(背锅)。

 

 

千万级大表的数据清理一般来说是比较耗时的,在此建议在设计中需要完善冷热数据分离的策略,可能听起来比较拗口,我来举一个例子,把大表的Drop 操作转换为可逆的DDL操作。

Drop操作是默认提交的,而且是不可逆的,在数据库操作中都是跑路的代名词,MySQL层面目前没有相应的Drop操作恢复功能,除非通过备份来恢复,但是我们可以考虑将Drop操作转换为一种可逆的DDL操作。

MySQL中默认每个表有一个对应的ibd文件,其实可以把Drop操作转换为一个rename操作,即把文件从testdb迁移到testdb_arch下面;从权限上来说,testdb_arch是业务不可见的,rename操作可以平滑的实现这个删除功能,如果在一定时间后确认可以清理,则数据清理对于已有的业务流程是不可见的,如下图所示。

 

 

 

此外,还有两个额外建议,一个是对于大表变更,尽可能考虑低峰时段的在线变更,比如使用pt-osc工具或者是维护时段的变更,就不再赘述了。

 

 

最后总结一下,其实就是一句话:

千万级大表的优化是根据业务场景,以成本为代价进行优化的,绝对不是孤立的一个层面的优化

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页