pg truncate

命令选项

TRUNCATE [ TABLE ] [ ONLY ] name [ * ] [, ... ][ RESTART IDENTITY | CONTINUE IDENTITY ] [ CASCADE | RESTRICT ]

1.ONLY:只truncate指定的表。当表有继承子表或有子分区时,默认会一起truncate;only可只truncate继承父表。分区父表不能指定only

--不能truncate only分区父表
=> truncate only parttable;
ERROR:  42809: cannot truncate only a partitioned table
HINT:  Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.
LOCATION:  ExecuteTruncate, tablecmds.c:1655
--truncate only继承父表,只清理父表
=> truncate table only parenttable;
TRUNCATE TABLE
=> select tableoid::regclass,count(*) from parenttable group by tableoid::regclass ;tableoid  | count 
------------+-------childtable |     1--直接truncate继承父表,子表也会被清理
=>  truncate table parenttable;
TRUNCATE TABLE
=> select tableoid::regclass,count(*) from parenttable group by tableoid::regclass ;tableoid | count 
----------+-------
(0 rows)

2.RESTART IDENTITY CONTINUE IDENTITY:列上的序列是否要重置,默认CONTINUE。

--bigserial 默认会创建列上的序列
=> create table tableserial (a  bigserial not null,b name);
CREATE TABLE
=> \d+ tableserial;Table "public.tableserial"Column |  Type  | Collation | Nullable |                Default                 | Storage | Stats target | Description 
--------+--------+-----------+----------+----------------------------------------+---------+--------------+-------------a      | bigint |           | not null | nextval('tableserial_a_seq'::regclass) | plain   |              | b      | name   |           |          |                                        | plain   |              | => insert into tableserial(b) select md5(random()::text) from generate_series(1,1000);
INSERT 0 1000 
--seq当前值为1000
=> select currval('tableserial_a_seq'::regclass);currval 
---------1000--直接truncate默认不会重置序列
=>  truncate table tableserial;
TRUNCATE TABLE
=> select currval('tableserial_a_seq'::regclass) cur,nextval('tableserial_a_seq'::regclass);cur  | nextval 
------+---------1000 |    1001--显示指定RESTART IDENTITY,重置序列
=>  truncate table tableserial RESTART IDENTITY;
TRUNCATE TABLE
--注意seq在nextval时重置了
=>  select currval('tableserial_a_seq'::regclass) cur,nextval('tableserial_a_seq'::regclass);cur  | nextval 
------+---------1001 |       1

3.CASCADE:清理表及其所有外键表的数据

--创建主表和外键表和数据
=>  create table pri_tab(id bigint primary key,name varchar(10));
CREATE TABLE=> insert into pri_tab values (1,'abc'),(2,'abc'),(3,'abc');
INSERT 0 3=>  create table frn_tab(id bigint,FOREIGN KEY (id) REFERENCES pri_tab(id));
CREATE TABLE=> insert into frn_tab values (1),(2);
INSERT 0 2=> select * from pri_tab;id | name 
----+------1 | abc2 | abc3 | abc
(3 rows)--外键表frn_tab依赖主表pri_tab的数据
=> select * from frn_tab;id 
----12
(2 rows)--有主表外键reference时,外键表必须跟cascade,否则无法清理
=> truncate table pri_tab ;
ERROR:  0A000: cannot truncate a table referenced in a foreign key constraint
DETAIL:  Table "frn_tab" references "pri_tab".
HINT:  Truncate table "frn_tab" at the same time, or use TRUNCATE ... CASCADE.
LOCATION:  heap_truncate_check_FKs, heap.c:3427--外键约束的表一起清空
=> truncate table pri_tab cascade;
NOTICE:  00000: truncate cascades to table "frn_tab"
LOCATION:  ExecuteTruncateGuts, tablecmds.c:1725
TRUNCATE TABLE
=> select * from pri_tab;id | name 
----+------
(0 rows)=>  select * from frn_tab;id 
----
(0 rows)

由于外键表依赖主表的数据,不能直接truncate主表,必须加cascade,此时外键表也跟随主表一起清空

4.RESTRICT
是否清理foreign key表。没什么用,default选项,加不加都是这样。清理附带的外键表应加CASCADE。

MVCC/transaction

pg官方文档有这么一段化

TRUNCATE is not MVCC-safe. After truncation, the table will appear empty to concurrent transactions, if they are using a snapshot taken before the truncation occurred.
TRUNCATE is transaction-safe with respect to the data in the tables: the truncation will be safely rolled back if the surrounding transaction does not commit.

transaction-safe意思是可以放在事务块里,可以回退
回滚truncate:

=> begin;
BEGIN
=> truncate t1;
TRUNCATE TABLE
=> rollback;
ROLLBACK
=> select count(*) from t1;count 
-------100

not MVCC-safe意思是一个会话在truncate前打了一个快照,快照期间如果发生truncate,这个快照是可以读到truncate清理后的结果的。这不符合MVCC。
不过这个问题不算太大,在会话场景下,因为truncate是8级锁,快照没有结束的话最低在表上有一个读共享锁,所以truncate不会执行。

This will only be an issue for a transaction that did not access the table in question before the DDL command started —any transaction that has done so would hold at least an ACCESS SHARE table lock, which would block the DDL command until that transaction completes

功能更新

在这里插入图片描述
truncate更新功能不多,只需要注意14的时候支持truncate foreign tables即可。truncate foreign tables前提是fdw得支持TRUNCATE API

Also it extends postgres_fdw so that it can issue TRUNCATE command to foreign servers, by adding new routine for that TRUNCATE API.

pg truncate和其他库的功能差异

在这里插入图片描述
在这里插入图片描述
truncate很快、8级锁等特性已经是人尽皆知的事情了,相对于其他数据库,pg还可以:选择是否重置序列RESTART IDENTITY CONTINUE IDENTITY)、回滚简单的授权

truncate做了什么

create table lzl(a int);
create index lzl_idx on lzl(a);
create sequence lzl_seq start with 1;
alter table lzl alter column a set default nextval('lzl_seq');
--select pg_relation_filepath('lzl');
--db路径
=> select oid from  pg_database where datname='lzldb';oid   
--------418679--刚创建时候各个rel的oid=relfilenode
=> select relname,oid,relfilenode,relkind from pg_class where  relname like 'lzl%';relname |  oid   | relfilenode | relkind 
---------+--------+-------------+---------lzl     | 428363 |      428363 | rlzl_idx | 428366 |      428366 | ilzl_seq | 428367 |      428367 | S
(3 rows)=> truncate table lzl;
TRUNCATE TABLE
=> select relname,oid,relfilenode,relkind from pg_class where  relname like 'lzl%';relname |  oid   | relfilenode | relkind 
---------+--------+-------------+---------lzl     | 428363 |      428370 | rlzl_idx | 428366 |      428371 | ilzl_seq | 428367 |      428367 | S
--truncate后,表和索引重建了,sequence却没有M=> truncate table lzl RESTART IDENTITY;
TRUNCATE TABLE
=> select relname,oid,relfilenode,relkind from pg_class where  relname like 'lzl%';relname |  oid   | relfilenode | relkind 
---------+--------+-------------+---------lzl     | 428363 |      428372 | rlzl_idx | 428366 |      428373 | ilzl_seq | 428367 |      428367 | S
--显示restart,sequence还是没有重建M=> alter sequence lzl_seq restart;
ALTER SEQUENCE
M=> select relname,oid,relfilenode,relkind from pg_class where  relname like 'lzl%';relname |  oid   | relfilenode | relkind 
---------+--------+-------------+---------lzl     | 428363 |      428372 | rlzl_idx | 428366 |      428373 | ilzl_seq | 428367 |      428374 | S
--显示restart sequence是会重建sequence的

truncate ···RESTART IDENTITY没有重建我们sequence,alter sequence lzl_seq restart重建了sequence。应该是RESTART IDENTITY没有理解对。在看下官方文档对RESTART IDENTITY的解释

Automatically restart sequences owned by columns of the truncated table(s).

sequence必须owned by表上的列,注意不是owner to。虽然\d可以看到表上的sequence,但是它可能不属于表

 \d+ lzl;Table "public.lzl"Column |  Type   | Collation | Nullable |           Default            | Storage | Stats target | Description 
--------+---------+-----------+----------+------------------------------+---------+--------------+-------------a      | integer |           |          | nextval('lzl_seq'::regclass) | plain   |              | 

owned by修改sequence的所属表

=> ALTER SEQUENCE lzl_seq OWNED BY lzl.a;
ALTER SEQUENCE--查看序列的所有者信息
SELECT s.relname AS seq, n.nspname AS sch, t.relname AS tab, a.attname AS col  
FROM pg_class s  
JOIN pg_depend d ON d.objid=s.oid AND d.classid='pg_class'::regclass AND d.refclassid='pg_class'::regclass  
JOIN pg_class t ON t.oid=d.refobjid  
JOIN pg_namespace n ON n.oid=t.relnamespace  
JOIN pg_attribute a ON a.attrelid=t.oid AND a.attnum=d.refobjsubid  
WHERE s.relkind='S' AND d.deptype='a';seq        |  sch   |     tab     | col 
-------------------+--------+-------------+-----tableserial_a_seq | public | tableserial | alzl_seq           | public | lzl         | a=> truncate table lzl RESTART IDENTITY;
TRUNCATE TABLE
M=> select relname,oid,relfilenode,relkind from pg_class where  relname like 'lzl%';relname |  oid   | relfilenode | relkind 
---------+--------+-------------+---------lzl     | 428363 |      428375 | rlzl_idx | 428366 |      428376 | ilzl_seq | 428367 |      428377 | S

sequence owned by表上的列时,truncate显示带RESTART IDENTITY就会restart这个sequence,也就重建了sequence。默认以serial/bigserial方式创建的序列是被表拥有的,随表drop而删除;那些不被表拥有的序列,drop不会删除
truncate重建特性汇总:

  • 直接truncate table会重建表和索引
  • truncate table+RESTART IDENTITY,会重建(也就是retart)属于这个表的sequence。只要不属于这个表的sequence,哪怕列上关联了seq的默认值,也不会重建这个seq

源码分析

truncate也是utility命令,很快就可以找到入口函数
src/backend/commands/tablecmds.c中的ExecuteTruncate为入口函数,注释其实已经说明truncate要获得exclusive lock,并检查权限和relation是否ok,递归检查所有需要truncate的表

voidExecuteTruncate(TruncateStmt *stmt){.../** Open, exclusive-lock, and check all the explicitly-specified relations*/foreach(cell, stmt->relations){...
LOCKMODE lockmode = AccessExclusiveLock;  //8级锁
...rel = table_open(myrelid, NoLock); //打开表void
ExecuteTruncate(TruncateStmt *stmt)
{
...foreach(cell, stmt->relations){
...LOCKMODE	lockmode = AccessExclusiveLock; //8级锁
.../* open the relation, we already hold a lock on it */rel = table_open(myrelid, NoLock); //打开表
...truncate_check_activity(rel);  //虽然已经有锁了,但是还是要验证是否在使用
...if (recurse) //递归执行{
...children = find_all_inheritors(myrelid, lockmode, NULL); //找到所有继承子表foreach(child, children){
...//上面只检查了父表,递归要检查子表truncate_check_rel(RelationGetRelid(rel), rel->rd_rel);truncate_check_activity(rel);rels = lappend(rels, rel);  //加入到待truncate的rel队列中relids = lappend_oid(relids, childrelid);
...}}//递归结束//发现truncate only分区父表,直接报错else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)ereport(ERROR,(errcode(ERRCODE_WRONG_OBJECT_TYPE),errmsg("cannot truncate only a partitioned table"),errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.")));}//主体函数	ExecuteTruncateGuts(rels, relids, relids_logged,stmt->behavior, stmt->restart_seqs);/* And close the rels */foreach(cell, rels){Relation	rel = (Relation) lfirst(cell);table_close(rel, NoLock);}
}

ExecuteTruncateGuts函数不仅被truncate命令调用,还被订阅端调用(发布订阅可以同步truncate)。

void
ExecuteTruncateGuts(List *explicit_rels,List *relids,List *relids_logged,DropBehavior behavior, bool restart_seqs)
{
...rels = list_copy(explicit_rels);if (behavior == DROP_CASCADE)  //如果指定了cascade选项,提取所有reference的relation{for (;;){
...newrelids = heap_truncate_find_FKs(relids); //找到fkif (newrelids == NIL)break;			/* nothing else to add */ //没有rel直接退出foreach(cell, newrelids){
...rel = table_open(relid, AccessExclusiveLock); //所有rel获得AccessExclusiveLockereport(NOTICE,(errmsg("truncate cascades to table \"%s\"",RelationGetRelationName(rel))));truncate_check_rel(relid, rel->rd_rel);  //检查是否是可以truncate的对象,得是存储数据的表truncate_check_perms(relid, rel->rd_rel);  //检查是否有权限truncate_check_activity(rel);  //检查是否在使用
...}}}...if (restart_seqs) //restart seq的处理{foreach(cell, rels){Relation	rel = (Relation) lfirst(cell);List	   *seqlist = getOwnedSequences(RelationGetRelid(rel));
...//只是做sequence的权限检查if (!pg_class_ownercheck(seq_relid, GetUserId()))aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,RelationGetRelationName(seq_rel));
...}}...//执行所有before truncate触发器foreach(cell, rels){ExecBSTruncateTriggers(estate, resultRelInfo);resultRelInfo++;}//正式开始truncateforeach(cell, rels){
...//如果是分区父表,啥都不做if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)continue;//如果是foreign table的处理if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE){...}...//如果是同一事务,因为可能会回退,直接执行heap_truncate_one_rel函数,不创建新的relfilenodeif (rel->rd_createSubid == mySubid ||rel->rd_newRelfilenodeSubid == mySubid){/* Immediate, non-rollbackable truncation is OK */heap_truncate_one_rel(rel);}else{
...//设置NewRelfilenodeRelationSetNewRelfilenode(rel, rel->rd_rel->relpersistence);heap_relid = RelationGetRelid(rel);//toast同理toast_relid = rel->rd_rel->reltoastrelid;if (OidIsValid(toast_relid)){Relation	toastrel = relation_open(toast_relid,AccessExclusiveLock);RelationSetNewRelfilenode(toastrel,toastrel->rd_rel->relpersistence);table_close(toastrel, NoLock);}...//重建索引reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST,&reindex_params);}pgstat_count_truncate(rel); //更新pgstat的truncate计算}...//重置sequence foreach(cell, seq_relids){Oid			seq_relid = lfirst_oid(cell);ResetSequence(seq_relid);}//写walif (list_length(relids_logged) > 0){...}//触发AFTER TRUNCATE triggersresultRelInfo = resultRelInfos;foreach(cell, rels){ExecASTruncateTriggers(estate, resultRelInfo);resultRelInfo++;}
...
}	

ExecuteTruncateGuts函数根据truncate选项进行处理,处理过程如下:

  1. 根据cascade选项找到所有reference的外键表
  2. 触发before truncate触发器
  3. 执行truncate
  • 如果是同一事务,不立即生成NewRelfilenode,直接调用函数heap_truncate_one_rel进行truncate
  • 如果不是同一事务,调用RelationSetNewRelfilenode新建NewRelfilenode
  1. reindex_relation函数重建索引
  2. 根据restart identity重置sequence
  3. 写wal日志
  4. 触发after truncate触发器

后面大概追了下函数,套娃比较多
RelationSetNewRelfilenode
table_relation_set_new_filenode
relation_set_new_filenode在这里插入代码片
heapam_relation_set_new_filenode
RelationCreateStorage
然后到src/backend/storage/smgr/smgr.c中的smgrcreatesmgr_create。后面就没看太懂了(一到函数指针就有点追不到的感觉,先这样吧~)···
对于smgr.c有这样的注释:

public interface routines to storage manager switch
All file system operations in POSTGRES dispatch through these routines.

任何文件系统操作都会经过smgr(storage manager);到这里就是文件系统操作了。

reference

https://www.postgresql.org/docs/15/sql-truncate.html
https://www.postgresql.org/docs/current/mvcc-caveats.html
https://pgpedia.info/t/truncate.html
https://www.orafaq.com/wiki/SQL_FAQ
https://learnsql.com/blog/difference-between-truncate-delete-and-drop-table-in-sql/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/228909.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】树与二叉树(廿五):树搜索给定结点的父亲(算法FindFather)

文章目录 5.3.1 树的存储结构5. 左儿子右兄弟链接结构 5.3.2 获取结点的算法1. 获取大儿子、大兄弟结点2. 搜索给定结点的父亲a. 算法FindFatherb. 算法解析c. 代码实现 3. 代码整合 5.3.1 树的存储结构 5. 左儿子右兄弟链接结构 【数据结构】树与二叉树(十九&…

2023年港澳台联考中录取分数高性价比的985和211大学来啦

导读 一直以来,985和211都是港澳台联考报名录取中,大家最关心的两类大学。其实每年的港澳台联考都有一些性价比很高的学校,今天我们就来看一下这些优秀的985和211大学吧!(景于行跟您承诺,本篇文章分享的分数…

SpringBoot+VUE3前后端分离-【支付宝支付】

1、支付宝沙箱应用申请 https://open.alipay.com/develop/sandbox/app 打开支付宝沙箱能够看到如下信息: 获取到appid; 2、获取应用私钥以及支付宝公钥 在接口加密方式选择公钥模式启用,根据操作即可获取应用公钥、应用私钥以及支付宝公钥…

uniapp使用vue3和ts开发小程序获取用户城市定位

这个组件的功能:可以重新定位获取到用户的具体位置,这个是通过getLocation这个api和高德地图的api获取到的,getLocation这个api需要在微信公众平台后台>开发管理> 接口管理里面申请才能使用的,不然无法使用哦,这…

如何处理枚举类型(上)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 我们会分为上、下两篇分…

Spring代理方式之静态、动态代理(JDK和CGlib动态代理)

目录 1、代理设计模式的概念 2、静态代理 3、动态代理(JDK和CGlib动态代理) 1. JDK动态代理是基于接口的代理(Interface-based proxy) 2. CGLIB代理是基于类的代理(Class-based proxy) ⭐比较&#x…

SpringBoot : ch11 整合RabbitMQ

前言 在当今的互联网时代,消息队列成为了构建高可靠、高性能系统的重要组件之一。RabbitMQ作为一个可靠、灵活的消息中间件,被广泛应用于各种分布式系统中。 本篇博客将介绍如何使用Spring Boot整合RabbitMQ,实现消息的发送和接收。通过这种…

真空工艺腔内潮湿有什么危害?

在半导体制程中,真空工艺腔被广泛使用。薄膜沉积,干法刻蚀,光刻,退火,离子注入等工序均需要在相应的真空腔室中完成相应制程。真空工艺腔在半导体制程中起着至关重要的作用,它能够提供一个高度控制的环境&a…

MT8390(Genio 700)安卓核心板_MTK联发科工业AI主板Linux开发板

MT8390 (Genio 700) 安卓核心板是一款高性能边缘人工智能物联网平台,尺寸仅为45452.2mm。该平台提供高度响应的边缘处理、先进的多媒体功能、各种传感器和连接选项,同时支持多任务操作系统。 Genio 700处理器拥有PS APU性能,高效的芯片内人工…

42. 接雨水(单调栈)

这道题我本来的做法是 使用两个指针定住两头第一个非零元素,然后计算中间有多少个0,就表示多少个“坑” 然后依次-1,再重复上述操作 但是遇到height[i]为上千的数字,就很蠢了。。。 并且超时 class Solution { public:int trap(v…

同旺科技 USB 转 RS-485 适配器

内附链接 1、USB 转 RS-485 适配器 基础版主要特性有:(非隔离) ● 支持USB 2.0/3.0接口,并兼容USB 1.1接口; ● 支持USB总线供电; ● 支持Windows系统驱动,包含WIN10 / WIN11系统32 / 64位…

SSL证书实惠品牌——JoySSL

随着互联网的普及和发展,网络安全问题日益严重。为了保护网站数据的安全,越来越多的网站开始使用SSL证书。JoySSL证书作为一款高性价比的SSL证书,受到了广泛的关注和好评。 目前市面上主流的证书基本上都是国外证书,也就是说你在验…