前言
在程序设计中有一种常用的提升数据查询性能的手段以--空间换时间。典型的场景就是使用“缓存”,在查询数据库之前加一层“全局共享缓存”(如:redis),更有甚者在应用实例内部在加一层“本地缓存”。以java应用+mysql数据库为例,该架构设计方式如下:
数据查询逻辑为:
本地缓存的查询速度是纳秒级
Redis缓存的查询速度是毫秒级
Msyql数据的查询速度是毫秒级-秒级
有人会说既然本地缓存是最快的为什么不直接用本地缓存,还要使用redis缓存呢?本地缓存说白就是jvm内存,空间毕竟是有限的,再者分布式多实例部署的应用有多个jvm实例,本地缓存分散在各个实例内部不便于同步更新,使用上存在局限。具体怎么去权衡“本地缓存”和“全局共享缓存”不是本次讨论的重点,不再过多介绍。
所以为了提高性能需要把同一份数据存放三份,这就是数据冗余,典型的“以空间换时间”的使用场景。
可以发现在该场景中,最终瓶颈还是msyql数据库(在缓存失效时都会落到数据库),如果想要进一步的优化性能,一个重要的优化点还是在msyql数据库查询性能优化上(当然还有一个点就是优化程序本身)。
“以空间换时间”的方式同样适用运用到mysql数据库的性能优化中,主要体现在三个地方:表字段冗余、读写分离之镜像复制、读写分离之非对称复制。下面根据不同的场景,分别进行讲解。
表字段冗余
读书期间,在数据库表设计章节,相信大家都学习过“范式”。如果完全遵循“三范式”设计的数据库,对于数据存储来说可以极大的降低数据的存储量。但对于数据查询来说,会有很多联表查询,这会非常影响性能。对于高频查询的sql语句来说 “联表查询”绝对是个灾难。
这时的常用手段就是“表字段冗余”,举个真实的案例:在一个项目初期,使用一张表名为“sale_info”的表存放活动信息。随着业务的发展,该表的字段越来越多,为了防止一张表的字段太多,最简单的做法就是新增一个扩展表“sale_info_ext”,后续新增的字段都放到这张扩展表里。
这种简单的处理方式,解决了“宽表”问题。但同时又引入了新的问题,在查询活动信息时,由于业务数据分散在两张表里,经常需要做“联表查询”:select a.xx,b.xx from sale_info a left join sale_info_ext b on a.id=b.sale_id where “省略其他条件”。刚开始没有发现问题,但随着业务的增长,两张表的数据越来越多,应用程序经常出现“timeout”现象,通过分析慢查询日志有一条高频“联表查询”语句在中暴露出来。发现该问题后,初步做法是优化索引,也就是对“省略其他条件”字段加索引,但效果并不明显。
最后的做法是:分析者两张表的所有“联表查询”sql语句,把主业务相关的字段迁移到sale_info表,把副业务相关的字段迁移到sale_info_ext表,对于主副业务都需要的常用字段 在两张表中做“冗余”存储。从而保证高频查询的sql语句,都是单表查询,最终解决该问题。对于一些“低频”查询的sql语句,仍然“联表查询”已经无所谓了(有时还应防止这些“低频”查询的sql语句转为“高频”)。
“表字段冗余”说起来原理简单,但实际操作有时会比较复杂。最重要的原则还是要根据业务划分,找准“冗余点”才能做到“以空间换时间”,否则“空间”消耗了“时间”减少--这就不是我们想要的效果了。
读写分离之镜像复制
对于“读多写少”的业务(其实大部分业务都是这种场景),最常见的以“空间换时间”的做法就是“读写分离”,要做读写分离 首先要做“主备”。对应“写”业务直接写“主库”,对于延迟要求不高的“读”业务读“从库”。现在的问题就变为 主从同步问题,数据同步始终会有延迟(即便是采用数据库自带的,如mysql的Replication)。所以,对于不允许“延迟”的业务,只能读“主库”。
所谓“镜像复制”就是所有备库的内容,跟主库内容完全一致。“镜像复制”,又存在两种情况“一主多备”、“多主多备”。对于,延迟要求不高的业务,可以采用“一主多备”;对于,延迟要求高的业务,可以采用“多主多备”,具体做法是:写入数据时,写入多个主库(或者说写库),对于不允许延迟的业务直接从主库中读取数据,对于延迟要求不高的业务到“从库”读取数据。“多主多备”的架构设计如下:
这时会出现在同一个应用中有多个数据源的情况,一般做法是:在spring配置文件中配置多个数据源,获取数据源时通过一个“工具类”获取:
public class DataSourceUtil { private List<DataSource> readDatasources;//通过spring配置注入 private List<DataSource> writeDatasources;//通过spring配置注入 private Random random = new Random(); public List<DataSource> getALLWrite(){//获取所有的写数据源 return readDatasources; } public DataSource getOneWrite(){//随机获取一个写数据源 用于“非延时”读 return readDatasources.get(random.nextInt(readDatasources.size())); } public DataSource getOneRead(){//随机获取一个写数据源 用于“可延时”读 return readDatasources.get(random.nextInt(readDatasources.size())); } }
最后这些数据源配置可以放到“配置管理”系统,可以实现在线切换“数据库”。
读写分离之非对称复制
前一种“镜像”同步方式,是主库和备库的内容是完全相同的。在“分库分表”的系统中,做数据冗余还有另外一种数据冗余方式:非对称复制,主要作用就是减少查询时多张表的join操作。下面看一个真实的场景:
在笔者所在的一个“活动页cms系统”中,需要记录每个页面上的sku(商品编码),每天上线的活动页数量成千上万个,每个页面上对应的sku从几十个到几百个不等。可见数据量,是比较大,一般会进行“分表”存储。
应用中经常会 根据“活动页”id查询页面上的sku列表,为了减少表的join次数,我们用“活动页”id做hash(可以直接取模,或者使用一致性hash) 进行分表,保证每个“活动页”id对应的sku都存在同一张表中。假设分8张表存储,分表方式如下:
现在要查询某个“活动页id”下的所有sku,首先通过“活动页id”mod 8,计算出数据所在的表,然后通过一条简单的select语句查询该表就搞定。
但现在问题来了,“业务方”想要知道某个sku 今天在哪些页面上出现过,用来对比各个不同的页面推广效果。怎么办呢?如果按照上述分表,包含某个sku的的“活动页id”分散在8张表里,需要进行7次join操作或者查询8次进行合并 采用获取到所有的“活动页id”。
这种场景就可以使用“非对称复制”,在写入数据时,我们可以用另外的8张表存储上述相同的数据,唯一不同的地方就是分表规则改为对 sku编号进行hash(取模或者一致性hash都可以),分表方式如下:
好了,现在要查询某天某个sku出现过的活动页面有哪些,也就同样简单了,首先通过sku编号 mod 8获取到所在表,再通过一个简单的selec语句查询该表就搞定。
也就是说:如果查询条件是“活动页id”就使用第一种分表规则;反正就是使用第二种分表规则。都是单表唯一索引查询,查询速度也非常快。只是存储空间翻倍,这也是典型的“空间换时间”的场景。
这里讲的是单库操作,按照第二冗余方式所讲,如果需要做“读写分离”,这时有两种方案。方案一:两种分表方式的写入操作,都在同一个“主库”中进行,再通过数据库自带的同步工具同步到“读库”,查询时直接读“读库”。方案二:在写入数据时,两种分表方式分别写入不同的数据库,这时直接借助程序自己实现,也可以借助一些数据库中间件来完成。第二种方式虽然麻烦些,但如果数据量大 同时查询又很频繁,采用这种方式可以进一步实现不同的查询业务“分库”,单个数据库可以存储更多数据 并且降低单个数据库并发查询压力。
总结
仅对sql语句层面进行优化始终有局限,作为项目里的架构师一定要从更高层次出发,根据不同的业务场景采用不同架构设计手段,以减轻数据库的压力。这里不是说优化sql语句不重要,优化sql是需要首先做。
另外以“空间换时间”是架构设计中的常用手段,可以在各种不同的场景下使用,达到以多个廉价的pc机(空间),换取需要使用昂贵的“大中型”机采用达到的性能(时间)。
出处:
http://moon-walker.iteye.com/blog/2405545
相关推荐
- 通过索引优化、查询优化、存储过程的使用、分区策略等手段提升数据库性能。此外,合理设计数据库的事务处理机制,平衡并发性能和数据一致性。 7. **数据库设计规范**: - 遵循一些最佳实践,例如,使用有意义的...
数据库连接池是管理数据库连接的机制,通过复用已存在的连接而不是每次请求时创建新的连接,从而提升数据库访问性能。 事务隔离级别有读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读...
最后,性能调优部分会教导读者如何通过索引、查询优化、资源管理等手段提升数据库的运行效率。 此外,书中可能还包含了一些实战项目和练习题,以帮助读者将所学知识运用到实际场景中,提升解决实际问题的能力。例如...
数据库性能优化是IT领域中的一个核心议题,尤其对于处理大量数据的企业级应用而言,高效的数据库性能至关重要。在SQL Server、MySQL和Oracle这三大主流数据库系统中,优化策略各有其特点和技巧。以下将针对这些...
- **重要性**:优化数据库性能不仅能够提升系统的稳定性和可靠性,还能改善用户体验,是IT项目成功的关键因素之一。 #### 二、数据库性能的影响因素 - **数据库设计**:包括表结构、索引设计等方面,直接影响到...
本章可能讲解如何通过索引、查询优化、数据分区和缓存等手段提升数据库的性能。 第十一章:数据库应用开发 这部分可能涵盖了数据库应用程序的开发过程,包括使用ODBC/JDBC接口、数据库连接池、存储过程和触发器的...
调整内存分配、设置合适的连接池大小、控制线程并发数量,以及优化I/O调度,都是提升数据库性能的重要手段。例如,为频繁访问的数据分配更多缓存,可以减少磁盘I/O,提高响应速度。 第五种武器:监控与调优工具 ...
此外,安全性也是数据库设计的重要方面。酒店管理系统涉及客户隐私,需要设置权限控制,确保只有授权人员可以访问和修改数据。这可能包括用户认证、角色管理以及访问权限的设定。 再者,性能优化是提高系统效率的...
4. **查询优化**:优化查询是提升数据库性能的重要手段。这包括合理使用索引,避免全表扫描,减少JOIN操作,以及优化子查询等。使用EXPLAIN或类似功能来分析查询执行计划,可以帮助找出查询的低效之处。 5. **索引...
理解范式理论,合理划分实体关系,以及在适当的地方使用分区和分片,都是提升数据库性能的重要手段。 5. **资源管理**:数据库管理系统需要有效地分配CPU、内存、磁盘I/O等资源。Oracle的Automatic Workload ...
《Oracle数据库性能优化实践指南》是一本专注于提升Oracle数据库运行...通过阅读《Oracle数据库性能优化实践指南》,读者将能够掌握一系列实用的优化技巧,提升数据库系统的整体效能,为企业信息化建设提供强大的后盾。
1. **意识**:这包括对数据库性能问题的敏感度,以及对整个流程的理解。就像小余反思自己的买鱼过程,我们需要对数据库操作的每个环节有深入认识,才能发现问题并提出解决方案。 2. **技能**:小余通过参加DTCC...
- 操作系统和数据库参数调优,例如调整Oracle的初始化参数文件,根据系统负载进行合理配置,有助于提升数据库性能。 - 使用Oracle的内置性能分析工具,如SQL Trace、AWR(Automatic Workload Repository)和ASH...
数据库性能调优是IT领域中的一个关键话题,特别是在大数据量和高并发的业务环境中,优化数据库性能至关重要。...通过深入学习并实践这些技术,可以显著提升数据库性能,为企业的数据服务提供坚实保障。
总之,"中国数据库技术大会数据库性能优化专场PPT资料"包含了丰富的理论知识和实践经验,无论是对于数据库管理员还是开发人员,都是提升数据库性能的宝贵资源。通过深入学习和实践,我们可以有效地提升数据库系统的...
优化硬件配置,如使用SSD硬盘、增加内存或使用更快的处理器,可以显著提升数据库性能。 5. **数据库参数调优**:每个数据库系统都有许多可调整的参数,如缓冲池大小、连接池设置、事务隔离级别等。合理设置这些参数...
本资料集“DB2 UDB AS400数据库性能和查询优化.rar”着重探讨了如何在AS/400环境中提升DB2 UDB的性能并优化查询。 1. **数据库性能优化**:性能优化涉及多个方面,如数据库设计、索引策略、存储配置、SQL语句优化等...
- 数据库性能监控:使用工具(如MySQL的SHOW STATUS、EXPLAIN)分析查询效率,找出性能瓶颈。 - 故障排查:遇到问题时,通过日志分析、调试等手段定位问题并解决。 以上是数据库实训报告可能涉及的主要知识点,...
在Oracle数据库性能优化中,以下几个核心概念和技术是至关重要的: 1. **SQL优化**:SQL查询是数据库操作的基础,优化SQL语句可以显著提升性能。这包括使用合适的索引、避免全表扫描、减少子查询以及利用绑定变量等...
磁盘阵列能够显著提高数据读写速度以及数据的安全性,因此是提升数据库性能的重要手段之一。 #### 磁盘阵列RAID简介 RAID是一种将多个独立磁盘组合成一个逻辑单元的技术,旨在通过数据冗余来提高数据的可靠性和...