大道至简,聊聊工作中需注意的数据库开发规范

        前言:最近要做一次数据访问组件的分享,想着趁此机会结合这几年的工作经历,好好梳理一下数据库相关的开发规范,之前我也写过很多这方面的文章了,且数据库相关的知识也没什么新意可言,但我之所以还是决定提笔再写一篇主要出于几个方面考虑:

       一是好多人只对前沿以及看似高深的技术感兴趣,总是高谈阔论,反而轻视、也忽视了CURD的重要性,我想提醒他们,这种思想会为你带来灾难,这并不是危言耸听,而是真实存在的,尤其是做财务、金融相关的,我这么多年就是一直从事这个行业,我亲眼见过太多血淋淋的惨痛案例了。实际上,我们开发的几乎所有业务系统,无论逻辑有多么复杂和高大上,最终还是以数据的落地为准,而基本的数据库操作就是最后一环,Last but not the Least。

        二是我深知费曼技巧的重要性,要持续输出,持续用最浅显、最直白的语言将自己所掌握的知识传递出去。理论也好,实践经验也罢,都是可共享、可学习的。

       三是本文并不是想把知识点单纯写下来,知识点网上一搜一大堆,没有什么意义,本文的目标是能够引起开发者们足够的重视,能够一定程度提高读者们数据库操作的风险意识。就算是只唤醒一个读者,也算没白写。

备注:我下面所提到的建议,一定是我亲眼见过的真实案例。为了保密性,我不会具体说什么业务、导致了什么结果、当事人受到什么处罚,只单纯从技术角度去阐述。

1、注意查询条件的边界问题

       实际应用中,sql的查询参数都是由前端或客户端传过来的,一些经验不足的开发者会想当然认为前端传过来的数据一定是OK的,可极端情况下所有查询参数可能都为空,因为客户端的传参很多情况下都依赖于客户的输入,如果前端本身没做好校验,就会出现这种情况,全空的条件最终导致数据库操作进行了全表扫描。   

       这个看似很小的边界问题是真实发生的,更糟糕的是,还是在分库分表的场景下进行的,最终引起的后果可想而知。    

       记得我刚去小米时,我们领导和我们说了一句话:”永远不要相信任何和你对接的上下游系统,你要假想他们一定会犯错,只要这样你才能避免你自己犯错“。这句话是非常受用的,对所有上下游系统保持怀疑态度,开发时要考虑到他们出现异常的所有可能性并做好预案。而sql语句的传参就是一种典型问题。如果所有条件都为空,可以直接向客户端抛出异常。

2、新增字段,且是必要查询条件,记得增加索引

        这是实际发生的一起事故,有个哥们在数据表上新增了一个字段,忘了加索引,但还会经常使用该列作为查询条件,上线后就出问题了。没有索引,又是一个典型的全表扫描,并发一旦上来就死翘翘了。

        索引就是一把利剑,通过索引可以减少磁盘IO次数,大幅度提高查询效率。用好他,他会给你惊喜。

3、复合条件注意优先级

        JAVA开发者经常用mybatis或者mybatis-plus,或者其他语言也都有成熟的ORM框架,我们通常会直接使用框架提供的API拼接sql, 当我们写一个简单sql时可能不会出错,但如果我们在写复合查询时,稍有不慎,就会出错。像这种既存在and,又有or的复合查询,API用不明白的话非常容易出错。如:

where a="haibo" and (b is null or b == "" or b =="0"),

        这是预期要写的where语句,可是实际上拼接的sql语句中却没有了括号,即:

where a="haibo"  and b is null or b == "" or b =="0"

        看到这里,大家都知道问题出在哪里了吧,缺少了括号,这可是差了十万八千里。这个算是我见过的,业务影响最恶劣的一次错误了,金额巨大。

        所以,如果开发中一定要用API拼接sql的话,那么就尽量把sql 打印出来,至少在开发、自测以及功能测试阶段,sql语句尽量 debug输出一下,这样你也能检测到自己的sql是不是符合自己的预期。

4、分库分表场景尽量不要做聚合类查询,最好保证必须有分片键

       在分库分表场景下直接做聚合查询的风险性是极高的,因为它会把每个子库的数据都查出来,然后进行聚合,这种情况下,对性能、对内存数据存储都是考验。我在第一点中提到了where条件都是空的问题,这个问题也导致sql把每个子库都做了一次全表扫描,且会查询出所有的数据,这引起的后果很严重,一是线程hang在那儿了,如果此时请求量较大,系统肯定崩了;二是过多的数据加载到内存,OOM就出现了,此时如果多次的GC之后内存占用依然很大的话,很有可能被kill掉的,因为Linux操作系统本身会检测内存占用情况,如果发现某个进程内存占用过多,他会根据某种策略kill掉相应进程。

          在高并发场景下,慢sql影响的不仅仅是数据库本身,也影响应用,影响整个系统。尤其是分库分表场景下。

             对于在线交易,尽量不要做聚合查询,一定要带上分片键。如果想做聚合查询,请采用其他的手段。可以将所有数据归档到一个聚合库,聚合库可以是关系型数据库,可以是列式存储数据库如Hbase,也可以采用目前流行的分布式数据库TiDB等等。这也是当前几乎所有大公司采用的方案。我们之前是以用户id为分片键,同一个用户的所有数据肯定都在一个子库上,ToC的所有操作都是带分片键userId的。

select  oid from shop_order where uid=35355

       对于像结算系统、数据组这种有聚合类的查询需求时,我们都是把子库数据以增量的方式同步到一个聚合库上的。我们当时用的是Mysql,借助canal和canal-adapter完成了数据聚合。

5、索引失效-避免隐式转换

         这是我之前遇到的一个问题,有一天我发现结算系统有个界面变慢了,慢到已经超过了Nginx最大响应时间了,最终定位到了原因是从我们公司的支付网关查询到的流水号是整数,但是我们自己数据库的支付表中流水号的字段是varchar,当我发起查询的时候,直接使用了网关返回的整数,而这导致了索引失效,查询变得异常慢。

具体看下面的示意图,图一没有使用上索引,而是全表扫描;图二使用了索引。

        之所以会产生这样的原因是:where的值是整数,但该字段是字符串类型的,它会将数据表里的所有该字段的值都转换为整数,然后再取匹配where条件里面的值,这就导致了全表扫描。

        不过这里要强调一下,像Mysql,当遇到类型不一致时,它会把字符串转成数字。也就是说,假如我们的字段是int类型,查询参数是字符串,Mysql会自动把字符串转成int,然后再去查询,此时索引是正常可用的。

6、索引失效-遵循最左匹配原则

   在使用联合索引时,往往会因为违背了最左匹配原则导致索引失效。如联合索引 (a,b,c),下面的查询都是无效的:

select a from table where b = "dd" ;select a from table where c= "c";select a from table where b = "b" and c= "c";

         这是因为联合索引的原理决定,b是局部有序,即只有确定了a,b在已确定的a的范围内才是有序的。而如果没有a,只拿b查询,全局b是无序的;同理C也一样。

 7、其他索引问题

         这里就简单列出来,包括索引使用函数、索引字段进行表达式计算、like进行模糊匹配,这些都会导致索引失效。

        建议对我们写的sql,可以通过explain执行计划看看是否真的用上了索引。

        关于索引,我会再写一篇文章,主要介绍Mysql的各种索引,包括B+tree,哈希索引,全文索引,介绍索引下推、MRR、索引原理等。  文章写的差不多了,只不过我还需要再完善一下之后发布出来:

8、不要一次性查出过多的数据

        实际生产发生的是内部的热点账户查询引起的该类问题。账户存在热点账户和非热点账户的,尤其是对公账户。如果我们只拿一个账户id去查,非热点账户把所有账户返回都是OK的,可对于热点账户,只通过一个账户id条件去查,会把所有的数据查询过来,数量级可达到千万级,最终导致OOM。

9、避免大事务

         前一阵子开会,有个项目是因为大事务的操作,且同一操作即有批量操作,又有删除语句,这导致在并发的情况下出现了死锁,线程长时间挂住,资源被耗尽。负责人狡辩说他们这不算大事务,每次都只操作一两百行,当场我就把他怼了回去,我和他解释了什么是大事务,告诉他大事务的判断不仅仅是单纯以条数为基准,一说一个不吱声。

         我再说一个例子,之前在小米的时候,有一次我们的系统上线之后,大家都轻松了很多,突然负责其中某个微服务的小伙伴收到了DBA的加急消息,他没回,DBA直接联系到了我们领导,领导火急火燎过来说,赶紧看看,DBA说咱们的sql有大事务操作,且大量的同级别sql请求打过去,数据库快受不了了。哈哈,我这人对类似的这种事情记的非常清楚。

10、谨慎使用 group by 

         分组聚合以及排序如果使用不当,会出现严重的性能问题。

         2019年米粉节,有个哥们写了一个根据用户uid进行的分组查询,大促时,N多个同样的查询打到数据库上,当时业务规模小,所有服务还都使用一套数据库,这导致他自己写的sql有问题导致数据库性能下降,间接影响到了其他服务,用户完全下不了单,好在商品服务都有缓存还能看,要不然整个系统都蹦了。他的那封给全员发的检讨邮件我印象深刻!!!

11、严格把关 drop、truncate、delete操作

        drop、truncate操作好说,DBA本身就不可能让业务直接操作,CREATE 、ALTER等等DDL、DML语句,业务都是不能直接操作的,都必须要提交工单执行。

        然而delete就必须要谨慎了,delete操作是我最忌讳做的事情,所以基本上,我之前写的业务都是能逻辑删除的就逻辑删除,而且在线业务也很少需要我们做删除。删除操作可能常应用于数据归档或者离线的一些任务。

         开发过程中,如果有delete操作,一定要格外注意,仔细检查where条件是否正常,千万不要出现 delete all data的问题。如果出现,那就赶紧跑路吧,或者赶紧给DBA磕个头,让他帮你做数据恢复。

12、谨慎使用join联查

        如果你看过阿里出版的数据库开发规范,一定会看到里面有一条说尽量不做多表联查,如果用,最好不要超过三张表。join联查底层是需要选择一个驱动表进行的,如果驱动表选择不当,或者是没有索引以及索引失效导致出现笛卡尔积,那性能简直是灾难级别的。

13、注意在线DDL操作

       有一天晚上,群里炸了,说用户领不了券了,领导问发生了什么。负责的同学说,我们在做数据表修改操作,由于数据比较多,导致在该阶段进行领券的用户无法领券,客户端提示系统异常(领券意味着数据库会进行Insert操作)。当时DBA的限制比较松,只要领导审批通过,DDL、DML等SQL语句都可以执行,所以出了这么大的事情。这件事情之后,DBA才优化,审批时首先要评估影响的行数,如果影响太大,DBA需要亲自手动操作的。这就涉及到了数据库的Online-DDL操作。感兴趣的可以看我写的文章:Mysql在线DDL操作 。

14、注意order by的性能问题

        去年我们去现场帮着一个业务排查系统问题,系统现象是他们在高并发场景下, 接口耗时特别大, 并发量怎么也上不去。后来排查发现他们写的大SQL的order by有问题,order by字段没有索引,当没有索引时会进行filesort,这个过程可能会非常慢。

15、其他开发规范

         其他规范暂时有的还没遇到相应的实际问题,就不写了,但其同样重要,比如分页注意性能优化、不要查出太多的列、尽量采用覆盖索引,避免回表等等,感兴趣的可以看我之前写的文章,比较全:Mysql数据开发规范 。

      

防范措施

    1、项目经理、组长、开发人员本身要具备风险意识,自己要对SQL有足够的重视,这是首要条件。如果做到了,大多数问题都不会出现了;

    2、公司可以开发一套sql过滤器或者访问组件,对于每个sql在执行之前,Filter或者组件会首先会根据预置的规则对sql进行拦截检测,可根据不同的风险级别,对可能有问题的sql进行实时的告警或者拦截。嗯,这也是我开发数据访问组件的初衷;

   3、 可以开发一套sql检测脚本,可让开发或者测试人员主动去检测源码里的sql,就像checkstyle检测我们的代码规范一样。另外,现在我们基本都在用devops来编译和部署应用,在构建流水线任务时,也可以将检测任务加到流水线里;

  4、上周我们大部门开了一个研讨会,领导派我参加,有一个其他处室领导说了一个办法,虽然比较low,但简单粗暴最有效,哈哈。他们是把所有sql语句都复制出来,在数据库CLI上手动执行一遍,看一下执行计划,他们主要是为了验证性能;

 5、要有一套数据库监控平台,主要是DBA负责。监控平台可对所有sql进行统计,主要监控和统计慢sql。当然,这是一种后置手段,并不是预防手段。

总结

           最后还要特别强调,希望大佬们引起足够的重视,CURD不仅不丢人,反而是最重要的,对此不屑的代价是惨痛的,尤其是和钱相关的,一个小失误导致少则几十万,多则几个亿,十几亿的金额影响都是我亲眼见过的。我做了很多年财务结算,我从来没有出现过错误,不是因为自己有多牛,完全是因为我发自内心的高度重视,无他。

      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/499366.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Studio level过滤查看各个等级的日志

Android Studio level过滤查看各个等级的日志 旧版as可以在下方的日志输出框选择debug、info,warn、error日志,新版的需要通过在过滤框手动/联想输入 level:xxx,过滤相应等级的日志,如图: android studio/idea返回/前进…

ulimit命令

ulimit命令 ulimit 命令用于查看和设置 shell 运行时的资源限制。它可以控制各种资源,如文件打开数量、堆栈大小、CPU 时间等。ulimit 命令通常用于限制 shell 启动的进程的资源使用量,以防止系统资源被耗尽。ulimit命令的主要作用是提高系统的性能和稳…

RabbitMQ 面试八股题整理

前言:本文是博主网络自行收集的一些RabbitMQ相关八股文,还在准备暑期实习,后续应该会持续更新...... 参考:三天吃透RabbitMQ面试八股文_牛客网 目录 RabbitMQ概述 什么是 RabbitMQ? 说一说RabbitMQ中的AMQP 为什么…

【Vue】路由

📝个人主页:五敷有你 🔥系列专栏:Vue ⛺️稳中求进,晒太阳 目录 路由 单页应用程序 总结: VueRouter 核心步骤: 组件存放目录的问题 路由的封装 声明式导航 声明式导航 - 导航链…

​LeetCode解法汇总2476. 二叉搜索树最近节点查询

目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: GitHub - September26/java-algorithms: 算法题汇总,包含牛客,leetCode,lintCode等网站题目的解法和代码,以及完整的mode类&#…

企业数字化转型的第一步:由被动多云向主动多云转变

随着经济环境、市场形势、技术发展、用户需求等诸多因素的变化,数字化转型为企业进一步提升效率和竞争力、提供更加丰富的个性化产品和服务、进行业务场景创新、探寻新的增长机会和运营模式提供了崭新的途径。越来越多的企业意识到,数字化转型已不是企业…

[UUCTF 2022 新生赛]ez_rce

这个题目是很简单的,有很多中解法,我来说几种,大家可以参考一下 1.有一个命令是l\s等于ls,但是我尝试codel\s结果还是被过滤了,于是使用printf命令包含l\s,加上反引号就是先执行反引号里的内容&#xff0c…

UE5 UE4 不同关卡使用Sequence动画

参考自:关于Datasmith导入流程 | 虚幻引擎文档 (unrealengine.com) 关卡中的Sequence动画序列,包含特定关卡中的Actor的引用。 将同一个Sequcen动画资源放入其他关卡,Sequence无法在新关卡中找到相同的Actor,导致报错。 Sequen…

我写了个ImageWindow应用

文章目录 0 引言1 应用简介2 主要功能和特点2.1 多图像同/异步像素级对比2.2 支持多达30种图像格式2.3 高效率的图像处理性能 3 简明使用教程3.1 软件下载安装与更新3.1.1 软件下载与安装3.1.2 软件更新 3.2 多视窗添加并自动最优排列3.3 多样化图像导入方式3.4 自动切换显示模…

综合练习(一)

目录 列出薪金高于部门 30 的所有员工薪金的员工姓名和薪金、部门名称、部门人数 列出与 ALLEN从事相同工作的所有员工及他们的部门名称、部门人数、领导姓名 Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209645 列出薪金高于部门 30 的所…

嵌入式学习第二十三天!(进程间通信)

进程间的通信: 1. 管道 2. 信号 3. 消息队列 4. 共享内存 5. 信号灯 6. 套接字 1. 管道: 1. 无名管道 无名管道只能用于具有亲缘关系的进程间通信 原因:无名管道没有名字,所有找不到管道的具体位置,那么在创建子进程前…

蓝牙耳机和笔记本电脑配对连接上了,播放设备里没有显示蓝牙耳机这个设备,选不了输出设备

环境: WIN10 杂牌蓝牙耳机6s 问题描述: 蓝牙耳机和笔记本电脑配对连接上了,播放设备里没有显示蓝牙耳机这个设备,选不了输出设备 解决方案: 1.打开设备和打印机,找到这个设备 2.选中这个设备&#…