前几次的编码最佳实践系列,我们都着眼于Java代码,今天我们换个话题,看看另外一个领域,和Java代码大相径庭的SQL。
这次作为素材出场的,是来自项目中的一段SQL,用于BlackWhiteList Adapter,在每次请求时检查一下,看当前用户是否在黑白名单中。
先介绍一下数据库结构,很简单的三张表:BWLIST记录黑白名单信息,BWLISTXADDRESS记录每个黑白名单下的地址列表,BWLISTXAPPLICATION 记录每个application关联到的黑白名单。查询时有三个输入:PARTNER_ID和APP_ID是当前application的唯一标识,通过这两个参数就可以在BWLISTXAPPLICATION中找到对应的黑白名单的id(可能有多个),然后通过黑白名单的id就可以在BWLISTXADDRESS找到对应的所有地址,结合第三个参数ADDRESS就可以得知到当前地址是否在黑背名单中。
还是直接看SQL吧,典型的3表联合查询:
String sql = "select A2.ID, A2.ADDRESS, A2.ADDRESS_TYPE, A2.DESCRIPTION, A1.type BWLIST_TYPE, A3.LIST_LEVEL, A3.SC_ID, A3.PARTNER_ID, A3.APP_ID
from BWLIST A1, BWLISTXADDRESS A2, BWLISTXAPPLICATION A3
where A1.id = A2.BWLIST_ID and A2.BWLIST_ID = A3.BWLIST_ID
and A3.PARTNER_ID = ? and A3.APP_ID = ?
and (INSTR(?, A2.ADDRESS ) > 0)";
直接看where 后面的内容,"A1.id = A2.BWLIST_ID and A2.BWLIST_ID = A3.BWLIST_ID "好理解, BWLISTXADDRESS和BWLISTXAPPLICATION两个表的BWLIST_ID字段是外键,对应到表BWLIST的主键ID,用来做关联。后面的"A3.PARTNER_ID = ? and A3.APP_ID = ? "是用来唯一标识当前application,为了加速查询,建有一个PARTNER_ID+APP_ID的索引:UNIQUE INDEX SYS_C0098362 (APP_ID(150), SP_PK, BWLIST_ID)。
关键在最后一个where条件:(INSTR(?, A2.ADDRESS ) > 0)"。这里使用INSTR()而不是简单的=,是考虑到地址可能有多种格式,比如"13900000000","tel:13900000000", "tel:+8613900000000",实际都是一个号码。因此考虑在数据库将A2.ADDRESS保存为"13900000000",这样无论当前输入的地址格式是"13900000000","tel:13900000000", 还是"tel:+8613900000000",都可以被正确处理。
同样为了加速查询,开发的同事为A2.ADDRESS这个字段增加了索引:UNIQUE INDEX SYS_C0098354 (ADDRESS(250), BWLIST_ID)。
这样在上述几个索引的支持下,前面的sql语句的where条件,按说基本都被索引优化到了 ————— 但是,相信一些有经验的同事可能第一时间就已经反映过来,最后的这个基于ADDRESS字段的索引,有问题!
问题出在(INSTR(?, A2.ADDRESS ) > 0),INSTR()是一个SQL函数,作为一个基本常识,大家都知道的:如果索引列是SQL函数的参数,那么索引在查询时是用不上的。
很遗憾,当时编写这个SQL的同学可能不知道或者一时没有反应过来,结果上述的SQL被写入到产品。随后更糟糕的是,在压力测试中,居然没有被发现,原因是测试时使用的数据规模太小,只为被测试的黑白名单准备了几十个地址,所以虽然索引无法被利用,但是对于区区几十条记录,不走索引反而能更快一些...... 就这样逃过测试,发布并部署运行于客户线上。
然后,后面的事情就可以想象了,客户实际跑的时候,BWLISTXADDRESS中记录的条数远不是几十,而是几十万,几百万......而黑白名单过滤功能一旦开启,是每个请求都要检查一次,上面的SQL每次都要执行一次。于是数据库理所当然的顶不住,整个系统的速度都被拖累,客户就抱怨说黑白名单开启之后性能出现大幅下降。而痛苦的是我们自己测试时不能重现问题,嗯,用那个只有几十个地址的名单当然重现不出来......
总之这个小bug引来了后面一堆的事情,我们不继续吐糟,回头来看看,问题是如何一步一步的产生:
1. 开发人员犯错,常识性的小错误:索引对SQL函数无效
2. 对于性能敏感的SQL,没有做慎重的处理:如果当时有用查询分析器看一下执行计划,就可以避免出现类似的索引失效而不自知的情况;如果有其他有经验的同事review这个SQL,也可以在早期发现问题
3. 性能测试时数据建模失误,没有模拟到真实线上数据的规模,以至于最后一道关卡被突破,让性能问题逃过了性能测试
第3条是另外一个话题,我们这里重点来看1和2:
1的问题本质上是一个老生常谈的问题:如何避免在同一个坑中跌倒多次?这里所说的同一个坑,针对不同的对象有不同的含义:对于个人,上次犯下的错误下次会不会还继续?对于一个团队,A同学出错的地方B同学是否能避免?对于整个公司,A产品线遇到的问题B产品线能不能有所借鉴?
可以说,我们现在的这个编码最佳实践的系列,就是为了解决类似的问题:将我们不同的产品线犯下的一些典型问题总结下来,分享给其他人,避免同一个坑不停的有人踩的尴尬和无奈。
而2的问题在于我们的WOW(Way Of Working)还不够完善,对于性能敏感的关键代码,应该保持足够的谨慎和细致,类似的每次查询都要执行一次的SQL,怎么都要看看执行计划才能放心写入产品。我们也应该有完善的code review机制来保证当有疏漏的时候应该能及时补救。
最后我们再回到原始问题,关于这个SQL,我们现在知道INSTR()函数用不上索引,通常的解决方案是使用函数索引,但是对于"INSTR(?, A2.ADDRESS ) > 0",函数索引也无能为力。因此只好修改业务处理方式,不再在SQL查询这个层次处理地址格式的问题,将格式问题抛给Java代码:在数据库中保存标准格式如"tel:13900000000",业务处理流程中对输入的地址格式做标准化,将地址匹配简化为简单的"="操作,这样可以极大的节约数据库查询开销。
最终修订版本的SQL如下:
String sql = "select A2.ID, A2.ADDRESS, A2.ADDRESS_TYPE, A2.DESCRIPTION, A1.type BWLIST_TYPE, A3.LIST_LEVEL, A3.SC_ID, A3.PARTNER_ID, A3.APP_ID
from BWLIST A1, BWLISTXADDRESS A2, BWLISTXAPPLICATION A3
where A1.id = A2.BWLIST_ID and A2.BWLIST_ID = A3.BWLIST_ID
and A3.PARTNER_ID = ? and A3.APP_ID = ?
and A2.ADDRESS=?";
这也是性能优化的常见方式:将复杂的业务逻辑尽量留给Java代码,尽可能的保持数据库操作的简单和高效。
分享到:
相关推荐
这份“WEB编程最佳实践的介绍PPT”详细讲解了几个关键方面,包括JAVA编码规范、JAVASCRIPT编码规范、数据库规约、网页展示过程介绍以及网页性能处理。以下是对这些内容的深入探讨: 1. **JAVA编码规范** - **命名...
在Django框架中进行开发时,遵循最佳实践和标准至关重要,这不仅能提高代码质量,还能增强项目的可维护性和团队协作效率。"django最佳实践 html"的主题涵盖了如何建立一个高效的Django项目结构,以及遵循的流行标准...
因此,掌握MySQL的最佳实践不仅能够帮助我们构建更加健壮的数据基础设施,还能够在日常工作中避免许多常见的错误和陷阱。 #### 二、开发前期准备 1. **需求分析与设计**: - 在进行任何编码工作之前,对业务需求...
不仅从语言角度系统而详细地阐述java安全编码的要素、标准、规范和最佳实践,而且从架构设计的角度分析了java api存在的设计缺陷和可能存在的安全风险,以及应对的策略和措施。可以将本书作为java安全方面的工具书,...
本篇将深入探讨Oracle PL/SQL的最佳实践,旨在帮助您提升代码质量、性能和可维护性。 1. **模块化编程**:将复杂的业务逻辑拆分成小型、可重用的存储过程和函数,有助于提高代码的清晰度和可读性。使用包(PACKAGE...
2. **安可考试**:"安可"可能是某个特定的安全认证或培训项目,旨在确保开发者掌握安全编码的最佳实践。这个考试可能涵盖了如何编写安全的SQL查询,识别并修复潜在的SQL注入漏洞等内容。 3. **正确与错误示例**:...
内容概要:本文档详细介绍了 Java 服务端开发的白皮书,涵盖技术栈的选择和最佳实践。主要内容包括 Spring Cloud Finchley 版本、Nacos、OpenFeign Gateway、Spring Boot 2.0.3、MyBatis、Redis 4.0、MySQL 5.7、...
对于那些无论是初学者还是资深开发者来说,掌握一些有效的编程规范和最佳实践是非常重要的。本文将深入探讨C语言的最佳实践,包括代码风格、注释、命名规则等方面,帮助读者提升编程技能。 #### 风格与指南 **代码...
以下是一些针对程序员的MySQL最佳实践,旨在提升数据库性能和编写高效代码: 1. **利用查询缓存**:MySQL提供了查询缓存功能,它可以存储已执行过的查询结果,以便后续相同查询直接从缓存中获取,提高查询速度。...
Flex SDK编码规范与最佳实践是指导开发者在使用ActionScript 3 (AS3)编写开源Flex框架组件时遵循的一套规则,旨在确保代码的整洁、一致性与专业性。这份文档虽然不完整,但已经包含了足够的信息来帮助开发者启动项目...
- **MySQL 数据库**:提供数据库设计与优化的最佳实践。 - **工程结构**:规范项目结构,便于维护。 - **设计规约**:涉及软件设计原则与模式。 - **三个等级**: - **强制**:必须遵守的规则,违反可能导致...
- **规约**:遵循最佳实践,避免潜在的编码陷阱。 - **说明**:了解常见的编程陷阱,可以在开发过程中避免不必要的错误。 ### 二、异常日志规约 #### (一) 异常处理 - **规约**:合理处理异常,区分运行时异常和...
**Laravel最佳实践** 在开发基于PHP的Web应用程序时,Laravel框架因其优雅的语法、强大的功能和丰富的社区支持而备受推崇。为了充分利用Laravel的优势并确保代码的可维护性、可扩展性和性能,遵循最佳实践至关重要...
- **接入层高可用设计最佳实践** 结合实际业务需求,选择合适的高可用方案,并通过持续的监控和调优来保障系统稳定运行。 - **实践案例** 如何根据不同的业务场景设计并实现高效的接入层架构。 #### 第六课:长...
在本文中,我们将深入探讨如何使用STM32微控制器实现编码器测速功能。编码器是一种传感器,通常用于测量旋转速度、位置或角度变化。在工业自动化、机器人技术和运动控制等领域,编码器测速是非常关键的技术。 STM32...
- 遵循异常处理的最佳实践,不要捕获所有异常,而是尽可能具体地捕获并处理。 遵循这些C#编码规范,不仅可以提升代码质量,还可以帮助团队建立一套高效、一致的工作流程,从而提高整体项目成功率。