场景
我有以下这些方法:
public void AddItemSecurity(int itemId, int[] userIds)
public int[] GetValidItemIds(int userId)
最初我考虑的是形式上的存储:
itemId -> userId, userId, userId
并且
userId -> itemId, itemId, itemId
AddItemSecurity
是基于我从第三方 API 获取数据的方式,GetValidItemIds
是我希望在运行时使用的方式。潜在有 2000 个用户和 1000 万个项目。项目 ID 的格式为:2007123456、2010001234(10 位数字,前四位表示年份)。
AddItemSecurity
不需要执行得特别快,但 GetValidIds
需要在子秒级别完成。此外,如果对现有的 itemId
进行更新,则需要将该 itemId 从不再存在于列表中的用户中移除。我正在考虑如何以最优方式存储此数据。最好是在磁盘上(带缓存),但我希望代码易于维护和清晰。
如果项目 ID 从 0 开始,我考虑为每个用户创建一个字节数组,长度为
MaxItemId / 8
,并设置一个 true/false 位来指示是否存在该项目。这将限制每个用户的数组长度略大于 1MB,并且提供快速查找以及一种更新用户���表的简单方法。通过将其持久化为 .Net 4 框架下的 内存映射文件,我认为我还能获得良好的缓存(如果机器有足够的 RAM),而无需自己实现缓存逻辑。解析 ID、剥离年份并存储按年分类的数组可能是一种解决方案。ItemId -> UserId[] 列表可以直接序列化到磁盘,并使用普通的
FileStream
来读写以保持列表并在更改时进行差异比较。每次添加新用户时,所有列表都必须更新,但可以在每晚完成此操作。
问题:我应该继续尝试这种方法,还是还有其他需要探索的方法?我认为 SQL Server 不会执行得足够快,并且它会产生一些开销(至少如果它托管在不同的服务器上),但我的假设可能是错误的。对此问题的任何想法或见解都将不胜感激。并且我希望尽量少地添加硬件 :)
[更新 2010-03-31] 我现在已在以下条件下测试了 SQL Server 2008。
- 两列表(userid,itemid),均为整数
- 这两列拥有聚集索引
- 为180个用户添加了约800,000个项目 - 总共有1.44亿行
- 已为SQL服务器分配了4GB的RAM
- 双核2.66GHz笔记本电脑
- SSD硬盘
- 使用SqlDataReader将所有itemid读入列表
- 循环遍历所有用户
如果我运行一个线程,平均时间为0.2秒。当我添加第二个线程时,它会上升到0.4秒,这仍然可以接受。从那时起结果是下降的。添加第三个线程会使很多查询时间达到2秒。第四个线程,则是4秒,第五个则会将一些查询峰值提高至50秒。
即使只有一个线程,CPU也会在进行此操作时繁忙。我的测试应用程序需要一些时间来处理快速循环,而sql则需要其余时间。
这导致我得出结论,它在性能方面不会很好地扩展。至少在我测试的硬件上是如此。是否有优化数据库的方法,例如为每个用户存储整数数组,而不是每个项目一条记录。但这样做会使删除项目更加困难。
[更新2010-03-31 #2]
我使用相同的数据进行了快速测试,将其作为内存映射文件中的位。它执行得更好。六个线程之间的访问时间介于0.02秒和0.06秒之间。纯粹受内存限制。映射文件由一个进程映射,同时由六个其他进程访问。由于sql基础占用了4GB,因此磁盘上的文件占用了23mb。
http://download.oracle.com/javase/6/docs/api/java/nio/channels/FileChannel.html#map(java.nio.channels.FileChannel.MapMode, long, long)
。 - user183037