新计划每天做一两道查漏补缺~ 以下题目来自:
PostgreSQL面试题集锦
MVCC:
多版本并发控制,核心作用:使得读写操作不相互阻塞,提升并发性能。
实现原理:通常有2种实现方法:
以pg为例:
与Oracle的差异:由于实现原理不同,两者优缺点也基本相反
pg优点
pg缺点
pg事务篇(一)—— 事务与多版本并发控制MVCC_Hehuyi_In的博客-CSDN博客_pg 事务
为什么会有表膨胀
从原理来说:
pg旧数据存储在数据文件中,并不立刻清理,只是标记为无效。这些旧数据如果不能及时清理,业务表和数据文件会越来越大,引发表膨胀。
从具体场景来说:哪些情况会导致旧数据不能及时清理
表膨胀的危害
对应前面具体场景,如何避免表膨胀
如何处理表膨胀
参考:揭开表膨胀的神秘面纱
长事务的危害
小事务但长期不提交:
大事务:除上面外可能还有
溯源
什么样的事务才会是有危害的长事务?
pg_stat_activity视图中 backend_xid或backend_xmin字段非空的事务。单纯begin tran; 不提交并不会有问题,因为它并没有真正申请事务id和获取快照。
backend_xid:已申请的事务号(virtualxid不算),从申请事务号开始持续到事务结束。
backend_xmin:进程快照xmin,表示在快照创建时最旧的未提交事务id(实际上就是Transaction Horizon)
因此监控语句需要加上这两个条件
select count(*) from pg_stat_activity where state <> 'idle'
and (backend_xid is not null or backend_xmin is not null)
and now()-xact_start > interval '3600 sec'::interval;
参考:
生产案例 | 费解的索引失效
https://foucus.blog.csdn.net/article/details/123230865
如何产生子事务
子事务的危害
子事务溢出分为以下两种情况:
① 每个会话最多可容纳 64个(源码为PGPROC_MAX_CACHED_SUBXIDS参数)未中止的子事务。如果超过,则快照被标记为suboverflowed,这种快照无法包括可见性判断需要的所有数据,因此pg有时不得不读取pg_subtrans目录文件,会造成性能急剧下降。
② 主库上的子事务(不要求超过64,高并发时即使1个也可能)和长事务的组合可能会造成备库性能急剧下降甚至不可用,这个问题只发生在备库上。从根本上说,问题的发生是因为副本在创建快照和检查元组可见性时的行为与主副本不同。问题2的主要特征:
- A. 在主库:使用到了子事务(不要求超过64),对一些记录进行更新;有一个正在运行的长事务;这里的"长"取决于系统中XID的增长:比如说,如果你有 1k TPS 递增的 XID,那么问题可能会在几十秒后出现,所以"长" = "几十秒长",如果 你有 10k TPS,"长"可能意味着"几秒钟长"。所以在某些系统中,一个常规的慢查询也可能变成这样一个令人头疼的事务
- B. 在备库:查询的元组被主库的子事务修改了
SELECT [some row] FOR UPDATE;
SAVEPOINT save;
UPDATE [the same row];
wait_event/wait_event_type组合上:
- LWLock:MultiXactMemberControlLock
- LWLock:MultiXactOffsetControlLock
- LWLock:multixact_member
- LwLock:multixact_offset
注意事项
参考
子事务滥用的危害
长事务与子事务
子事务的危害
子事务及其性能
另外补充非表结构变更,但是非online的操作:
简单的验证方法是看操作后pg_class中的relfilenode值是否改变。
alter table的部分online操作是指不用rewrite table,而不是不需要获取8级锁。如果表正在执行一个大查询,对它执行新增字段也会被阻塞,同时阻塞后面该表所有语句。
alter table 这部分源码在 tablecmds.c,简单看了下:
ATController -> ATRewriteTables -> ATRewriteTable
AlterTableGetLockLevel 中有各种操作使用的锁级别
switch (cmd->subtype)
{
/*
* These subcommands rewrite the heap, so require full locks.
*/
case AT_AddColumn: /* may rewrite heap, in some cases and visible
* to SELECT */
case AT_SetTableSpace: /* must rewrite heap */
case AT_AlterColumnType: /* must rewrite heap */
cmd_lockmode = AccessExclusiveLock;
case AT_SetLogged: /* SET LOGGED */
...
/* force rewrite if necessary; see comment in ATRewriteTables */
if (tab->chgPersistence)
{
tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
}
pass = AT_PASS_MISC;
break;
case AT_SetUnLogged: /* SET UNLOGGED */
...
/* force rewrite if necessary; see comment in ATRewriteTables */
if (tab->chgPersistence)
{
tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
}
pass = AT_PASS_MISC;
break;
参考:
PostgreSQL: Documentation: 14: ALTER TABLE
That Guy From Delhi: What Postgres SQL causes a Table Rewrite?
备份完成后务必记得执行pg_stop_backup关闭备份状态。
pg_start_backup函数会启动force full page write,备份期间对页的修改会将整页写入WAL日志,导致WAL量暴增。如果忘记关闭会导致磁盘空间快速增加、dml性能下降、从库要应用的日志过多可能出现主从延迟,另外可能影响服务器IO性能。
推荐使用pg_basebackup或者pg_rman等集成工具,自动执行start和stop
备份前会启动一个事务,9.1版本开始默认隔离级别为REPEATABLE READ(之前为SERIALIZABLE),这样可以在整个备份期间使用事务开启时的快照,导出的所有表读取的都是该时间点的数据。如果加 --serializable-deferrable 参数,则使用的是可串行化隔离级别。
另外逻辑备份会对表加1级锁,避免备份过程中表结构被改变或者表被drop、truncate等。
以下代码在pg_dump.c 的 setup_connection 函数中
/*
* Start transaction-snapshot mode transaction to dump consistent data.
*/
ExecuteSqlStatement(AH, "BEGIN");
// pg 9.1版本及以上,默认为REPEATABLE READ隔离级别;9.1以下默认为SERIALIZABLE隔离级别
if (AH->remoteVersion >= 90100)
{
if (dopt->serializable_deferrable && AH->sync_snapshot_id == NULL)
ExecuteSqlStatement(AH,
"SET TRANSACTION ISOLATION LEVEL "
"SERIALIZABLE, READ ONLY, DEFERRABLE");
else
ExecuteSqlStatement(AH,
"SET TRANSACTION ISOLATION LEVEL "
"REPEATABLE READ, READ ONLY");
}
else
{
ExecuteSqlStatement(AH,
"SET TRANSACTION ISOLATION LEVEL "
"SERIALIZABLE, READ ONLY");
}
以下代码在pg_dump.c 的 getTables 函数中
/*
* Read-lock target tables to make sure they aren't DROPPED or altered
* in schema before we get around to dumping them.
*
* We only need to lock the table for certain components; see
* pg_dump.h
*/
if (tblinfo[i].dobj.dump &&
(tblinfo[i].relkind == RELKIND_RELATION ||
tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
appendPQExpBuffer(query,
"LOCK TABLE %s IN ACCESS SHARE MODE",
fmtQualifiedDumpable(&tblinfo[i]));
ExecuteSqlStatement(fout, query->data);
}
参考:
pg_dump一致性备份以及cache lookup failed错误的原因分析_weixin_33835690的博客-CSDN博客
PostgreSQL backup and recovery - online logical backup & recovery-阿里云开发者社区
不能清理
select * from pg_stat_archiver;
清理慢
产生速度过快
核心作用:提升元组可见性判断效率
pg事务篇(三)—— 事务状态与Hint Bits(t_infomask)_Hehuyi_In的博客-CSDN博客_pg事务篇(三)
索引是否存储空值
BTree 索引存储空值(SQL Server也存,Oracle不存),在官方文档也有提到
Also,an IS NULL or IS NOT NULL condition on an index column can be used with a B-Tree index。
PostgreSQL: Documentation: 14: 11.2. Index Types
空值是如何存储的
在pg元组头数据中,有一个t_bits 的数组,用于存储空值位图。当元组中没有null值的时候,t_bits可以被认为是空的,当元组有null值的列时,t_bits使用一个bit来表示列是否为null。
详情参考:postgresql源码学习(55)—— 列中的NULL值是如何存储和判断的?Hehuyi_In的博客-CSDN博客
避免两种场景下的“部分写”(数据块不一致)问题:
无论是崩溃恢复还是备份还原的恢复,都无法基于不一致的数据块进行。
postgresql源码学习(34)—— 事务日志⑩ - 全页写机制_pgsql 全页写_Hehuyi_In的博客-CSDN博客
不能用
可能是创建中途被取消,也可以手动置为invalid。注意这种索引虽然不能用,但还是会被更新,增加开销。
update pg_index set indisvalid=false where indexrelid='i_ii'::regclass;
- 字段上用函数,immutable类型函数可以建函数索引
- 字段上做运算
- 非操作符条件,not in 、<> 、!= 、not like
- like左边带%(使用pg_trgm插件创建gin索引除外)
不想用
优化器认为走索引cost比全表扫描更高
- 很小的表,例如10行的表返回9行
- 大表,但符合条件的过多(例如字段in大量值)
- 唯一值过少(例如性别)
- 数据倾斜(例如deleted=0 10行,deleted=1 99999行)
参考
67-oracle数据库,有索引,但是没有被使用的N种情况,以及应对方法(上篇)
68-oracle数据库,有索引,但是没有被使用的N种情况,以及应对方法(下篇)
聊一聊索引失效
保存事务最终状态,用于在可见性判断中确定事务的运行状态(在t_infomask未设置时,会根据clog来判断事务是否提交)。
postgresql源码学习(51)—— 提交日志CLOG 原理 用途 管理函数_Hehuyi_In的博客-CSDN博客
图片来自PGCA课程《物理连接》
图片来自《PG DBA的一天》
pg采用元组级常规锁+xmax结合的方式实现行锁。不单纯用元组级常规锁,是为了避免事务修改行过多时,锁表急剧增大导致性能劣化,并且锁表在共享内存中的大小是有限的。因此,行锁也是不存储在内存中的。
行锁等级与兼容性
pg中通常有两种方式会用到行锁:
由于pg行锁是由常规锁+xmax结合实现的,其实行锁的等级也是借助常规锁等级实现了映射。上面的四种行锁分别对应常规锁的8、7、2、1,锁之间的兼容性也一致。
xmax保存什么
通常如果只有一个事务增加行锁,那么直接将行的xmax设为事务id,并在infomask中设置对应锁类型即可。如果有多个事务对一个元组加共享锁,pg则会将多个事务组合,并为其指定唯一的MultiXactId,此时在xmax处保存的就是MultiXactId。
参考:
postgresql源码学习(十三)—— 行锁①-行锁模式与xmax_Hehuyi_In的博客-CSDN博客_postgresql锁
第28题
流复制和逻辑复制的区别
对比项 | 流复制 | 逻辑复制 |
引入版本 | pg 9.0 | pg 10 |
实现原理 | 将WAL文件传送到备库,由备库进行物理级replay | 将WAL文件传送到备库,按照配置规则解析为SQL语句并执行 |
数据一致性 | 高,主备库物理完全一致 | 一般,主备库物理可能一致,数据可能不一致 |
安装要求 | 1. 同构平台、大版本一致 2. wal_level 至少为 replica 3. 复制槽非必须 |
1. 平台和大版本可以不一致 2. wal_level = logical 3. 需要逻辑复制槽 |
同步范围 | 实例级,可同步所有对象的dml,ddl操作 | 表级,可同步表的dml及部分ddl操作(14版本支持truncate) |
同步级别 | 整个实例只能设置为同步或异步 | 可以对不同订阅单元设置不同同步级别 |
同步架构 | 一主多从、级联从库 | 一对多、多对一、多对多、级联 |
适用场景
流复制:
逻辑复制:
postgresql源码学习(48)—— 流复制冲突(备库锁阻塞与Vacuum冲突)_Hehuyi_In的博客-CSDN博客
最常用的如下:
PostgreSQL: Documentation: 14: 5.7. Privileges
synchronous_commit 五种级别的区别
首先要看节点是单实例还是主从架构,两者的可用级别和含义是不一样的。
单实例
主从架构
remote_write:主库提交事务时需要等相应WAL数据写入从库操作系统缓存中,才向客户端返回成功(同步流复制)。
on:主库提交事务时需要等相应WAL数据写入从库WAL文件中,才向客户端返回成功(同步流复制)。
remote_apply:主库提交事务时需要等相应WAL数据需在从库中replay完,才向客户端返回成功(同步流复制)。
为什么备库的查询不能立马看到主库插入的数据
pg 之 synchronous_commit参数_Hehuyi_In的博客-CSDN博客_synchronous_commit
事务ID回卷的原因
pg将总共可用的事务id(约42亿)视为一个环,并一分为二,对于某个特定的事务id,其后约21亿个id属于未来,均不可见;其前约21亿个id属于过去,均可见。
由于目前事务id只有32位,在大业务量下很可能用完,触发事务id回卷(循环使用)。一旦新事务使用了旧id,旧事务将可以看到新事务数据,新事务又看不到旧事务数据,打破数据一致性。
为此,pg引入了冻结机制,将不再需要使用的事务id进行冻结,冻结后的事务id被认为比所有事务id都旧。这样既保证了数据一致性,又使得有限的事务id可以循环复用。
如何维护优化
postgresql_internals-14 学习笔记(三)冻结、rebuild-CSDN博客
pg事务篇(二)—— 事务ID回卷与事务冻结(freeze)_Hehuyi_In的博客-CSDN博客_autovacuum_freeze_max_age
vacuum / autovacuum 的作用
如何调优
pg事务篇(四)—— vacuum官方文档_Hehuyi_In的博客-CSDN博客_pg 手动vacuum语法
postgresql_internals-14 学习笔记(二)常规vacuum_Hehuyi_In的博客-CSDN博客
函数三态
volatile函数(不稳定,默认)
stable函数(稳定)
immutable函数(非常稳定)
函数稳定性通过查看pg_proc.provolatile得到
函数为什么需要有execute
与普通SQL不同,plpgsql中默认使用Plan Caching,会自动将SQL以prepare方式执行,尝试生成和缓存generic plan 进行软解析。但是,如果有数据倾斜问题,缓存的执行计划可能是低效的,对部分核心业务来说是不可接受的。此时可以考虑使用execute语句,强制根据每个变量值生成对应执行计划,提高准确度。
参考:
PostgreSQL函数
PostgreSQL: Documentation: 14: 38.7. Function Volatility Categories
PostgreSQL: Documentation: 14: 43.11. PL/pgSQL under the Hood
第33题
为什么要用CIC
降低锁级别,提升业务并发度。create index需要持有5级锁,会阻塞对表的DML操作;而CIC只需要持有4级锁,与DML操作兼容,基本可以做到不影响业务。
CIC的危害
有一些算不上危害这么严重,但需要注意:
参考
PostgreSQL在线创建索引你不得不注意的"坑" - 墨天轮
PostgreSQL CREATE INDEX CONCURRENTLY 的原理以及哪些操作可能堵塞索引的创建-阿里云开发者社区
PostgreSQL: Documentation: devel: CREATE INDEX
没有HOT时的场景(Heap Only Tuple)
当pg更新一条数据时,实际是将旧数据标记为dead再新插入一条数据。当列上有索引时,即使更新的不是索引键字段,也要再新增一个索引项,指向新元组(因为整条元组的物理位置改变了)。当update的数据量大时,不仅性能低,也会引发索引的膨胀,同时还会增加额外的索引清理成本。
什么是HOT(堆内元组)
pg 8.3版本引入,满足以下条件的元组被称为堆内元组:
HOT原理
当进行HOT更新时,不需要再新增索引项和指针,索引仍指向旧元组,查询数据时通过ctid访问新版本元组即可。这大大提升了带索引字段的更新效率,也减少了索引的膨胀。
另外,结合pg的page pruning技术,可以在平时的操作(例如select)时就对页内死元组和无用的索引指针进行清理,而不需要等到vacuum执行,减少了vacuum的工作量。
更新后还会设置元组标记:
参考
postgresql_internals-14 学习笔记(一)_Hehuyi_In的博客-CSDN博客
https://www.cnblogs.com/duanleiblog/p/14378565.html
https://www.cnblogs.com/abclife/p/13620700.html
PostgreSQL: Documentation: 14: 70.7. Heap-Only Tuples (HOT)
普通行锁没有。pg的行锁实现机制并使它不需要在内存中记录修改行的信息,因此可以有无限个行锁,不需要使用锁升级。像SqlServer就有锁升级,在持有大量行锁时可能升级为页锁甚至表锁,避免锁占用大量内存。
These locks are acquired when internal fields of a row are being updated (or deleted or marked for update). Postgres doesn't remember any information about modified rows in memory and so has no limit to the number of rows locked without lock escalation.
补充:pg中的predicate lock(谓词锁)存在锁升级,但它仅用在可串行化的隔离级别,普通业务不会用到。
参考:
PostgreSQL: Documentation: 6.5: Locking and Tables
[译文] PostgreSQL 中的锁:其他锁 - 墨天轮
第17题
复制槽的作用
启用hot_feedback_on后,备库会将WAL接收的位置告知主库,创建复制槽后这个信息会保存在复制槽。对于物理复制,可以保证主库不提前删除备库尚未使用的日志,避免主从同步中断,不过物理复制并不是必须的。对逻辑复制而言,逻辑复制槽是必须的。
复制槽的危害
如果备库接收WAL过慢,主库会堆积大量WAL导致磁盘空间暴增。另外可能造成主库vacuum可以清理的元组非常少,加剧表膨胀问题。
为什么会有死锁
事务间出现了相互等待,使得其中的每个事务都无法进行下一步动作。此时需要有死锁检测机制发现死锁,并终止其中一个事务,打破循环等待。
死锁检测机制
事务T1在等待事务T2,可以用一个有向图表示。如果每个等待是一条“边”,那么死锁检测其实就是一个找“环”的过程。
为了尽量减少死锁,pg将持锁事务与等待队列中事务之间的等待称为一条实边,而将等待队列中事务的等待称为虚边。如果环中包含虚边,由于尚未真正持有锁,还可以尽量调整。而实边中如果出现了环,检测就会停止,报错出现死锁并将其记录到日志中。
postgresql源码学习(十五)—— 行锁③-死锁检测_postgresql 行锁_Hehuyi_In的博客-CSDN博客
整体慢
单个慢
① 一直慢
② 突然/偶尔变慢
为什么需要使用分区表
① 管理优势
② 性能优势
分区表劣势
9.x - 13.0 postgresql 分区表新特性及简单用法_Hehuyi_In的博客-CSDN博客_pg13 实时 分区表
SQL只负责告诉数据库要查什么,但一般不会告诉它要怎么查。
硬解析
对于一个SQL语句,优化器首先需要进行词法分析、语法分析,将其转换为pg能识别的查询树,再对其解析重写和优化,生成执行计划树,执行器才能知道如何执行该语句,这种完整的解析叫做硬解析。
软解析
显然,如果每个语句每次都执行如此复杂的步骤,效率会很低,因此pg会将SQL解析出来的执行计划缓存在进程内存中,符合一定条件时可以直接使用,提高效率,这种解析叫做软解析。
PG绑定变量SQL解析的 五次机制
五次机制是为了防止数据倾斜,导致使用低效的执行计划。
强制使用软/硬解析
PG 12 中引入了 force_custom_plan 参数,有以下可选值:
两种计划的使用次数
PG 14 在pg_prepared_statements视图中新增了generic_plans和custom_plans两列,可以看到两种计划的次数。由于pg的执行计划只是缓存在进程中,pg_prepared_statements视图只能看到本会话的SQL情况,看不到其他会话和全局信息。
PREPARE plane(integer) AS SELECT * FROM test WHERE id = $1;
-- 可以带不同值多次执行
EXECUTE plane(1);
-- 必须在同会话执行
select * from pg_prepared_statements;
可以看到第6次的执行计划中,Index Cond 由 id = 1 变成了 id = $1,说明第6次已经是软解析了。
postgres=# PREPARE plane(integer) AS SELECT * FROM test WHERE id = $1;
PREPARE
postgres=# explain analyze EXECUTE plane(1);
QUERY PLAN
-------------------------------------------------------------------------------------------------
----------------
Index Scan using test_pkey on test (cost=0.15..8.17 rows=1 width=44) (actual time=0.009..0.010
rows=1 loops=1)
Index Cond: (id = 1)
Planning Time: 0.444 ms
Execution Time: 0.026 ms
(4 rows)
postgres=# explain analyze EXECUTE plane(1);
QUERY PLAN
-------------------------------------------------------------------------------------------------
----------------
Index Scan using test_pkey on test (cost=0.15..8.17 rows=1 width=44) (actual time=0.010..0.011
rows=1 loops=1)
Index Cond: (id = 1)
Planning Time: 0.123 ms
Execution Time: 0.023 ms
(4 rows)
postgres=# explain analyze EXECUTE plane(1);
QUERY PLAN
-------------------------------------------------------------------------------------------------
----------------
Index Scan using test_pkey on test (cost=0.15..8.17 rows=1 width=44) (actual time=0.010..0.012
rows=1 loops=1)
Index Cond: (id = 1)
Planning Time: 0.075 ms
Execution Time: 0.023 ms
(4 rows)
postgres=# explain analyze EXECUTE plane(1);
QUERY PLAN
-------------------------------------------------------------------------------------------------
----------------
Index Scan using test_pkey on test (cost=0.15..8.17 rows=1 width=44) (actual time=0.012..0.014
rows=1 loops=1)
Index Cond: (id = 1)
Planning Time: 0.129 ms
Execution Time: 0.030 ms
(4 rows)
postgres=# explain analyze EXECUTE plane(1);
QUERY PLAN
-------------------------------------------------------------------------------------------------
----------------
Index Scan using test_pkey on test (cost=0.15..8.17 rows=1 width=44) (actual time=0.010..0.011
rows=1 loops=1)
Index Cond: (id = 1)
Planning Time: 0.090 ms
Execution Time: 0.024 ms
(4 rows)
postgres=# explain analyze EXECUTE plane(1);
QUERY PLAN
-------------------------------------------------------------------------------------------------
----------------
Index Scan using test_pkey on test (cost=0.15..8.17 rows=1 width=44) (actual time=0.023..0.024
rows=1 loops=1)
Index Cond: (id = $1)
Planning Time: 0.118 ms
Execution Time: 0.051 ms
(4 rows)
这部分源码在plancache.c文件GetCachedPlan函数中,网上查到的基本是旧版本:
PG 14 中,将选择执行计划的部分单独拆到了choose_custom_plan函数,由上层的GetCachedPlan函数调用。
/*
* GetCachedPlan: get a cached plan from a CachedPlanSource.
*
* This function hides the logic that decides whether to use a generic
* plan or a custom plan for the given parameters: the caller does not know
* which it will get.
*
*/
...
/* Decide whether to use a custom plan */
customplan = choose_custom_plan(plansource, boundParams);
...
if (customplan)
{
/* Build a custom plan */
plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv);
/* Accumulate total costs of custom plans */
plansource->total_custom_cost += cached_plan_cost(plan, true);
plansource->num_custom_plans++;
}
else
{
plansource->num_generic_plans++;
}
...
choose_custom_plan函数,5次机制的5就是从这来的
/*
* choose_custom_plan: choose whether to use custom or generic plan
*
* This defines the policy followed by GetCachedPlan.
*/
static bool
choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
{
...
/* Generate custom plans until we have done at least 5 (arbitrary)
5次机制的5就是从这来的 */
if (plansource->num_custom_plans < 5)
return true;
}
参考:PostgreSQL 14 pg_prepared_statements新增统计软/硬解析次数_mob604756fa6ad7的技术博客_51CTO博客
postgresql_internals-14 学习笔记(一)_Hehuyi_In的博客-CSDN博客
由于内容比较多,整理了一个思维导图
Linux 内存回收,思维导图记录_Hehuyi_In的博客-CSDN博客
① 如何判断内存不足
② 内存回收方式
kswapd是一个内核进程,定期运行,在watermark low阈值以下时会被唤醒,在后台进行内存回收,回收到high阈值以上时会休眠。由于是异步回收,不会阻塞其他进程,但由于涉及磁盘读写,对于时间敏感的SQL,可能会感受到性能下降。
由于kswapd是定时运行,如果有进程一次申请过多内存,可用内存可能会降到min阈值以下,此时kswapd会触发直接内存回收。这种内存回收方式是同步的,会阻塞其他进程,造成长时间的延迟,系统IO、CPU利用率会升高,SQL执行明显变慢,因此要尽量避免直接内存回收。
如果直接内存回收后,系统的可用内存还不足以进行内存分配,则会进一步触发OOM机制。
OOM机制会根据算法选择并kill掉一个占用物理内存较高的进程,以释放内存资源,目标是回收到high阈值以上。如果达不到,会继续杀死占用物理内存较高的进程,直到达到high阈值。由于DB通常是占用内存最多的,通常都是最先被OOM杀掉的,造成严重事故。
③ pdflush线程(感觉类似bg writer进程)
pdflush的作用是同步内存和磁盘数据,数据写入磁盘前可能会缓存在内存,pdflush试图保证内存和磁盘的数据是一致的,不会因为缓存功能而造成数据丢失或损坏。它和kswapd进程一样,都是定期被唤醒,都是以守护进程的形式存在。
缓存写入磁盘一般有三个原因:
kswapd和直接内存回收 主要回收的对象是内存中的文件页和匿名页。
参考
【Linux内核】内存管理——内存回收机制_linux内存回收机制_Ethan-Code的博客-CSDN博客
https://www.cnblogs.com/centos-python/articles/8522364.html
备库cpu定时冲高的有趣案例
D进程的危害
D指Disk Sleep,D进程为不可中断睡眠进程,通常是在进行IO操作。大量D进程出现时说明服务器遇到IO问题,可能导致服务器性能急剧下降甚至卡死。
可能的原因
Storage Basics and Fundamentals | Mycloudwiki - Page 7
参考:从物理磁盘到数据库 —— 存储IO链路访问图-CSDN博客