PostgreSQL的序列奇快无比的秘密

1. 引言

曾经有篇流传较广的文章Don’t Assume PostgreSQL is
Slow
展示了PostgreSQL生成序列的速度不亚于redis的INCRs。而在此之前我就曾做过相关的测试(参考PostgreSQL的序列的性能验证),发现PG生成序列的速度远高于同类的关系数据库。根据PostgreSQL的序列的性能验证 中测试结果,在没有启用序列cache的情况下,PG的每次调用nextval('seq1')的额外时间消耗大概是0.3us,也就是333w/s,所以即使做批量数据加载也不用担心序列拖后腿;而Oracle的nocache序列生成速度大概只有5w/s,当Oracle序列 cache了50以上时,速度才开始接近pg。
这个结果很惊人,但细一想,PG快得有点离谱。为什么这么说?因为当时测试的select nextval('seq1')在4核虚机上达到了7w/s的qps,而那个测试环境估计支撑不了这么高的iops,所以猜测PG一定对序列做了某种优化而不是每次刷盘。

2.
代码分析

关键代码见src/backend/commands/sequence.c的nextval_internal()函数,有个叫SEQ_LOG_VALS的常量,控制PG每产生32个序列值才记一次WAL。这相当于PG对序列做了全局缓存,而PG的create
sequence语法上的cache是指每个进程(也就是连接)的本地cache。由于全局缓存优化的已经足够好了,所以一般不需要再启用本地cache。

src/backend/commands/sequence.c

点击(此处)折叠或打开

  1. fetch = cache = seq->cache_value;
  2. log = seq->log_cnt;
  3. ...
  4. /*
  5. * Decide whether we should emit a
    WAL log record. If so, force up the
  6. * fetch count to grab SEQ_LOG_VALS more values than we actually
    need to
  7. * cache. (These will
    then be usable without logging.)
  8. *
  9. * If this is the
    first nextval after a checkpoint, we must
    force a new
  10. * WAL record to be written anyway, else replay
    starting from the
  11. * checkpoint would fail to advance the sequence past the logged
    values.
  12. * In this case
    we may as well fetch extra values.
  13. */
  14. if (log < fetch || !seq->is_called)//此处fetch值为1.每次调nextval()log_cnt会递减,减到0时设置logit标志位
  15. {
  16. /* forced log
    to satisfy local demand for values */
  17. fetch = log = fetch
    + SEQ_LOG_VALS;
  18. logit = true;
  19. }
  20. else
  21. {
  22. XLogRecPtr redoptr =
    GetRedoRecPtr();
  23. if (PageGetLSN(page) <=
    redoptr)
  24. {
  25. /* last update of seq was before checkpoint */
  26. fetch = log = fetch
    + SEQ_LOG_VALS;
  27. logit = true;
  28. }
  29. }
  30. ...
  31. if (logit &&
    RelationNeedsWAL(seqrel))
  32. {
  33. xl_seq_rec xlrec;
  34. XLogRecPtr recptr;
  35. /*
  36. * We don't log the
    current state of the tuple, but rather the
    state
  37. * as it would appear after
    "log" more fetches. This lets us skip
  38. * that many future WAL
    records, at the cost that we lose those
  39. * sequence values if we crash.
  40. */
  41. XLogBeginInsert();
  42. XLogRegisterBuffer(0, buf,
    REGBUF_WILL_INIT);
  43. /* set values
    that will be saved in xlog */
  44. seq->last_value = next;//WAL中记录的last_value是下一轮的序列值,所以pg
    crash再通过WAL恢复后,新产生的序列会跳过几个值
  45. seq->is_called = true;
  46. seq->log_cnt =
    0;
  47. xlrec.node = seqrel->rd_node;
  48. XLogRegisterData((char *) &xlrec,
    sizeof(xl_seq_rec));
  49. XLogRegisterData((char *) seqtuple.t_data,
    seqtuple.t_len);
  50. recptr = XLogInsert(RM_SEQ_ID,
    XLOG_SEQ_LOG);
  51. PageSetLSN(page, recptr);
  52. }

3. 实测验证

3.1
WAL写入时机

PG通过内部log_cnt计数器控制是否要记录序列更新的WAL,新建的序列,计数器初始值为0。

  1. postgres=# create sequence seq1;
  2. CREATE SEQUENCE
  3. postgres=# \d seq1
  4. Sequence "public.seq1"
  5. Column | Type | Value
  6. ---------------+---------+---------------------
  7. sequence_name | name | seq1
  8. last_value | bigint | 1
  9. start_value | bigint | 1
  10. increment_by | bigint | 1
  11. max_value | bigint | 9223372036854775807
  12. min_value | bigint | 1
  13. cache_value | bigint | 1
  14. log_cnt | bigint | 0
  15. is_cycled | boolean | f
  16. is_called | boolean | f

取得第一个序列值后,log_cnt变成32。

  1. postgres=# select nextval('seq1');
  2. nextval
  3. ---------
  4. 1
  5. (1 row)
  6. postgres=# \d seq1
  7. Sequence "public.seq1"
  8. Column | Type | Value
  9. ---------------+---------+---------------------
  10. sequence_name | name | seq1
  11. last_value | bigint | 1
  12. start_value | bigint | 1
  13. increment_by | bigint | 1
  14. max_value | bigint | 9223372036854775807
  15. min_value | bigint | 1
  16. cache_value | bigint | 1
  17. log_cnt | bigint | 32
  18. is_cycled | boolean | f
  19. is_called | boolean | t

这个过程中,通过strace监视"wal
writer process"进程,可以发现发生了WAL写入和刷盘。

  1. [root@localhost ~]# strace -efsync,write,fdatasync -p 2997
  2. Process 2997 attached
  3. --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=3209, si_uid=1001}
    ---
  4. write(11, "\0", 1) = 1
  5. write(3,
    "\207\320\5\0\1\0\0\0\0\300\273\t\0\0\0\0I\10\0\0\0\0\0\0\n\0\0004-\0\0\0"...,
    8192) = 8192
  6. fdatasync(3) = 0

以后每次获取序列值,log_cnt会减1,但只有log_cnt减到0,并从0重新跳到32的时候,strace中才能看到WAL写入。

  1. postgres=# select nextval('seq1');
  2. nextval
  3. ---------
  4. 2
  5. (1 row)
  6. postgres=# \d seq1
  7. Sequence "public.seq1"
  8. Column | Type | Value
  9. ---------------+---------+---------------------
  10. sequence_name | name | seq1
  11. last_value | bigint | 2
  12. start_value | bigint | 1
  13. increment_by | bigint | 1
  14. max_value | bigint | 9223372036854775807
  15. min_value | bigint | 1
  16. cache_value | bigint | 1
  17. log_cnt | bigint | 31
  18. is_cycled | boolean | f
  19. is_called | boolean | t

3.2 PG
crash后的序列值

PG在记录序列的WAL时,记录的是当前值+32。所以如果PG crash再恢复后,将跳过一部分从未使用的序列值。这样做避免了产生重复序列的可能,但不能保证序列的连续,这是优化WAL写入而付出的必要代价。
下面是使用kill
-9杀PG进程的情况。

点击(此处)折叠或打开

  1. postgres=# select nextval('seq1');
  2. nextval
  3. ---------
  4. 20
  5. (1 row)
  6. postgres=# \d seq1
  7. Sequence "public.seq1"
  8. Column | Type | Value
  9. ---------------+---------+---------------------
  10. sequence_name | name | seq1
  11. last_value | bigint | 20
  12. start_value | bigint | 1
  13. increment_by | bigint | 1
  14. max_value | bigint | 9223372036854775807
  15. min_value | bigint | 1
  16. cache_value | bigint | 1
  17. log_cnt | bigint | 28
  18. is_cycled | boolean | f
  19. is_called | boolean | t
  20. postgres=# select
    nextval('seq1');
  21. server closed the connection unexpectedly
  22. This probably means the server terminated abnormally
  23. before or while processing the
    request.
  24. The connection to the server was
    lost. Attempting reset: Succeeded.
  25. postgres=# select
    nextval('seq1');
  26. nextval
  27. ---------
  28. 49
  29. (1 row)

不过,正常关闭或重启PG是不会出现这种问题的。

时间: 2024-11-08 19:02:53

PostgreSQL的序列奇快无比的秘密的相关文章

dataset-C#控件DataGridView 用逐行添加数据的方式 加载速度奇慢无比 求解答

问题描述 C#控件DataGridView 用逐行添加数据的方式 加载速度奇慢无比 求解答 大概1000条 16列 如果把数据放到Dataset中然后绑定DataSource 加载基本是瞬间完成,速度很快. 但是如果 foreach{ DataGridViewRow dgvr = this.dataGridView1.Rows[this.dataGridView1.Rows.Add()]; dgvr.Cells["productName"].Value = ps.Name; .....

百度杀毒进展奇快

百度正以惊人的速度建设"国防部",用"三剑客"守卫庞大的帝国疆界. 面对360"挟安全以令诸侯"的凶悍打法,面对BAT中另两个大佬的布防--腾讯重金双线发力PC.手机安全的决心,传闻中阿里巴巴拟入股360,安静内敛的李彦宏不声不响,但手上动作明显加快. [百度杀毒进展奇快] 1月21日,从国际知名反病毒测试机构AV-TEST传来消息:在AV-TEST最新发布的反病毒软件测试中,百度杀毒通过了安全防护.易用性以及对系统的影响三项性能测试,获得AV-

奇虎360正秘密研发输入法,并将很快推出

摘要: 腾讯科技从多方获悉,奇虎360正秘密研发输入法,并将很快推出,这意味着360将在搜狗最核心的领域展开进攻.不过,搜狗CEO王小川曾公开表示,360没必要舍近求远做输入法,搜狗内部 腾讯科技从多方获悉,奇虎360正秘密研发输入法,并将很快推出,这意味着360将在搜狗最核心的领域展开进攻.不过,搜狗CEO王小川曾公开表示,360没必要舍近求远做输入法,搜狗内部称毫无压力. 据了解,360拥有一项强大的武器就是浏览器,浏览器能收集用户一切搜索行为,能够对用户搜索词.搜索点击详细分析.安全领域人

struts2 fielderror 输出格式simple 模式 仍然 奇丑无比,,,求解决方法。

问题描述 实在无奈搞个ul{list-style-type:none;float:left;}li{float:left;}稍微好看了点可是错误信息还是显示在input下面一行,怎么设样式才能让他在同行显示啊,,display:inline无效..郁闷还有这样会影响整个页面的ULLI怎么弄啊.昏哦 解决方案 解决方案二:谁知道啊,,,解决方案三:没在用struts2!解决方案四:引用楼主dingjian3的帖子: 实在无奈搞个ul{list-style-type:none;float:left;

用不到1000美元攒一台深度学习用的超快的电脑:继续深度学习和便宜硬件的探奇!

是的,你可以在一个39美元的树莓派板子上运行TensorFlow,你也可以在用一个装配了GPU的亚马逊EC2的节点上跑TensorFlow,价格是每小时1美元.是的,这些选择可能比你自己攒一台机器要更现实一点.但是如果你和我是一样的人,你绝对想自己攒一台奇快无比的深度学习的电脑. 好吧,一千块钱对于一个DIY项目来说是太多了.但是一旦你把机器搞定,你就能构建数百个深度学习的应用啦,从拥有增强大脑的机器人到艺术创作(至少这是我为花这些钱找的理由).最差的理由也是,这个机器至少能轻松打败那个2800

ASP.NET负压测试

asp.net 前  言 对于直接面对互联网用户的WEB应用,在开发设计的时候必须格外小心,因为谁也不知道在单位时间内WEB程序访问和运行的速度.所以,在程序设计完成以后,最后针对程序进行一些严格的甚至是苛刻的测试,以确定程序在复杂的网络环境和服务器高压负荷下是否可以保持正常和相对稳定的效率.  什么是负压测试 负压测试一般针对WEB应用进行,比如网站等.进行负压测试的目的是为了确定WEB应用在现实的网络环境中是否可以正确而安全的处理各种用户提交的不确定请求,检验程序的健壮性. 负压测试的另外一

我们怎样组合使用 Scrum 和 XP(《硝烟中的Scrum和XP - 我们如何实施 Scrum》)

InfoQ 中文站<硝烟中的 SCRUM 和 XP - 我们如何实施 Scrum> Scrum  注重的是管理和组织实践,而  XP  关注的是实际的编程实践. 这就是为什么它们可以很好地协同工作 --它们解决的是不同领域的问题,可以互为补充,相得益彰. 13 我们怎样组合使用 Scrum和XP 要说组合使用Scrum和XP(极限编程)可以带来累累硕果,这毫无争议.我在网上看到过的绝大多数资料都证实了这一点,所以我不会花时间去讨论为什么要这么做. 不过,我还是会提到一点.Scrum注重的是管理

使用腾讯分析更加准确地掌握访客信息

中介交易 SEO诊断 淘宝客 云主机 技术大厅 打开网站时候,FireFox不住的"正在连接--""等待--"提示让人狂暴,不用说,很长时间之后浏览器依旧是白茫茫一片--N多的统计工具JS加载简直想挤爆宿舍那条弯弯曲曲.缠着塑料胶布的网线. 有的坛子一下用了很多统计工具,百X统计啊.CNXZ之类的,感觉很不正统.凌乱不说,效果不一定好.俗话说美女配英雄.飞机配大炮.与我一样,广大的站长朋友是多么想让自己辛苦打造的论坛配一款优秀的完美契合Dz的统计工具啊.某年某月某日

《卸甲笔记》-PostgreSQL和Oracle的SQL差异分析之二:序列的使用

PostgreSQL是世界上功能最强大的开源数据库,在国内得到了越来越多机构和开发者的青睐和应用.随着PostgreSQL的应用越来越广泛,Oracle向PostgreSQL数据库的数据迁移需求也越来越多.数据库之间数据迁移的时候,首先是迁移数据,然后就是SQL.存储过程.序列等程序中不同的数据库中数据的使用方式的转换.下面根据自己的理解和测试,写了一些SQL以及数据库对象转换方面的文章,不足之处,尚请多多指教. 序列 序列是一种数据库对象.可以供多个用户同时使用,得到不重复的.递增的数字值.O