Pgsql原理解析——Vacuum

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: title: Pgsql原理解析——Vacuum date: 2019-03-19 12:23:31 categories: - Postgresql - PgInternal vacuum的两个主要作用在之前的"并发控制"中已经有了一些介绍,本章针对vacuum的原理、使用做进一步讲解。 1 引言:案例分析 1.1 慢查询案例 tdb0529=&g
title: Pgsql原理解析——Vacuum
date: 2019-03-19 12:23:31
categories: 
- Postgresql 
- PgInternal

vacuum的两个主要作用在之前的"并发控制"中已经有了一些介绍,本章针对vacuum的原理、使用做进一步讲解。

1 引言:案例分析

1.1 慢查询案例

tdb0529=> create table tbl01(id int, info text);
CREATE TABLE
tdb0529=> create index idx_tbl01_id on tbl01(id);
CREATE INDEX
tdb0529=> alter table tbl01 set (autovacuum_enabled =off);
ALTER TABLE
tdb0529=> insert into tbl01 select generate_series(1,1000000), md5(clock_timestamp()::text);
INSERT 0 1000000
tdb0529=> explain (analyze,verbose,timing,costs,buffers) select * from tbl01 where id=1;
                                                        QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Index Scan using idx_tbl01_id on public.tbl01 (cost=0.42..8.44 rows=1 width=37) (actual time=0.024..0.024 rows=1 loops=1)
  Output: id, info
  Index Cond: (tbl01.id = 1)
  Buffers: shared hit=4
Planning time: 0.363 ms
Execution time: 0.054 ms
(6 rows)

扫描了4个数据块。另起事务插入大量数据块(不提交),查询发现扫描了大量数据块

tdb0529=> begin;
BEGIN
tdb0529=> insert into tbl01 select 1, md5(clock_timestamp()::text) from generate_series(1,1000000);
INSERT 0 1000000
tdb0529=> abort;
ROLLBACK

tdb0529=> explain (analyze,verbose,timing,costs,buffers) select * from tbl01 where id=1;
                                                        QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on public.tbl01 (cost=4.38..8.40 rows=1 width=37) (actual time=48.241..133.774 rows=1 loops=1)
  Output: id, info
  Recheck Cond: (tbl01.id = 1)
  Heap Blocks: exact=8335
  Buffers: shared hit=6652 read=5161 dirtied=3636 written=557
  -> Bitmap Index Scan on idx_tbl01_id (cost=0.00..4.38 rows=1 width=0) (actual time=47.006..47.006 rows=1000001 loops=1)
        Index Cond: (tbl01.id = 1)
        Buffers: shared hit=1952 read=1526
Planning time: 0.101 ms
Execution time: 133.913 ms

事务提交、提交这些扫描都无法避免,原理请参考《3 并发控制》

回收必须使用vacuum,回收后,SQL执行时间恢复正常

tdb0529=> vacuum verbose tbl01;
INFO: vacuuming "public.tbl01"
INFO: scanned index "idx_tbl01_id" to remove 1000000 row versions
DETAIL: CPU: user: 0.24 s, system: 0.00 s, elapsed: 0.25 s
INFO:  "tbl01": removed 1000000 row versions in 8334 pages
DETAIL: CPU: user: 0.02 s, system: 0.00 s, elapsed: 0.02 s
INFO: index "idx_tbl01_id" now contains 1000000 row versions in 6237 pages
DETAIL:  1000000 index row versions were removed.
3474 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  "tbl01": found 1000000 removable, 1000000 nonremovable row versions in 16667 out of 16667 pages
DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 574
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.45 s, system: 0.03 s, elapsed: 0.48 s.
INFO:  "tbl01": truncated 16667 to 8334 pages
DETAIL: CPU: user: 0.00 s, system: 0.01 s, elapsed: 0.03 s
INFO: vacuuming "pg_toast.pg_toast_16386"
INFO: index "pg_toast_16386_index" now contains 0 row versions in 1 pages
DETAIL:  0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  "pg_toast_16386": found 0 removable, 0 nonremovable row versions in 0 out of 0 pages
DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 575
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM

tdb0529=> explain (analyze,verbose,timing,costs,buffers) select * from tbl01 where id=1;
                                                        QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Index Scan using idx_tbl01_id on public.tbl01 (cost=0.42..8.44 rows=1 width=37) (actual time=0.012..0.012 rows=1 loops=1)
  Output: id, info
  Index Cond: (tbl01.id = 1)
  Buffers: shared hit=4
Planning time: 0.135 ms
Execution time: 0.031 ms

1.2 页面分析案例

create table tbl02(id int, info text);
CREATE TABLE
alter table tbl02 set (autovacuum_enabled='off');
ALTER TABLE
-- superuser
create extension pageinspect;
CREATE EXTENSION

insert into tbl02 select id, repeat(md5(random()::text), 16) from generate_series(1,20) t(id);
INSERT 0 20

select pg_column_size(repeat(md5(random()::text), 16));
pg_column_size
----------------
           516

查看第页面1(页面结构请参考"存储结构"

  • lp指针分配到80

  • 数据的最低位点到576

  • 当前页面的free space=576-80=496

SELECT * FROM page_header(get_raw_page('tbl02', 0));
  lsn     | checksum | flags | lower | upper | special | pagesize | version | prune_xid
------------+----------+-------+-------+-------+---------+----------+---------+-----------
0/AC7E6400 |        0 |     0 |    80 |   576 |    8192 |     8192 |       4 |         0

查看具体某一条记录的偏移量

select lp,lp_off from heap_page_items(get_raw_page('tbl02', 0));
lp | lp_off
----+--------
 1 |   7648
 2 |   7104
 3 |   6560
 4 |   6016
 5 |   5472
 6 |   4928
 7 |   4384
 8 |   3840
 9 |   3296
10 |   2752
11 |   2208
12 |   1664
13 |   1120
14 |    576
(14 rows)

查看靠近页面末尾的记录信息

select * from heap_page_items(get_raw_page('tbl02', 0)) where lp=1;
-[ RECORD 1 ]--------------
lp         | 1
lp_off     | 7648
lp_flags   | 1
lp_len     | 544
t_xmin     | 579
t_xmax     | 0
t_field3   | 0
t_ctid     | (0,1)
t_infomask2 | 2
t_infomask | 2050
t_hoff     | 24
t_bits     |
t_oid       |
t_data     | \x01000000100800003435353366633031356233363938373961356537313761316461343337333634343535336663303135623336393837396135653731376131646134333733363434353533666330313562333639383739613565373137613164613433373336343435353366633031356233363938373961356537313761316461343337333634343535336663303135623336393837396135653731376131646134333733363434353533666330313562333639383739613565373137613164613433373336343435353366633031356233363938373961356537313761316461343337333634343535336663303135623336393837396135653731376131646134333733363434353533666330313562333639383739613565373137613164613433373336343435353366633031356233363938373961356537313761316461343337333634343535336663303135623336393837396135653731376131646134333733363434353533666330313562333639383739613565373137613164613433373336343435353366633031356233363938373961356537313761316461343337333634343535336663303135623336393837396135653731376131646134333733363434353533666330313562333639383739613565373137613164613433373336343435353366633031356233363938373961356537313761316461343337333634

间隔删除数据,使用ctid(页面号,lp号)作为条件,发现数据并没有真正的从页面中删除

delete from tbl02 where ctid not in ('(0,1)','(0,3)','(0,5)','(0,7)','(0,9)','(0,11)','(0,13)');
DELETE 13

tdb0529=# select lp,lp_off,lp_flags,lp_len,t_xmin,t_xmax,t_field3,t_ctid,t_infomask2,t_infomask,t_hoff,t_bits,t_oid from heap_page_items(get_raw_page('tbl02', 0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------
 1 |   7648 |        1 |    544 |    579 |      0 |        0 | (0,1) |           2 |       2306 |     24 |       |
 2 |   7104 |        1 |    544 |    579 |    580 |        0 | (0,2) |        8194 |        258 |     24 |       |
 3 |   6560 |        1 |    544 |    579 |      0 |        0 | (0,3) |           2 |       2306 |     24 |       |
 4 |   6016 |        1 |    544 |    579 |    580 |        0 | (0,4) |        8194 |        258 |     24 |       |
 5 |   5472 |        1 |    544 |    579 |      0 |        0 | (0,5) |           2 |       2306 |     24 |       |
 6 |   4928 |        1 |    544 |    579 |    580 |        0 | (0,6) |        8194 |        258 |     24 |       |
 7 |   4384 |        1 |    544 |    579 |      0 |        0 | (0,7) |           2 |       2306 |     24 |       |
 8 |   3840 |        1 |    544 |    579 |    580 |        0 | (0,8) |        8194 |        258 |     24 |       |
 9 |   3296 |        1 |    544 |    579 |      0 |        0 | (0,9) |           2 |       2306 |     24 |       |
10 |   2752 |        1 |    544 |    579 |    580 |        0 | (0,10) |        8194 |        258 |     24 |       |
11 |   2208 |        1 |    544 |    579 |      0 |        0 | (0,11) |           2 |       2306 |     24 |       |
12 |   1664 |        1 |    544 |    579 |    580 |        0 | (0,12) |        8194 |        258 |     24 |       |
13 |   1120 |        1 |    544 |    579 |      0 |        0 | (0,13) |           2 |       2306 |     24 |       |
14 |    576 |        1 |    544 |    579 |    580 |        0 | (0,14) |        8194 |        258 |     24 |       |

进行垃圾回收,发现lp并没有改变但是记录的空间已经被回收了

tdb0529=# vacuum verbose tbl02;
INFO: vacuuming "public.tbl02"
INFO:  "tbl02": removed 13 row versions in 2 pages
INFO:  "tbl02": found 13 removable, 7 nonremovable row versions in 2 out of 2 pages
DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 582
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  "tbl02": truncated 2 to 1 pages
DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
INFO: vacuuming "pg_toast.pg_toast_16413"
INFO: index "pg_toast_16413_index" now contains 0 row versions in 1 pages
DETAIL:  0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  "pg_toast_16413": found 0 removable, 0 nonremovable row versions in 0 out of 0 pages
DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 583
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM

tdb0529=# select lp,lp_off,lp_flags,lp_len,t_xmin,t_xmax,t_field3,t_ctid,t_infomask2,t_infomask,t_hoff,t_bits,t_oid from heap_page_items(get_raw_page('tbl02', 0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------
 1 |   7648 |        1 |    544 |    579 |      0 |        0 | (0,1) |           2 |       2306 |     24 |       |
 2 |      0 |        0 |      0 |       |       |         |       |             |           |       |       |
 3 |   7104 |        1 |    544 |    579 |      0 |        0 | (0,3) |           2 |       2306 |     24 |       |
 4 |      0 |        0 |      0 |       |       |         |       |             |           |       |       |
 5 |   6560 |        1 |    544 |    579 |      0 |        0 | (0,5) |           2 |       2306 |     24 |       |
 6 |      0 |        0 |      0 |       |       |         |       |             |           |       |       |
 7 |   6016 |        1 |    544 |    579 |      0 |        0 | (0,7) |           2 |       2306 |     24 |       |
 8 |      0 |        0 |      0 |       |       |         |       |             |           |       |       |
 9 |   5472 |        1 |    544 |    579 |      0 |        0 | (0,9) |           2 |       2306 |     24 |       |
10 |      0 |        0 |      0 |       |       |         |       |             |           |       |       |
11 |   4928 |        1 |    544 |    579 |      0 |        0 | (0,11) |           2 |       2306 |     24 |       |
12 |      0 |        0 |      0 |       |       |         |       |             |           |       |       |
13 |   4384 |        1 |    544 |    579 |      0 |        0 | (0,13) |           2 |       2306 |     24 |       |
14 |      0 |        0 |      0 |       |       |         |       |             |           |       |       |

vacuum full一下

vacuum full tbl02;
VACUUM

select lp,lp_off,lp_flags,lp_len,t_xmin,t_xmax,t_field3,t_ctid,t_infomask2,t_infomask,t_hoff,t_bits,t_oid from heap_page_items(get_raw_page('tbl02', 0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------
 1 |   7648 |        1 |    544 |    579 |      0 |        0 | (0,1) |           2 |       2818 |     24 |       |
 2 |   7104 |        1 |    544 |    579 |      0 |        0 | (0,2) |           2 |       2818 |     24 |       |
 3 |   6560 |        1 |    544 |    579 |      0 |        0 | (0,3) |           2 |       2818 |     24 |       |
 4 |   6016 |        1 |    544 |    579 |      0 |        0 | (0,4) |           2 |       2818 |     24 |       |
 5 |   5472 |        1 |    544 |    579 |      0 |        0 | (0,5) |           2 |       2818 |     24 |       |
 6 |   4928 |        1 |    544 |    579 |      0 |        0 | (0,6) |           2 |       2818 |     24 |       |
 7 |   4384 |        1 |    544 |    579 |      0 |        0 | (0,7) |           2 |       2818 |     24 |       |
  • 这里可以得出结论,垃圾回收并不会改变lp,这样索引就不需要改变了

  • 垃圾回收的记录会有页内offset

  • vacuum full会改变lp,重新组织页面结构

2 使用vacuum

vacuum提供了两种使用接口

vacuum
vacuum full

两种方式的加锁级别不同

vacuum:
SHARE UPDATE EXCLUSIVE

vacuum full:
ACCESS EXCLUSIVE

为便于记忆总结一下vacuum full加排他锁,最高级别的锁和所有其他锁冲突。 而vacuum冲突SQL语句为:

-- http://www.postgres.cn/docs/10/explicit-locking.html
VACUUM
ANALYZE
CREATE INDEX CONCURRENTLY
CREATE STATISTICS
ALTER TABLE
CREATE INDEX
REFRESH MATERIALIZED VIEW CONCURRENTLY
ALTER TABLE
DROP TABLE
TRUNCATE
REINDEX
CLUSTER
VACUUM FULL
REFRESH MATERIALIZED VIEW

vacuum的使用除了上述手动执行外,还提供了守护进程autovacuum自动化执行的方式。

2 清理tuple

后面的内容需要了解底层存储结构,请先阅读《1 存储结构》

清理步骤

(1)  FOR each table
(2)       Acquire ShareUpdateExclusiveLock lock for the target table

        第一步:
(3)       扫描页面,冻结记录
(4)       删除垃圾记录的索引记录

        第二步:
(5)       FOR each page of the table
(6)           移除垃圾记录,重新组织页面结构
(7)           更新FSM和VM
          END FOR

        第三步:
(8)       Truncate the last page if possible
(9)       Update both the statistics and system catalogs of the target table
          Release ShareUpdateExclusiveLock lock
      END FOR

      /* Post-processing */
(10) Update statistics and system catalogs
(11) Remove both unnecessary files and pages of the clog if possible

2.1 第一步

首先,PostgreSQL扫描目标表建立垃圾元组列表,可能的话也会冻结过旧元组。 (冻结请直接到后面第6部分) 列表存储在内存中,由maintenance_work_mem控制(调大可以提高vacuum性能)。

maintenance_work_mem (integer)s Specifies the maximum amount of memory to be used by maintenance operations, such as VACUUM, CREATE INDEX, and ALTER TABLE ADD FOREIGN KEY. It defaults to 64 megabytes (64MB). Since only one of these operations can be executed at a time by a database session, and an installation normally doesn't have many of them running concurrently, it's safe to set this value significantly larger than work_mem. Larger settings might improve performance for vacuuming and for restoring database dumps. https://www.postgresql.org/docs/10/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY

扫描完成后,PostgreSQL通过垃圾元组列表来删除对应的索引元组。 当maintenance_work_mem已满时,PostgreSQL继续进行下一个任务,即(4)到(7); 然后返回到(3)继续扫描。

2.2 第二步

从上面的“1.2 页面分析案例”我们可以看到,记录被删除后,空间并不会立即释放,实际上删除的动作只是标记记录为垃圾记录,真正回收页面空间的还是由vacuum来做的。

 上图中发生了四件事情:

  1. 删除语句删掉了记录tuple1和记录tuple3,页面中会标记为垃圾元组,注意这里的行指针并不会发生变化,如果行指针变了,对应的索引块也必须跟着更新。

  2. vacuum整理数据空间,避免出现碎片。

  3. vacuum清理垃圾元组对应的索引块中的记录。

  4. vacuum更新FSM和VM。

2.3 第三步

  1. 更新统计信息和相关的系统表、视图。

  2. 如果最后一页没有记录,直接删除最后一个页面。

2.4 最后

可能会触发清理clog,参考第五部分“5 清理CLOG”。

3 Visibility Map

前面我们了解到,MVCC机制下元组的更新和删除并不会立即从文件中做物理删除,而是通过事务ID标记,vacuum经过判断后删除。

可见性映射表的设计目的是为了加快vacuum的速度,原理比较简单:每个表文件都会有一个可见性映射表,里面保存了表文件中每个页面的可见性(有垃圾元组的页面用1来表示,没有的页面用0来表示)

cd $PGDATA/base/13213
ls | grep 3601
3601
3601_fsm
3601_vm

4 Freeze

为什么要FREEZE?

目录
相关文章
|
2月前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
113 14
|
3月前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
134 3
|
6天前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
53 22
解析静态代理IP改善游戏体验的原理
|
3天前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
46 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
4月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
85 3
|
1月前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
2月前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
120 16
|
2月前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
172 3
|
3月前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
117 17
|
3月前
|
运维 持续交付 虚拟化
深入解析Docker容器化技术的核心原理
深入解析Docker容器化技术的核心原理
83 1

热门文章

最新文章

推荐镜像

更多