V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
bthulu
V2EX  ›  数据库

上万条数据, 短时间内连续查询几千次, 是数据库查, 还是内存查更好一点?

  •  
  •   bthulu · 248 天前 · 9158 次点击
    这是一个创建于 248 天前的主题,其中的信息可能已经有所发展或是发生改变。

    客户库存有一万多, 要将这一万多库存按客户要求分散到几百个工位上, 需要针对库存的多个属性进行多次查询, 循环里套循环, 能查上千次甚至更多.

    这种情况下, 我是直接数据库查, 还是将数据一次全拉到内存, 在内存里查更好?

    数据库查可以走索引, 内存里可就没索引查一次就是全量遍历一次了. 如果有内存里支持索引的列表就好了. 语言是.net8.

    别说什么优化查询方案一次查询搞定的了, 这不可能. 现实业务就是各个工位之间关系也是错综复杂, 只能这样查了.

    第 1 条附言  ·  246 天前
    感觉大家伙好像没 get 到这个问题难点在哪里.
    数据量是不大, 查一次不管怎么查都问题不大.

    但是, 这里是要在几百毫秒内完成几千次查询, 每次查询用到的字段值都不相同, 用到的字段名, 字段数量也会有变化.

    程序跑在一台戴尔(Dell)成就 3710 上, 数据库也在这上面.

    不要问为什么不用专用服务器, 这是制造业, 项目几个亿, 但是服务器确实就是普通家用机器. 我天天蹲在集装箱里开发你们敢信?
    76 条回复    2024-04-18 11:51:42 +08:00
    wxf666
        1
    wxf666  
       248 天前
    全读出来,存到 HashMap 里,不也相当于有索引了吗?

    Chad0000
        2
    Chad0000  
       248 天前 via iPhone
    就这么点儿数据,内存里面就算没索引也是比数据库快很多。
    bthulu
        3
    bthulu  
    OP
       248 天前
    @wxf666 这个你咋存? 比如某个属性 width, 范围从 800, 850, 900, 一直到 3000. width=800 的几十条, width=850 的有几条, width=900 的又有几十条, 咋个存到 hashmap 里去?

    难道按 id 作为 key 存进去? 问题是我这查询条件没有一个地方用到 id, 都是根据属性查的.
    me1onsoda
        4
    me1onsoda  
       248 天前
    财大气粗就用内存呗
    bthulu
        5
    bthulu  
    OP
       248 天前
    @Chad0000 问题是不走索引每次都是遍历啊.

    比如 1 万条查 1 千次, 平均每次查出来 10 条数据, 对于有索引来说, 每次查询只需要定位最大值和最小值, 然后将中间的依次取出即可. 那 10 条数据走一次索引, 接触到的数据可能还不到 20 条. 一千次就是 2 万条.

    如果走内存遍历, 查一次就是接触 1 万条数据. 一千次就是一千万条

    也就是数据库多了一千次 IO, 但少遍历了 998 万条数据.
    xmumiffy
        6
    xmumiffy  
       248 天前 via Android
    存成二维数组就行,不过几万条你直接遍历也没事,内存遍历也比走网络查数据库快多了
    Chad0000
        7
    Chad0000  
       248 天前 via iPhone
    @bthulu
    数据就在你手头上,直接定义 c#数组加.where 跑跑试试呗。
    q727729853
        8
    q727729853  
       248 天前   ❤️ 1
    直接查库,也不用判断索不索引的。那么丁点数据,有啥问题?
    macaodoll
        9
    macaodoll  
       248 天前 via Android
    几万条,直接内存呗。这才多大点
    raycool
        10
    raycool  
       248 天前
    几万条内存不随便放么。
    wxf666
        11
    wxf666  
       248 天前
    @bthulu #3 键是宽度值,值是(库存 ID/引用/指针/…)数组?

    或者,你也模仿数据库的底层存储结构,内存里也用 B 树存储呗。。
    scys
        12
    scys  
       248 天前
    一万条数据?不会增加和减少?
    那就全部内存,直接正则匹配即可,快到让你怀疑人生。
    yufeng0681
        13
    yufeng0681  
       248 天前
    描述里面的 很短时间也没说清楚,
    1 、 能不能平均到 每秒多少次 sql 查询
    2 、是几百个终端近乎同时发起查询?有这么高频的查询
    3 、mysql 支持读写分离,有没有额外机器给你弄同步数据库用于查询

    讲原始需求会好很多,方案都可能变掉,比如用 redis 先查一部分,然后再去 mysql 里面查更详细信息。(瞎举例)
    bthulu
        14
    bthulu  
    OP
       248 天前
    @yufeng0681 不需要每秒查询, 无并发. 就一个定时任务, 每 30 秒左右执行一次, 执行时间尽量控制在几百毫秒内. mysql 就普通电脑, 这是工业用的, 不存在读写分离什么的, 就一普通家用 dell PC 机上运行.
    inframe
        15
    inframe  
       248 天前
    Mysql 内存缓冲区写大点比如实际上库 10G 、PoolSize 64G ,数据库整个就自动放进内存里了
    devliu1
        16
    devliu1  
       248 天前 via Android
    @bthulu 这个量你怎么做都行
    huijiewei
        17
    huijiewei  
       248 天前
    你猜为啥 Redis 要放内存里面

    只要内存没爆,就全放内存肯定不会错
    wanguorui123
        18
    wanguorui123  
       248 天前   ❤️ 1
    拉到本地存 SQLite 在查询 SQLite 里的数据库快很多,SQLite 充当一级缓存
    ivvei
        19
    ivvei  
       248 天前
    用数据库查啊,这么点量会很慢吗?用数据库查实现起来多简单啊。建议先按数据库写,实测不行了再想优化方案。
    mark2025
        20
    mark2025  
       248 天前
    把数据库放到内存中……
    seers
        21
    seers  
       248 天前
    设计数据库的又不是傻子,你这种热数据肯定读到内存缓冲池后后面都是从内存读了,有时候真的建议多看看数据库设计原理
    vance123
        22
    vance123  
       248 天前 via iPhone
    @seers 数据库处理这几千个请求就得创建几千次线程,这不耗费资源吗?
    seers
        23
    seers  
       248 天前
    @vance123 还是那句话,设计数据库的又不是傻子,数据库内部有线程池实现,提问前多动动脑子
    cnbatch
        24
    cnbatch  
       248 天前
    那就复制一份数据到 redis 再查

    dotnet 8 的话,还有另一个选择:微软最新推出的 Garnet ,也是用 dotnet 8 写的,是 Redis 的替代品
    ZZ74
        25
    ZZ74  
       248 天前
    才几千次,还能走索引,怕个球。来点护城河,还可以把一小部分逻辑变成存储过程,就更不用担心了
    kb666
        26
    kb666  
       248 天前
    一万多条其实很少了.. 你可以先用数据库查,看看性能表现
    night98
        27
    night98  
       248 天前
    最简单的直接根据你要的属性去分组就行了,不会占多少内存的,速度嗖嗖的。
    lvlongxiang199
        28
    lvlongxiang199  
       248 天前
    动手实现一遍, 之后压测试试呗
    kongkongyzt
        29
    kongkongyzt  
       248 天前
    其实这些数据并不多的,直接内存查吧,走 MySQL 的磁盘 IO 很慢的
    jackOff
        30
    jackOff  
       247 天前
    有一种很愚蠢但是可能非常高效的做法,就是把上万条数据全部在一个目录下一一对应生成本地文件,查询的时候直接判断对应路径是否存在,这个理论上的时间复杂度是 o(1),如果你查的数据不敏感并且更新数据比较少+懒惰的话你可以试试这个
    moyuuuu
        31
    moyuuuu  
       247 天前 via iPhone
    精确查找还是需要范围查找?

    如果是精确查找,可以用 HashMap 数据结构存,key 是 width ,value 是一个列表,那通过查询 key=800 就能直接查出那几十条 width=800 的数据;

    如果需要范围查找,可以定义一个按需要查询的条件排好序的列表,然后写一个简单的二分法进行查找最小值,然后再进行遍历,直到最大值的时候停止,这样比遍历全部数据效率要高得多;

    如果是有多个需要查询的条件,那就需要为每一个查询条件创建一个 HashMap 或者有序 List ,其实就跟数据库的原理一样,通过多个条件查询查出来后,将结果做一次交集,就是最终需要的结果了。

    当然,这样肯定会增加开发成本,就看你怎么权衡了。
    moyuuuu
        32
    moyuuuu  
       247 天前
    @moyuuuu 其实就是仿数据库的索引一样,在内存手动给每个字段做一个索引
    jianchang512
        33
    jianchang512  
       247 天前
    mysql 表使用内存表,ENGINE=MEMORY ,这么点数据都放内存。只需要改表类型,其他都不需要动
    goldenAndGreen
        34
    goldenAndGreen  
       247 天前
    10000 的数据量, 直接放内存 list, for each data line, 用你的所有条件 filter 就行.
    一次 filter 不行要么就属性打平做大宽表, 那么多做 filter,存内存肯定比 io 快
    laminux29
        35
    laminux29  
       247 天前
    机器配置,系统架构,业务逻辑,啥也不说,鬼看了都难搞。

    万一楼主的服务器是 J1900 上的 PVE 里的虚拟机里的 docker 呢?

    万一楼主的服务器接在一台百兆交换机上呢?
    mingl0280
        36
    mingl0280  
       247 天前 via Android
    一万多条数据,你直接内存数据库查就行了,剩下的都不用你管(量太小了)
    GeruzoniAnsasu
        37
    GeruzoniAnsasu  
       247 天前
    30 秒一次,单机数据库,无并发,总共就一万多条数据

    …… 这么点压力应该远远没到需要优化性能瓶颈的地步吧
    opengps
        38
    opengps  
       247 天前
    你要做的工作远不止这个问题,而这个问题实际写代码测试一下,你收获的结果会更多
    bianhui
        39
    bianhui  
       247 天前
    dotet 不是有 Linq 吗?而且这种情况通常最好的建议是,通过业务逻辑,用几个字段对表数据进行分组。
    例如:有个数学成绩属性,全班有 30%数学成绩在 80+,40%是 60+,30%是 30+,全班 60 人,你不应该无脑查询 60 次,也不应该一次全查出,你应该是先查出 80 分以上的,然后再内存中对这些数据进一步筛选。这样一共查下三次,内存容量操作也适中。反正大概这个意思,做业务开发不要脱离业务逻辑。
    klo424
        40
    klo424  
       247 天前
    我觉得这应该是个算法问题,而不是查询问题。
    shenjinpeng
        41
    shenjinpeng  
       247 天前
    Mysql 做好索引, 开启查询缓存, 使用 chunk 或者 游标 cursor 查询, 每次多查点 , 使用连接池 , 需要并发的话可以使用协程
    tearsinchina
        42
    tearsinchina  
       247 天前
    redis
    RangerWolf
        43
    RangerWolf  
       247 天前
    1. 目前如果优化数据库索引设计是否可以达到你的要求?
    2. 如果各种优化了还是达不到性能要求,上 Redis 大概率是一个不会错的选项
    不一定是最优解,但是大概率不会错。
    3. 底层是 MySQL 的话,别太期望 Query cache(这玩意默认应该是禁用的)

    我的思路跟理念:
    能少一个模块就少一个模块,维护成本压到最低。

    以 Redis 为例,Redis 数据的更新策略? Redis DB 自己的维护成本如何?是否熟悉 Redis 数据迁移?
    所以,不要一开始就 over design ,建议先尽量优化数据库设计(也是对技术深入研究的一个自我要求)
    RangerWolf
        44
    RangerWolf  
       247 天前
    @shenjinpeng 兄弟,查询缓存默认被关闭应该是有原因的,可以研究看看是否真的适合自己的业务场景再开启
    dif
        45
    dif  
       247 天前
    参考 Hive 和 Impala ,第一次查询时比较慢的,尤其是刷完元数据以后,但查询一次以后,就比较快了。 你就参考这两个设计就行。
    yyyyyyh
        46
    yyyyyyh  
       247 天前
    如果数据库和应用服务器是同一台服务器 , 就用数据库。 原因因为你的数据结构不是单纯的 k-v ,利用数据库的索引更好的实现查询。 放在内存里的开销也没有想象中那么低 ,几万条数据,可能要经过几次查询遍历,相当于循环几万次。

    如果不是同一台服务器,还是建议内存吧,不然就算数据库很快,最基本的网络 IO 开销都挺大了 ,可能最简单的查询都需要 10~20ms
    zephyrzdf
        47
    zephyrzdf  
       247 天前 via iPhone
    @bthulu
    数据存数据库,数据量很小,又是定时任务,实时性不高,没必要放内存,不会慢,稍微慢点问题也不大。
    业务复杂要考虑的是业务代码的可读性,可维护性和可扩展性。
    可以考虑按照业务模型做缓存,防止重复查询的频繁查库即可
    whusnoopy
        48
    whusnoopy  
       247 天前
    上万条数据真没多少,担心 DB 没有索引或有远程网络调用开销,读到内存里来,每次遍历这个 O(n) 的 n 也不过 10K+,相比内存访问带宽和 CPU 处理能力,完全不用看

    假定上万条是 10 万条数据好了,这就是 100K ,每条数据占 1KB 内存,那也就是 100MB ,普通的 PC 机还能挪不出这 100MB 的内存来?

    具体还是要看业务场景,是同机器上的数据库还是不同机器提供的数据库,数据检索是单条件还是多条件,带不带索引,建索引和维护索引的成本,查询条件更大概率是方便 SQL 化的还是方便代码实现的
    coderzhangsan
        49
    coderzhangsan  
       247 天前
    如果只是 1 万多条数据,数据库查询完全就能解决,你需要的是如何拆解业务逻辑做到最优化的方案,例如业务查询分组等等,使数据库查询代价最小。
    sockpuppet9527
        50
    sockpuppet9527  
       247 天前
    提一个可能大家都没注意的点:page cache + 数据库 cache 。

    实际上短期大量查询大概率都是在 cache 中的。如果觉得内存中操作太麻烦了话,还是考虑走 db 把。
    zephyru
        51
    zephyru  
       247 天前
    这个数据量并不多...几百万的数据 sqlite 在树莓派上,做这种关联查询单次也能压到几百 ms 内...
    不过看描述感觉是先用一次查询获得下次查询的条件,进行嵌套查询,其实很多时候自己写各种数据结构算法还真不一定有数据库或者语言自带的数组便利好用,建议在实际运行的环境下试试再说
    jorneyr
        52
    jorneyr  
       247 天前
    普通单机 MySQL 每秒可以查询 1 万多次。
    whitedroa
        53
    whitedroa  
       247 天前
    怎么楼上这么多菜鸡。
    先说结论: 如果要查询一千多次,肯定是先全查出来放到内存里啊。
    你知道 1 千多万次遍历要执行多久吗?不会超过几毫秒!
    每次去 mysql 查,网络耗时有多久???
    所以这还用比较吗
    barbery
        54
    barbery  
       247 天前
    内存表?
    1018ji
        55
    1018ji  
       247 天前
    mysql 不是有内存表,再找找其他的呗,同步就是了
    kandaakihito
        56
    kandaakihito  
       247 天前
    我现在在做的一个项目就跟你比较像,不过数据量比你还要大上十几倍。

    这种数据量看着对于现代数据库来说不算什么,但是能保证后面实际业务过程中数据量不会增加吗?
    确定不会有多个用户同时操作?
    确定其他模块不需要如此大量读写数据?
    确定其他模块不会调用这种大数据量的方法?
    能确定实际部署的时候,客户的服务器不是石头盘或者数据库玩拆分服务器 cpu 跟得上?

    直接放数据库在硬盘读写的话大概率后面用户体验很差,或者只能把这种方法写成异步处理,但是业务流程得小改。
    janwarlen
        57
    janwarlen  
       247 天前
    @q727729853 #8 查询次数太多了,会有额外的 IO 耗时消耗
    watzds
        58
    watzds  
       247 天前
    我自己没试过,可以考虑这种,读取一次 mysql 数据到本地内存数据库,内存数据库也可以建索引,之后查内存数据库

    比如:sqlite 内存模式 memory 索引
    kandaakihito
        59
    kandaakihito  
       247 天前
    而且不知道你们交付的时候有没有压测流程,到时候运维找上门问为什么硬盘 IO 资源打满了就有趣了
    watzds
        60
    watzds  
       247 天前
    另外这个 cqengine 看着也不错,可以快速查询的集合


    https://github.com/npgall/cqengine
    22092
        61
    22092  
       247 天前
    你提到是 mysql 做定时任务, 可以直接 Stored Procedure + Scheduled Jobs 做, 准时高效(mysql,sqllite 可以啟用内存模式)
    想自由更改而用 NET8 的 可以将数据一次全拉到内存(用 ReadOnlyMemory) 然后用 SimdLinq 查询
    zealotpuppy
        62
    zealotpuppy  
       247 天前
    一万条数据,qps 不到 1 万,这你随便用什么东西都能搞定的事情,你确定瓶颈是在查询数据本身吗?
    jstony
        63
    jstony  
       247 天前
    才几万条数据还折腾啥,直接一股脑读到内存里,爱咋查咋查。
    imokkkk
        64
    imokkkk  
       247 天前
    全查出来内存里再搞事情吧 内存里操作和 IO 效率不是一个量级的
    SmiteChow
        65
    SmiteChow  
       247 天前
    这么点数据啊,全返回给前端,爱怎么用怎么用。
    duluosheng
        66
    duluosheng  
       247 天前
    就这点数据,缓存+优化索引就行。
    Y25tIGxpdmlk
        67
    Y25tIGxpdmlk  
       247 天前
    纠结遍历干啥,这么点东西,直接内存不就完了,遍历有咋滴,你心疼内存还是心疼电费啊?
    JKeita
        68
    JKeita  
       247 天前
    几万条一次性全查出来筛选就行了。
    freewind
        69
    freewind  
       247 天前
    用 Dict<int, List<T>>保存, 先用 width 取 List,再用 Linq where 查一下
    cat1879
        70
    cat1879  
       246 天前
    几万条应该没有什么压力吧,直接数据库查呀,你一边减库存还得一边加库存吧。做好锁表动作就好,问题不大
    bthulu
        71
    bthulu  
    OP
       246 天前
    @cat1879 查一遍是没压力, 这是要瞬间根据不同条件查几千遍.
    815979670
        72
    815979670  
       246 天前
    @wanguorui123 甚至用 SQLite 的内存模式 不落盘 也可以
    wxf666
        73
    wxf666  
       246 天前
    @bthulu 楼上这么多人给出的方案,你给点反馈呀?

    1. 全读出来,存数组里,直接遍历找
    2. 全读出来,存哈希表里,精确查找
    3. 全读出来,排序后存数组里,可范围查找
    4. 全读出来,丢到 Redis 里,再查
    5. 全读出来,每个丢到一个文件里,根据文件名查
    6. 用 MySQL 内存表
    7. 用 SQLite 内存表
    8. 用 MySQL 存储过程写逻辑


    个人认为,从速度上说,内存里用哈希表/ B 树/排序后数组二分查找,

    > SQLite 内存表(这个每秒只能几万次)> MySQL 存储过程(页面缓存还是有些慢)>数组遍历( 99%无用功)

    >丢到 Redis 查(几千次网络开销)> MySQL 内存表(几千次网络开销)>存几万个文件再查(几千次文件系统开销)
    bthulu
        74
    bthulu  
    OP
       246 天前
    @wxf666 我目前采用的方案: 直接拉到内存里, 按 id 生成字典. 然后对 2 个区分度相对高的必查字段排序后生成了 1 个 List<Index>.

    ```
    public struct Index(int Width, GsmId[] GsmIds);
    public struct GsmId(int Gsm, int Id);
    ```

    每次查询时, 先二分法查找 Width, 再对结果集遍历, 在 GsmIds 中二分法查找 Gsm, 得出满足 Width 和 Gsm 的 id 结果集, 再从字典中取回数据本体生成新的结果集.

    这时候的结果集就已经只有几十条了, 其余字段查找, 直接在这个结果集中遍历
    bthulu
        75
    bthulu  
    OP
       246 天前
    针对 Width 的二分查找, 在找到符合范围的数据后, 继续往上 100 条一跳查找是否满足条件, 不满足则往上 10 条一跳查找, 还不满足就往上一条条查找, 找到起始点. 同样往下找到结束点.
    针对 Gsm 的二分查找, 因为每个 Width 对应的 Gsm 不会有太多, 二分查找到符合条件的数据后, 就不做 100 条一跳了, 直接 10 条一跳转 1 条一跳.
    22092
        76
    22092  
       244 天前
    怪,用到 NET8 又要效率为甚么还用 List<T>,而不用 ReadOnlyMemory<T>,Span<T>等内存优化合集
    生成字典似乎已经遍历一次,又要生成 List<T>占内存,LINQ 性能有应该不会这么差吧.(为甚么不用呢)
    主要是生成的字典用一個查询就失效了,字典的重用时优勢
    而且直接遍历一次,又有必查字段,可以做个迭代器 EX 在途中找到符合条件的数据再 yield 出去处理..
    最后都拉到内存了,用 ConcurrentDictionary , SimdLinq 以及 Parallel 作个多线程查询更好
    如果这些属性可组织起來,可以看下抽象语法树,表达式树有没有帮助

    i7-12700 16GB 256G
    一万多库存, 定时任务, 30 秒左右执行一次, 执行时间尽量控制在几百毫秒内, 完成几千次查询(每次查询需要针对库存的多个属性进行多次查询)
    从这些情况看,不是大家的方案不好,而是你认为机器太差不合适
    有独立机器,不是必要在 VM 内的 Docker 跑程序已经很好了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2922 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 14:41 · PVG 22:41 · LAX 06:41 · JFK 09:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.