分区表的优点,种类,选择

分区表的实现

  1. -- 添加分区表
  2. -- CREATE TABLE device_10 PARTITION OF history_main FOR VALUES FROM (10) TO (11);
  3. -- CREATE INDEX history_idx_device_10 ON device_10 USING btree(device_id);
  4. -- 添加模拟数据
  5. -- INSERT INTO history_main (device_id, geometry, valid_time, unvalid_time)
  6. -- select generate_series(0,99),
  7. -- point(random()* (518373-515834)+ 515834, random() * (3690194-3688409)+ 3688409)::geometry,
  8. -- generate_series('2022-04-01T12:00:00'::timestamp(6) with time zone, '2022-04-028T12:00:00'::timestamp(6) with time zone, '1 minutes') ,
  9. -- generate_series('2022-04-01T12:01:00'::timestamp(6) with time zone, '2022-04-028T12:01:00'::timestamp(6) with time zone, '1 minutes');
  10. truncate table history_main;
  11. INSERT INTO history_main (device_id, geometry, valid_time, unvalid_time)
  12. select * from (select generate_series(0,99), point(random()* (518373-515834) + 515834, random() * (3690194 - 3688409)+ 3688409)::geometry ) AS tb1
  13. CROSS JOIN
  14. (select
  15. generate_series('2020-04-28T12:00:00'::timestamp(6) with time zone, '2022-04-028T12:9:59'::timestamp(6) with time zone, '1 minutes') as t1,
  16. generate_series('2020-04-28T12:01:00'::timestamp(6) with time zone, '2022-04-028T12:10:59'::timestamp(6) with time zone, '1 minutes') as t2 ) as t3;
  17. -- select generate_series(0,99),floor(random()*100);
  18. -- 只查看主表数据
  19. -- select * from only history_main;
  20. -- 通过主表查询所有数据
  21. -- select * from history_main;
  22. -- 查询分区表的某个子表信息
  23. -- SELECT
  24. -- nmsp_parent.nspname AS parent_schema ,
  25. -- parent.relname AS parent ,
  26. -- nmsp_child.nspname AS child_schema ,
  27. -- child.relname AS child
  28. -- FROM
  29. -- pg_inherits JOIN pg_class parent
  30. -- ON pg_inherits.inhparent = parent.oid JOIN pg_class child
  31. -- ON pg_inherits.inhrelid = child.oid JOIN pg_namespace nmsp_parent
  32. -- ON nmsp_parent.oid = parent.relnamespace JOIN pg_namespace nmsp_child
  33. -- ON nmsp_child.oid = child.relnamespace
  34. -- WHERE
  35. -- parent.relname = 'history_main' and child.relname='device_7'
  36. -- 根据范围 创建分区表
  37. -- Table: public.history_main
  38. -- DROP TABLE IF EXISTS public.history_main;
  39. -- -- CREATE SEQUENCE IF NOT EXISTS history_main_id_seq;
  40. -- CREATE TABLE IF NOT EXISTS public.history_main
  41. -- (
  42. -- geometry geometry NOT NULL,
  43. -- device_id Integer NOT NULL,
  44. -- id serial,
  45. -- state character varying(8) DEFAULT 0,
  46. -- valid_time timestamp(6) with time zone,
  47. -- unvalid_time timestamp(6) with time zone,
  48. -- type character varying(8) ,
  49. -- meta character varying(200) ,
  50. -- CONSTRAINT history_main_pkey PRIMARY KEY (id, device_id)
  51. -- ) PARTITION BY RANGE(device_id);
  52. -- ALTER TABLE IF EXISTS public.history_main
  53. -- OWNER to postgres;
  54. -- 根据list 创建分区表
  55. -- Table: public.history_main
  56. -- DROP TABLE IF EXISTS public.history_main;
  57. -- CREATE TABLE IF NOT EXISTS public.history_main
  58. -- (
  59. -- geometry geometry NOT NULL,
  60. -- device_id character varying(100) NOT NULL,
  61. -- id serial,
  62. -- state character varying(8) DEFAULT 0,
  63. -- valid_time timestamp(6) with time zone,
  64. -- unvalid_time timestamp(6) with time zone,
  65. -- type character varying(8) ,
  66. -- meta character varying(200) ,
  67. -- name character varying(100),
  68. -- CONSTRAINT history_main_pkey PRIMARY KEY (id, device_id)
  69. -- ) PARTITION BY LIST(device_id);
  70. -- -- 添加分区表
  71. -- CREATE TABLE device_0 PARTITION OF history_main FOR VALUES in('0');
  72. -- CREATE INDEX history_idx_device_0 ON device_0 USING btree(device_id);

优化目标

  1. 通过时间点查询每个设备的位置
  2. 通过时间段和设备id,查询设备在当前时间内的轨迹
  3. 通过name, 查询对应的设备id和轨迹
  4. 数据量很大时,对表的分区优化

数据表结构

根据需求主要由以下几个字段

字段 类型 约束和规则 备注
id serial 主键 自增主键
name varchar(50) 普通索引 名字,警员的名字或者警车的名字
device_id varchar(20) 分区键 设备id,可根据设备id进行分区
geom geometry
epsg:4549投影坐标
state enum enum{0,1,2,3,4,5,6} 设备状态:0关机;1运行中;2损坏:3未分配;4未知
valid_time timestamp 唯一索引 生效时间
unvalid_time timestamp 唯一索引 失效时间
meta varchar(200) 可以存储警务通视频源的地址

总的数据库优化思路:

创建分区表

根据psql12的新特性,使用声明式分区,创建history表时,将其声明成分区表,并对其进行分区

  1. -- Table: public.history_main
  2. DROP TABLE IF EXISTS public.history_main;
  3. CREATE TABLE IF NOT EXISTS public.history_main
  4. (
  5. id serial,
  6. geometry geometry NOT NULL,
  7. device_id character varying(100) NOT NULL,
  8. state character varying(8) DEFAULT 0,
  9. valid_time timestamp(6) with time zone,
  10. unvalid_time timestamp(6) with time zone,
  11. type character varying(8) ,
  12. meta character varying(200) ,
  13. CONSTRAINT history_main_pkey PRIMARY KEY (id, device_id)
  14. ) PARTITION BY LIST(device_id);
  15. -- -- 添加分区表
  16. CREATE TABLE device_0 PARTITION OF history_main FOR VALUES in('0');

看数据量,如果后续数据量过大,可以进行多级分区优化。但是分区表的子表不能超过1024。如果超过就修改配置文件中 /data/postgresql.conf 中的 max_locks_per_transaction 配置。

创建索引

创建索引https://blog.csdn.net/horses/article/details/85986558

  1. -- 对姓名创建hash索引, 只做相等查询
  2. CREATE INDEX history_idx_name ON history_main USING hash(name);
  3. -- 对生效时间创建btree索引, 需要做大小比较查询
  4. CREATE INDEX history_idx_valid_time ON history_main USING btree(valid_time);
  5. -- 对失效时间创建btree索引,需要做大小比较查询
  6. CREATE INDEX history_idx_unvalid_time ON history_main USING btree(unvalid_time);

事务优化

  1. select * from users;
  2. begin;
  3. update users set score = 50 where player = '库里';
  4. update users set score = 60 where player = '哈登';
  5. commit;
  6. select * from users;
  7. begin;
  8. update users set score = 0 where player = '库里';
  9. update users set score = 0 where player = '哈登';
  10. rollback;
  11. select * from users;

性能测试

添加测试数据

设置数据量:
实验设置:
普通存储,索引优化,分区,分区优化+索引优化
对比操作:初始化,更新,条件查询


操作时间
普通存储 初始化表结构和数据 11分19秒
更新设备点坐标
根据时间段和设备id查询轨迹 首次50s,后续30s左右
根据时间点查询各个设备的位置 首次14s,后续6s
根据名字和当期时间查询对应的设备位置
分区优化 初始化表结构和数据 7分22秒
更新设备点坐标
根据时间段和设备id查询轨迹 首次50s,后续的100ms
根据时间点查询各个设备的位置 首次8s,后续8s
根据名字和当期时间查询对应的设备位置
索引优化 初始化表结构和数据 12分47秒
更新设备点坐标
根据时间段和设备id查询轨迹
根据时间点查询各个设备的位置
根据名字和当期时间查询对应的设备位置
分区+索引优化 初始化表结构和数据 12分44秒
更新设备点坐标
根据时间段和设备id查询轨迹 首次50s,后续100ms
根据时间点查询各个设备的位置 首次7s,后续3s
根据名字和当期时间查询对应的设备位置
时间 普通存储 分区存储 索引优化 分区+索引优化
初始化和插入数据 11分19秒 7分22秒 12分47秒 12分44秒
根据时间段和设备id查询轨迹 首次50s,后续30s左右 首次50s,后续的100ms 首次50s,后续100ms
根据时间点查询各个设备的位置 首次14s,后续6s 首次8s,后续8s 首次7s,后续3s
测试环境 MBP M1 PRO 16G, pg14, 数据量1e8左右

创建不同类型的数据表

  1. -- 1. history_normal 普通表
  2. DROP TABLE IF EXISTS public.history_normal;
  3. CREATE TABLE IF NOT EXISTS public.history_normal
  4. (
  5. id serial NOT NULL,
  6. geometry geometry NOT NULL,
  7. device_id character varying(100) NOT NULL,
  8. state character varying(8) DEFAULT 0,
  9. valid_time timestamp(6) with time zone,
  10. unvalid_time timestamp(6) with time zone,
  11. type character varying(8) ,
  12. meta character varying(200) ,
  13. name character varying(100),
  14. CONSTRAINT history_normal_pkey PRIMARY KEY (id,device_id)
  15. );
  16. -- 2. history_index 索引表
  17. DROP TABLE IF EXISTS public.history_index;
  18. CREATE TABLE IF NOT EXISTS public.history_index
  19. (
  20. id serial NOT NULL,
  21. geometry geometry NOT NULL,
  22. device_id character varying(100) NOT NULL,
  23. state character varying(8) DEFAULT 0,
  24. valid_time timestamp(6) with time zone,
  25. unvalid_time timestamp(6) with time zone,
  26. type character varying(8) ,
  27. meta character varying(200) ,
  28. name character varying(100),
  29. CONSTRAINT history_index_pkey PRIMARY KEY (id,device_id)
  30. );
  31. -- 对姓名创建hash索引, 只做相等查询
  32. CREATE INDEX history_idx_name ON history_index USING hash(name);
  33. -- 对生效时间创建btree索引, 需要做大小比较查询
  34. CREATE INDEX history_idx_valid_time ON history_index USING btree(valid_time);
  35. -- 对失效时间创建btree索引,需要做大小比较查询
  36. CREATE INDEX history_idx_unvalid_time ON history_index USING btree(unvalid_time);
  37. -- 3. history_div 分区表
  38. DROP TABLE IF EXISTS public.history_div;
  39. CREATE TABLE IF NOT EXISTS public.history_div
  40. (
  41. geometry geometry NOT NULL,
  42. device_id character varying(100) NOT NULL,
  43. id serial,
  44. state character varying(8) DEFAULT 0,
  45. valid_time timestamp(6) with time zone,
  46. unvalid_time timestamp(6) with time zone,
  47. type character varying(8) ,
  48. meta character varying(200) ,
  49. name character varying(100),
  50. CONSTRAINT history_div_pkey PRIMARY KEY (id, device_id)
  51. ) PARTITION BY LIST(device_id);
  52. -- CREATE TABLE device_0 PARTITION OF history_main FOR VALUES in('0');
  53. -- 4. history_main 分区+索引
  54. DROP TABLE IF EXISTS public.history_main;
  55. CREATE TABLE IF NOT EXISTS public.history_main
  56. (
  57. geometry geometry NOT NULL,
  58. device_id character varying(100) NOT NULL,
  59. id serial,
  60. state character varying(8) DEFAULT 0,
  61. valid_time timestamp(6) with time zone,
  62. unvalid_time timestamp(6) with time zone,
  63. type character varying(8) ,
  64. meta character varying(200) ,
  65. name character varying(100),
  66. CONSTRAINT history_main_pkey PRIMARY KEY (id, device_id)
  67. ) PARTITION BY LIST(device_id);
  68. -- CREATE INDEX history_main_name ON history_main USING hash(name);
  69. -- 对生效时间创建btree索引, 需要做大小比较查询
  70. CREATE INDEX history_main_valid_time ON history_main USING btree(valid_time);
  71. -- 对失效时间创建btree索引,需要做大小比较查询
  72. CREATE INDEX history_main_unvalid_time ON history_main USING btree(unvalid_time);

测试插入模拟数据

  1. -- 创建测试数据
  2. DROP VIEW IF EXISTS public.test_data;
  3. create view test_data as
  4. select device_id::character varying(100) as device_id, point(random() * (518373-515834) + 515834, random() * (3690194 - 3688409)+ 3688409)::geometry as geom, valid_time, unvalid_time,
  5. device_id::character varying(100) as name,
  6. ''::character varying(8) as state,
  7. ''::character varying(8) as type,
  8. ''::character varying(200) as meta
  9. from (select generate_series(0,999) as device_id, point(random() * (518373-515834) + 515834, random() * (3690194 - 3688409)+ 3688409)::geometry as geom) AS tb1
  10. CROSS JOIN
  11. (select
  12. generate_series('2020-04-28T12:00:00'::timestamp(6) with time zone, '2022-04-028T12:9:59'::timestamp(6) with time zone, '1 hours') as valid_time,
  13. generate_series('2020-04-28T12:10:00'::timestamp(6) with time zone, '2022-04-028T12:19:59'::timestamp(6) with time zone, '1 hours') as unvalid_time ) as t3;
  14. truncate table history_main;
  15. truncate table history_div;
  16. truncate table history_index;
  17. truncate table history_normal;
  18. -- 测试插入普通表
  19. INSERT INTO history_normal(geometry, device_id, state, valid_time, unvalid_time ,type, meta, name)
  20. select geom, device_id , state ,valid_time ,unvalid_time , type, meta , name
  21. from test_data;
  22. -- 测试插入索引优化表
  23. INSERT INTO history_index(geometry, device_id, state, valid_time, unvalid_time ,type, meta, name)
  24. select geom, device_id , state ,valid_time ,unvalid_time , type, meta , name
  25. from test_data;
  26. -- 测试分区优化表
  27. INSERT INTO history_div(geometry, device_id, state, valid_time, unvalid_time ,type, meta, name)
  28. select geom, device_id , state ,valid_time ,unvalid_time , type, meta , name
  29. from test_data;
  30. -- 测试索引+分区优化表
  31. INSERT INTO history_main(geometry, device_id, state, valid_time, unvalid_time ,type, meta, name)
  32. select geom, device_id , state ,valid_time ,unvalid_time , type, meta , name
  33. from test_data;
表类型 普通表 索引优化 分区优化 分区+索引优化
测试数据插入用时 1’35 11’30 3’1 3’55
根据时间段和id 查询 3’’328 2’’781 4’’576 1’’911
根据时间点查找设备 2’’913 3’’353 3’’268 1’’77
测试环境 数据量1.7e7,pg12, AMD Ryzen 5 3400G,16G内存

条件查询

  1. -- 根据时间段和id 查询轨迹
  2. select device_id, geometry, valid_time, unvalid_time from public.history_normal
  3. where device_id = (floor(random() * 1000)::varchar)
  4. and valid_time > '2022-1-1T0:0:0'
  5. and unvalid_time < '2022-4-28T0:0:0';
  6. -- 根据时间点查询所有设备的位置
  7. select device_id, geometry, valid_time, unvalid_time from history_normal
  8. where valid_time <= '2022-1-1T0:0:0'
  9. and unvalid_time > '2022-1-1T0:0:0';