从前有个大师,率领一群徒弟,为客户做了一个软件系统。某天,客户提出了一个新的需求,向系统中的所有用户发送系统消息。由于当时系统刚上线不久,系统中的用户也就几十个。大师为了考验自己的徒弟,便将该需求分配给他的徒弟,要求每个人都做一套方案出来,于是便有了下面的故事。
徒弟们接收到该项任务后,每个人都想到了先建一张系统消息表,每次发送系统消息时,将数据保存在词表中,用户就能从该表中读取他个人的系统消息。用户信息表的模型如下:
基于上面的数据库模型,徒弟们分别作了不同的实现方案,如下:
实现方案一:
小A是个急性子,领到任务后。立即开始了他的编程思路:将系统中的所有用户都取出来,然后遍历所有的用户,每次迭代时插入一条系统消息。伪代码如下:
1
2
3
4
5
6
7
|
List<FavUser> userList = favUserService.getAllUser();
for
(FavUser favUser : userList){
SysMessage sysMessage =
new
SysMessage();
...
sysMessage.setReceiveUserId(favUser.getUserId());
sysMessageService.addSysMessage(sysMessage);
}
|
由于系统中的用户较少,小A几遍测试,发现系统中运行良好,便将该方案提交了上去。
实现方案二:
小B接到任务后,想到应该先把系统中所有的用户Ids取出来,然后遍历这些ids,每次迭代时都插入一条系统消息。基于此,小B的伪代码如下:
1
2
3
4
5
6
7
|
List<Integer> userIdsList = favUserService.getAllUserIds();
for
(Integer userId : userIdsList ){
SysMessage sysMessage =
new
SysMessage();
...
sysMessage.setReceiveUserId(userId);
sysMessageService.addSysMessage(sysMessage);
}
|
由于系统中的用户较少,小B几遍测试,发现系统中运行良好,也将该方案提交了上去。
实现方案三:
小C接到任务后,考虑到每次插入的系统消息,除了用户id不同外,其余的数据项都相同,便想到了批量插入数据。由于MySQL数据库支持批量插入数据,小C设计出了执行的SQL语句与伪代码:
执行的SQL语句如下:
1
2
3
4
5
6
7
8
9
|
<
insert
id=
"addBatchSysMessage"
parameterType=
"com.favccxx.favsoft.SysMessage"
>
insert
into
sys_message (MESSAGE_TITLE, MESSAGE_CONTENT, MESSAGE_STATUS, RECEIVE_USER_ID, RECEIVE_TIME, CREATE_TIME
)
values
<foreach collection=
"list"
item=
"item"
index
=
"index"
separator=
","
>
( #{item.messageTitle,jdbcType=
VARCHAR
},#{item.messageContent,jdbcType=
VARCHAR
}, #{item.messageStatus,jdbcType=
CHAR
}, #{item.receiveUserId,jdbcType=
INTEGER
},#{item.receiveTime,jdbcType=
TIMESTAMP
}, #{item.createTime,jdbcType=
TIMESTAMP
}
)
</foreach>
</
insert
>
|
伪代码如下:
1
2
3
4
5
6
7
8
|
List<FavUser> userList = favUserService.getAllUser();
List<SysMessage> dataList =
new
ArrayList<SysMessage>();
for
(FavUser favUser : userList){
sysMessage.setReceiveUserId(favUser.getUserId());
dataList.add(sysMessage);
}
List<SysMessage> subList = dataList.subList(
0
,
1000
);
sysMessageService.addBatchSysMessage(subList);
|
小C向系统中添加了几千个模拟用户,测试系统运行良好。但发现将系统中的用户增加至几万时,发送系统消息速度明显变慢。于是,小C采用了分组的方式进行插入,每10,000条插入一次,系统运行良好。
实现方案四
小D接到任务后,考虑的也是批量插入数据,但与小C不同的是,他想通过执行一次SQL完成批量插入数据。即先将待发送的消息存入数据库中,然后通过MySQL查询并同时将数据插入系统消息。小D的MySQL设计如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<
insert
id=
"addAllSysMessage"
parameterType=
"com.favccxx.favsoft.SysMessage"
>
insert
into
sys_message ( MESSAGE_TITLE,
MESSAGE_CONTENT, MESSAGE_STATUS, RECEIVE_USER_ID,
RECEIVE_TIME, CREATE_USER_ID, CREATE_TIME
)
select
a.MESSAGE_TITLE
as
MESSAGE_TITLE,
a.MESSAGE_CONTENT
as
MESSAGE_CONTENT,
0
as
MESSAGE_STATUS,
b.user_id
AS
RECEIVE_USER_ID,
now()
as
RECEIVE_TIME,
now()
as
CREATE_TIME
from
sys_message_send_info a,
(
select
user_id
FROM
auth_user
) b
where
sendInfoId=#{sendInfoId}
</
insert
>
|
1
|
int
insertCount = sysMessageService.addAllSysMessage(sendInfoId);
|
小D向系统中插入了10万个模拟用户,经测试,系统运行良好。以下是向113508个用户发送消息的花费时间。
“可爱 的测试”发送给系统中【113508】个用户,共用时【2241】毫秒 “fdgsdfg”发送给分组中【113508】个用户,共用时【2236】毫秒 “平台所有用户系统消息”发送给系统中【113508】个用户,共用时【1916】毫秒 “发生的飞洒的”发送给系统中【113508】个用户,共用时【1217】毫秒 “测试用户组33”发送给分组中【113508】个用户,共用时【1617】毫秒 |
上面的故事,便是我们在开发中经常遇到的场景,要实现系统中的一个功能时,往往有很多种方法,最先实现的,代码最简便的并不一定是效率最好的。代码本身就是一个黑盒子,不要考虑程序的现有性能,更要考虑好程序的扩展性能。