首页
解决方案
数据库专业技术服务全栈式PostgreSQL解决方案Oracle分布式存储化数据库云PolarDB一体化解决方案
产品
CLup:PostgreSQL高可用集群平台 CMiner: PostgreSQL中的CDC CData高性能数据库云一体机 CBackup数据库备份恢复云平台 CPDA高性能双子星数据库机 CSYun超融合虚拟机产品 ZQPool数据库连接池 ConshGuard数据保护产品 APCC: Greenplum管理平台
文档
文章
客户及伙伴
中启开源
关于我们
公司简介 联系我们
中启开源
登录
×
修改密码

物化视图是一种预先计算并存储查询结果的数据结构,它可以显著加速查询响应时间。在某些场景下,物化视图需要定期更新以反映源表中的最新数据变化。CloudberryDB的增量物化视图提供了一种高效的方式来处理这种更新需求,尤其适用于那些写入较少但读取频繁且需要实时数据的场景。

适合的场景

  1. 实时分析与监控:例如,实时监控系统需要不断地显示最新数据,如网络流量监控、设备性能监控等。在这种场景下,源表的数据更新相对较少,但查询需要实时反映最新的数据状态。
  2. 商业智能与报告:在商业智能应用中,分析师需要定期生成各种报表,而这些报表往往基于历史数据加上最新的业务数据。在这种情况下,增量物化视图可以在数据变化时自动更新,保证报表的实时性和准确性。
  3. 金融交易分析:金融领域的交易数据需要实时处理,但大多数交易系统在一段时间内的数据更新量较小。增量物化视图可以确保交易分析系统能够快速提供最新的分析结果。

增量物化视图的优势与局限

增量物化视图的主要优势在于它能够在数据发生变化时仅更新受影响的部分,而不是重新计算整个视图。这有助于保持查询性能的同时,确保视图的实时性。但是这种设计也带来了一定的局限性,尤其是在写入性能方面。由于每次基表更新都会触发视图的更新,因此在写入操作较多的场景下,写入性能可能会有所下降。

官方的TPC-H测试结果如下:

Data volume Normal inserts Inserts with incremental materialized view Queries with regular view Queries with incremental materialized view
1 GB 2712 ms 3777 ms 813 ms 43 ms
5 GB 15681 ms 29363 ms 3040 ms 63 ms
10 GB 43011 ms 73188 ms 7057 ms 102 ms

可以看到,增量物化视图相比于普通的视图,显著提升了查询性能,但代价是插入性能严重下滑。基于增量物化视图的实现原理,随着增量物化视图数量的增加,基表的插入性能退化倍数和增量物化视图个数成正比。

官方并没有给出随着增量物化视图数量增加,插入性能退化的相关测试数据,下面让我们来进行简单的测试。

使用下面的python脚本来测试增量物化视图数量为0个、1个、2个、3个的情况下,插入性能情况:

  1. import psycopg2
  2. from faker import Faker
  3. import random
  4. import uuid
  5. from datetime import datetime
  6. import time
  7. import threading
  8. # 测试参数
  9. num_car_models = 100
  10. num_sales = 1*1000
  11. materialized_view_names = ['brand_sales', 'brand_model_sales', 'color_sales']
  12. materialized_view_sqls = [
  13. """
  14. CREATE INCREMENTAL MATERIALIZED VIEW brand_sales AS
  15. SELECT
  16. brand,
  17. COUNT(*) AS sales
  18. FROM
  19. car_model
  20. JOIN sale ON car_model.id = sale.car_model
  21. GROUP BY
  22. brand;
  23. """,
  24. """
  25. CREATE INCREMENTAL MATERIALIZED VIEW brand_model_sales AS
  26. SELECT
  27. brand,
  28. model,
  29. COUNT(*) AS sales
  30. FROM
  31. car_model
  32. JOIN sale ON car_model.id = sale.car_model
  33. GROUP BY
  34. brand,
  35. model;
  36. """,
  37. """
  38. CREATE INCREMENTAL MATERIALIZED VIEW color_sales AS
  39. SELECT
  40. color,
  41. COUNT(*) AS sales
  42. FROM
  43. car_model
  44. JOIN sale ON car_model.id = sale.car_model
  45. GROUP BY
  46. color;
  47. """
  48. ]
  49. # 初始化 Faker
  50. fake = Faker()
  51. # 生成汽车品牌数据
  52. brands = ['Toyota', 'Honda', 'Ford', 'Chevrolet', 'Nissan', 'Volkswagen', 'Mercedes-Benz', 'BMW', 'Audi', 'Hyundai', 'Kia', 'Subaru', 'Mazda', 'Jeep', 'Lexus', 'Ram', 'Buick', 'GMC', 'Cadillac', 'Acura', 'Infiniti', 'Porsche', 'Volvo', 'Lincoln', 'Land Rover', 'Chrysler', 'Mitsubishi', 'Jaguar', 'Mini', 'Fiat', 'Bentley', 'Scion', 'Smart', 'Maserati', 'Ferrari', 'Tesla', 'Aston Martin', 'Rolls-Royce', 'Lamborghini', 'Lotus', 'McLaren', 'Bugatti', 'Alfa Romeo', 'Genesis', 'Karma', 'Rivian', 'Lucid', 'Polestar', 'Rivian', 'Byton', 'Faraday Future']
  53. def generate_car_model_data(num_records):
  54. global brands
  55. for _ in range(num_records):
  56. yield {
  57. 'id': str(uuid.uuid1()),
  58. 'brand': random.choice(brands),
  59. 'model': fake.word(),
  60. 'color': fake.color_name(),
  61. 'year': random.randint(2000, 2023)
  62. }
  63. def generate_sale_data(car_models, num_records):
  64. car_models_ids = [cm['id'] for cm in car_models]
  65. for _ in range(num_records):
  66. yield {
  67. 'id': str(uuid.uuid4()),
  68. 'car_model': random.choice(car_models_ids),
  69. 'date': fake.date_this_decade()
  70. }
  71. def recreate_table(conn, view_num):
  72. cur = conn.cursor()
  73. for name in materialized_view_names:
  74. cur.execute(f"""
  75. DROP MATERIALIZED VIEW IF EXISTS {name};
  76. """)
  77. cur.execute("""
  78. DROP TABLE IF EXISTS sale;
  79. """)
  80. cur.execute("""
  81. DROP TABLE IF EXISTS car_model;
  82. """)
  83. cur.execute("""
  84. CREATE TABLE IF NOT EXISTS car_model (
  85. id UUID PRIMARY KEY,
  86. brand TEXT,
  87. model TEXT,
  88. color TEXT,
  89. year INT
  90. );
  91. """)
  92. cur.execute("""
  93. CREATE TABLE IF NOT EXISTS sale (
  94. id UUID PRIMARY KEY,
  95. car_model UUID REFERENCES car_model(id),
  96. date DATE
  97. );
  98. """)
  99. if view_num == 0:
  100. conn.commit()
  101. cur.close()
  102. return
  103. for sql in materialized_view_sqls[:view_num]:
  104. cur.execute(sql)
  105. conn.commit()
  106. cur.close()
  107. def get_conn():
  108. return psycopg2.connect(
  109. dbname="your_database_name",
  110. user="your_username",
  111. password="your_password",
  112. host="your_host",
  113. port="your_port"
  114. )
  115. def thread_insert_sale_data(car_models, num_sales):
  116. conn = get_conn()
  117. # conn.autocommit = True
  118. cur = conn.cursor()
  119. sales = []
  120. for sale in generate_sale_data(car_models, num_sales):
  121. sales.append((sale['id'], sale['car_model'], sale['date']))
  122. cur.executemany("""
  123. INSERT INTO sale (id, car_model, date)
  124. VALUES (%s, %s, %s);
  125. """, sales)
  126. conn.commit()
  127. cur.close()
  128. conn.close()
  129. if __name__ == '__main__':
  130. total_times = []
  131. thread_num = 1
  132. # 进行四次测试,分别测试物化视图数量为 0, 1, 2, 3 的情况下,插入数据的性能
  133. for i in range(4):
  134. print(f"Testing with {i} materialized view(s)...")
  135. conn = get_conn()
  136. insert_time = 0
  137. recreate_table(conn, i)
  138. # 生成并插入汽车型号数据
  139. print("Generating and insert car model data...")
  140. cur = conn.cursor()
  141. car_models = list(generate_car_model_data(num_car_models))
  142. for car_model in car_models:
  143. cur.execute("""
  144. INSERT INTO car_model (id, brand, model, color, year)
  145. VALUES (%s, %s, %s, %s, %s);
  146. """, (car_model['id'], car_model['brand'], car_model['model'], car_model['color'], car_model['year']))
  147. conn.commit()
  148. cur.close()
  149. conn.close()
  150. # 生成并插入销售数据
  151. thread_pool = []
  152. print("Generating and insert sale data...")
  153. now = datetime.now()
  154. for _ in range(thread_num):
  155. thread_worker = threading.Thread(target=thread_insert_sale_data, args=(car_models, num_sales//thread_num))
  156. thread_worker.start()
  157. thread_pool.append(thread_worker)
  158. for t in thread_pool:
  159. t.join()
  160. insert_time = (datetime.now() - now).total_seconds()
  161. total_times.append(insert_time)
  162. # 打印测试结果
  163. for i, time in enumerate(total_times):
  164. print(f"Insert time with {i} materialized view(s): {time:.2f} seconds")

测试仅用于探索随着增量物化视图数量的增加,对插入性能的影响比例。测试结果如下,随着增量物化视图数量的增加,插入性能有所降低,降低多少插入性能和增量物化视图的复杂度呈正相关:

Data Row Materialized View Num Insert Time/s
1000 0 5.46
1000 1 15.97
1000 2 25.86
1000 3 35.47