数库据设计最佳实践

        中老年程序员,从业生涯设计过很多数据库,有用上的也有没用上的,有精心设计花无数心思更改了无数次的也有敷衍了事能用就行的,有最糟糕的设计也有感觉还不错的。在设计和修改过程中有很多疑问和感悟,在此记录一下以方便自己查阅如果还能给后来人一些帮助和启发那就十分容幸了。

        本文主要是以程序员角度讨论关系型数据库的设计思路及对程序代码编写造成的影响,以随想方式发布,想起来的就写一点,想不起来就算了。

可统计字段

      在设计库的时候总有一些字段是可加可不加,是可以通过其它数据统计出来的,到底要不要加上这个字段呢,最典型的例子就是“余额”字段,这个值是可以通过明细表用SQL统计出来的,那要不要加上这个字段呢,这个答案很明显,肯定是要加上的,不仅要加,还要通过结算表把每期的余额都记录下来,以便以需要重新算。那么这是为什么呢?其它不那么明显的字段要不要加上呢?加不加的考虑因素有以下几点:

  • 事务隔离级别:以“余额”为例,如果没有这个字段,那么每次事务都要把整个明细表锁住来操作,而有这个字段的话,那么一个行锁就解决了,也就是说加和不加这个字段在程序开发时需要的事务隔离级别是不同的,要尽量使用低级的隔离级别,事务隔离级别越低性能越好,而且可以有效防止死锁的产生,所以我推荐程序开发时以较低的隔离级别做为默认值,在有需要的操作时提高隔离级别,如果这个字段存在可以有效降低所需事务的隔离级别,那么加上吧。
  • 是否要频繁读取:主要出于性能考虑,这个很容理解可以提高查询性能,如果只是月报上需要的一个字段,果断不用加。
  • 使用时的实时性和准确性:主是这个字段数据的实时性和准确性要求有多高,如果这个字段实时性要求很高,在读取它的时候需要加锁才能进行读取那么就加上它,反之可以不加。这是出于对以上两点的综合考虑。
  • 一致性:如果这个字段会频繁变化,且对偏差容忍度很低。在这里“余额”是一个反例,它确实会频繁,且对偏差容忍度也不高。这个时候还加上了这个字段,那么就要做一些补偿性设计了。以“余额”为例:
    • 在所有会产生此字段变化的操作适当提高事务的隔离级别。
    • 增加结算表来记录每期余额。
    • 最终解释权,余额与明细不符时以余额为准,这不是个笑话,说的是在程序开发时要优先保证“余额”数据的准确性,明细表什么的可以延迟变动或通过重试机制来处理明细操作时发生的错误。

快照表

        有一些数据是以快照的形式存在的,也就是一但完成操作成为历史那么是不应该也不可以进行更改的,例如帐户的明细表,非要更改也是另加一条冲帐的明细来进行另类的更改,在设计的时候区分出这些快照表那么表中需不需要加乐观锁,建立时间,更新时间等这种字段就不言自明了。

        对历史信息特别敏感且有多种数据来源时我建议加一张快照索引表,以房屋为例:由测绘信息快照,登记信息快照,规化信息快照等多张表组合连接到一张房屋信息快照索引表中,然后由房屋表记录快照索引表的ID,在每次信息发生变化时,由业务生成新的快照索引重新连接发生变化的快照表索引ID并在完成时更新房屋表的索引ID,这样就能保证房屋的历史档案信息在任何时候查看都不会发生变化,要不要加快照索引表就看业务的数据来源吧,像前面帐户的例子就不需要,因为明细数据来源比较单一。

        这么设计需要在查询时增加很多LEFT JOIN,造成需要很长的SQL才能查全信息,那么就需要一些补偿设计:

  • 程序中使用缓存,由于快照记录一但建立就不会发生变化,也不会被删除,这简直是最理想的缓存对象。使用Spring boot cache 配合 Caffeine 可以使得这种操作简单到只需加一个注解就完成了,由于不用担心缓存数据不一致问题, 根本不需要考虑什么时候要更新缓存。(注:Spring boot 3.2 以前的版本对Mono 和  Flux 的缓存有点问题,它缓存的是Mono对象本身而不是Mono中的内容,所以在缓存方法返回时要加一下.cache()防止读缓存时重复执行响影链,3.2 及以后的版开始支持Mono 和Flux,不需要再加cache()方法,加了有时反尔会有问题)
  • 服务前加一级Redis,由redis来组合各个快照分片并扁平化对象提供实时信息,并提高访问性能。
  • 数据库后加一级Elasticsearch同步,由于redis是目录型数据库,对于拉列表和查询无能为力,所以后端我使用了canal 将数据推送到es中提供查询和拉列表及统计的功能,canal这货的坑很多,参见我另一篇文章吧。

        以上几点对于由于微服务分库造成的数据存储分散也是一个不错的解决方案。

乐观锁

        乐观锁的原理很简,实现更简单Spring boot data 一个注解的事,这里不作讨论,我总结加乐观锁有以下几点需要注意一下:

  • 保证一次原子操作只有一个乐观锁,可以减少不必要的版本检查和发生不必要的异常。
  • 最后更新有乐观锁的表,这主要是基于如果有后台数据推送的情况时,因为一般情况下有乐观锁的表也就是推送数据时被监听的表,最后更新可时使得推送被监听到时就可以取得所有操作的相关数据,同时还可以根据数据版本号来过滤重复和多余的推送,如果只有子表发生变化包含乐观锁的表没有变化,最好也更新一下这张表,这样才能触发后面的数据推送。
  • 不要使用Update语句直接更新带有乐观锁的表,这样会造成乐观锁的版本号不正确。

        关于乐观锁要不要传递给前端,总觉得把这个锁传递给前端再由前端传递回来感觉不太好,虽然叫乐观锁,传递给前端的话有点过于乐观了吧,传递链太长,时间也太长,想来想去还是看具体情况吧,我认为仅量还是不要传给前端,如果要传递,那么前端就要做好错误处理和页面超时。例如,两个同时发起请求,由于前端用户操作时长的原因,两个更改的到达时间是不确定的,如果把锁传递给了前端,那么其中一个肯定会得到一个错误,而如果不传递那么两个都会成功,只是修改完成后最终的结果是不确定的,但如果操作有完善的操作记录也给以给用户一个合理的解释,可以减少前端的错误处理,这不仅仅是为了减少前端的开发难度,而是因为在前端不管你错误处理的多么优雅,对最终用户来说都感觉像是要出大事了,它们会立刻变的警觉起来,大声宣布系统出问题了,根本不会去读给出的错误提示,不敢再进行任何操作,并且把以前和以后出现的所有问题都归咎于系统出错了,所以不把锁传到前端可以有效减少程序开发人员对用户的打骂次数。

主键的选择

主键的选择我知道的有以下几种,各有优缺点:

  • UUID
  • 数据库自增
  • 有意义的编号
  • 雪花ID
  • 机器ID加序号

每种的优缺点网上说的很多,我简单说下 ,

  •  UUID使用上最简单,一个注解就行,而且是无限的,其它总有用尽的时候虽然时间长到可以忽略不计,问题是它是无序的。
  •  数据库自增和雪花ID都有序的,而且自增ID还是连续的但使用时需要先存储后才能获得
  •  雪花ID和机器ID加序号都需要几位机器ID,使用起来相比其它要麻烦一些,但是这两个都有开源的实现,百度有一个分布式的雪花ID的实现,但是机器ID的获取有问题(太过于浪费而且不能重复利用),我根据它的代码重写一份通过spring cloud的注册服务来获取机器ID减少浪费,并支持Mono和Spring autoconfig,已在github上开源发布。美团也开源了一份同时支持这两种类型的实现,没用过不知到怎么样。
  •  有意义的编号最后都需要有几位序号,最终还是要靠一个单一的源来实现不够分布式,而且有意义的编号最终都会变的没有意义,接触过编码规范做的最好的就是身份证号了,包含了很多信息,而且可以自校验,但里面的信息最后都会变成错的,例如,出生地区划代码,很多地方的区划代码会因行政级别的变化而改变,更别说里面还有性别信息了,当你看到一个男性大美女你会在惊呆的同时怀疑自己的程序出Bug了还是怀疑自已眼睛出现问题了呢?

        这几种类型的主键我都用过,最终我认为最好的选择就是数据库自增ID和雪花ID组合起来使用,有一些没有关连表的主键使用自增ID,需要做关联的使用雪花ID。偶尔也可以使用有意义的编号。 但要注意一点永远不要使用外部的编号来做主键,例如:社会信用统一代码(除非你就是颁发部门)因为使用之后你就会发现它所声称的唯一和不变在你的程序中就是个笑话。

外键和索引

        索引和主键一样是一定要加的,不然性能和使用文本文件存数据区别不大,在数据库里加外键约束会自动根据外键字段生成索引,听说阿里是不允许在库里加外键约束,应该是出于对性能的考虑,不过我还是推荐加上外键约束的,一来大部分程序都不像阿里都有那么大的访问量和对性能极至的追求,二来如果有历史数据需要导入可以及时发现问题,不然程序会时不时的因为历史数据出一些莫名奇秒的问题,很让人头秃。如果真的不想要,可以在程序运行稳定很长时间后再全部移除。

        关于索引的加法,我不是专业的DBA,给不出太专业的意见,我的习惯是前期先建立一些基本的索引,在程序开发时每写一条查询就根据SQL再建一个索引,最后在测试阶段开启慢查询再补一遍索引,索引真的很关键,我宁可多加也不想漏加,不要舍不得那点磁盘空间了,当然有条件也可以把这些工做都丢给DBA,毕竟人家是专业的。

字段类型的选择

  • Blob字段能不用就别用,见过很多次往数据库里存图片的,我认为这是个最糟糕的设计,索引时浪费磁盘空间,查询缓存时浪费内存,传输时浪费IO,及度影响性能。有很多开源好用的小文件存储引擎可以选用,而且只要是个云服务供应商都会提供这类存取服务。
  • 时间类型,优先选择时区相关的时间类型,要知道中国的早8点和美国的早8点是完全不同的。 以mysql为例有两种,datetime和timestamp,其中timestamp是时区相关的,但是需要注意的是timestamp 是会根据服务器所在时区变化的,而且早期版本这个类型默会自动更新为最后更新时间(不理解早期为什么要这么设置,当你在数据库上使用update时批量更新数据,你会惊喜的发现所有的时间字段内的数据都丢了),有的时候也应该选用datetime这种时区无关的类型来存储的,因为有些时间在语义上是自带时区信息的,例如身份证上的出生日期,无论是在哪里,意义都是中国时区上的时间。
  • 字符类型的长度,这个要注意的是不仅要考虑内容所需长度,还要考虑最终在用户界面上的显示方式,不然最终用户界面是个什么样就很难估计了。
  • 字典类型,尽量使用枚举以字符串的型式在数据库中映射存储而不是字典表,正常的ORM框架对枚举的映射都不是什么问题,这么做不仅可以减少关联表查询,还可以减少很多因为预计不到的字典项产生的错误,而且可以让枚举现实一个接口来减少代码中对IF的使用。使用字符串而不是数值映射是因为这会使数据内容更容易理解,防止别人看到库里的一个数值想知道是啥意思的时候忍不住问候你的亲人。
  • 不要依赖数据库的默认值,如果要指定默认值,最好的选择就是 null 。例如:updateAt,createAt等字段,现在的ORM框架也能很好的实现默认值,例如Spring 的 @CreatedDate注解用起来也很方便,如果是依赖默值来实现,参考上面timestamp类型默认值问题,你可能会因为预计不到的惊喜而脱发,

存储过程

        很多文章不推荐使用存储过程,因为会影响数据库迁移。但我认为有的功能的实现使用存储过程还是必要的,有些功能使用存储过程配合数据库的定时任务是会很方便,例如,月末结算,定期无用数据清理等。使用存储过程可以屏蔽不必要的外来影响,例如网络,IO等资源,除此之外性能优势也很明显因为自己在内部就解决了不用将数据传来传去,需要注意的是在调用存储过程和使用定任务的代码处一定要写好注释。另外想要为程序更换数据库的话可不是只要在ORM中更改个方言类型就完事了,别天真了。

        触发器还是别用了,会使用的行为结果不太好预测,你会因为忘记某个触发器,而产生一些不可理解的结果。

微服务分库

        主要是分库后的数据冗余存储。

结语

        文章中的“你”都是现在的我对将来的我的称呼代号,没有任何贬低他人的意思。

        以上都是多年在实际开发应用中基于爬过的坑总结的个人见解,错漏难免,欢迎指正。

写累了,下次再说,待续...

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

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

相关文章

HTML静态网页成品作业(HTML+CSS)——世博园介绍(2个页面)

🎉不定期分享源码,关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 🏷️本套采用HTMLCSS,未使用Javacsript代码,共有2个页面。 二、作品演示 三、代…

BUUCTF-Ezsql1

1.打开靶机 打开第一个链接 2.万能密码 使用万能密码:a or 1 # 密码为随意 第二个用kali打开 3.ssh连接靶机 ssh ctf284490d0-7600-4c65-9160-5ced02f45633.node5.buuoj.cn -p 28191 由题可知密码为123456 4.找到并修改index.php文件 找到index.php文件 #内容如…

spring启动自动执行方法

原文链接: Spring Boot 启动时,让方法自动执行的 4 种方法!-阿里云开发者社区 (aliyun.com) -------------------------------------------------------------------------------------------------------------------------------- 在springBoot中我…

Springboot整合支付宝沙箱支付

2.配置说明 要记住这几个重要的配置 appId 这个是appIdprivateKey 商户私钥publicKey 支付宝公钥, 即对应APPID下的支付宝公钥notifyUrl 支付成功后异步回调地址(注意是必须是公网地址)returnUrl #支付后回调地址signType 签名类型 一般写 RSA2charset utf-8format json #网关…

数据结构奇妙旅程之红黑树

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

印染厂区污水怎么处理

印染厂污水处理是一项十分重要且紧迫的环境问题。随着工业化的快速发展,印染厂所排放的大量废水不仅对环境造成了严重影响,也对人们的生活和健康带来了潜在的威胁。因此,寻找有效的印染厂污水处理方法显得尤为重要。 针对印染厂污水的特点&am…

通过Arthas修改并热发布代码

通过Arthas修改并热发布代码 主要使用jad、mc、retransform三个命令: jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.javamc /tmp/UserController.java -d /tmpretransform /tmp/com/example/demo/arthas/user/UserCo…

tp8 mpdf 导出pdf

1. 安装mpdf composer require mpdf/mpdf 2. 然后 使用 use mpdf\Mpdf; 或者 require_once __DIR__ . /vendor/autoload.php; 官方文档 mPDF – mPDF 手册 文档里有很多东西 可以自己去研究 3. 编写代码 下载 (支持中文) $mpdf new Mpdf([mode > utf-8,"autoS…

【技术干货】MediaCrawler:一站式社交平台数据抓取利器,带你玩转小红书、抖音、快手、B站和微博数据分析

一、引言 大数据时代,社交媒体平台上的海量信息为我们提供了丰富洞察市场、研究用户行为的机会。然而,如何高效便捷地收集和整理这些分散在各平台的数据呢?今天,我们将为您揭秘一款专为小红书、抖音、快手、B站和微博打造的强大数…

用python实现视频异常检测

视频异常检测是一个复杂的任务,它涉及到对视频帧的分析和比较,以检测与正常模式不符的异常行为或事件。下面是一个基本的步骤指南,以及如何使用Python实现视频异常检测: 步骤指南 视频帧提取:首先,你需要…

实在智能与中国信通院联合牵头智能体(Agent)标准编制

近日,中国信息通信研究院(以下简称“信通院”)启动国内首个《智能体(Agent)技术要求与评估方法》系列标准编制,实在智能作为参编单位并牵头编制技术能力部分,深度参与该标准对智能体技术要求和评…

阿里云ecs服务器配置反向代理上传图片

本文所有软件地址: 链接:https://pan.baidu.com/s/12OSFilS-HNsHeXTOM47iaA 提取码:dqph 为什么要使用阿里云服务器? 项目想让别人通过外网进行访问就需要部署到我们的服务器当中 1.国内知名的服务器介绍 国内比较知名的一些…

机器学习复习(9)——自定义dataset

目录 第一种dataset(文件夹名即为标签) 用于将格式(1)转换为格式(2) 第二种dataset(标签在labels文件夹下的对应的txt文件里面) 第一种dataset(文件夹名即为标签) 数据组织格式(1) --data ----train …

续上篇 qiankun 微前端配置

上篇文章地址:微前端框架 qiankun 配置使用【基于 vue/react脚手架创建项目 】-CSDN博客 主应用: src/main.js 配置: import Vue from vue import App from ./App.vue import router from ./router import { registerMicroApps, start } …

【小程序开发】蓝牙设备API——单点蓝牙应用程序编程接口整理(二)

ty.device.getBLEDeviceRSSI 获取 BLE 外设的信号 需引入DeviceKit,且在>1.2.6版本才可使用 参数 Object object 属性类型默认值必填说明deviceIdstring是设备模型 deviceId 设备 Idcompletefunction否接口调用结束的回调函数(调用成功、失败都会执…

jar读取目录配置、打包jar后无法获取目录下的配置

jar读取目录配置、打包jar后无法获取目录下的配置 jar读取目录配置、打包jar后无法获取目录下的配置。java打成jar包后获取不到配置文件路径。解决项目打成jar包上线无法读取配置文件。打包jar后无法读取resource下的配置文件 场景 需要读取 src/main/resources/mapper下的所…

大机中的汇编语言该怎么学

提起程序开发,就不得不说合久必分,分久必合,反成各个程序段之间都有这个关系,而多个程序又组成一个功能组,可以完成一项业务,ASM比JCL难很多,因为它涉及到地址和业务。 一 ASM 的难度如何 比方…

UGUI界面性能优化3-合理规划界面层级结构

在Unity中,UGUI(Unity GUI)是一种用于创建用户界面的工具。合理规划界面层级结构对于开发一个可维护和易于使用的界面非常重要。以下是一种合理的UGUI界面层级结构规划方式: Canvas(画布):Canva…

【python】flask框架的生命周期,多种查询参数的获取方式

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…