当前位置: 首页 > 科技观察

系统上线前,SQL脚本的九大陷阱

时间:2023-03-22 16:10:25 科技观察

前言系统上线时,非常容易出错。甚至在之前的测试环境中,都执行过SQL脚本。但是有时候,当系统上线后,在生产环境中执行同样的SQL脚本,还是会出现一些问题。对于一些小公司来说,SQL脚本是自己开发执行的,风险很大。一些大厂有专业的DBA来检查,但是DBA也不是万能的,一些错误的SQL脚本在生产环境中还是有可能执行的,比如:update语句的顺序是错误的。今天就和大家聊一聊系统上线时SQL脚本的9大坑,让大家吸取教训,预防它的发生。希望对您有所帮助。1缺少脚本我们上线时执行的SQL脚本,最常见的问题应该是缺少脚本。有时缺少一个字段。有时字段的注释没有及时修改。有时不会创建一些新表。有时忘记修改字段类型。等等,我们的SQL脚本中存在很多脚本缺失的情况。那么,如何解决这个问题呢?答案:使SQL脚本成为代码的一部分。在项目的代码中,创建一个专门的sql目录,并根据每次迭代在该目录下创建一个子目录,如:mv3.2.1,将SQL脚本存放在mv3.2.1下。我们开发环境中任何与表相关的操作,如:添加字段、修改字段类型、修改注释、添加索引、创建表等,都需要通过SQL语句进行操作,然后将SQL语句组织成SQL脚本。最后提交到公司的GitLab。当我们在测试环境和生产环境发布版本时,我们会去GitLab中寻找相关的SQL脚本迭代版本来执行。这样就基本可以解决脚本丢失的问题了。2Scriptsyntaxerror有的朋友看到??这个标题可能会有点疑惑。SQL脚本不是在测试环境执行过的吗?为什么我仍然收到语法错误?比如有这样一个场景:本来你的SQL脚本没问题,但是你没有按照规范给一个表加了多个字段,写了多条ALTER语句。例如:altertablet_useraddcolumn`work`varchar(30)DEFAULTNULLCOMMENT'work';altertablet_useraddcolumn`provice`varchar(10)DEFAULTNULLCOMMENT'hometown';当你上线的时候,你向DBA提供SQL工单时,工单下发,工单被DBA审核拒绝,退回。然后,情急之下,你匆匆把多条ALTER语句改成了一条ALTER语句。例如:altertablet_useradd`work`varchar(30)DEFAULTNULLCOMMENT'work',add`provice`varchar(10)DEFAULTNULLCOMMENT'hometown';可能会出现SQL语法错误。因此,不管是什么SQL语句,都要养成一个好习惯。只要修改了,一定要记得先到开发环境的数据库中去,先做一个测试。不要直接引用生产环境。即使你非常确定,你也需要更加谨慎。这样基本可以避免SQL语法错误的问题。3脚本顺序错误有时候,我们上线的时候,DBA执行SQL脚本的时候并没有报错,而是最终的数据错了。可能是脚本顺序错误导致的。比如有这样一个场景:你通过insert初始化一条数据到某张表。例如:INSERTINTO`sue`.`t_user`(`id`,`code`,`age`,`name`,`height`,`address`,`work`,`provice`)VALUES(1,'101',21,'周星驰',173,'香港',NULL,NULL);另一个人需要根据你的数据通过更新来修改数据。例如:updatet_usersetage=25whereid=1;你提到了两个SQL脚本。对方先提,你后提。DBA首先通过了他的SQL工单的审计,首先更新了数据。这时候通过id查不到数据,受影响的行数为0。然后DBA会审核你的SQL工单,如果审核通过,就会插入一条数据。由于SQL脚本的顺序不正确,最终系统上线时数据会不正确。那么如何解决这个问题呢?双方一定要提前沟通,在你的初始化脚本中加入另一个同事的SQL脚本。当你的脚本初始化时,你可以直接修改数据。例如:INSERTINTO`sue`.`t_user`(`id`,`code`,`age`,`name`,`height`,`address`,`work`,`provice`)VALUES(1,'101',25,'周星驰',173,'香港',NULL,NULL);这样可以避免执行顺序问题。4执行时机错误。部分系统功能已经上线。在后续迭代中,为了尽可能少的影响线上功能,可以添加pre(即预生产环境)。环境与生产环境类似,连接相同的数据库,使用相同的apollo配置。但唯一不同的是,前置环境没有实际用户流量,只能由公司内部人员访问。一般在迭代版本上线前,首先要将系统功能发布到pre环境,测试通过后才能发布到prod(即生产环境)。但是有些SQL脚本不能在前置环境执行,否则会影响生产环境。例如:修改字段类型,将int改为varchar,或者在初始化数据时初始化一个新添加的枚举数据。由于pre环境运行的是最新的代码,prod环境运行的依然是旧代码。如果在pre环境发布时直接执行SQL脚本,可能会导致prod环境功能异常。所以需要弄清楚什么时候执行SQL脚本,哪些应该在pre环境执行,哪些应该在prod环境执行。我们在提到SQL工单的时候,一定不能一下子全部提到,一定要分清时机。发布pre环境的时候,或者不提prod环境的SQL脚本。或者,在工单名称上进行区分,比如在prod_开头加一个标识。这样可以解决SQL脚本执行的时序问题。5对数据库的误解有时候,我们的数据库分为数据库和表,或者增加了一个备份数据库。在执行SQL脚本的时候,由于我们自己的疏忽,在提交SQL工单的时候选择了错误的数据库,或者是DBA的疏忽,在执行SQL工单的时候,执行了错误的数据库,就会出现问题。建议在我们的SQL脚本中加入库名,例如:altertablesue.t_useradd`work`varchar(30)DEFAULTNULLCOMMENT'work';在此处添加库名称:sue。这样基本可以避免选错数据库的问题。6脚本耗时过长。有时候,我们的SQL脚本需要批量修改生产环境中的一些数据。一般情况下,一条update语句就可以搞定。例如:updateusersetstatus=0wherestatus=1;但是由于user表的数据量较大,我们在执行SQL脚本之前并没有预先评估SQL脚本的耗时情况,而是在实现中直接选择了生产环境的数据库。如果SQL脚本耗时很长,比如执行10分钟,可能会导致用户表被长时间锁定,影响正常的业务功能。SQL脚本执行过程中,可能会出现业务函数操作导致的死锁问题。所以,这种大规模的数据更新操作,建议在用户较少的凌晨,分批次多次进行。我们希望尽可能少地影响在线用户的功能。另外,生产环境中的加字段、加索引等操作,也有可能造成长期表锁。还要避免在用户访问高峰期执行相关的SQL脚本。7脚本无法回滚。大部分系统都能成功上线。虽然过程中会遇到很多问题,但如果能及时解决,是可以顺利上线的。但是,如果有一些问题不能在规定的时间内解决,很可能会导致无法上线。如果上线失败,说明数据库的代码和SQL脚本需要回滚。如果只回滚代码,不回滚数据库,可能会引起很多系统异常。所以,我们在准备SQL语句的时候要慎重,考虑SQL语句是否可以回滚。可以在update语句中加入修改时间:updatet_usersetage=25,time=now(3)whereid=1;这样就可以通过这个时间回溯一个SQL操作修改的数据,方便后期回滚。有时候我们要更新的数据是通过多条SQL查询出来的,比如:要使用的id。为了方便回滚,我们可以加一张临时表,把这些id保存起来,以后可以追溯。当然,一些开源的数据库管理平台,比如Archery,内置了SQL审计和回滚功能。8忘记添加索引。添加字段后,很容易忽略的一件事是:添加索引。尤其是当当前表的数据量很大,而且添加的字段是另一个表的id时,强烈建议在这种情况下添加索引。如果我们上网,在SQL脚本中,我们忘记给这个字段加索引。如果大批量访问id字段,全表扫描可能会导致数据库性能急剧下降,出现大量超时问题。所以建议我们在开发的时候,如果要添加字段,要养成良好的习惯,想想这个字段是否需要被索引。如果不确定数据量,可以先去生产环境查询真实的用户数据,不然后面可能会造成比较大的生产事故。9字段重命名对于生产环境中的表字段,一般情况下,我们是不允许修改名称的。如果在发布前置环境时通过SQL脚本修改某个表的字段名称,前置环境代码使用新的名称,系统是没有问题的。但是prod环境仍然使用旧名称,所有使用该名称的SQL语句在代码执行时都会报错。因此生产环境中禁止通过SQL脚本修改字段名。当然,除了系统上线时SQL脚本的这些坑,还存在系统发布失败、代码合并错误分支、mq消息被pre消费、无法回滚等诸多问题。