PostgreSQL 10 分区表探密

数据库 其他数据库 PostgreSQL
PostgreSQL的分区需要先建立主表,然后再建立子表,使用继承的特性,但不需要手动写触发器/规则了,目前支持range、list分区,10 正式版发布时不知道会不会有其他的,后面我会介绍我基于10 Beta2添加的hash分区。

[[201663]]

分区介绍

PostgreSQL的分区需要先建立主表,然后再建立子表,使用继承的特性,但不需要手动写触发器/规则了,目前支持range、list分区,10 正式版发布时不知道会不会有其他的,后面我会介绍我基于10 Beta2添加的hash分区。

range分区

分区语法:

  1. postgres=# create table r (r_id int, r_name name, r_date date) partition by range (r_id); 
  2. CREATE TABLE 
  3. postgres=# create table r1 partition of r for values from (1) to (10); 
  4. CREATE TABLE 
  5. postgres=# create table r2 partition of r for values from (10) to (20); 
  6. CREATE TABLE 
  7. postgres=# create table r3 partition of r for values from (20) to (30); 
  8. CREATE TABLE 
  9. postgres=# insert into r select id, md5(random()::text), now() + (id||'day')::interval from generate_series(1, 29) t(id); 
  10. INSERT 0 29 
  11. postgres=# select *, tableoid::regclass from r; 
  12.  r_id |  r_name  |   r_date   | tableoid  
  13. ------+----------------------------------+------------+---------- 
  14.     1 | 1d0d0680930198d2962b3b5f9cf82083 | 2017-08-09 | r1 
  15.     2 | 47ba81de41d71bd51b18c7861a594bdf | 2017-08-10 | r1 
  16.     3 | 820b0b1affe3bf0e5705aee3e77b0b29 | 2017-08-11 | r1 
  17.     4 | 0cc06451bd0652d2583a733374d787b3 | 2017-08-12 | r1 
  18.     5 | 642108381b2fc203b830f1215a0d7c6a | 2017-08-13 | r1 
  19.     6 | 57e3869b2ab8ee1c0bca96b1cf022a5d | 2017-08-14 | r1 
  20.     7 | 5357fa6de3c1c559edb78cddb4eae902 | 2017-08-15 | r1 
  21.     8 | 6ea5a7dba4dfc6c81ca5932be86a9341 | 2017-08-16 | r1 
  22.     9 | d3d4dcb9dc48e0629042ede7ed9c7a33 | 2017-08-17 | r1 
  23.    10 | 248d6f3e072c6c137a3402d11fc5b1d7 | 2017-08-18 | r2 
  24.    11 | ae3a671045ded43260bc4d0bbcb7e428 | 2017-08-19 | r2 
  25.    12 | acdc89bb326d9f0caaeeb86bfeac3a76 | 2017-08-20 | r2 
  26.    13 | 147b6e975d7299db66e170874b913b25 | 2017-08-21 | r2 
  27.    14 | 6041a6b84b1af615bdb34a5926d72a33 | 2017-08-22 | r2 
  28.    15 | 3d96e08395af120dd36e10a0252ce29c | 2017-08-23 | r2 
  29.    16 | 5e613d10c9cac126453413ddfc17c210 | 2017-08-24 | r2 
  30.    17 | e92fc34d180be652e72a63b92d327f1b | 2017-08-25 | r2 
  31.    18 | 3109c4e8f4da701721151df11a4d266f | 2017-08-26 | r2 
  32.    19 | 35ba5892f3b88aa3254445fbf5267eea | 2017-08-27 | r2 
  33.    20 | c92d1df47257784bb11d7bfbb52b5710 | 2017-08-28 | r3 
  34.    21 | d076a5498d17ade8f317bf47cfa322c3 | 2017-08-29 | r3 
  35.    22 | a66c2e83f1e54e1392964ed71d5b8e20 | 2017-08-30 | r3 
  36.    23 | 6a94df0f08921728aa0af9455d05c9f8 | 2017-08-31 | r3 
  37.    24 | 248c46d80b926c66c093c500f309614d | 2017-09-01 | r3 
  38.    25 | 4da3be147fd1831e8605fc400e7a7503 | 2017-09-02 | r3 
  39.    26 | 3029d7e22b7c963e8983200a93894669 | 2017-09-03 | r3 
  40.    27 | 720d6d04249e9f3595a19cf59f075332 | 2017-09-04 | r3 
  41.    28 | 95b5e5492591c38ddd864d83265e26c4 | 2017-09-05 | r3 
  42.    29 | 2628c14bd3f67699ab0411b6fd402460 | 2017-09-06 | r3 
  43. (29 rows
  44.  
  45. postgres=# explain select * from r where id = 20; 
  46. ERROR:  column "id" does not exist 
  47. LINE 1: explain select * from r where id = 20; 
  48.   ^ 
  49. postgres=# explain select * from r where r_id = 20; 
  50. QUERY PLAN 
  51. ---------------------------------------------------------- 
  52.  Append  (cost=0.00..20.12 rows=4 width=72) 
  53.    ->  Seq Scan on r3  (cost=0.00..20.12 rows=4 width=72) 
  54.  Filter: (r_id = 20) 
  55. (3 rows
  56.  
  57. postgres=# set constraint_exclusion = off
  58. SET 
  59. postgres=# explain select * from r where r_id = 20; 
  60. QUERY PLAN 
  61. ---------------------------------------------------------- 
  62.  Append  (cost=0.00..60.38 rows=12 width=72) 
  63.    ->  Seq Scan on r1  (cost=0.00..20.12 rows=4 width=72) 
  64.  Filter: (r_id = 20) 
  65.    ->  Seq Scan on r2  (cost=0.00..20.12 rows=4 width=72) 
  66.  Filter: (r_id = 20) 
  67.    ->  Seq Scan on r3  (cost=0.00..20.12 rows=4 width=72) 
  68.  Filter: (r_id = 20) 
  69. (7 rows
  70.  
  71. postgres=#  
  72.  
  73. postgres=# create index on r1 (r_id); 
  74. CREATE INDEX 
  75. postgres=# explain select * from r where r_id = 5; 
  76. QUERY PLAN 
  77. ---------------------------------------------------------------------------------- 
  78.  Append  (cost=5.53..25.54 rows=161 width=72) 
  79.    ->  Bitmap Heap Scan on r1  (cost=5.53..25.54 rows=161 width=72) 
  80.  Recheck Cond: (r_id = 5) 
  81.  ->  Bitmap Index Scan on r1_r_id_idx  (cost=0.00..5.48 rows=161 width=0) 
  82.    Index Cond: (r_id = 5) 
  83. (5 rows
  84.  
  85. postgres=# \d+ r* 
  86.  Table "public.r" 
  87.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  88. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  89.  r_id   | integer |   |  | | plain   |  |  
  90.  r_name | name|   |  | | plain   |  |  
  91.  r_date | date|   |  | | plain   |  |  
  92. Partition key: RANGE (r_id) 
  93. Partitions: r1 FOR VALUES FROM (1) TO (10), 
  94. r2 FOR VALUES FROM (10) TO (20), 
  95. r3 FOR VALUES FROM (20) TO (30) 
  96.  
  97. Table "public.r1" 
  98.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  99. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  100.  r_id   | integer |   |  | | plain   |  |  
  101.  r_name | name|   |  | | plain   |  |  
  102.  r_date | date|   |  | | plain   |  |  
  103. Partition of: r FOR VALUES FROM (1) TO (10) 
  104. Partition constraint: ((r_id IS NOT NULLAND (r_id >= 1) AND (r_id < 10)) 
  105. Indexes: 
  106. "r1_r_id_idx" btree (r_id) 
  107.  
  108.    Index "public.r1_r_id_idx" 
  109.  Column |  Type   | Definition | Storage  
  110. --------+---------+------------+--------- 
  111.  r_id   | integer | r_id   | plain 
  112. btree, for table "public.r1" 
  113.  
  114. Table "public.r2" 
  115.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  116. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  117.  r_id   | integer |   |  | | plain   |  |  
  118.  r_name | name|   |  | | plain   |  |  
  119.  r_date | date|   |  | | plain   |  |  
  120. Partition of: r FOR VALUES FROM (10) TO (20) 
  121. Partition constraint: ((r_id IS NOT NULLAND (r_id >= 10) AND (r_id < 20)) 
  122.  
  123. Table "public.r3" 
  124.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  125. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  126.  r_id   | integer |   |  | | plain   |  |  
  127.  r_name | name|   |  | | plain   |  |  
  128.  r_date | date|   |  | | plain   |  |  
  129. Partition of: r FOR VALUES FROM (20) TO (30) 
  130. Partition constraint: ((r_id IS NOT NULLAND (r_id >= 20) AND (r_id < 30))  

说明:

  • 创建分区时必须指定主表
  • 分区表和主表列数量、定义必须完全一致
  • 分区表的列可以单独添加约束、索引
  • 向主表插入数据,自动插入到对应分区,如果找不到对应分区,抛出错误
  • range分区范围>=最小值、<***值
  • 修改主表字段名、字段类型,会自动修改所有分区
  • truncate主表会清除所有分区表数据
  • drop主表会把所有子表一起drop
  • \d、\d+ 可查看分区表详细定义

在PostgreSQL10的分区表功能中, 范围分区的KEY支持由多个字段组成,多列组成的KEY可看做是范围分区表的组合约束。

  1. [postgres@localhost bin]$ ./psql  
  2. psql (10beta2) 
  3. Type "help" for help. 
  4.  
  5. postgres=# create table r(a int, b int) partition by range (a, b); 
  6. CREATE TABLE 
  7. postgres=# create table r1 partition of r for values from (1, 60) to (10, 80); 
  8. CREATE TABLE 
  9. postgres=# create table r2 partition of r for values from (10, 80) to (20, 60); 
  10. CREATE TABLE 
  11.  
  12. postgres=# \d+ r* 
  13.                                      Table "public.r" 
  14.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  15. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  16.  a      | integer |           |          |         | plain   |              |  
  17.  b      | integer |           |          |         | plain   |              |  
  18. Partition key: RANGE (a, b) 
  19. Partitions: r1 FOR VALUES FROM (1, 60) TO (10, 80), 
  20.             r2 FOR VALUES FROM (10, 80) TO (20, 60) 
  21.  
  22.                                     Table "public.r1" 
  23.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  24. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  25.  a      | integer |           |          |         | plain   |              |  
  26.  b      | integer |           |          |         | plain   |              |  
  27. Partition of: r FOR VALUES FROM (1, 60) TO (10, 80) 
  28. Partition constraint: ((a IS NOT NULLAND (b IS NOT NULLAND ((a > 1) OR ((a = 1) AND (b >= 60))) AND ((a < 10) OR ((a = 10) AND (b < 80)))) 
  29.  
  30.                                     Table "public.r2" 
  31.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  32. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  33.  a      | integer |           |          |         | plain   |              |  
  34.  b      | integer |           |          |         | plain   |              |  
  35. Partition of: r FOR VALUES FROM (10, 80) TO (20, 60) 
  36. Partition constraint: ((a IS NOT NULLAND (b IS NOT NULLAND ((a > 10) OR ((a = 10) AND (b >= 80))) AND ((a < 20) OR ((a = 20) AND (b < 60)))) 
  37.  
  38. postgres=# insert into r values (10, 70); 
  39. INSERT 0 1 
  40. postgres=# insert into r values (10, 80); 
  41. INSERT 0 1 
  42. postgres=# insert into r values (10, 90); 
  43. INSERT 0 1 
  44. postgres=# select tableoid::regclass, * from r; 
  45.  tableoid | a  | b   
  46. ----------+----+---- 
  47.  r1       | 10 | 70 
  48.  r2       | 10 | 80 
  49.  r2       | 10 | 90 
  50. (7 rows
  51.  
  52. postgres=#   

这里需要注意它的分区约束,from (10, 80) to (20, 60),最初还以为是有bug,其实不是,Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND ((a > 10) OR ((a = 10) AND (b >= 80))) AND ((a < 20) OR ((a = 20) AND (b < 60))))

list分区

语法:

  1. postgres=# create table l (l_id int, l_name name, l_date date) partition by list (l_id); 
  2. CREATE TABLE 
  3. postgres=# create table l1 partition of l for values in (1); 
  4. CREATE TABLE 
  5. postgres=# create table l2 partition of l for values in (2); 
  6. CREATE TABLE 
  7. postgres=# create table l3 partition of l for values in (3); 
  8. CREATE TABLE 
  9. postgres=# create table l4 partition of l for values in (4); 
  10. CREATE TABLE 
  11. postgres=# insert into l select id, md5(random()::text), now() + (id||'day')::interval from generate_series(1, 5) t(id); 
  12. ERROR:  no partition of relation "l" found for row 
  13. DETAIL:  Partition key of the failing row contains (l_id) = (5). 
  14. postgres=# insert into l select id, md5(random()::text), now() + (id||'day')::interval from generate_series(1, 4) t(id); 
  15. INSERT 0 4 
  16. postgres=# explain select * from l where l_id = 2; 
  17. QUERY PLAN 
  18. ---------------------------------------------------------- 
  19.  Append  (cost=0.00..20.12 rows=4 width=72) 
  20.    ->  Seq Scan on l2  (cost=0.00..20.12 rows=4 width=72) 
  21.  Filter: (l_id = 2) 
  22. (3 rows
  23.  
  24. postgres=#   

hash分区

语法:

  1. [postgres@localhost bin]$ ./psql yonj1e 
  2. psql (10beta2) 
  3. Type "help" for help. 
  4.  
  5. yonj1e=# create table h (h_id int, h_name name, h_date date) partition by hash(h_id); 
  6. CREATE TABLE 
  7. yonj1e=# create table h1 partition of h; 
  8. CREATE TABLE 
  9. yonj1e=# create table h2 partition of h; 
  10. CREATE TABLE 
  11. yonj1e=# create table h3 partition of h; 
  12. CREATE TABLE 
  13. yonj1e=# create table h4 partition of h; 
  14. CREATE TABLE 
  15. yonj1e=# insert into h select id, md5(random()::text), now() + (id||'day')::interval from generate_series(1, 50) t(id); 
  16. INSERT 0 50 
  17. yonj1e=# select *,tableoid::regclass from h; 
  18.  h_id |  h_name  |   h_date   | tableoid  
  19. ------+----------------------------------+------------+---------- 
  20.     5 | 21fe9a616ce20868769904bbda56aa3e | 2017-08-14 | h1 
  21.     6 | 8fa0f42bf4239c05c1cd46a814f71eaa | 2017-08-15 | h1 
  22.     8 | 858e324311506fb5c5000a4741b9af3c | 2017-08-17 | h1 
  23.    12 | ef4ce7a0f6168605a7c243a709f28bc3 | 2017-08-21 | h1 
  24.    13 | 2273522a7b3c286e214a8f57e010568e | 2017-08-22 | h1 
  25.    17 | 8bca453f60f13278d3a02149b30394d2 | 2017-08-26 | h1 
  26.    19 | 0c2f14a6a8e675341b4e7bdb6ed161de | 2017-08-28 | h1 
  27.    23 | f10fff43558393b577d417127bf6a163 | 2017-09-01 | h1 
  28.    26 | 1dd0851728458b67a053d500bbb837ae | 2017-09-04 | h1 
  29.    28 | 00f67b8636b4d225d3b62bcca9c6d527 | 2017-09-06 | h1 
  30.    40 | d3a217d39b6808ff5e37ed3977513e05 | 2017-09-18 | h1 
  31.    41 | 0f4c765d809c3db3fa608e986aed1247 | 2017-09-19 | h1 
  32.    42 | 022ff983201352092d5d7cb735e9f531 | 2017-09-20 | h1 
  33.    44 | c3dba31501b3625aac7f3d4f41512855 | 2017-09-22 | h1 
  34.    49 | 21c697e92f936982840b928e03151204 | 2017-09-27 | h1 
  35.    11 | 625ad3b0c9d40f7cae26be84a7ae054d | 2017-08-20 | h2 
  36.    14 | 7ee39c8df46d7ec61923dffe6f58ec07 | 2017-08-23 | h2 
  37.    22 | 77c9230f9eeeb9faaa5c30ff518bbf60 | 2017-08-31 | h2 
  38.    29 | 5a6e0895b2477026bcfa4996650797a8 | 2017-09-07 | h2 
  39.    31 | 37d84c0c0956df75407e0bfc67a782ed | 2017-09-09 | h2 
  40.    34 | f43f07545fba020b47c84952c6af6cc7 | 2017-09-12 | h2 
  41.    35 | 2fa08f1311c20ac45a6726bfbc8a4f05 | 2017-09-13 | h2 
  42.    36 | d01940a876b86c2de8d67a37813ab89d | 2017-09-14 | h2 
  43.    38 | f21e401cb38c6d625b264f53fb59fb8b | 2017-09-16 | h2 
  44.    43 | c42fcb14c2d5e5c067db8231d502daae | 2017-09-21 | h2 
  45.    45 | cc5670020ba35ae2c324dd33a6efff98 | 2017-09-23 | h2 
  46.     1 | 2881b2aadddd2dfef14477369a107319 | 2017-08-10 | h3 
  47.     2 | e22e0bba2716e6d969a62502d34fc518 | 2017-08-11 | h3 
  48.     9 | a933091df7f51f5b0b6ab43816e5d765 | 2017-08-18 | h3 
  49.    15 | 92ee6dc6670ea8e02e746ee508b51022 | 2017-08-24 | h3 
  50.    21 | 67b7140105e81730f364ee9de195e0a0 | 2017-08-30 | h3 
  51.    46 | f4c47b9e055f6c732dff55cb4fd152b3 | 2017-09-24 | h3 
  52.    50 | 1b419faea293d3edab2cfb7f8efb55f8 | 2017-09-28 | h3 
  53.     3 | 6ea55fe46f2119f084edd66abe486d91 | 2017-08-12 | h4 
  54.     4 | 094b16011f0e9b34c878caa7d95e067f | 2017-08-13 | h4 
  55.     7 | c916c264c77ba90b3463143b9513bc15 | 2017-08-16 | h4 
  56.    10 | d333ce15f3a8d01a39df9ae317bf64b7 | 2017-08-19 | h4 
  57.    16 | b136e8466bbafc58f917e14919b1edf1 | 2017-08-25 | h4 
  58.    18 | 6c3fb7b3e473f793575407299006e6a3 | 2017-08-27 | h4 
  59.    20 | bac12655b2d54855c58d19c9facc1579 | 2017-08-29 | h4 
  60.    24 | ebcafb42d26654eff04bb5f8b35fdd69 | 2017-09-02 | h4 
  61.    25 | 494e25facb9fe46e00037c716e2052e7 | 2017-09-03 | h4 
  62.    27 | 961b1728893e7f6d46ed827ee9b4809e | 2017-09-05 | h4 
  63.    30 | ebf840a36af46cc3dd8f29c94013cb71 | 2017-09-08 | h4 
  64.    32 | 7c5083fed360079bcbc23c6ee803a4d6 | 2017-09-10 | h4 
  65.    33 | 707e98eac5ba349c80df6f9d8f062676 | 2017-09-11 | h4 
  66.    37 | e1cb546e66cd45b00441493100fb7752 | 2017-09-15 | h4 
  67.    39 | 0bfa7e7ccb00a477add00df4d0327c52 | 2017-09-17 | h4 
  68.    47 | d894969c3cdbf00fe7dce9683c6f9a17 | 2017-09-25 | h4 
  69.    48 | 09ca86c5e5bd87ba786533f34c4ebf25 | 2017-09-26 | h4 
  70. (50 rows
  71.  
  72. yonj1e=# explain select * from h where h_id = 20; 
  73. QUERY PLAN 
  74. ---------------------------------------------------------- 
  75.  Append  (cost=0.00..20.12 rows=4 width=72) 
  76.    ->  Seq Scan on h4  (cost=0.00..20.12 rows=4 width=72) 
  77.  Filter: (h_id = 20) 
  78. (3 rows
  79. yonj1e=# \d+ h* 
  80.  Table "public.h" 
  81.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  82. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  83.  h_id   | integer |   |  | | plain   |  |  
  84.  h_name | name|   |  | | plain   |  |  
  85.  h_date | date|   |  | | plain   |  |  
  86. Partition key: HASH (h_id) 
  87. Partitions: h1 SERIAL NUMBER 0, 
  88. h2 SERIAL NUMBER 1, 
  89. h3 SERIAL NUMBER 2, 
  90. h4 SERIAL NUMBER 3 
  91.  
  92. Table "public.h1" 
  93.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  94. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  95.  h_id   | integer |   |  | | plain   |  |  
  96.  h_name | name|   |  | | plain   |  |  
  97.  h_date | date|   |  | | plain   |  |  
  98. Partition of: h SERIAL NUMBER 0 
  99. Partition constraint: (h_id IS NOT NULL
  100.  
  101. Table "public.h2" 
  102.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  103. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  104.  h_id   | integer |   |  | | plain   |  |  
  105.  h_name | name|   |  | | plain   |  |  
  106.  h_date | date|   |  | | plain   |  |  
  107. Partition of: h SERIAL NUMBER 1 
  108. Partition constraint: (h_id IS NOT NULL
  109.  
  110. Table "public.h3" 
  111.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  112. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  113.  h_id   | integer |   |  | | plain   |  |  
  114.  h_name | name|   |  | | plain   |  |  
  115.  h_date | date|   |  | | plain   |  |  
  116. Partition of: h SERIAL NUMBER 2 
  117. Partition constraint: (h_id IS NOT NULL
  118.  
  119. Table "public.h4" 
  120.  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description  
  121. --------+---------+-----------+----------+---------+---------+--------------+------------- 
  122.  h_id   | integer |   |  | | plain   |  |  
  123.  h_name | name|   |  | | plain   |  |  
  124.  h_date | date|   |  | | plain   |  |  
  125. Partition of: h SERIAL NUMBER 3 
  126. Partition constraint: (h_id IS NOT NULL
  127.  
  128. yonj1e=#   

HASH分区语法还不支持,以后或许会支持。 

责任编辑:庞桂玉 来源: yonj1e的博客
相关推荐

2023-10-11 13:42:21

2021-01-20 08:07:52

oracle分区单表

2009-06-24 10:26:41

Oracle约束分区表

2022-01-11 08:46:56

Oracle 在线重定义数据库

2010-10-11 10:16:17

Mysql分区表

2010-11-22 15:06:46

MySQL分区表

2021-09-07 17:54:04

OpenGauss分区表索引

2010-10-11 09:50:32

Mysql分区表

2021-04-19 08:16:38

Hive数据类型大数据技术

2009-08-17 08:35:22

Linux挂载分区表fstab

2020-05-14 19:30:12

数据库分区表PostgreSQL

2011-07-20 16:03:06

SQL Server数分区表

2023-01-30 09:13:17

Oracle分区表技术

2019-03-04 13:54:18

MySQL分区表数据

2011-04-11 11:32:29

Oracle分区表磁盘IO冲突

2024-07-04 11:34:59

2010-04-19 14:01:22

Oracle查看分区表

2016-09-23 09:29:08

mysq分区表l互联网

2021-08-10 15:44:37

PostgreSQL表分区分区表

2021-03-08 22:22:20

MySQL分区表SQL
点赞
收藏

51CTO技术栈公众号