PostgreSQL 的 LIMIT 操作相比 MySQL 性能是否有极大提高? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yejinmo
V2EX    数据库

PostgreSQL 的 LIMIT 操作相比 MySQL 性能是否有极大提高?

  •  
  •   yejinmo 2017-12-19 15:00:06 +08:00 2631 次点击
    这是一个创建于 2855 天前的主题,其中的信息可能已经有所发展或是发生改变。

    手上的项目由于客户对历史记录查询的要求颇高,MySQL 的性能不太够用了

    历史记录表需要在 4 个 int 字段上做筛选

    以及一个时间字段上做范围筛选

    数据量在千万级,现在 SELECT 一下子要几十秒,主要耗时在了 LIMIT 上

    由于筛选之后数据不连续,想不到什么可以优化的地方了

    查了查资料说 PostgreSQL 对大量数据有特殊优化

    特想问下 PostgreSQL 的分页是不是要比 MySQL 好些

    或者有什么别的潇洒方法解决这个问题

    谢谢

    第 1 条附言    2017-12-19 17:01:34 +08:00


    CREATE TABLE `event_info` (
    `event_name` char(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '事件名称',
    `event_info` char(80) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '事件信息',
    `event_time` bigint(20) NOT NULL COMMENT '事件时间',
    `event_id` bigint(20) NOT NULL COMMENT '事件编号',
    `event_value` double(16, 4) NOT NULL COMMENT '事件值',
    `state0` int(8) NOT NULL COMMENT '自定义 0',
    `state1` int(8) NOT NULL COMMENT '自定义 1',
    `state2` int(8) NOT NULL COMMENT '自定义 2',
    `state3` int(8) NOT NULL COMMENT '自定义 3',
    INDEX `key_event_time`(`event_time`) USING BTREE,
    INDEX `key_state0`(`state0`) USING BTREE,
    INDEX `key_state1`(`state1`) USING BTREE,
    INDEX `key_state2`(`state2`) USING BTREE,
    INDEX `key_state3`(`state3`) USING BTREE
    ) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = FIXED;
    44 条回复    2017-12-20 18:31:30 +08:00
    glues
        1
    glues  
       2017-12-19 15:03:40 +08:00   1
    你的问题应该是 OFFSET 而不是 LIMIT 吧?
    yejinmo
        2
    yejinmo  
    OP
       2017-12-19 15:04:24 +08:00
    @glues #1
    对。。。OFFSET。。
    mokeyjay
        3
    mokeyjay  
       2017-12-19 15:05:04 +08:00   1
    楼上+1,mysql 的 offset 是有很大缺陷的,用其他方法替代吧
    yejinmo
        4
    yejinmo  
    OP
       2017-12-19 15:07:34 +08:00
    @mokeyjay #3
    本来不是做数据库的硬生生的啃了好几天数据库。。
    分页居然是读了 OFFSET + LIMIT 然后丢掉 OFFSET 只拿 LIMIT
    好大的坑
    glues
        5
    glues  
       2017-12-19 15:08:09 +08:00
    @yejinmo 自己搭个 Postgres,随机生成个几千万数据,测试一下就知道了
    分页的话,用游标,不要用 OFFSET
    topbandit
        6
    topbandit  
       2017-12-19 15:14:00 +08:00   1
    表结构和 SQL 贴出来,专业 DBA 优化下,千万级别 SELECT,那还不是遛得飞起
    yejinmo
        7
    yejinmo  
    OP
       2017-12-19 15:23:17 +08:00
    @topbandit #6
    感觉。。这不是优化不优化的问题了吧,硬件条件限制了吧,单机 MySQL 干这个好吃力
    CREATE TABLE `event_info` (
    `event_name` char(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '事件名称',
    `event_info` char(80) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '事件信息',
    `event_time` bigint(20) NOT NULL COMMENT '事件时间',
    `event_id` bigint(20) NOT NULL COMMENT '事件编号',
    `event_value` double(16, 4) NOT NULL COMMENT '事件值',
    `state0` int(8) NOT NULL COMMENT '自定义 0',
    `state1` int(8) NOT NULL COMMENT '自定义 1',
    `state2` int(8) NOT NULL COMMENT '自定义 2',
    `state3` int(8) NOT NULL COMMENT '自定义 3',
    INDEX `key_event_time`(`event_time`) USING BTREE,
    INDEX `key_state0`(`state0`) USING BTREE,
    INDEX `key_state1`(`state1`) USING BTREE,
    INDEX `key_state2`(`state2`) USING BTREE,
    INDEX `key_state3`(`state3`) USING BTREE
    ) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = FIXED;
    yinjiayi
        8
    yinjiayi  
       2017-12-19 15:31:29 +08:00   1
    路过看看,收藏
    QAPTEAWH
        9
    QAPTEAWH  
       2017-12-19 15:32:39 +08:00   1
    想办法用 where 代替 offset
    yejinmo
        10
    yejinmo  
    OP
       2017-12-19 15:35:20 +08:00
    @QAPTEAWH #9
    数据没有连续性啊。。如果是 MySQL 实在是想不到什么好办法了
    crazyneo
        11
    crazyneo  
       2017-12-19 15:52:06 +08:00   2
    MariaDB 和 Percona 有讲 pagination 的,基本思路是在前端过滤掉不相关的查询进而摆脱 offset,使用自增主键来计算当前 pagination 所需的查询列表。
    https://mariadb.com/kb/en/library/pagination-optimization/
    https://www.percona.com/files/presentations/ppc2009/PPC2009_mysql_pagination.pdf
    jhdxr
        12
    jhdxr  
       2017-12-19 16:14:29 +08:00   1
    这个问题无解,我所知道的数据库(当然我知道的也不多)基本上要么不支持要么就是扫描后丢掉的。

    优化思路上面的也都说了,另外这个和数据是否连续有什么关系。。。你只有有单调增减的字段即可(无需连续)。
    bugsnail
        13
    bugsnail  
       2017-12-19 16:20:33 +08:00   1
    这个只能程序那边做手脚了, 方法和 @crazyneo #11 类似

    https://iexplore.cc/2016/offsetSlow/
    topbandit
        14
    topbandit  
       2017-12-19 16:21:06 +08:00   1
    1. 不知道你用 MYSQL 哪个版本的,我了解到的:MYSQL 5.7 起,innodb 比 5.6 以前版本有质的飞跃(这应该是由 oracle 团队带来的),性能应该跟上来了,建议用新版的 InnoDB,也可以和 PG 做下对比

    2. SELECT 语句和建表语句要反复锤炼下
    建表语句字段和索引的创建,要和 SELECT 要结合起来考虑; SELECT 语句的底层处理逻辑,用 explain 打印下执行计划,检查下逻辑上的问题,有无改写提升的可能; SELECT 语句会产生许多中间表(临时表),需要查看中间表 Profile 性能瓶颈

    3. 数据库优化有本入门好书,《高性能 MySQL 》第 3 版,翻译的不太好,也能看

    (PS: 只是个指引,也可能不对,本人曾经在搞数据的公司待过,略懂些,nosql 没接触过)
    jjianwen68
        15
    jjianwen68  
       2017-12-19 16:23:16 +08:00   1
    不太可能为了这个换系统使用的数据库吧
    yejinmo
        16
    yejinmo  
    OP
       2017-12-19 16:28:02 +08:00
    @jhdxr #12
    表中有一个 id 的字段,由写入历史时提供,虽是唯一但不一定单调
    取 id 然后拿后边的记录的思路,是不能有条件查询的吧
    yejinmo
        17
    yejinmo  
    OP
       2017-12-19 16:30:16 +08:00
    @topbandit #14
    MyISAM 在单纯读写性能上要比 InnoDB 略高些吧
    我也只是个码农不懂数据库优化啊心好累
    yejinmo
        18
    yejinmo  
    OP
       2017-12-19 16:31:01 +08:00
    @jjianwen68 #15
    还没有正式交付,换数据库什么的还来得及
    zhNaMore
        19
    zhNaMore  
       2017-12-19 16:34:07 +08:00   1
    先看看瓶颈在哪里吧。是硬件问题还是你 select 语句问题。
    要不你把 sql 或者解析结果发出来大家看看。单纯的换数据库我觉得并不能解决问题。
    jhdxr
        20
    jhdxr  
       2017-12-19 16:35:06 +08:00   1
    @yejinmo 保证单调是必须的。如果没有其实你可以自己建一个自增字段。
    另外当然是可以有查询条件的,排序也没问题。关键字 游标分页

    缺点就是不能指定跳转到第几页。如果你的需求是这样子的,建议你查两次,第一次只查单调的字段,查出来范围后第二次再根据那个字段去查真正的数据。这样子在第一次查询时丢弃的数据可以少一些_(:з」∠)_
    runntuu
        21
    runntuu  
       2017-12-19 16:50:35 +08:00 via iPhone   1
    @yejinmo MyISAM 只是单纯读性能好,在写并发和事务支持上跟 InnoDB 没有可比性。
    另外对于索引的建立,多个独立列的索引不如一个适合大多数查询的覆盖索引。
    另外还没有看到你的查询计划,能否一并提出来参考一下?
    likuku
        22
    likuku  
       2017-12-19 16:54:08 +08:00   1
    翻到笔记, 2013 年前做过对比测试,同一软硬件环境,非常简单的单表,180 万行:

    postgres=# select count(*) from status;
    count
    ---------
    1841495
    (1 row)

    Time: 516.717 ms

    mysql Innodb 表:

    mysql> select count(*) from status;
    +----------+
    | count(*) |
    +----------+
    | 1841495 |
    +----------+
    1 row in set (3.53 sec)

    表结构:

    | status | CREATE TABLE `status` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `date` date NOT NULL,
    `license` varchar(100) NOT NULL,
    `localdomain` varchar(100) NOT NULL,
    `mailing` varchar(50) NOT NULL DEFAULT 'unknown',
    `isp` varchar(50) NOT NULL,
    `success` int(50) NOT NULL,
    `spamrelay` int(50) NOT NULL,
    `overlimit` int(50) NOT NULL,
    `badmailbox` int(50) NOT NULL,
    `timeexpired` int(50) NOT NULL,
    `unknown` int(50) NOT NULL DEFAULT '0',
    `open` int(50) NOT NULL DEFAULT '0',
    `click` int(50) NOT NULL DEFAULT '0',
    `olapstamp` char(19) NOT NULL DEFAULT '0000-00-00 00:00:00',
    `updatestamp` char(19) NOT NULL DEFAULT '0000-00-00 00:00:00',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1911671 DEFAULT CHARSET=utf8 |
    +--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    1 row in set (0.00 sec)
    yejinmo
        23
    yejinmo  
    OP
       2017-12-19 17:03:53 +08:00
    @zhNaMore #19
    @runntuu #21
    相关信息已 Append
    之所以考虑换掉 MySQL
    是觉得以 MySQL 的机制对大表查询不够友好啊
    yejinmo
        24
    yejinmo  
    OP
       2017-12-19 17:05:25 +08:00
    @likuku #22
    额。。postgres 上没有对表和查询做特殊的优化么?
    likuku
        25
    likuku  
       2017-12-19 17:11:30 +08:00
    @yejinmo

    count(*) 是比较特别的需求(我那时就是需要这个),对你参考价值有限,建议你直接装一个 PG 载入数据实际测测看。

    这个测试里 postgresql 没有作特殊优化,debian 下直接 apt-get 装完就测试。

    MySQL 是作了优化,设置了 InnoDB MEM Pool 为内存一半,其他相关优化也做过。

    对了,MyISAM 类型表,count(*) 是直接返回数值,没有实际查询耗时(因为它对这个操作设计记得就是删改记录时维护一个计数器,count()直接返回数值)。MyISAM 不支持事务,只有表级锁,没有全文索引,表特别容易坏,只适合纯查询,Innodb 这些年性能也提高很多了,一般也没人用 MyISAM 了。
    likuku
        26
    likuku  
       2017-12-19 17:14:11 +08:00
    @yejinmo 你这台机是 SSD 么?换用 SSD 对 DB 的效能提升简直就是银子弹。
    yejinmo
        27
    yejinmo  
    OP
       2017-12-19 17:16:35 +08:00
    @likuku #25
    膜拜大手 我去跑一遍试试
    另外配置使用默认会对性能造成很大影响么?
    likuku
        28
    likuku  
       2017-12-19 17:19:42 +08:00   1
    @yejinmo [配置使用默认] MySQL ? PostgreSQL ?

    MySQL 使用默认,尤其 InnoDB 使用默认,则极大影响性能。
    likuku
        29
    likuku  
       2017-12-19 17:22:01 +08:00   1
    mysql, postgresql 都有内置的 benchmark 基准测试功能,可以用这个测试作对比
    likuku
        30
    likuku  
       2017-12-19 17:25:50 +08:00   1
    PostgreSQL 性能优化方法 - 1-博客-云栖社区-阿里云 : https://yq.aliyun.com/articles/214

    发现这个博客,PG 相关还有好几篇

    Greenplum 是基于 PostgreSQL 的并行计算版本,假若单机 DB 还不能满足,可以尝试用它
    runntuu
        31
    runntuu  
       2017-12-19 17:48:47 +08:00 via iPhone   1
    @yejinmo mysql 的 offset 是个坑,offset 的逻辑是读取 500050 条记录,然后抛弃前面的 50w 条数据,返回 50 条记录。所以这么简单的查询,即使走了索引,你的 show profile 还是花了 46s 在 sending data 从磁盘读。
    建议你考虑使用字段 event_id 来分页,然后在 limit 里仅限制返回行数。
    wucancc
        32
    wucancc  
       2017-12-19 18:18:17 +08:00   1
    有一个思路。

    题主提到了数据不是连续的,是否可以这么解决了。
    offset limit 的速度很慢,但是数据表加了索引后对 between and 的查询速度极快。
    比如说你的是数据是:1,2,3,4,8,9,10,需要每次取 3 条。
    先用 select * from table between n and n+10; 每次多取一些。
    再对结果取前 3 条。
    如果结果不足 3 条,再查一次 select * from table between n and n+20; 直到结果多余 3 条。
    tabris17
        33
    tabris17  
       2017-12-19 18:25:54 +08:00
    现在不是都不用分页了么,都是从当前数据加载更多啊
    wucancc
        34
    wucancc  
       2017-12-19 18:34:37 +08:00
    @tabris17 对于后台来说不是一样么,都是再获取一页的数据。在前台展现的是不清空当前数据继续追加。
    ke1e
        35
    ke1e  
       2017-12-19 19:02:31 +08:00 via Android   1
    或者可以考虑分表
    likuku
        36
    likuku  
       2017-12-19 19:14:14 +08:00   1
    [历史记录表需要在 4 个 int 字段上做筛选] 历史记录筛选,这个是是交由 hadoop 来作个 mapreduce 会更好?
    whx20202
        37
    whx20202  
       2017-12-19 19:14:29 +08:00   3
    我来说说吧
    如果仅仅是为了分页的话,可以参考 openstack 的实现
    第一页:
    select * from instances limit 20;
    第二页:
    select * from instances where id> 第 20 行的 ID limit 20;

    改进版:
    select * from instances where id> 第 20 行的 ID and created_at>第 20 行的时间 limit 20;

    仅供参考
    chengs
        38
    chengs  
       2017-12-20 10:01:20 +08:00
    @whx20202 为什么不用 pg 呢
    whx20202
        39
    whx20202  
       2017-12-20 10:20:41 +08:00   1
    @chengs
    不太懂你说的哪一个领域不用 pg 哈
    我司搞 openstack 用的是 pg
    开源社区 openstack 主要是 mysql
    我个人项目两个都用

    pg 如果单纯 limit offset 也很慢,得用一些高级特性或者技巧,德哥的博客里说了不少
    我说的那个办法,pg mysql 都能用啊
    alcarl
        40
    alcarl  
       2017-12-20 13:06:20 +08:00 via Android   1
    可以考虑用子查询搜索第一条记录 id 的方法再加个时间排序,一般都是这么优化。但数据库不是万能的,还要考虑针对业务来优化一下,几千万行在里面一页一页翻这种需求我是没想出来谁会去翻。。。。
    yejinmo
        41
    yejinmo  
    OP
       2017-12-20 13:43:54 +08:00
    @runntuu #31
    @wucancc #32
    @alcarl #40

    类似的解决思路想过,包括 @wucancc #32 所说,但这样依旧会造成极大的性能损失,因为数据实在是不连续呀。。。
    还有需要 COUNT 进行总页数的返回,实在是没有思路了

    可能 关系库 真的不适合做这样的事情,要考虑在数据存储上入手了
    谢过大家了
    runntuu
        42
    runntuu  
       2017-12-20 16:12:45 +08:00 via iPhone
    @yejinmo 试一下这个查询吧,在字段 event_id 建一个索引
    select * from event_info inner join ( select event_id from event_info where state0=0 limit 500000,50) q using (event_id)
    yejnmo
        43
    yejinmo  
    OP
       2017-12-20 16:35:28 +08:00
    @runntuu #42
    此条语句五千万数据要一秒,子查询中的 where 拖慢了运行时间

    目前考虑使用实时库做历史查询,将数据自定协议二进制化后存入实时库,再自建索引,根据时间查询,测试了些数据是很快的
    wucancc
        44
    wucancc  
       2017-12-20 18:31:30 +08:00
    @yejinmo count 操作是不需要 count(*)的,count(*)实在是太慢了。pg 在存数据的时候在 scheme 里面存了数据表的条数,可以直接取。
    可以参考: http://www.voidcn.com/article/p-qrcvdalt-bbz.html

    我目前用的,between and 是在建索引后最快的方式了。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1465 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 16:44 PVG 00:44 LAX 09:44 JFK 12:44
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86