MySQL中ON_DUPLICATE_KEY_UPDATE的用法实践

前言

最近在平台推送业务的特性开发中,使用到了MySQL的"ON DUPLICATE KEY UPDATE"语法,这里结合业务场景对该语法的使用做个介绍。

业务场景

使用平台的推送服务之前,需要注册设备。对于终端的注册信息,主要有玩家的user_id、对应于某渠道的客户端包名bundle_id、以及其它相关信息,例如当前设备的推送类型(FCM、APNS、Baidu等)、设备的device_token、依托于AWS推送服务的Amazon 资源名称 (ARN)、 该终端设备对应玩家的默认语言、所在国家或地区等信息。

不同游戏终端设备的注册逻辑大同小异,基本都是在玩家每次上线时由游戏服务器向平台的推送服务发起注册请求。如果当前终端设备之前注册过,则只是更新该终端设备的其它注册信息(例如推送渠道更改、设备device_token变更、默认语言、国家地区信息的变更等);假若当前终端设备未注册过时,就写入一条新的记录。

对于iOS、应用宝以外的渠道下载的客户端(以bundle_id作区分),原则上玩家账号(user_id)是可以互通的。也就是说,某个游戏中同一个user_id可以对应多个bundle_id,一条终端设备的注册记录以自增id作为PRIMARY KEY,同时以(user_id, bundle_id)作为UNIQUE KEY,一组(user_id, bundle_id)可以唯一标识一条注册记录。

描述到这里就清楚了,当前业务可以抽象描述为:

1.对于每条终端设备的注册信息,通过user_id和bundle_id确定其唯一性;

2.对于已注册的终端设备,每次重新注册时可能会更新其它字段(update);

3.对于未注册的终端设备,则在注册时新写入一条注册信息(insert)。

用法实践

不太好的处理方法

对于上文所述的业务场景,如果想要插入或更新一条注册信息,使用功能单一的SQL语句,需要怎么做呢?

1.执行SELECT语句,条件为user_id & bundle_id,确认待插入的记录是否已存在;

2.若记录已存在,则执行UPDATE语句更新相应记录其它字段值,条件依然为user_id & bundle_id;

3.若记录不存在,则执行INSERT语句,插入一条新记录。

可见前后需要执行三条SQL语句才能完成一个功能,而且为确保业务逻辑的前后一致,可能还需要将此三条SQL组合成一个事务,确保中间操作出现异常可以回滚到最初状态,不会造成脏数据等问题。比较繁琐。

使用ON DUPLICATE KEY UPDATE

鉴于此,我们采用以下sql语句进行设备注册时新设备注册信息的写入,或已有设备的信息更新:

INSERT INTO `table_name`(`section1`,`section2`,`section3`)VALUES(`value1`,`value2`,`value3`) ON DUPLICATE KEY UPDATE `section3`=`value3`

具体含义就是:

1.当字段section1、section2、section3对应的值value1、value2、value3不存在时,便插入一条新记录,其中字段section1、section2、section3对应的值分别为value1、value2、value3,如果表中有其它未显式指定的字段,则使用默认值;

2.当字段section1、section2、section3中包含PRIMARY KEY或UNIQUE KEY时,跳转至UPDATE部分,执行部分字段的更新操作。

相比之前的SELECT + UPDATE/INSERT方案,简单明了。

补充说明

1.该用法对mysql表的影响行数计算:

如果是作为一条新记录写入表中,则影响的行数为1;

如果是对原有记录的更新操作,则受影响的行数为2(update操作完成后,主键id也会自增1);

2.与PRIMARY KEY或UNIQUE KEY的关系:

如果该条INSERT语句插入表中会导致当前PRIMRAY KEY或某个已存在的UNIQUE KEY出现重复值,那么就执行后半段的UPDATE语句,更新被命中记录的部分字段值;

3.写法的简化:

若当前被插入的记录共有5个字段,其中section1和section2组成了表的联合索引,则SQL语句时传统写法是:

INSERT INTO `table_name`(`section1`,`section2`,`section3`,`section4`,`section5`)VALUES('?','?','?','?','?') ON DUPLICATE KEY UPDATE `section3`='?',`section4`='?',`section5`='?';

本文所遇的真实业务场景,需要插入的字段不止5个,而是20+,可想而知以现在的方式写出来的sql语句,光变量需要填40个,有重复不说,还可能把变量顺序填返,造成数据错误,又不好修复。

相对精简的写法,省去了后面update部分重复手写的变量,使得传入的参数数量少了一半,代码可读性提高,变量填错位的可能性也会大大降低:

INSERT INTO `table_name`(`section1`,`section2`,`section3`,`section4`,`section5`)VALUES('?','?','?','?','?') ON DUPLICATE KEY UPDATE `section3`=VALUES(`section3`),`section4`=VALUES(`section4`),`section5`=VALUES(`section5`);

更进一步,对于一次性需要插入或更新多条记录的场景,该UPDATE部分的精简写法可以动态传入要修改的值给对应某行,这样就可以满足需要给不同的记录插入不同的值的情况,例如:

INSERT INTO `table_name`(`section1`,`section2`,`section3`,`section4`,`section5`)VALUES('?','?','?','?','?'),('?','?','?','?','?') ON DUPLICATE KEY UPDATE `section3`=VALUES(`section3`),`section4`=VALUES(`section4`),`section5`=VALUES(`section5`);
4.特殊之处

该语法是MySQL特有的语法,不是SQL的标准语法

小结

使用前需要根据实际业务场景评估是否使用该语法,并且确认目标表的字段、主键、联合索引等符合(或改造后符合)该语法的使用场景。

总体感觉该语法挺好用的,其它没了。

你可能感兴趣的:(MySQL中ON_DUPLICATE_KEY_UPDATE的用法实践)