分析PostgreSQL SetupLockInTable方法中与OOM相关的代码
这篇文章主要介绍“分析PostgreSQL SetupLockInTable方法中与OOM相关的代码”,在日常操作中,相信很多人在分析PostgreSQL SetupLockInTable方法中与OOM相关的代码问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”分析PostgreSQL SetupLockInTable方法中与OOM相关的代码”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
有时候我们可能会在PG的日志发现如下信息:
2020-01-09 16:29:19.062 CST,"pg12","testdb",6193,"[local]",5e16dccd.1831,1,"CREATE TABLE",2020-01-09 15:57:01 CST,2/34,1512004206,ERROR,53200,"out of shared memory",,"You might need to increase max_locks_per_transaction.",,,,"CREATE TABLE a13030 (id int);",,,"psql"
2020-01-09 16:29:19.379 CST,"pg12","testdb",6193,"[local]",5e16dccd.1831,2,"CREATE TABLE",2020-01-09 15:57:01 CST,2/0,1512004206,ERROR,25P02,"current transaction is aborted, commands ignored until end of transaction block",,,,,,"CREATE TABLE a13031 (id int);",,,"psql"
直观上来看,OOM似乎与max_locks_per_transaction扯不上什么关系,为什么PG会提示增加max_locks_per_transaction的值呢?
一、源码解读
测试脚本
\pset footer off
\pset tuples_only
\o /tmp/drop.sql
SELECT 'drop table if exists tbl' || id || ' ;' as "--"
FROM generate_series(1, 20000) AS id;
\i /tmp/drop.sql
\pset footer off
\pset tuples_only
\o /tmp/create.sql
SELECT 'CREATE TABLE tbl' || id || ' (id int);' as "--"
FROM generate_series(1, 20000) AS id;
\o /tmp/ret.txt
begin;
\i /tmp/create.sql
数据结构
HTAB
struct HTAB
{
//指向共享的控制信息
HASHHDR *hctl;
//段开始目录
HASHSEGMENT *dir;
//哈希函数
HashValueFunc hash;
//哈希键比较函数
HashCompareFunc match;
//哈希键拷贝函数
HashCopyFunc keycopy;
//内存分配器
HashAllocFunc alloc;
//内存上下文
MemoryContext hcxt;
//表名(用于错误信息)
char *tabname;
//如在共享内存中,则为T
bool isshared;
//如为T,则固定大小不能扩展
bool isfixed;
//不允许冻结共享表,因此这里会保存相关状态
bool frozen;
//保存这些固定值的本地拷贝,以减少冲突
//哈希键长度(以字节为单位)
Size keysize;
//段大小,必须为2的幂
long ssize;
//段偏移,ssize的对数
int sshift;
};
struct HASHHDR
{
//#define NUM_FREELISTS 32
FreeListData freeList[NUM_FREELISTS];
//这些域字段可以改变,但不适用于分区表
//同时,就算是非分区表,共享表的dsize也不能改变
//目录大小
long dsize;
//已分配的段大小(<= dbsize)
long nsegs;
//正在使用的最大桶ID
uint32 max_bucket;
//进入整个哈希表的模掩码
uint32 high_mask;
//进入低于半个哈希表的模掩码
uint32 low_mask;
//下面这些字段在哈希表创建时已固定
//哈希键大小(以字节为单位)
Size keysize;
//所有用户元素大小(以字节为单位)
Size entrysize;
//分区个数(2的幂),或者为0
long num_partitions;
//目标的填充因子
long ffactor;
//如目录是固定大小,则该值为dsize的上限值
long max_dsize;
//段大小,必须是2的幂
long ssize;
//端偏移,ssize的对数
int sshift;
//一次性分配的条目个数
int nelem_alloc;
#ifdef HASH_STATISTICS
long accesses;
long collisions;
#endif
};
typedef struct
{
//该空闲链表的自旋锁
slock_t mutex;
//相关桶中的条目个数
long nentries;
//空闲元素链
HASHELEMENT *freeList;
} FreeListData;
typedef struct HASHELEMENT
{
//链接到相同桶中的下一个条目
struct HASHELEMENT *link;
//该条目的哈希函数结果
uint32 hashvalue;
} HASHELEMENT;
//哈希表头部结构,非透明类型,用于dynahash.c
typedef struct HASHHDR HASHHDR;
//哈希表控制结构,非透明类型,用于dynahash.c
typedef struct HTAB HTAB;
//hash_create使用的参数数据结构
//根据hash_flags标记设置相应的字段
typedef struct HASHCTL
{
//分区个数(必须是2的幂)
long num_partitions;
//段大小
long ssize;
//初始化目录大小
long dsize;
//dsize上限
long max_dsize;
//填充因子
long ffactor;
//哈希键大小(字节为单位)
Size keysize;
//参见上述数据结构注释
Size entrysize;
//
HashValueFunc hash;
HashCompareFunc match;
HashCopyFunc keycopy;
HashAllocFunc alloc;
MemoryContext hcxt;
//共享内存中的哈希头部结构地址
HASHHDR *hctl;
} HASHCTL;
//哈希桶是HASHELEMENTs链表
typedef HASHELEMENT *HASHBUCKET;
//hash segment是桶数组
typedef HASHBUCKET *HASHSEGMENT;
typedef uint32 (*HashValueFunc) (const void *key, Size keysize);
typedef int (*HashCompareFunc) (const void *key1, const void *key2,
Size keysize);
typedef void *(*HashCopyFunc) (void *dest, const void *class="lazy" data-src, Size keysize);
typedef void *(*HashAllocFunc) (Size request);
get_hash_entry
分配一个新的哈希表条目.如内存溢出则返回NULL.
static HASHBUCKET
get_hash_entry(HTAB *hashp, int freelist_idx)
{
HASHHDR *hctl = hashp->hctl;
HASHBUCKET newElement;
for (;;)
{
//循环
//如为分区哈希表,在访问条目和空闲链表时,必须锁定
if (IS_PARTITIONED(hctl))
SpinLockAcquire(&hctl->freeList[freelist_idx].mutex);
//从空闲链表中尝试获取一个条目
newElement = hctl->freeList[freelist_idx].freeList;
if (newElement != NULL)
break;
if (IS_PARTITIONED(hctl))
SpinLockRelease(&hctl->freeList[freelist_idx].mutex);
if (!element_alloc(hashp, hctl->nelem_alloc, freelist_idx))
{
//本空闲链表不能分配内存
int borrow_from_idx;
if (!IS_PARTITIONED(hctl))
//非分区哈希表,返回NULL,意味着内存溢出了.
return NULL;
//尝试从其他空闲链表浏览元素
borrow_from_idx = freelist_idx;
for (;;)
{
//------- 开始遍历其他空闲链表
borrow_from_idx = (borrow_from_idx + 1) % NUM_FREELISTS;
if (borrow_from_idx == freelist_idx)
//已经完成整个空闲链表的遍历,退出
break;
//获取自旋锁
SpinLockAcquire(&(hctl->freeList[borrow_from_idx].mutex));
newElement = hctl->freeList[borrow_from_idx].freeList;
if (newElement != NULL)
{
hctl->freeList[borrow_from_idx].freeList = newElement->link;
SpinLockRelease(&(hctl->freeList[borrow_from_idx].mutex));
//小心:在合适的空闲链表上统计新的元素
SpinLockAcquire(&hctl->freeList[freelist_idx].mutex);
hctl->freeList[freelist_idx].nentries++;
SpinLockRelease(&hctl->freeList[freelist_idx].mutex);
return newElement;
}
SpinLockRelease(&(hctl->freeList[borrow_from_idx].mutex));
}
//已无可用空间,内存溢出
return NULL;
}
}
//从空闲链表中移除条目,nentries+1
hctl->freeList[freelist_idx].freeList = newElement->link;
hctl->freeList[freelist_idx].nentries++;
if (IS_PARTITIONED(hctl))
SpinLockRelease(&hctl->freeList[freelist_idx].mutex);
return newElement;
}
二、跟踪分析
跟踪SetupLockInTable,进入hash_search_with_hash_value
(gdb) b SetupLockInTable if lockmode == 8
Breakpoint 2 at 0x8ccf4e: file lock.c, line 1131.
(gdb) c
Continuing.
Breakpoint 2, SetupLockInTable (lockMethodTable=0xc8dba0 <default_lockmethod>, proc=0x7f293e800b70,
locktag=0x7fff167a9250, hashcode=1823181291, lockmode=8) at lock.c:1131
1131 lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash,
(gdb) step
hash_search_with_hash_value (hashp=0x1210160, keyPtr=0x7fff167a9250, hashvalue=1823181291,
action=HASH_ENTER_NULL, foundPtr=0x7fff167a90bf) at dynahash.c:925
925 HASHHDR *hctl = hashp->hctl;
查看HTAB(hashp)和HASHHDR(hctl)
(gdb) n
926 int freelist_idx = FREELIST_IDX(hctl, hashvalue);
(gdb) p *hashp
$8 = {hctl = 0x7f293e443980, dir = 0x7f293e443cd8, hash = 0xa79ac6 <tag_hash>, match = 0x47cb70 <memcmp@plt>,
keycopy = 0x47d0a0 <memcpy@plt>, alloc = 0x8c3419 <ShmemAllocNoError>, hcxt = 0x0,
tabname = 0x12101c0 "LOCK hash", isshared = true, isfixed = false, frozen = false, keysize = 16, ssize = 256,
sshift = 8}
(gdb) p *hctl
$9 = {freeList = {{mutex = 0 '\000', nentries = 389, freeList = 0x0}, {mutex = 0 '\000', nentries = 392,
freeList = 0x0}, {mutex = 0 '\000', nentries = 400, freeList = 0x0}, {mutex = 0 '\000', nentries = 382,
freeList = 0x7f293ebd1b00}, {mutex = 0 '\000', nentries = 439, freeList = 0x7f293ebc89a0}, {
mutex = 0 '\000', nentries = 391, freeList = 0x7f293ebc1b20}, {mutex = 0 '\000', nentries = 411,
freeList = 0x7f293eb91680}, {mutex = 0 '\000', nentries = 395, freeList = 0x0}, {mutex = 0 '\000',
nentries = 402, freeList = 0x0}, {mutex = 0 '\000', nentries = 416, freeList = 0x7f293ebcb338}, {
mutex = 0 '\000', nentries = 431, freeList = 0x7f293ebc3170}, {mutex = 0 '\000', nentries = 421,
freeList = 0x0}, {mutex = 0 '\000', nentries = 406, freeList = 0x7f293eb995a8}, {mutex = 0 '\000',
nentries = 409, freeList = 0x7f293ebd2238}, {mutex = 0 '\000', nentries = 411, freeList = 0x7f293ebd1f98},
{mutex = 0 '\000', nentries = 386, freeList = 0x0}, {mutex = 0 '\000', nentries = 412, freeList = 0x0}, {
mutex = 0 '\000', nentries = 424, freeList = 0x0}, {mutex = 0 '\000', nentries = 394,
freeList = 0x7f293ebd1da0}, {mutex = 0 '\000', nentries = 401, freeList = 0x0}, {mutex = 0 '\000',
nentries = 408, freeList = 0x7f293eb99500}, {mutex = 0 '\000', nentries = 437, freeList = 0x0}, {
mutex = 0 '\000', nentries = 429, freeList = 0x7f293ebd2778}, {mutex = 0 '\000', nentries = 386,
freeList = 0x0}, {mutex = 0 '\000', nentries = 410, freeList = 0x7f293ebd2d60}, {mutex = 0 '\000',
nentries = 416, freeList = 0x0}, {mutex = 0 '\000', nentries = 412, freeList = 0x7f293ebd1c50}, {
mutex = 0 '\000', nentries = 436, freeList = 0x7f293ebd1ba8}, {mutex = 0 '\000', nentries = 380,
freeList = 0x7f293ebd1cf8}, {mutex = 0 '\000', nentries = 428, freeList = 0x0}, {mutex = 0 '\000',
nentries = 405, freeList = 0x0}, {mutex = 0 '\000', nentries = 372, freeList = 0x0}}, dsize = 256,
nsegs = 16, max_bucket = 4095, high_mask = 8191, low_mask = 4095, keysize = 16, entrysize = 152,
num_partitions = 16, ffactor = 1, max_dsize = 256, ssize = 256, sshift = 8, nelem_alloc = 48}
可以看到,hctl中的freelist数组,数组中每个元素的freeList,如不为0x0(NULL),则说明还有空闲空间,否则说明已无空间.
(gdb) n
949 if (action == HASH_ENTER || action == HASH_ENTER_NULL)
(gdb) p freelist_idx
$10 = 11
(gdb) p hctl->freeList[11]
$11 = {mutex = 0 '\000', nentries = 421, freeList = 0x0}
(gdb) n
956 if (!IS_PARTITIONED(hctl) && !hashp->frozen &&
(gdb)
965 bucket = calc_bucket(hctl, hashvalue); --> hash桶
(gdb)
967 segment_num = bucket >> hashp->sshift; --> 根据hash桶获取段
(gdb)
968 segment_ndx = MOD(bucket, hashp->ssize);--> 根据hash桶获取段内偏移
(gdb)
970 segp = hashp->dir[segment_num];--> 获取hash段指针
(gdb)
972 if (segp == NULL)
(gdb) p bucket
$12 = 2539
(gdb) p segment_num
$13 = 9
(gdb) p segment_ndx
$14 = 235
(gdb) p *segp
$15 = (HASHBUCKET) 0x7f293e44de98
(gdb) p **segp
$16 = {link = 0x0, hashvalue = 3817199872}
(gdb) n
975 prevBucketPtr = &segp[segment_ndx]; --> HASHBUCKET指针的指针
(gdb)
976 currBucket = *prevBucketPtr; --> HASHBUCKET指针
(gdb)
981 match = hashp->match;
(gdb)
982 keysize = hashp->keysize;
(gdb) p match
$17 = (HashCompareFunc) 0x47cb70 <memcmp@plt> --> hash函数
(gdb) n
984 while (currBucket != NULL) --> 沿着碰撞链循环获取hash桶
(gdb)
986 if (currBucket->hashvalue == hashvalue && --> 退出条件:找到匹配的元素
(gdb)
989 prevBucketPtr = &(currBucket->link);
(gdb)
990 currBucket = *prevBucketPtr;
(gdb)
984 while (currBucket != NULL) --> 退出条件:hash桶指针为NULL,没有找到元素
(gdb)
997 if (foundPtr)
(gdb) p foundPtr
$18 = (_Bool *) 0x7fff167a90bf
(gdb) n
998 *foundPtr = (bool) (currBucket != NULL);
(gdb)
1003 switch (action)
(gdb)
1042 Assert(hashp->alloc != DynaHashAlloc);
(gdb)
1047 if (currBucket != NULL)
(gdb)
1051 if (hashp->frozen)
(gdb)
1055 currBucket = get_hash_entry(hashp, freelist_idx);
(gdb) step
get_hash_entry (hashp=0x1210160, freelist_idx=11) at dynahash.c:1252
1252 HASHHDR *hctl = hashp->hctl;
(gdb) n
注 : #define IS_PARTITIONED(hctl) ((hctl)->num_partitions != 0)
1258 if (IS_PARTITIONED(hctl))
(gdb) p *hctl
$19 = {freeList = {{mutex = 0 '\000', nentries = 389, freeList = 0x0}, {mutex = 0 '\000', nentries = 392,
freeList = 0x0}, {mutex = 0 '\000', nentries = 400, freeList = 0x0}, {mutex = 0 '\000', nentries = 382,
freeList = 0x7f293ebd1b00}, {mutex = 0 '\000', nentries = 439, freeList = 0x7f293ebc89a0}, {
mutex = 0 '\000', nentries = 391, freeList = 0x7f293ebc1b20}, {mutex = 0 '\000', nentries = 411,
freeList = 0x7f293eb91680}, {mutex = 0 '\000', nentries = 395, freeList = 0x0}, {mutex = 0 '\000',
nentries = 402, freeList = 0x0}, {mutex = 0 '\000', nentries = 416, freeList = 0x7f293ebcb338}, {
mutex = 0 '\000', nentries = 431, freeList = 0x7f293ebc3170}, {mutex = 0 '\000', nentries = 421,
freeList = 0x0}, {mutex = 0 '\000', nentries = 406, freeList = 0x7f293eb995a8}, {mutex = 0 '\000',
nentries = 409, freeList = 0x7f293ebd2238}, {mutex = 0 '\000', nentries = 411, freeList = 0x7f293ebd1f98},
{mutex = 0 '\000', nentries = 386, freeList = 0x0}, {mutex = 0 '\000', nentries = 412, freeList = 0x0}, {
mutex = 0 '\000', nentries = 424, freeList = 0x0}, {mutex = 0 '\000', nentries = 394,
freeList = 0x7f293ebd1da0}, {mutex = 0 '\000', nentries = 401, freeList = 0x0}, {mutex = 0 '\000',
nentries = 408, freeList = 0x7f293eb99500}, {mutex = 0 '\000', nentries = 437, freeList = 0x0}, {
mutex = 0 '\000', nentries = 429, freeList = 0x7f293ebd2778}, {mutex = 0 '\000', nentries = 386,
freeList = 0x0}, {mutex = 0 '\000', nentries = 410, freeList = 0x7f293ebd2d60}, {mutex = 0 '\000',
nentries = 416, freeList = 0x0}, {mutex = 0 '\000', nentries = 412, freeList = 0x7f293ebd1c50}, {
mutex = 0 '\000', nentries = 436, freeList = 0x7f293ebd1ba8}, {mutex = 0 '\000', nentries = 380,
freeList = 0x7f293ebd1cf8}, {mutex = 0 '\000', nentries = 428, freeList = 0x0}, {mutex = 0 '\000',
nentries = 405, freeList = 0x0}, {mutex = 0 '\000', nentries = 372, freeList = 0x0}}, dsize = 256,
nsegs = 16, max_bucket = 4095, high_mask = 8191, low_mask = 4095, keysize = 16, entrysize = 152,
num_partitions = 16, ffactor = 1, max_dsize = 256, ssize = 256, sshift = 8, nelem_alloc = 48}
(gdb) n
1259 SpinLockAcquire(&hctl->freeList[freelist_idx].mutex);
(gdb)
1262 newElement = hctl->freeList[freelist_idx].freeList; --> 尝试从空闲链表中获取新元素
(gdb)
1264 if (newElement != NULL) --> 不为NULL,则返回,否则,尝试扩展
(gdb) p newElement
$20 = (HASHBUCKET) 0x0
(gdb) n
1267 if (IS_PARTITIONED(hctl))
(gdb)
1268 SpinLockRelease(&hctl->freeList[freelist_idx].mutex);
(gdb)
1282 if (!element_alloc(hashp, hctl->nelem_alloc, freelist_idx)) --> 扩展
(gdb) step
element_alloc (hashp=0x1210160, nelem=48, freelist_idx=11) at dynahash.c:1659 --> 进入element_alloc
1659 HASHHDR *hctl = hashp->hctl;
(gdb) n
1666 if (hashp->isfixed)
(gdb)
1670 elementSize = MAXALIGN(sizeof(HASHELEMENT)) + MAXALIGN(hctl->entrysize); --> 确保地址对齐
(gdb)
1672 CurrentDynaHashCxt = hashp->hcxt;
(gdb) p elementSize
$21 = 168
(gdb) n
1673 firstElement = (HASHELEMENT *) hashp->alloc(nelem * elementSize); --> 分配空间
(gdb)
1675 if (!firstElement)
(gdb) p firstElement
$22 = (HASHELEMENT *) 0x0
(gdb) p nelem * elementSize
$23 = 8064
(gdb) n
1676 return false;--> 扩展失败,返回false
(gdb)
1700 }
(gdb)
get_hash_entry (hashp=0x1210160, freelist_idx=11) at dynahash.c:1286
1286 if (!IS_PARTITIONED(hctl))
(gdb)
1290 borrow_from_idx = freelist_idx; --> 该空闲链表无法扩展,寻找下一个空闲链表
(gdb)
1293 borrow_from_idx = (borrow_from_idx + 1) % NUM_FREELISTS; --> 简单的+1后取模
(gdb) p freelist_idx
$24 = 11
(gdb) n
1294 if (borrow_from_idx == freelist_idx) --> 找了一圈,回到原点,内存不足,这时候会报错
(gdb) p NUM_FREELISTS
$25 = 32
(gdb) p borrow_from_idx
$26 = 12
(gdb) n
1297 SpinLockAcquire(&(hctl->freeList[borrow_from_idx].mutex));
(gdb)
1298 newElement = hctl->freeList[borrow_from_idx].freeList;
(gdb)
1300 if (newElement != NULL)
(gdb)
1302 hctl->freeList[borrow_from_idx].freeList = newElement->link;
(gdb)
1303 SpinLockRelease(&(hctl->freeList[borrow_from_idx].mutex));
(gdb)
1306 SpinLockAcquire(&hctl->freeList[freelist_idx].mutex);
(gdb)
1307 hctl->freeList[freelist_idx].nentries++;
(gdb)
1308 SpinLockRelease(&hctl->freeList[freelist_idx].mutex);
(gdb) p newElement
$27 = (HASHBUCKET) 0x7f293eb995a8
(gdb) n
1310 return newElement; --> 找到了空闲元素,返回
(gdb)
1329 }
(gdb)
hash_search_with_hash_value (hashp=0x1210160, keyPtr=0x7fff167a9250, hashvalue=1823181291,
action=HASH_ENTER_NULL, foundPtr=0x7fff167a90bf) at dynahash.c:1056
1056 if (currBucket == NULL)
(gdb)
1073 *prevBucketPtr = currBucket;
(gdb)
1074 currBucket->link = NULL;
(gdb)
1077 currBucket->hashvalue = hashvalue;
(gdb)
1078 hashp->keycopy(ELEMENTKEY(currBucket), keyPtr, keysize);
(gdb)
1087 return (void *) ELEMENTKEY(currBucket);
(gdb)
1093 }
(gdb)
SetupLockInTable (lockMethodTable=0xc8dba0 <default_lockmethod>, proc=0x7f293e800b70, locktag=0x7fff167a9250,
hashcode=1823181291, lockmode=8) at lock.c:1136
1136 if (!lock)
(gdb)
1142 if (!found)
(gdb)
1144 lock->grantMask = 0;
(gdb)
1145 lock->waitMask = 0;
(gdb)
1146 SHMQueueInit(&(lock->procLocks));
(gdb)
1147 ProcQueueInit(&(lock->waitProcs));
(gdb)
1148 lock->nRequested = 0;
(gdb)
1149 lock->nGranted = 0;
(gdb)
1150 MemSet(lock->requested, 0, sizeof(int) * MAX_LOCKMODES);
(gdb)
1151 MemSet(lock->granted, 0, sizeof(int) * MAX_LOCKMODES);
(gdb)
1165 proclocktag.myLock = lock;
(gdb)
1166 proclocktag.myProc = proc;
(gdb)
1168 proclock_hashcode = ProcLockHashCode(&proclocktag, hashcode);
(gdb)
1173 proclock = (PROCLOCK *) hash_search_with_hash_value(LockMethodProcLockHash,
(gdb)
1178 if (!proclock)
(gdb) p proclock
$28 = (PROCLOCK *) 0x7f293ebc6ec0
(gdb) n
1203 if (!found)
(gdb)
1205 uint32 partition = LockHashPartition(hashcode);
(gdb)
1217 proclock->groupLeader = proc->lockGroupLeader != NULL ?
(gdb)
1218 proc->lockGroupLeader : proc;
(gdb)
1217 proclock->groupLeader = proc->lockGroupLeader != NULL ?
(gdb)
1219 proclock->holdMask = 0;
(gdb)
1220 proclock->releaseMask = 0;
(gdb)
1222 SHMQueueInsertBefore(&lock->procLocks, &proclock->lockLink);
(gdb)
1223 SHMQueueInsertBefore(&(proc->myProcLocks[partition]),
(gdb)
1275 lock->nRequested++;
(gdb)
1276 lock->requested[lockmode]++;
(gdb)
1277 Assert((lock->nRequested > 0) && (lock->requested[lockmode] > 0));
(gdb)
1283 if (proclock->holdMask & LOCKBIT_ON(lockmode))
(gdb)
1289 return proclock;
(gdb)
1290 }
(gdb)
LockAcquireExtended (locktag=0x7fff167a9250, lockmode=8, sessionLock=false, dontWait=false,
reportMemoryError=true, locallockp=0x7fff167a9248) at lock.c:956
956 if (!proclock)
(gdb) c
Continuing.
到此,关于“分析PostgreSQL SetupLockInTable方法中与OOM相关的代码”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341