前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >大型分布式业务平台数据库优化方法(上)

大型分布式业务平台数据库优化方法(上)

作者头像
用户2991389
发布于 2018-09-05 04:31:51
发布于 2018-09-05 04:31:51
9940
举报

微信版的原文转载出处

文章摘要:一个小小的MySQL数据库B-Tree索引可能会带来意想不到的性能优化提升……

一、数据库优化的必要性

目前在云计算大数据、电商、金融支付等应用领域,几乎所有的业务平台都免不了与数据库打交道。由于服务本身是无状态的,一个涉及全链路的业务操作往往需要访问多个数据库实例来完成。在SOA与微服务架构设计大行其道的今天,虽然解决了业务平台的组件化和服务化的问题,然而业务平台上线运行后积累的业务数据呈指数型增长,将成为系统性能的瓶颈。因此,有必要考虑业务平台数据库的性能优化问题。对于热点数据表往往多添加一个B-Tree/B+Tree索引后对平台吞吐量、平均响应时间的性能提升,往往比通过加机器、提高网络带宽能力等硬件资源水平扩展方式更为有效和节约建设成本。

由于业务快速迭代或发布上线的压力,一般在建设业务平台初期,对后端服务的数据存储设计往往会采用“单库单表”模式。大家会觉得对于业务上线初期,“单库单表”模式往往足够,更多的精力可能会放在业务流程的设计和业务代码编程上。

不知道哪天,当运维同事收到监控告警,显示数据库中存量业务数据在慢慢增多,同时在进行巡检时发现业务平台对业务访问的平均响应时间明显超过往常。研发同事通过系统日志分析发现,系统中执行每一条sql语句都耗时比较长,上线之初空空如也的数据表中不知不觉已经接近500W的数据。研发同事遇到的问题也就是比较常见的“数据库存储优化”问题,当单表数据过大时候将对业务平台的增删改查操作造成巨大的影响。下面我们将通过以下几点从原理出发一步步分析如何进行数据库优化

二、DB单表优化方法

对于业务平台来说,除非单表存储的数据会持续增长,否则一般不需要考虑拆分库表。因此,拆分库和表虽然可以提高单库和单表的容量以及系统的吞吐量,但是会对系统的逻辑、部署、运维等带来不小的影响并提高复杂度。一般,MySQL数据库的单表通过主键、索引以及分区表等方案可以存储千万级别以下的数据。如果加上本地缓存或者redis这样子的分布式缓存做缓冲,还可以进一步将表的存储容量控制在千万级别以上。下面以MySQL数据库为例,主要介绍数据库单表的优化方法。

1.数据字段类型

一般在创建库和数据表时,就应该考虑每张表的数据类型大小。为每个表的数据字段选择合适的类型会减少数据表每一行的存储大小。设想下,每行节约十几个字节,那么100W行的数据量节约的存储量是相当可观的,且存储量越小的表执行查询的速度也就越快。对于数据表的字段类型常用设计原则如下:

a、尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED,VARCHAR的长度只分配真正需要的空间;

b、尽量少使用DOUBLE和DECIMAL类型;

c、时间类型上,尽量使用TIMESTAMP而非DATETIME,其存储空间只需要DATETIME类型的一半;

d、单表不要有太多字段,建议在20以内;

e、尽量设置NOT

NULL,避免使用NULL字段,NULL字段很难查询优化且占用额外索引空间;

f、使用枚举或整数代替字符串类型;

2.添加索引以及索引原理

可能读到这个标题的时候,很多童鞋可能会觉得,添加索引嘛,这个很简单,一个MySQL语句或者客户端操作下,应该是SoEasy的事儿。其实不然,数据库的索引作用和如何添加索引相信对于大家来说应该是比较熟悉的。下文将主要从MySQL的两种主要数据库引擎(MyISAM和InnoDB)以及索引的数据结构B-Tree(B+Tree)出发,介绍如何添加正确的索引来提高业务平台的增删改查操作的性能效率。

(1)索引的本质

这里借用下MySQL官方对索引的定义:“索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。”

大家都知道数据库查询是DB的最基本的功能之一。任何人都希望查询数据能够尽可能地快,因此数据库的设计者也从查询性能的角度尽可能地去优化查询算法和选择合适的数据结构。在MySQL中,主要采用B-/B+Tree作为索引的结构(至于为什么选择这两种数据结构,限制篇幅就不展开叙述了)。

(2)MySQL两种存储引擎的索引实现

目前,大家用MySQL数据库建数据表时主要用MyISAM和InnoDB两种主流的存储引擎。对于这两种数据库存储引擎索引结构的差异可能不太会去深究,下面先介绍下这两种数据库存储引擎的差异,其中也分析了索引结构的差异。

如上面表格中“索引的结构”一栏所述,MyISAM存储引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。下面是MyISAM中索引的原理图:

MyISAM中索引检索的算法先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。这里我们假设这个表仅有三列,分别是Col1、Col2和Col3列。在MyISAM中,主索引和辅助索引(Secondary

key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。假设我们在Col2列上建立一个辅助索引则索引结构如下:

虽然InnoDB也是使用B+Tree作为索引结构,但是具体实现方式与MyISAM截然不同。InnoDB的索引结构如下图所示:

在上面索引结构的具体实现方式上有两个区别,第一个区别在于InnoDB的数据文件本身就包含了索引部分。而从上文两种存储引擎区别的表格中可以知道,MyISAM索引和数据部分是分离的,索引文件仅保存的是数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。

第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。例如,下图为定义在Col3上的一个辅助索引示意图:

了解该两种MySQL主流存储引擎的索引实现方式对正确使用和索引优化还是非常有帮助的。比如,了解InnoDB的索引实现后,就容易明白为什么不应该使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再比如,用非单调的字段作为主键在InnoDB存储引擎中并不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。

(3)通过索引优化sql性能方法

a、应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致数据库存储引擎放弃使用索引而进行全表扫描;

b、索引不是越多越好,根据业务代码的查询有针对性的创建,考虑在WHERE、ORDER/GROUP

BY命令上涉及的列建立索引,同时通过MySQL的执行计划命令EXPLAIN(关于执行计划的EXPLAIN下文会讲到)来查看自己的SQL语句是否落到索引上还是执行全表扫描;字段值分布很稀少的字段不适合建索引,例如"性别"这种只有两个值的枚举字段;

c、字符串值的字段只需建前缀索引(最左前缀匹配);同时该类字段最好不要用做主键;

d、尽量不用外键,由业务代码约束保证;

e、使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引;

f、合理地使用覆盖索引,避免不必要地回表操作;

3.EXPLAIN的具体用法

上一节在讲通过添加索引优化sql语句时候已经带到过数据库EXPLAIN执行计划,下面将主要叙述下这个EXPLAIN关键词的用户,并通过一个现实开发中遇到的问题示例进行讲解。MySQL的EXPLAIN关键字用于SQL语句的查询执行计划(QEP)。这条命令的输出结果能够让我们了解MySQL优化器是如何执行SQL语句的。然而,这条命令的输出结果并没有提供任何SQL优化的建议,但它提供的重要信息可以帮助我们做出SQL调优决策。

(1)EXPLAIN的基本用法

MySQL的EXPLAIN语法可以运行在SELECT语句或者特定表上。如果作用在表上,那么此命令等同于DESC表命令。UPDATE和DELETE的语句需要进行性能改进时,当这些SQL语句不是直接在表的主键或者索引上执行时,为了确保最优化的索引使用率,需要把它们改写成SELECT语句(以便对它们执行EXPLAIN命令)。

一般我们用EXPLAIN关键字查看SELECT查询SQL语句的QEP时,每个执行的QEP中均会出现如下几个主要结果字段。

a、key:指出优化器选择使用的索引。一般来说SQL查询中的每个表都仅使用一个索引。也存在索引合并的少数例外情况,如给定表上用到了两个或者更多索引;

b、rows:提供了QEP试图分析所有存在于累计结果集中的行数目的MySQL优化器估计值。QEP很容易描述这个很困难的统计量;

c、possible_keys:指出SQL优化器为查询选定的索引;

d、key_len:定义用于SQL语句的连接条件的键的长度;

e、table:EXPLAIN命令输出结果中的一个单独行的唯一标识符。这个值可能是表名、表的别名或者一个为查询产生临时表的标识符;

f、select_type:提供了各种表示table列引用的使用方式的类型。最常见的值包括SIMPLE(不包含子查询和其他复杂语法的简单查询)、PRIMARY(为复杂查询创建的首要表)、DERIVED(当查询的表不是物理表时,那么就用该关键字标识)、UNION(执行union的sql语句的SQL元素)、DEPENDENT SUBQUERY(为使用子查询而定义的);

g、Extra:提供了有关不同种类的MySQL优化器路径的一系列额外信息;

h、type:代表QEP中指定的表使用的连接方式,const(当前表只有一行匹配时出现该关键字)、eq_ref(表示有一行是为每个之前确定的表而读取的)、ref(表示所有具有匹配的索引值的行都被用到)、range(所有符合一个给定范围值的索引行都被用到)、ALL(表示需要一次全表扫描其他类型的值);

i、ref:可以被用来标识那些用来进行索引比较的列或者常量;

(2)EXPLAIN执行计划性能调优的示例

以某业务平台的日志表历史记录查询的SQL语句为例,在没有对数据表添加索引的情况下,对业务逻辑代码中通过MyBatis—ORM框架执行的SQL语句进行EXPLAIN的QEP查询。具体的语句如下:

EXPLAINSELECT * FROM `os_biz_operator_log` WHERE user_id ='CIDC-U-a5b982db32364abbb6ff28c893d19255' AND operator_time BETWEEN"2017-01-01" AND"2017-02-28"

可以得到结果如下:

由于执行计划的结果TYPE为ALL,存在全表扫描问题,同时可以看出该条SQL语句的每次执行查询后返回行数为256656。通过查看日志发现,每次平均执行的时间为258毫秒

因此,可以通过对USER_ID和OPERATOR_TIME两个字段为添加索引:

ALTER TABLE`os_biz_operator_log` ADD INDEX user_id_time_index(USER_ID,OPERATOR_TIME)

添加索引后通过查看执行日志,可以看出执行时间为50毫秒,速率提升超过6倍

4.分区表解决单表数据过大

当数据库单表存的数据量过大时候,可以考虑采用分区表的方案来解决。MySQL的分区表是由多个相关的物理子表组成,这些表也是可以由句柄对象表示,所以对于用户也可以直接访问各个分区,存储引擎管理分区的各个物理表和管理普通表一样(所有的底层表都必须使用相同的存储引擎),分区表的索引只是在各个底层表上各自加上一个相同的索引。

MySQL在创建表时使用PARTITION BY子句定义每个分区存放的数据。在执行查询时,优化器会根据分区定义过滤那些没有需要数据的分区,这样查询就无须扫描所有分区—只需查找包含数据的分区即可。

在分区表上操作按照下面的操作逻辑进行:

a、select查询:当查询一个分区表的时候,分区层先打开并锁住所有的底层表,优化器判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据;

b、insert操作:当写入一条记录时,分区层打开并锁住所有的底层表,然后确定哪个分区接受这条记录,再将记录写入对应的底层表;

c、delete操作:当删除一条记录时,分区层先打开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层表进行删除操作;

d、update操作:当更新一条数据时,分区层先打开并锁住所有的底层表,mysql先确定需要更新的记录在哪个分区,然后取出数据并更新,再判断更新后的数据应该放在哪个分区,然后对底层表进行写入操作,并对原数据所在的底层表进行删除操作;

虽然每个SQL操作都会打开并锁住所有的底层表,但这并不是说分区表在处理过程中是锁住全表的,如果存储引擎能够自己实现行级锁,如:innodb,则会在分区层释放对应的表锁,这个加锁和解锁过程与普通innodb上的查询类似。

(1)MySQL分区表的优点:

a、单表可以存储更多的数据;

b、分区表的数据更容易维护,可以通过清除整块分区以批量删除大量数据,也可以增加新的分区来支持新插入的数据;

c、部分查询能够从查询条件确定只落在少数分区上,查询执行速度比较快;

d、分区表的数据还可以分布在不同的物理设备上,从而高效地利用多个硬件设备;

e、可以使用分区表来避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争;

f、可以备份和恢复单个分区;

(2)分区表的限制和缺点

a、一个表最多只能分1024个区;

b、如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引都必须包含;

c、分区表中无法使用外键约束;

下面是根据时间字段来建立分区表的一个示例:

CREATETABLE IF NOT EXISTS`cloudmaster`.`session_history_month` (

`ID` INT NOT NULL AUTO_INCREMENT COMMENT '主键id自增',

`USER_NAME` VARCHAR(50) NOT NULL COMMENT '用户登录名,这里是用户名唯一',

`LOGIN_DATE`DATETIME NULL COMMENT '登录时间',

`LOGOUT_DATE` DATETIME NULL COMMENT '登出时间',

`EXPIRE_DATE` DATETIME NULL COMMENT '登录过期时间',

`CREATE_DATE` DATETIME NULL COMMENT '创建时间',

`MODIFY_DATE` DATETIME NULL COMMENT '修改时间',

`VERSION` INT NULL DEFAULT 1 COMMENT '版本号',

PRIMARY KEY (`ID`,`CREATE_DATE`))

ENGINE = INNODB

PARTITION BY RANGE (TO_DAYS(CREATE_DATE)) (

PARTITION y2016_m12 VALUES LESS THAN(TO_DAYS('2017-10-01')),

PARTITION y2017_m01 VALUES LESS THAN(TO_DAYS('2017-11-01')),

PARTITION y2017_m02 VALUES LESS THAN (TO_DAYS('2017-12-01')),

PARTITION y2017_m03 VALUES LESS THAN(TO_DAYS('2018-01-01')),

PARTITION m_d VALUES LESS THAN MAXVALUE);

(3)分区表适合的场景

a、最适合的场景数据的时间序列性比较强,则可以按时间来分区,查询时加上时间范围条件效率会非常高,同时对于不需要的历史数据能很容易批量删除;

b、如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将热点数据单独放在一个分区,让这个分区的数据能够有机会都缓存在内存中,查询时只访问一个很小的分区表,能够有效使用索引和缓存;

对于数据库的单表在千万级以内的数据量,通过以上的DB单表优化方法一般都可以应应对,但是当数据量超过千万级别时,就需要通过分库分表、读写分离数据和缓存并用等方案进行技术架构优化改造来解决。限于篇幅原因,将在下篇文章继续为大家进行阐述。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017.10.13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
只取小数点后两位函数公式_js四舍五入保留两位小数
大家好,我是架构君,一个会写代码吟诗的架构师。今天说一说只取小数点后两位函数公式_js四舍五入保留两位小数,希望能够帮助大家进步!!!
Java架构师必看
2022/04/30
4.3K0
如何在 Python 里面精确四舍五入?
花下猫语:如何精确地计算浮点数?这是计算机科学的大难题。那 Python 是如何处理浮点数的四舍五入问题的呢?今天分享的文章,对此展开了深入的剖析。
Python猫
2019/04/23
5.1K0
如何在 Python 里面精确四舍五入?
python保存两位小数的几种方法,python2保留小数
decimal 英 /’desɪm(ə)l/ 小数的 quantize 英 /’kwɒntaɪz/ 量化
全栈程序员站长
2022/09/22
3K0
python保存两位小数的几种方法,python2保留小数
为什么你需要少看中文技术博客以及如何在Python里面精确四舍五入
如果你在Google或者百度上搜索,你会发现大量的来自CSDN或者简书上面的文章讲到这一点,但是他们的说法无外乎下面几种:
青南
2019/04/25
7140
为什么你需要少看中文技术博客以及如何在Python里面精确四舍五入
python 怎么保留小数「建议收藏」
大部分语言都可以使用字符串格式化的方法来实现保留两位小数的效果,python也不例外:
全栈程序员站长
2022/09/21
9030
Python与人工智能——13、浮点数保留2位小数-保留2位有效数字
Python作为当前最为流行的一种语言与身份程序员的大家们几乎是时时刻刻分不开的,无论是做任何方面的工作基本上不会缺少Python的出现,就好似现阶段各平台的低代码Agent开发都支持的是Python语言,对其它的语言友好度都不是很高,那么,我们就非常的有必要将Python深入的了解一下,本系列文章的目的就是为了让大家对于Python有个更加直观的了解,并且要使用Python做很多的小应用,只有真正的实操了才能更好的掌握它。
红目香薰
2024/09/23
3050
Python与人工智能——13、浮点数保留2位小数-保留2位有效数字
【Python 千题 —— 基础篇】保留两位小数
这里的 "{:.2f}" 是一个格式化字符串,其中 :.2f 表示要将浮点数格式化为小数点后两位的形式。
繁依Fanyi
2023/10/12
8020
js保留两位小数方法总结
  最近在做结算系统,经常需要用到金额保留两位小数,刚开始我一直用的是Angular中的过滤器number |2,但是,这无法满足我的需求。问题是,当用户离开文本框时,我需要将用户输入的内容转换成保留两位小数的格式,我想了好久,没有想出来,然后我试了toFined()方法,这个方法也不可行,因为它将数据转换成了字符串,传给后台是错的。然后,我就找了其他方法。现在刚好有空,所以就把相关保留两位小数的方法总结了一下,不同的场景用不同的方法,即用即取。
半指温柔乐
2018/10/11
12.8K0
Python随记(2)数据类型(小数,分数) 分支循环
整形(int) 布尔类型(bool) 浮点型(float,e记法1.5e11=1.5*10的11次方) 字符串(str)类型的获取**type()**函数type('abc') <class 'str'> **isinstance()**函数isinstance('abc',str) >>True 扩展: s 为字符串 s.isalnum() 所有字符都是数字或者字母,为真返回 True,否则返回 False。 s.isalpha() 所有字符都是字母,为真返回 True,否则返回 False。 s.isdigit() 所有字符都是数字,为真返回 True,否则返回 False。 s.islower() 所有字符都是小写,为真返回 True,否则返回 False。 s.isupper() 所有字符都是大写,为真返回 True,否则返回 False。 s.istitle() 所有单词都是首字母大写,为真返回 True,否则返回 False。 s.isspace() 所有字符都是空白字符,为真返回 True,否则返回 False常用操作符:x%y 求x除以y的余数; x//y 地板除取小的整数(3//2==1); abs(x)绝对值; dirmod(x,y)=(x//y,x%y); pow(x,y)x的y次方; complex(re,im)复数(实部,虚部); a=a+1 可化简为 a += 1 c = c*5 c *=5优先级:幂运算 >:正负号>算术操作符>比较操作符>逻辑运算符(not>and>or) not 1 or 0 and 1 or 3 and 4 or 5 and 6 or 7 and 8 and 9 ==4 ;(not 1) or (0 and 1) or (3 and 4) or (5 and 6) or (7 and 8 and 9)=0 or 0 or 4 or 6 or 9= 4
用户7886150
2020/12/22
7780
【测试开发】python系列教程:decimal库
Python decimal库是Python标准库中的一部分,用于处理数字货币和金融交易。它提供了一个完整的货币处理API,可以处理各种货币常见的业务,如货币兑换、汇率计算、支付处理等。
雷子
2023/08/21
3550
【测试开发】python系列教程:decimal库
java保留两位小数
四舍五入   double   f   =   111231.5585;   BigDecimal   b   =   new   BigDecimal(f);   double   f1   =   b.setScale(2,   BigDecimal.ROUND_HALF_UP).doubleValue();   保留两位小数 ---------------------------------------------------------------
bear_fish
2018/09/19
6.3K0
万字长文,史上最全Python字符串格式化讲解
今天分享的是一篇来自群友小王(王暖暖)同学的投稿,可以说是非常的细节,堪称史上最全对字符串格式化输出的讲解了!
可以叫我才哥
2022/04/12
4.7K0
万字长文,史上最全Python字符串格式化讲解
【Python从入门到精通】(四)Python的内置数据类型有哪些呢?数字了解一下
您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦。我命由我不由天,今天依然是学习的一天。本文是【Python从入门到精通】系列的第四篇,其主要介绍Python的内置数据类型中的数字 干货满满,建议收藏,需要用到时常看看。小伙伴们如有问题及需要,欢迎踊跃留言哦~ ~ ~。
码农飞哥
2021/11/02
7240
面试复习-Python-数据类型
Python 是一种高级编程语言,具有丰富的数据类型。了解这些数据类型对于有效地编写 Python 代码至关重要。以下是对 Python 主要数据类型的详细介绍。
宅蓝三木
2024/10/09
890
python中关于round函数的小坑「建议收藏」
第一个参数是一个浮点数,第二个参数是保留的小数位数,可选,如果不写的话默认保留到整数。
全栈程序员站长
2022/09/07
8350
04. Python数据类型转换与运算符详解
全栈若城
2025/02/25
1300
04. Python数据类型转换与运算符详解
string类型保留两位小数_js保留4位小数
第一种,先把小数边整数:Math.floor(15.7784514000 * 100) / 100
全栈程序员站长
2022/09/23
8.9K0
java float四舍五入保留两位小数,java四舍五入float保留两位小数
摘要 腾兴网为您分享:java四舍五入float保留两位小数,远离手机,相机美颜,未来屋,微视等软件知识,以及流光,证券从业随身学,老a工具箱,polarr,特斯拉app,ae插件合集,福奈特,app名称,哈士奇表情,电视台直播源,思兔,门海,电子台账软件,3c电池,smartflashrecovery等软件it资讯,欢迎关注腾兴网。四舍五入我们大家都知道是什么但在java中四舍五入函数是什么如何实现float保留指定位数?具体我们来看小编整理的一些例子。 例子1 float f = 34.237323f; BigDecimal b = new BigDecimal(f); float f…
全栈程序员站长
2022/08/31
8400
js保留两位小数四舍五入_parsefloat保留两位小数
如果大家想对javascript有系统深入的学习,可以参阅 JavaScript启示录 PDF原书完整版 这本经典书籍
全栈程序员站长
2022/09/23
5.1K0
在Python里想要四舍五入有多麻烦?
然而让人没想到的是,一个简单的四舍五入操作,在Python里居然这么难搞,网上还一堆错误的教程。
Crossin先生
2024/03/26
1630
在Python里想要四舍五入有多麻烦?
推荐阅读
相关推荐
只取小数点后两位函数公式_js四舍五入保留两位小数
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档