2021SC@SDUSC

目录

  • 概述
  • 源码分析
    • TOAST的起源
    • TOAST的原理
    • TOAST的文件
    • TOAST的相关操作
      • toast_insert_or_update函数(插入更新)
      • toast_delete函数(删除)
      • heap_tuple_untoast_attr函数(获取)
  • 总结
    • 关于操作调用的总结
    • 收获

概述

这次来分析postgreSQL关于大数据的存储方式,一共有两种方式:TOAST机制和大对象机制,这次就先来分析TOAST机制。
TOAST,全称为The Oversized-Attribute Storage Technique,中文名为超尺寸属性存储技巧,是postgreSQL提供的一种存储大数据的机制。

源码分析

TOAST的起源

在postgreSQL中存在可变长度的数据类型:

  • varchar(n)
  • text
  • ……

导致的问题
这就导致一个问题的出现,我们假设这样一种极限的状态:一个text非常大,大到比一个表块还要大的时候,它该如何存放呢?
通过查阅官方的文档,发现了官方对这一问题的解答:

PostgreSQL使用固定的页面尺寸(通常是8KB),并且不允许元组跨越多个额页面。因此不可能直接存储非常大的域值。为了克服这个限制,大的域值会被压缩并/或分解成多个物理行。这些处理对用户都是透明的,只是在大部分的后端代码上有一些小的影响。这个技术的昵称是TOAST(或者“切片面包之后的最好的东西”)。TOAST 机制也被用来提升内存中大型数据值的处理。

这说明,在原本限制的情况下,单个数据类型的大小是有极限的,只能限制在单个块内。而为了解决大数据的存放问题,TOAST应运而生。当然,这并不意味着只要有变长类型就会触发TOAST机制,只有在准备向支持TOAST的属性中存储超过BLCKSZ/4 (默认为8K/4=2K)字节的数据时,TOAST机制才会触发。如官方文档所说,TOAST机制会将要存储的数据进行压缩或线外存储(把数据存储在其他的表中),直到数据低于刚才提到的阈值,或者没有办法获得更好的结果时才会停止。

变长类型数据结构
关于变长类型的数据结构,位于src/include/c.h文件中。

struct varlena
{char		vl_len_[4];		/*表示值的总长度*/char		vl_dat[FLEXIBLE_ARRAY_MEMBER];	/*存放数据内容*/
};

先分析一下这个数据结构,vl_len数组有4个字节,也就是32位。
TOAST占用使用变长类型的长度字(vl_len)的最高两个二进制位( 大端法机器上的高位,小端法机器上的低位),这样就把任何可TOAST值的逻辑长度限制在1GB((230 - 1)字节)。这两个二进制位的作用如下:

  • 00,表示数值是该数据类型的一个普通的未TOAST的数值,余下的30位表示数据的大小。
  • 01,表示该数据已经被压缩了,使用该数据前必须先解压。余下的30位像上面说的,表示压缩后数据的大小,并且在这四个字节之后还会附加32位来表示压缩之前的数据大小。
  • 1x,第一位为1时,表明数据头部仅使用了1个字节。这时会出现两种情况:
    1. 当这个字节为10000000时,表示这是线外存储的数据,跟在后面的1个字节用于记录指针的大小(TOAST_POINTER_SIZE),数据区域用于记录TOAST指针。
    2. 当该字节剩余部分不全为0时,表明存储的为短字符串(<128B),该字节剩下的7位用于表示字符串的长度。

但这样表示的话会漏掉一种情况,也就是数据可能在线外存储时也可能会被压缩,这种情况就需要通过TOAST指针中的va_rawsize和va_extsize来进行比较。
上面提到的压缩
源码位于src/common/pg_lzcompress.c,是 LZ 压缩技术家族中一种相对简单且非常快速的成员。这里不是我分析的重点,因此就不展开讲了。有时间的话我会专门开一帖博客来分析这个压缩算法的源码。

TOAST的原理

TOAST指针
源码位于src/include/postgres.h

//磁盘线外存储指针
typedef struct varatt_external
{int32		va_rawsize;  //原始数据大小,包含文件头。int32		va_extsize;	 //线外存储的大小,不包含文件头。Oid			va_valueid;	 //在toast表中值的独有的Oid,也就是线外存储的数据的Oid。Oid			va_toastrelid; //toast表的Oid
}			varatt_external;//内存线外存储指针
typedef struct varatt_indirect
{struct varlena *pointer; //指向内存的可变属性指针
}	varatt_indirect;

前面提到过既压缩又线外存储的情况,就可以根据这个结构体的属性来进行判断:当 va_extsize < va_rawsize - VARHDRSZ 时,就说明这个数据被压缩了。这个公式很简单,表达的意思是 线外存储的大小(不包含文件头) 如果小于(原始数据大小(包含文件头)-文件头大小)时,很显然,小的那部分数据是因为被压缩了。
文件头的定义如下:

#define VARHDRSZ		((int32) sizeof(int32))

顾名思义,VARHDRSZ全名就是Var Header Size。
存储策略
TOAST代码代码识别四种不同的在磁盘上存储可TOAST列的策略:

  1. PLAIN避免压缩或者线外存储;而且它禁用变长类型的单字节头部。这是不可TOAST数据类型列的唯一可能的策略。只是对那些不能TOAST的数据类型才有可能。

  2. EXTENDED允许压缩和线外存储。这是大多数可TOAST数据类型的默认策略。 首先将尝试进行压缩,如果行仍然太大,那么则进行线外存储。

  3. EXTERNAL允许线外存储,但是不许压缩。使用EXTERNAL将令那些在宽text和 bytea列上的子串操作更快(代价是增加了存储空间), 因此这些操作被优化为只抓取未压缩线外数据中需要的部分。

  4. MAIN允许压缩,但不允许线外存储(实际上,在这样的列上仍然会进行线外存储,但只是作为没有办法把行变得足以放入一页的情况下的最后手段)。

这些存储策略是由用户在创建表时通过SQL选择使用的,也可以在以后进行修改。

相关源码如下(位于src/include/catalog/pg_attribute.h):
位于一个名为FormData_pg_attribute的结构体内,由于比较长,我只放上相关属性和注释:

	/*----------* attstorage 可能的取值如下:*		'p': 即上面的PLAIN,避免压缩或线外存储*		'e': 即上面的EXTERNAL,允许线外存储,但不允许压缩。*		'm': 即上面的MAIN,允许压缩,但不允许线外存储。*		'x': 即上面的EXTENDED,允许压缩和线外存储,也是默认策略。*----------*/char		attstorage;

TOAST的文件

主要源码位于这里src/backend/access/heap/toasting.c
本来想在源码里找一下toast表的结构,结果找了半天没有找到。于是就研究了一下关于toast表的创建函数,发现确实不存在toast表的结构体,但函数大概声明了toast表文件的结构。部分源码如下:

tupdesc = CreateTemplateTupleDesc(3);TupleDescInitEntry(tupdesc, (AttrNumber) 1,"chunk_id",OIDOID,-1, 0);TupleDescInitEntry(tupdesc, (AttrNumber) 2,"chunk_seq",INT4OID,-1, 0);TupleDescInitEntry(tupdesc, (AttrNumber) 3,"chunk_data",BYTEAOID,-1, 0);

可以从里面窥探出,toast表一共由三个属性组成,我用表格的形式来描述一下:

属性名数据类型简介
chunk_idOID线外存储时为整个TOAST数据分配的OID
chunk_seqINT4序列号,存储该片段在整个TOAST数据中的位置
chunk_dataBYTEA存储该片段实际的数据

当然,这个TOAST表结构只有在线外存储时才会启用。线外数据会被分割成不超过TOAST_MAX_CHUNK_SIZE(下面有详细介绍)字节的片段,每个片段都作为独立的元组存储在相关联的TOAST表中。
根据表的结构,每一个被线外存储的TOAST数据都会被分配一个OID,通过这个OID可以在TOAST表中找到属于该TOAST数据的所有片段,进而可以重组该数据,同时,它也是构成上面提到的TOAST指针的一部分。
TOAST片段的大小限制
TOAST的chunk(片段)的最大大小TOAST_MAX_CHUNK_SIZE定义如下(位于src/include/access/tuptoaster.h):

//定义toast片段的最大大小 大概就是线外存储的元组的最大大小减去一系列头部数据的大小
#define TOAST_MAX_CHUNK_SIZE	\(EXTERN_TUPLE_MAX_SIZE -							\MAXALIGN(SizeofHeapTupleHeader) -					\sizeof(Oid) -										\sizeof(int32) -									\VARHDRSZ)//线外存储的元组的最大大小 = 每个元组最大字节数
#define EXTERN_TUPLE_MAX_SIZE	MaximumBytesPerTuple(EXTERN_TUPLES_PER_PAGE)//每个TOAST页(块)包含的元组数量
#define TOAST_TUPLES_PER_PAGE	4//用于计算每个元组最大字节数的函数
#define MaximumBytesPerTuple(tuplesPerPage) \MAXALIGN_DOWN((BLCKSZ - \MAXALIGN(SizeOfPageHeaderData + (tuplesPerPage) * sizeof(ItemIdData))) \/ (tuplesPerPage))

这里要讲解一个知识点:换行标记: \

在C语言程序编写中,我们有时会遇到一行代码太长而影响阅读或者出现与部分公司或组织要求的编码规范不符的情况,此时我们需要将这行代码分成多行来写。在编译时,\后面的换行符将被忽略,当做一行处理。比如宏定义时使用
#define my_puts(x) printf("%s", \
x);
和写作
#define my_puts(x) printf("%s",x);
是没区别的。

TOAST的相关操作

src/backend/access/heap/tuptoaster.c这个文件中定义了和TOAST相关的操作。
根据源码开始的注释,有以下三个函数:

  • toast_insert_or_update负责更新和插入元组
  • toast_delete删除TOAST元组
  • heap_tuple_untoast_attr获取TOAST元组

我们就逐个开始分析:

toast_insert_or_update函数(插入更新)

这段源码比较长,也比较复杂,因此难以做到十分详尽的分析,但重点地方还是会详尽的分析。

HeapTuple
toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,int options)
//传入参数中有两个元组,一个为新版本元组,另一个为旧版本元组。
//旧版本元组的作用是根据旧版本元组是否为空来判断进行插入还是更新操作。
{HeapTuple	result_tuple;//最终处理后的元组,用于返回TupleDesc	tupleDesc;//元组描述符int			numAttrs;//属性数量int			i;//for循环用变量bool		need_change = false;//是否线外存储了属性,存储了则为真(需要新建立一个结果元组)bool		need_free = false;//内存是否需要释放bool		need_delold = false;//旧元组的某一属性是否需要删除bool		has_nulls = false;//新元组是否含有NULL属性Size		maxDataLen;//存储最大的数据长度Size		hoff;char		toast_action[MaxHeapAttributeNumber];//存放对元组属性的操作,有' ' 表示默认处理方式,'p'表示已经处理过了,'x'表示不可以进行压缩,但是可以线外存储。bool		toast_isnull[MaxHeapAttributeNumber];//存放新元组该属性是否为Nullbool		toast_oldisnull[MaxHeapAttributeNumber];//存放旧元组该属性是否为NullDatum		toast_values[MaxHeapAttributeNumber];//存放新元组属性的值Datum		toast_oldvalues[MaxHeapAttributeNumber];//存放旧元组属性的值struct varlena *toast_oldexternal[MaxHeapAttributeNumber];//存放旧元组线外存储(toast表)的指针int32		toast_sizes[MaxHeapAttributeNumber];//存放toast表的大小,只对可变长属性并且toast_action为'p'的属性有效bool		toast_free[MaxHeapAttributeNumber];//存放是否释放旧元组属性bool		toast_delold[MaxHeapAttributeNumber];//存放是否删除旧元组的属性//获取元组的描述符并且将元组分解成不同的属性tupleDesc = rel->rd_att;numAttrs = tupleDesc->natts;Assert(numAttrs <= MaxHeapAttributeNumber);//属性数量不能超过限制,否则报错heap_deform_tuple(newtup, tupleDesc, toast_values, toast_isnull);//从新元组中提取出属性数组(包括属性的描述和值)等信息if (oldtup != NULL)//如果旧元组不为NULL,说明是要进行修改操作。heap_deform_tuple(oldtup, tupleDesc, toast_oldvalues, toast_oldisnull);//从旧元组中提取出属性数组(包括属性的描述和值)等信息memset(toast_action, ' ', numAttrs * sizeof(char));//初始化toast_action数组,全部置为' 'memset(toast_oldexternal, 0, numAttrs * sizeof(struct varlena *));//初始化toast_oldexternal数组,全部置为0memset(toast_free, 0, numAttrs * sizeof(bool));//初始化toast_free数组,全部置为0memset(toast_delold, 0, numAttrs * sizeof(bool));//初始化toast_delold数组,全部置为0for (i = 0; i < numAttrs; i++)//对每个属性进行检查{Form_pg_attribute att = TupleDescAttr(tupleDesc, i);struct varlena *old_value;//旧元组的线外存储的属性的指针struct varlena *new_value;//新元组的线外存储的属性的指针if (oldtup != NULL)//如果旧的元组不为NULL,进行修改操作{old_value = (struct varlena *) DatumGetPointer(toast_oldvalues[i]);//获取旧元组的对应属性new_value = (struct varlena *) DatumGetPointer(toast_values[i]);//获取新元组的对应属性//判断是否需要对旧值变动if (att->attlen == -1 && !toast_oldisnull[i] &&VARATT_IS_EXTERNAL_ONDISK(old_value))//如果旧的线外存储值已经存储在磁盘上{if (toast_isnull[i] || !VARATT_IS_EXTERNAL_ONDISK(new_value) ||memcmp((char *) old_value, (char *) new_value,VARSIZE_EXTERNAL(old_value)) != 0)//如果新元组的该属性不为null或者没有线外存储在磁盘上或者旧的属性和新的属性的值不相等,则需要进行更新{//则旧的线外存储的数据在更新后就会失去作用,可以直接删除toast_delold[i] = true;//将该删除旧数据标记为真need_delold = true;//设置需要删除为真}else//这时无需更新,可以不做处理{toast_action[i] = 'p';//将该属性的操作标记为p,意为无需处理。continue;}}}else//这时旧元组为NULL,需要进行插入操作{new_value = (struct varlena *) DatumGetPointer(toast_values[i]);//获取新属性的值,用于插入。}//处理NULL属性if (toast_isnull[i])//如果新元组的该属性为空{toast_action[i] = 'p';//将该属性的操作设置为P,即无需处理has_nulls = true;//含有NULL设置为真continue;//此时无论插入还是修改都没有意义,所以继续检查下一个属性}//线外存储属性相关信息检查if (att->attlen == -1)//如果是变长属性,则需要进行TOAST处理。{if (att->attstorage == 'p')//如果该属性的存储策略(上面有详细分析)为p,则不进行线外存储和压缩toast_action[i] = 'p';//将新元组该属性对应操作设置为pif (VARATT_IS_EXTERNAL(new_value))//如果新元组的属性是线外存储的{toast_oldexternal[i] = new_value;if (att->attstorage == 'p')//如果该属性的存储策略为p(没有进行压缩、线外存储)new_value = heap_tuple_untoast_attr(new_value);//直接用 heap_tuple_untoast_attr函数读取TOAST数据else//否则(可能进行了压缩、线外存储)new_value = heap_tuple_fetch_attr(new_value);//用heap_tuple_fetch_attr从线外获取TOAST数据(保留压缩特性,该函数不会对压缩的数据进行解压)toast_values[i] = PointerGetDatum(new_value);//给新元组的属性赋值toast_free[i] = true;//设置为需要释放need_change = true;//需要修改设置为真need_free = true;//需要释放设置为真}toast_sizes[i] = VARSIZE_ANY(new_value);//记录该属性的大小}else//并非变长属性,则只能p模式存储。{toast_action[i] = 'p';//将该属性的操作设置为p,即不压缩也不线外存储。}}hoff = SizeofHeapTupleHeader;//获取元组文件头的长度if (has_nulls)//如果新元组中有null属性hoff += BITMAPLEN(numAttrs);//将刚才文件头的长度再加上属性数据的长度hoff = MAXALIGN(hoff);//元组中没有null属性则可直接赋值maxDataLen = RelationGetToastTupleTarget(rel, TOAST_TUPLE_TARGET) - hoff;//利用刚才计算出的hoff来限制元组的数据大小

第一次while循环
先处理存储策略为’x’和’e’的两种属性。
主要流程:

  1. 遍历属性,找到大小最大并且仍未进行TOAST处理的属性,不存在则直接进入下一个while循环。
  2. 如果属性的存储策略为 ‘x’,即EXTENDED,允许压缩和线外存储,则先进行压缩,如果压缩成功则返回压缩后的新值,压缩失败则将对应的toast_action设置为p,即以后都忽略压缩(避免重复)。
  3. 如果属性的存储策略为 ‘x’,即EXTERNAL,允许线外存储,不允许压缩,将对应的toast_action设置为p,即以后都忽略压缩。
  4. 经过刚才的处理,如果属性大小仍超过限制,则调用toast_save_datum将其进行线外存储。
	 //第一个while循环,寻找可以压缩的属性对其进行压缩while (heap_compute_data_size(tupleDesc,toast_values, toast_isnull) > maxDataLen)//如果元组仍然超过限制大小{int			biggest_attno = -1;int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);Datum		old_value;Datum		new_value;for (i = 0; i < numAttrs; i++)//遍历属性,寻找还没有被处理的最大的线内属性{Form_pg_attribute att = TupleDescAttr(tupleDesc, i);//赋值if (toast_action[i] != ' ')continue;if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))//如果数据存储在线外continue;	if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))//如果该属性的数据已经被压缩continue;//跳到下一个属性继续if (att->attstorage != 'x' && att->attstorage != 'e')//如果存储策略既不为x又不为e,即不可以压缩,则跳到下一个属性继续continue;if (toast_sizes[i] > biggest_size)//通过比较找出大小最大的属性{biggest_attno = i;//设置最大的属性的数组序号biggest_size = toast_sizes[i];//赋值}}if (biggest_attno < 0)//没有找到符合要求的(初值为-1,结果<0说明没有找到)break;//终止第一个while循环i = biggest_attno;//能运行到这里,说明一定找到了可以压缩的属性,将序号赋值给iif (TupleDescAttr(tupleDesc, i)->attstorage == 'x')//如果该属性的存储策略为x,即允许压缩{old_value = toast_values[i];//获取未压缩时的值new_value = toast_compress_datum(old_value);//将压缩后的值赋给new_valueif (DatumGetPointer(new_value) != NULL)//如果成功拿到到了压缩后的属性的指针{//说明压缩成功if (toast_free[i])//判断第i个属性的内存空间是否需要释放pfree(DatumGetPointer(old_value));//释放内存空间toast_values[i] = new_value;//将新值赋给存储属性值的数组toast_free[i] = true;toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i]));//获取新的值的大小need_change = true;need_free = true;}else//压缩失败,可能数据是不可压缩的{toast_action[i] = 'x';//将操作设置为x,忽视后面的压缩过程。}}else//存储策略不为x,则说明策略为e,此时不允许压缩,但是允许线外存储{toast_action[i] = 'x';//将操作设置为x,忽视后面的压缩过程。}if (toast_sizes[i] > maxDataLen &&rel->rd_rel->reltoastrelid != InvalidOid)//如果经过刚才的压缩处理后,大小仍然超出限制{old_value = toast_values[i];//将处理后的值赋给old_valuetoast_action[i] = 'p';//将操作设置为ptoast_values[i] = toast_save_datum(rel, toast_values[i],toast_oldexternal[i], options);//将值存储到线外//关于toast_save_datum函数,将一个属性保存到线外并且返回该属性的引用。//内存释放if (toast_free[i])pfree(DatumGetPointer(old_value));toast_free[i] = true;need_change = true;need_free = true;}}

第二次while循环
找到可以线外存储但未线外存储的属性(即刚才处理的x和e),进行线外存储的操作。
主要流程:

  1. 找到最大的,且存储策略为x或e,并且仍未进行线外存储的大小最大的属性,如果未找到则进入下一个while循环。
  2. 调用函数toast_save_datum将其存储到线外,并将对应的toast_action设置为p,从而忽略以后的压缩和线外存储操作。
	 //第二次循环:寻找存储策略为x或者e,也就是可以线外存储的属性while (heap_compute_data_size(tupleDesc,toast_values, toast_isnull) > maxDataLen &&rel->rd_rel->reltoastrelid != InvalidOid)//如果元组仍然超过限制大小{int			biggest_attno = -1;//记录最大的属性的数组序号int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);//初始化最大大小Datum		old_value;//存储旧数据for (i = 0; i < numAttrs; i++)//遍历所有属性,找出存储策略为x或e,并且仍未进行线外存储的最大的属性{Form_pg_attribute att = TupleDescAttr(tupleDesc, i);if (toast_action[i] == 'p')//如果操作为p,则无需压缩或线外存储continue;//继续下一个属性if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))//如果已经线外存储(这种情况不会出现,因为线外存储以后会将操作设置为p,在上面就被过滤掉了)continue;//继续下一个属性if (att->attstorage != 'x' && att->attstorage != 'e')//如果存储策略不为x或econtinue;//继续下一个属性if (toast_sizes[i] > biggest_size)//和上面一样,选出最大值{biggest_attno = i;biggest_size = toast_sizes[i];}}if (biggest_attno < 0)//没有找到符合条件的break;//跳出while循环//走到这里说明找到了可以进行线外存储的属性,将开始线外存储的操作i = biggest_attno;old_value = toast_values[i];toast_action[i] = 'p';//将操作设置为p,从而忽略以后的操作。toast_values[i] = toast_save_datum(rel, toast_values[i],toast_oldexternal[i], options);//将数据存储到线外,并返回引用//释放内存if (toast_free[i])pfree(DatumGetPointer(old_value));toast_free[i] = true;need_change = true;need_free = true;}

第三次while循环
这次循环处理存储类型为m的属性,即只允许线内压缩而不允许线外存储的类型。
主要流程:

  1. 遍历属性,寻找存储类型为m且仍未被处理过的最大的属性,如果未找到则进入下一个循环。
  2. 将找到的属性尝试压缩,如果压缩未成功则设置对应的toast_action为’x’,忽略以后的压缩操作。
	 //第三次循环:处理策略为m的允许压缩但未压缩的属性while (heap_compute_data_size(tupleDesc,toast_values, toast_isnull) > maxDataLen)//如果元组仍然超过限制大小{int			biggest_attno = -1;//初始化最大序号int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);//初始化比较大小Datum		old_value;Datum		new_value;for (i = 0; i < numAttrs; i++)//遍历属性,寻找最大的未压缩的属性{if (toast_action[i] != ' ')//操作为空则继续continue;if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))//如果是线外存储的数据则跳过continue;		if (VARATT_IS_COMPRESSED(DatumGetPointer(toast_values[i])))//如果数据已经压缩过了则跳过continue;if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')//如果存储策略不为m则跳过continue;//用于比较获取最大值if (toast_sizes[i] > biggest_size){biggest_attno = i;biggest_size = toast_sizes[i];}}if (biggest_attno < 0)//不存在则退出循环break;//执行到这里说明找到了对应的属性,开始尝试压缩i = biggest_attno;old_value = toast_values[i];//赋值要压缩的值到old_valuenew_value = toast_compress_datum(old_value);//进行压缩,并将结果返回给new_valueif (DatumGetPointer(new_value) != NULL)//如果压缩成功{if (toast_free[i])//如果需要释放内存则释放pfree(DatumGetPointer(old_value));toast_values[i] = new_value;//存储压缩后的值//压缩完成后修改一下相关信息toast_free[i] = true;toast_sizes[i] = VARSIZE(DatumGetPointer(toast_values[i]));need_change = true;need_free = true;}else//压缩失败{//将操作设置为x,后续压缩操作直接忽略。toast_action[i] = 'x';}}

第四次while循环
此时已经经过了前面三次的循环,如果此时元组的大小仍然超过限制,但是前面线外存储和压缩都已经尝试过了,还有什么办法继续缩小元组的大小呢?我想答案已经呼之欲出了,那就是将压缩过的数据进行线外存储。而这种数据,只能是存储策略为m的数据(只允许压缩,不允许线外存储),其他存储类型的数据已经没有可以继续转移的可能了。因此,第四次循环会将存储策略为m且已经被处理过的属性存储到线外。

大体流程:

  1. 将存储策略为’m’且已经被压缩过的属性进行线外存储。
  2. 判断是否需要构造一个结果元组,如果需要,则利用新元组的信息以及TOAST处理过的属性值构造一个结果元组,再从旧元组中删除没有在新元组中重用的线外存储数据。
  3. 返回结果元组。
while (heap_compute_data_size(tupleDesc,toast_values, toast_isnull) > maxDataLen &&rel->rd_rel->reltoastrelid != InvalidOid)//如果元组仍然超过限制大小{int			biggest_attno = -1;//初始化最大的属性的序号int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);//初始化最大值Datum		old_value;for (i = 0; i < numAttrs; i++)//遍历所有属性,寻找最大的且已经处理过的m类型数据。{if (toast_action[i] == 'p')//操作类型为p,直接跳过continue;if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i])))//已经线外存储,直接跳过continue;		if (TupleDescAttr(tupleDesc, i)->attstorage != 'm')//如果存储策略不为m,直接跳过continue;//符合条件,则判断是否为最大的if (toast_sizes[i] > biggest_size){biggest_attno = i;biggest_size = toast_sizes[i];}}if (biggest_attno < 0)//没有找到,则跳出循环break;//程序执行到这里,说明已经找到对应的属性,进行线外存储即可(前面分析过)i = biggest_attno;old_value = toast_values[i];toast_action[i] = 'p';toast_values[i] = toast_save_datum(rel, toast_values[i],toast_oldexternal[i], options);//释放内存以及修改相关信息if (toast_free[i])pfree(DatumGetPointer(old_value));toast_free[i] = true;need_change = true;need_free = true;}//来到这里时,我们已经对很多数据进行了toast的相关操作,所以需要通过新元组的信息以及TOAST处理后的属性值来构造一个结果元组。if (need_change){HeapTupleHeader olddata = newtup->t_data;//原来的文件头HeapTupleHeader new_data;//结果生成元组的文件头int32		new_header_len;//结果元组头的长度int32		new_data_len;//结果元组数据的长度int32		new_tuple_len;//结果元组的长度//计算结果元组的长度new_header_len = SizeofHeapTupleHeader;//首先是文件头的长度if (has_nulls)//如果元组中含有Null数据new_header_len += BITMAPLEN(numAttrs);new_header_len = MAXALIGN(new_header_len);new_data_len = heap_compute_data_size(tupleDesc,toast_values, toast_isnull);new_tuple_len = new_header_len + new_data_len;result_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + new_tuple_len);//分配内存空间//一系列的赋值result_tuple->t_len = new_tuple_len;result_tuple->t_self = newtup->t_self;result_tuple->t_tableOid = newtup->t_tableOid;new_data = (HeapTupleHeader) ((char *) result_tuple + HEAPTUPLESIZE);result_tuple->t_data = new_data;//复制已有的元组文件头,调整natts和t_hoffmemcpy(new_data, olddata, SizeofHeapTupleHeader);HeapTupleHeaderSetNatts(new_data, numAttrs);new_data->t_hoff = new_header_len;//复制数据并按需填充位图为null的部分heap_fill_tuple(tupleDesc,toast_values,toast_isnull,(char *) new_data + new_header_len,new_data_len,&(new_data->t_infomask),has_nulls ? new_data->t_bits : NULL);}else//如果没有进行线外存储result_tuple = newtup;//无需生成结果元组,直接返回新元组即可if (need_free)//如果需要释放空间for (i = 0; i < numAttrs; i++)//遍历所有属性if (toast_free[i])//如果该属性需要释放(临时数据)pfree(DatumGetPointer(toast_values[i]));//释放临时数据的空间if (need_delold)//如果需要删除旧元组的线外存储的数据for (i = 0; i < numAttrs; i++)//遍历所有属性if (toast_delold[i])//判断该属性是否需要删除toast_delete_datum(rel, toast_oldvalues[i], false);//调用toast_delete_datum删除线外存储的数据return result_tuple;
}

toast_delete函数(删除)

当包含TOAST数据的元组被删除时,其对应的TOAST数据也需要被删除,该函数就是负责这一操作并且回收toast存储空间的。

void
toast_delete(Relation rel, HeapTuple oldtup, bool is_speculative)
{TupleDesc	tupleDesc;//元组描述符int			numAttrs;//属性数量int			i;//for循环使用Datum		toast_values[MaxHeapAttributeNumber];//存储要删除的toast元组的属性bool		toast_isnull[MaxHeapAttributeNumber];//存储要删除的toast元组的属性是否为nullAssert(rel->rd_rel->relkind == RELKIND_RELATION ||rel->rd_rel->relkind == RELKIND_MATVIEW);//对于采取plain策略的关系或者物理视图,不会采用toast机制,因此无需进行toast删除tupleDesc = rel->rd_att;//获取元组描述符numAttrs = tupleDesc->natts;//获取元组的属性数量Assert(numAttrs <= MaxHeapAttributeNumber);//属性数量超过最大数量则报错heap_deform_tuple(oldtup, tupleDesc, toast_values, toast_isnull);//获取数据//关于是使用这个函数还是heap_getattr()函数获取数据是存在疑问的:当只有很少的变长属性时,heap_getattr的速度更快,反之,heap_deform_tuple的速度更快。for (i = 0; i < numAttrs; i++)//对所有属性进行检查{if (TupleDescAttr(tupleDesc, i)->attlen == -1)//如果该属性为变长属性{Datum		value = toast_values[i];//获取该属性的数据if (toast_isnull[i])//如果该属性为nullcontinue;//继续检查下一个属性else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value)))//如果该属性的数据线外存储在磁盘上toast_delete_datum(rel, value, is_speculative);//调用toast_delete_datum函数删除存储在磁盘上的TOAST数据}}
}

对用采用PLAIN和MAIN策略的数据来说,TOAST数据并没有线外存储,因此会包含在元组内一起被删除。
对于采用了线外存储的策略的数据来说,还需要删除线外存储的数据,需要级联删除,这是就会调用上面的函数进行删除。

heap_tuple_untoast_attr函数(获取)

struct varlena *
heap_tuple_untoast_attr(struct varlena *attr)
//入参为线外存储的数据类型指针(上面有具体分析),涵盖了具体的TOAST策略,因此可以用于根据不同的TOAST策略来取数据。
{if (VARATT_IS_EXTERNAL_ONDISK(attr))//如果该数据是线外存储在磁盘上的{attr = toast_fetch_datum(attr);//将数据从磁盘上取回来if (VARATT_IS_COMPRESSED(attr))//如果数据被压缩了{struct varlena *tmp = attr;//生成一个临时指针存放原数据attr = toast_decompress_datum(tmp);//将解压后的数据赋给原指针pfree(tmp);//释放临时指针}}else if (VARATT_IS_EXTERNAL_INDIRECT(attr))//如果该数据是线外存储在内存中的{struct varatt_indirect redirect;//内存线外存储指针(上面有分析)。VARATT_EXTERNAL_GET_POINTER(redirect, attr);//获取线外存储数据的指针attr = (struct varlena *) redirect.pointer;Assert(!VARATT_IS_EXTERNAL_INDIRECT(attr));//如果取回来的数据仍然是线外存储在内存上的,则为嵌套的间接存储,这是不允许的。attr = heap_tuple_untoast_attr(attr);//以递归的形式取数据,防止因数据以其他形式进行了扩展而没有成功获取。if (attr == (struct varlena *) redirect.pointer)//如果该数据并没有其他形式的扩展,我们可以直接复制它。{struct varlena *result;//用于复制的指针result = (struct varlena *) palloc(VARSIZE_ANY(attr));//为指针分配等量的内存空间memcpy(result, attr, VARSIZE_ANY(attr));//将attr的值复制到resultattr = result;//将attr指向复制后的内存区,返回attr}}else if (VARATT_IS_EXTERNAL_EXPANDED(attr))//如果是在线外扩展的(上面递归获取数据时可能会碰到这种情况){attr = heap_tuple_fetch_attr(attr);//从线外存储获取TOAST数据Assert(!VARATT_IS_EXTENDED(attr));//拿回的数据不允许是EXTENDED策略的。}else if (VARATT_IS_COMPRESSED(attr))//如果数据是没有线外存储但经过压缩的{attr = toast_decompress_datum(attr);//解压后返回数据}else if (VARATT_IS_SHORT(attr))//如果数据是短数据{Size		data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;//去除短文件头的长度,获取数据的长度。Size		new_size = data_size + VARHDRSZ;//添加正常的4字节的文件头长度,和数据长度拼接成正常长度。struct varlena *new_attr;//生成新属性的指针new_attr = (struct varlena *) palloc(new_size);//使用palloc函数为指针分配刚才获取的正常长度大小的内存空间SET_VARSIZE(new_attr, new_size);//为新的属性设置大小(给新文件头赋值)memcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);//将原本属性的数据复制到新属性的数据区attr = new_attr;//返回新生成的属性}return attr;
}

读取TOAST数据的流程大体如下(详细的分析都写在源码注释中):

  1. 如果数据是线外存储的,则先调用函数toast_fetch_datum从TOAST表中获取该数据的片段来重组数据。获取线外数据的原理非常简单:根据TOAST属性中存储的TOAST指针,从TOAST表中得到所有属于该TOAST数据的片段元组,然后根据元组中记录的片段顺序号将片段拼成TOAST数据。接下来对从线外取回的数据进行判断,如果是经过压缩的则用解压算法进行解压缩然后返回数据,否则直接返回数据。
  2. 如果数据没有线外存储但是经过压缩的,则解压缩然后返回数据。
  3. 如果数据在线外还有扩展,则调用函数heap_tuple_fetch_attr从TOAST表中获取该数据的片段来重组数据,然后返回。
  4. 如果数据是短数据,直接返回数据。

关于如何判断数据类型,在上面有详细的分析,就是借助vl_len的前两位来进行判断。

总结

关于操作调用的总结

TOAST操作
更新和插入TOAST元组
获取TOAST元组
删除TOAST元组
toast_insert_or_update函数
heap_tuple_untoast_attr函数
toast_delete函数
toast_fetch_datum函数
toast_delete_datum函数

收获

这次分析的源码非常多,花费了很多时间,终于算是理清了TOAST机制的相关结构体、文件、以及操作的大体思路和一些细节。

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

相关文章

  1. 软件测试面试常见题目

    软件的生命周期 需求分析——计划——设计——编码——测试机——运行维护 软件测试过程 单元测试——集成测试——确认测试——系统测试——验收测试 单元测试&#xff1a;分别完成每个单元的测试任务&#xff0c;此步骤大量采用白盒测试方法集成测试&#xff1a;把已经测…...

    2024/4/15 13:00:19
  2. 一张图搞懂原型链(简单版)

    function Star(){};Star.prototype {constructor:Star,//指回原来的构造函数SeyHi:function(){alert(hello)},//向 原型添加方法Move:function(){alert(world)}//向 原型添加方法}var liu new Star();//实例对象console.log(liu.__proto__); // 指向 构 造函数原型对象protot…...

    2024/4/15 18:09:40
  3. 新能源汽车销量一片长红,盛世昊通一众供应链平台迎来春天

    2021年以来&#xff0c;中国新能源汽车产销增长接连打破历史记录&#xff0c;增长速度远超预期&#xff0c;销量可谓一片长红。新能源市场销量暴涨&#xff0c;然而我们面对的形势并不乐观&#xff0c;如原材料紧缺问题导致的芯片持续短缺&#xff0c;芯片技术不足&#xff0c;…...

    2024/4/19 16:21:54
  4. 鼠标可改形状

    cursor: default; //形状为默认 cursor: pointer; //形状为手指 cursor: crosshair; //光标呈现为十字线。...

    2024/4/15 13:00:09
  5. 偶尔报错 for user ‘root‘ using method ‘mysql_native_password‘

    解决&#xff1a; 引起此错误的主要原因是在Mysql5.7及其以上版本中引入了SSL验证方式&#xff0c;如果不需要用到SSL验证&#xff0c;则在连接字符串时需要加入"SslModeNone"就可以了...

    2024/5/6 14:05:38
  6. vue的watch监听属性和computed计算属性

    1.computed计算属性 computed属性不用在data中定义且不能与data和methods中重名&#xff1b; computed会有缓存&#xff1b; computed不支持异步 如果computed属性属性值是函数&#xff0c;那么默认会走get方法&#xff1b;函数的返回值就是属性的属性值&#xff1b;在compute…...

    2024/4/6 12:57:12
  7. 31.你是怎么处理vue项目中的错误的?

    一、错误类型 任何一个框架&#xff0c;对于错误的处理都是一种必备的能力 在Vue 中&#xff0c;则是定义了一套对应的错误处理规则给到使用者&#xff0c;且在源代码级别&#xff0c;对部分必要的过程做了一定的错误处理。 主要的错误来源包括&#xff1a; 后端接口错误 代…...

    2024/4/15 13:00:04
  8. 端计算(4)-kotlin(2)

    package com.example.s1fun main(){val a: Int 1 // 立即赋值val b 2 // 自动推断出 Int 类型val c: Int // 如果没有初始值类型不能省略c 3 // 明确赋值println("$a,$b,$c") }1,2,3...

    2024/4/15 12:59:49
  9. Jquery MD5 加密算法

    Jquery的MD5加密算法记录&#xff1a; /*** jQuery MD5 hash algorithm function* * <code>* Calculate the md5 hash of a String * String $.md5 ( String str )* </code>* * Calculates the MD5 hash of str using the RSA Data Security, Inc. MD5 Mes…...

    2024/4/17 15:36:55
  10. 21天养成好习惯第十天

    今天写题...

    2024/4/18 3:27:02
  11. 牛客模拟练习

    题目地址 注&#xff1a;有的题(第三个)并没有ac,先放个错误答案。以及好多细节处理的并不好 F #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <map>using namespace std;typedef pair<int,int…...

    2024/4/15 13:00:39
  12. Java——JVM篇,分层架构模式的优缺点

    2 运行过程&#xff1a; 我们都知道 Java 源文件&#xff0c;通过编译器&#xff0c;能够生产相应的.Class 文件&#xff0c;也就是字节码文件&#xff0c; 而字节码文件又通过 Java 虚拟机中的解释器&#xff0c;编译成特定机器上的机器码 。 也就是如下&#xff1a; ① Java …...

    2024/4/15 13:00:49
  13. 【C语言】打印二叉树树形(制表符实现,清晰+高拓展)

    —— 目录 ——0. 前言1. 效果展示2. 核心代码及解读3. 辅助代码0. 前言 上 CSDN 找了很多份树形代码&#xff0c;一个共通的感觉就是拓展性不够强 比如说树大了节点之间被撑开无法兼容性不好 没有使用树枝连接的画显示稍显混乱&#xff0c;使用斜线当树枝又不好拓展 还有一部…...

    2024/5/7 17:09:31
  14. Scratch青少年编程能力等级测试模拟题(四级)

    「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 我们将有关编程题目的教学视频已经发布到抖音号21252972100&#xff0c;小马老…...

    2024/4/15 13:01:04
  15. Python冒泡排序

    冒泡排序Bubble Sort 交换排序 相邻元素两两比较大小&#xff0c;有必要则交换。 元素越小或越大&#xff0c;就会在数列中慢慢的交换并“浮”向顶端&#xff0c;如同水泡咕嘟咕嘟往上冒。 核心算法 排序算法&#xff0c;一般都实现为就地排序&#xff0c;输出为升序 扩大有序…...

    2024/4/19 0:00:51
  16. 手写简单实现promise

    promise的回调执行关注以下两点 链式调用注意点&#xff1a;需要判断上一个then的回调的执行结果&#xff0c;也就是它返回的新的promise的状态。promise对象的then执行&#xff0c;其实&#xff0c;就是先执行then方法把回调函数存储起来&#xff0c;然后&#xff0c;执行器函…...

    2024/4/19 19:52:08
  17. JavaWeb快速入门--JQuery(1),jdk8下载教程

    attr和prop区别&#xff1f; 如果操作的是元素的固有属性&#xff0c;则建议使用prop 如果操作的是元素自定义的属性&#xff0c;则建议使用attr 对class属性操作 addClass():添加class属性值 removeClass():删除class属性值 toggleClass():切换class属性 toggleClass(“…...

    2024/4/20 7:09:04
  18. docker可视化图形工具portainer

    一&#xff0c;Portainer 介绍 Portainer 是一个可视化容器镜像的图形管理工具&#xff0c;利用 Portainer 可以轻松构建&#xff0c;管理和维护 Docker 环境。而且完全免费&#xff0c;基于容器化的安装方式&#xff0c;方便高效部署。 二&#xff0c;Portainer 架构概述 P…...

    2024/4/15 13:01:04
  19. Keithley 提供的驱动器可用于实现 Lake Shore 探针台温度控制的自动化

    我们之前已经讨论过如何将 Lake Shore 探针台的温度控制器与 Keithley 半导体表征系统结合使用&#xff0c;以实现温度测量的自动化。现在这是一个更简单的集成过程。这是因为吉时利在其最新版本的4200 型分析仪软件版本 (KTE Interactive V9.1) 中为我们的 336 型控制器提供了…...

    2024/4/15 13:01:09
  20. linux学习笔记——gcc用法

    -E: 只执行预处理命令 gcc -E hello.c > pianoapan.txt gcc -E hello.c | more gcc -E main.c > main.i #编译器将main.c预处理结果输出 main.i 文件。这个不生成文件, 你需要把它重定向到一个输出文件里面。 -S&#xff1a;只执行预处理和编译&#xff0c;就是指把文…...

    2024/4/5 7:03:36

最新文章

  1. C语言每日一练(11、判断素数)

    探索 C 语言中的素数判断 在 C 语言的编程世界里&#xff0c;判断一个数是否为素数是一个常见而有趣的任务。 素数&#xff0c;那些只能被 1 和自身整除的正整数&#xff0c;有着独特的魅力。让我们一起来看看如何用 C 语言来实现素数的判断吧。 首先&#xff0c;我们需要一…...

    2024/5/7 22:22:34
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/7 10:36:02
  3. 瑞芯微RK3568调试Android 11的各种方法

    调试瑞芯微RK3568运行Android 11的设备时&#xff0c;你可以采用多种方法&#xff0c;每种都适合不同的调试场景和需求&#xff1a; 1. 接调试串口&#xff08;UART&#xff09; 使用方法&#xff1a; 查找RK3568开发板上的串口引脚。使用USB转TTL串行电缆连接RK3568设备和你…...

    2024/5/2 6:17:51
  4. 用html实现在页面底部养鱼的效果

    <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>在网页底部养鱼</title><link rel"stylesheet" href"./style.css"> </head> <body> <div id"fi…...

    2024/5/7 1:08:12
  5. npm常用命令技巧

    NPM (Node Package Manager) 是 JavaScript 的包管理工具&#xff0c;广泛用于管理项目中的依赖。无论是前端项目还是Node.js后端项目&#xff0c;NPM 都扮演着重要的角色。本文将介绍 NPM 中常用的几个命令&#xff0c;并提供相应的代码示例。 1. 初始化项目&#xff1a;npm …...

    2024/5/7 10:07:20
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/7 5:50:09
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/7 9:45:25
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/7 14:25:14
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/6 21:42:42
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57