提升阅读的目的性,带着问题阅读,效率更高。
每章节开始阅读前,问自己为什么要看这章,想通过阅读这节内容获取到什么信息,阅读完后,总结内容,看是否能回答这些问题。
第一章 introduction
问题:了解这本书讲了啥?为什么要看这本书?
这本书主要目的是了解不同类型的7个数据库,以帮助你选择数据库或者数据库组合来最优的解决你的问题。
本章节的主要内容:
-
noSQL DB 已经蓬勃发展,但是SQL DB仍然大量存在并有它的使用场景,同时作为基础与noSQL数据库进行对比
-
本书涉及的数据库类型有哪些:
- relational: PostgreSQL, mysql, SQLite
- key-value: Redis,DynamoDB,memcached,Riak
- columnar: Hbase,Cassandra
- document-oriented: MongoDB, CouchDB
- graph: Neo4j
-
了解每种类型数据库用于解决哪种类型的问题,它的使用场景以及它的能力和限制是什么,以帮助我们做判断
-
当你选择一个数据库时,你应该问自己应该使用它吗(是最好的选择吗),而不是我可以使用它吗
-
发展的方向:没有任何一个数据库可以适应所有使用场景,数据库会越来越往专业化场景发展,多个数据库会一起联合使用,
组合他们的长处,形成一个更强大的生态系统
第二章 PostgreSQL
问题:介绍了PG的哪些内容? PG有哪些独特的特性?他的能力和限制分别是什么?什么场景应该使用PG?PG是如何存储和索引数据的?与MySQL 相比,他的优势和劣势在哪里?
Day 1:Relations, CRUD, Joins
- MacOS 使用installer安装,参考菜鸟教程 https://www.runoob.com/postgresql/postgresql-tutorial.html
- 常用操作:
- createdb 7dbs;
- psql 7dbs;
- create table;
- CRUD
- Join:
- Inner join : 求交集 SELECT e.title, v.name FROM events e JOIN venues v ON e.venue_id = v.venue_id;
- left join:求左并集 SELECT e.title, v.name FROM events e LEFT JOIN venues v ON e.venue_id = v.venue_id;
- right join: 求右并集
- full join : 求全集
- create index:
- hash index: CREATE INDEX events_title ON events USING hash (title);
- btree index: CREATE INDEX events_starts ON events USING btree (starts);
- Foreign key, primary key
Day 2 Advance Query, Code, and Rules
-
aggregate function
-
count: SELECT count(title) FROM events WHERE title LIKE ‘%Day%’;
-
min, max
SELECT min(starts), max(ends) FROM events INNER JOIN venues
ON events.venue_id = venues.venue_id WHERE venues.name = ‘Crystal Ballroom’;
-
-
group by
-
having
SELECT venue_id FROM events GROUP BY venue_id HAVING count(*) >= 2 AND venue_id IS NOT NULL;
-
distinct: SELECT DISTINCT venue_id FROM events;
-
-
Window function
- over partition by: SELECT title, count(*) OVER (PARTITION BY venue_id) FROM events;
-
transaction
- ACID,
- begin transaction; end;
-
Store Procedures
- 存储过程功能强大,可用于执行各种任务,从执行SQL中不支持的复杂数学运算到触发级联的事件序列,再到将数据写入表之前进行预验证,远远不止于此。一方面,存储过程可以提供巨大的性能优势。 但是架构成本可能很高(有时不值得)。慎重考虑
- 让我们创建一个过程(或FUNCTION),以简化在场地上插入新事件的过程,而不需要场地编号。 该过程将完成以下步骤:如果不存在该场地,则将首先创建该场地,然后在新事件中对其进行引用。 该过程还将返回一个布尔值,指示是否添加了新场所作为有用的奖励。
#postgres/add_event.sql, 使用PL/pgSQL 语言;他还支持PL/Tcl, PL/perl, PL/python CREATE OR REPLACE FUNCTION add_event( title text, starts timestamp, ends timestamp, venue text, postal varchar(9), country char(2)) RETURNS boolean AS $$ DECLARE did_insert boolean := false; found_count integer; the_venue_id integer; BEGIN SELECT venue_id INTO the_venue_id FROM venues v WHERE v.postal_code=postal AND v.country_code=country AND v.name ILIKE venue LIMIT 1; IF the_venue_id IS NULL THEN INSERT INTO venues (name, postal_code, country_code) VALUES (venue, postal, country) RETURNING venue_id INTO the_venue_id; did_insert := true; END IF; -- Note: this is a notice, not an error as in some programming languages RAISE NOTICE 'Venue found %', the_venue_id; INSERT INTO events (title, starts, ends, venue_id) VALUES (title, starts, ends, the_venue_id); RETURN did_insert; END; $$ LANGUAGE plpgsql;
7dbs=# \i add_event.sql
This stored procedure is run as a SELECT statement.
SELECT add_event(‘House Party’, ‘2018-05-03 23:00’, ‘2018-05-04 02:00’, ‘Run’’s House’, ‘97206’, ‘us’);
-
Pull the triggers
-
发生某些事件(例如插入或更新)时,触发器会自动触发存储过程。 它们允许数据库强制执行某些必需的行为以响应不断变化的数据
-
让我们创建一个新的PL / pgSQL函数,该函数会在事件更新时进行记录。
- 首先,创建一个日志表来存储事件更改。 这里不需要主键,因为它只是一个日志。
CREATE TABLE logs ( event_id integer, old_title varchar(255), old_starts timestamp, old_ends timestamp, logged_at timestamp DEFAULT current_timestamp
);
- Next, we build a function to insert old data into the log.
OLD变量代表将要更改的行(NEW代表传入的行)
#postgres/log_event.sql CREATE OR REPLACE FUNCTION log_event() RETURNS trigger AS $$ DECLARE BEGIN INSERT INTO logs (event_id, old_title, old_starts, old_ends) VALUES (OLD.event_id, OLD.title, OLD.starts, OLD.ends); RAISE NOTICE 'Someone just changed event #%, #%, #%', OLD.event_id, OLD.ends, NEW.ends; RETURN NEW; END; $$ LANGUAGE plpgsql;
- Finally, we create our trigger to log changes after any row is updated.
CREATE TRIGGER log_events AFTER UPDATE ON events FOR EACH ROW EXECUTE PROCEDURE log_event();
-
UPDATE events SET ends=’2018-05-04 01:00:00’ WHERE title=’House Party’;
-
And the old end time was logged.
SELECT event_id, old_title, old_ends, logged_at FROM logs;
-
Triggers can be created before or after updates and before or after inserts
-
-
Viewing the world
-
视图是可以像其他任何表一样使用的复杂查询的结果;
-
假设我们只想查看包含“ Day”一词且没有地点的假期:
CREATE VIEW holidays AS SELECT event_id AS holiday_id, title AS name, starts AS date FROM events WHERE title LIKE ‘%Day%’ AND venue_id IS NULL;
在幕后它还是是简单的旧 events 表, 可以往events 表插入一行数据再查询holiday表来证明。
如果要向视图添加新列,则该列必须来自基础表。
首先,在基础表上增加 新列
ALTER TABLE events ADD colors text ARRAY;
然后,更新视图定义:
CREATE OR REPLACE VIEW holidays AS SELECT event_id AS holiday_id, title AS name, starts AS date, colors FROM events WHERE title LIKE ‘%Day%’ AND venue_id IS NULL;
-
无法直接更新视图。
-
创建物化视图以提升性能 CREATE MATERIALIZED VIEW, 它们存储在磁盘上的“实际”表中
每当您为它们运行REFRESH命令时,就会填充物化视图表,您可以自动以定义的时间间隔或响应触发器来运行它们。
您也可以像在常规表上一样在成熟的视图上创建索引。 物化视图的缺点是它们确实会增加磁盘空间使用量。但是在许多情况下,性能提升是值得的
-
-
RULEs
规则描述了如何更改已解析的查询树。 每次Postgres运行一条SQL语句时,它都会将该语句解析为一个查询树(通常称为抽象语法树)
实际上,视图就是一个规则;通过explain verbose 查看下面两个语句的执行计划,可以发现他们的功能是一样的(输出一样):
EXPLAIN VERBOSE SELECT * FROM holidays;
EXPLAIN VERBOSE SELECT event_id AS holiday_id, title AS name, starts AS date, colors FROM events
WHERE title LIKE ‘%Day%’ AND venue_id IS NULL;
因此,为了允许我们对视图进行更新,需要制定一个规则来告诉Postgres如何处理UPDATE。 我们的规则将捕获对视图的更新,替换为对基础表运行更新,从伪关系NEW和OLD中提取值。 NEW 包含设置的值,而OLD包含查询的值。
# postgres/create_rule.sql CREATE RULE update_holidays AS ON UPDATE TO holidays DO INSTEAD UPDATE events SET title = NEW.name, starts = NEW.date, colors = NEW.colors WHERE title = OLD.name;
之后我们就可以在视图上进行更新:
UPDATE holidays SET colors = ‘{“red”,”green”}’ where name = ‘Christmas Day’;
创建插入规则
create rule insert_holidays as on insert to holidays do instead insert into events(title, starts, colors)
values(NEW.name, NEW.date, NEW.colors);
执行插入:
insert into holidays(name, date, colors) values(‘New Year Day’, ‘2022-01-01 00:00:00’, ‘{“yellow”, “black”}’);
-
Crosstab
按月统计事件数,貌似不支持了,后续查一下文档
(这个功能很强大,可以用于辅助习惯系统的实现,展示每一天,每个习惯的完成情况)
-
总结
-
homework
Day 3 full text and multidimensionals
-
实现一个电影查询系统,电影推荐系统,支持模糊匹配
-
扩展模块的使用:在数据库中使用 create extension module_name
-
模糊匹配
-
LIKE,ILIKE(case-insensitive version LIKE)
% matches any number of any characters while _ matches exactly one character
SELECT title FROM movies WHERE title ILIKE ‘stardust_%’; // not at the end of
-
在Postgres中,正则表达式匹配由〜运算符引导,带有可选的!(表示不匹配)和*(表示不区分大小写)
SELECT COUNT() FROM movies WHERE title !~ ‘^the.*’; // not begin with ‘the’
可以通过创建 text_pattern_ops运算符类别索引来为 模式与先前查询匹配的字符 串编制索引,只要这些值以小写形式编制索引即可。
CREATE INDEX movies_title_pattern ON movies (lower(title) text_pattern_ops);
不同的字段类型对应不同的运算符类别索引,
varchars, chars, name 对应 varchar_pattern_ops, bpchar_pattern_ops, and name_pattern_ops.
-
fuzzystrmatch 扩展包中的levenshtein函数可以计算两个字符间的编辑距离
create extension fuzzystrmatch;
select levenshtein(‘bat’, ‘fads’); // 3
SELECT movie_id, title FROM movies WHERE levenshtein(lower(title), lower(‘a hard day nght’)) <= 3;
字符大小写也会增加距离,因此可以在计算之前转为相同的大写或者小写模式
-
Trigram 三字母组是从字符串中提取的一组三个连续的字符。 pg_trgm contrib模块将字符串分成尽可能多的三字组
查找匹配的字符串就像计算匹配的三字母组的数目一样简单。 匹配度最高的字符串最相似。 如果您可以进行轻微的拼写错误或什至遗漏了少量单词的搜索,这对进行搜索非常有用。 字符串越长,trigram越多,匹配的可能性就越大,这对电影标题之类的东西非常有用,因为它们的长度相对相似。 我们将使用通用索引搜索树(GIST)创建针对电影名称的Trigram索引,该索引是PostgreSQL引擎提供的通用索引API。
create extension pg_trgm;
select show_trgm(‘avatar’); // {“ a”,” av”,”ar “,ata,ava,tar,vat}
CREATE INDEX movies_title_trigram ON movies USING gist (title gist_trgm_ops);
SELECT title FROM movies WHERE title % ‘Avatre’;
Trigram是接受用户输入而不用通配符复杂性权衡查询的绝佳选择。
-
-
全文搜索: full-text searching
-
TSVector 和 TSQuery
Let’s look for a movie that contains the words night and day.
This is a perfect job for text search using the @@ full-text query operator.
SELECT title FROM movies WHERE title @@ ‘night & day’;结果如下
title
A Hard Day’s Night Six Days Seven Nights Long Day’s Journey Into Night
尽管单词Day是所有格形式,并且两个单词在查询中顺序混乱,但该查询仍返回诸如A Hard Day’s Night之类的标题。 @@运算符将名称字段转换为tsvector,并将查询转换为tsquery。
tsvector是一种数据类型,可将字符串拆分为token数组(或向量),并针对给定查询进行搜索,而tsquery代表在某种语言中(例如英语或法语)的查询。 该语言对应于字典(后文介绍)。 上一个查询与以下查询等效(如果您的系统语言设置为英语):
SELECT title FROM movies WHERE to_tsvector(title) @@ to_tsquery(‘english’, ‘night & day’);
看下这两个函数是如何工作的:
SELECT to_tsvector(‘A Hard Day’’s Night’), to_tsquery(‘english’, ‘night & day’);
to_tsvector | to_tsquery ---------------------------+----------------- 'day':3 'hard':2 'night':5 | 'night' & 'day'
后面的数字代表他们在短语中的位置。
注意到 a 字符并没有包含在结果中,如果试图通过这些简单的英语单词来查找,结果将被忽略。
这些简单的英语单词叫stop words( 停用词),可以在pg安装目录下用如下命令查看所有的stop words
cat share/postgresql/tsearch_data/english.stop
可以将a从上面的列表中移除,或者使用simple字典,如:
SELECT to_tsvector(‘simple’, ‘A Hard Day’’s Night’);
to_tsvector ---------------------------------------- 'a':1 'day':3 'hard':2 'night':5 's':4
Simple 只是将字符串用非单词字符分解并使其小写
-
其他语言
每种语言的解析配置都不一样,可以用下面的命令查看所有配置
\dF
查看所有词典:
\dFd
使用ts_lexize 函数测试任意词典:
select ts_lexize(‘english_stem’, ‘Day’’s’); // stem 词干
一下命令也可以用于其他语言,如德语:
SELECT to_tsvector(‘german’, ‘was machst du gerade?’);
-
索引词素 indexing lexemes
EXPLAIN SELECT * FROM movies WHERE title @@ ‘night & day’;
EXPLAIN SELECT * FROM movies WHERE title @@ 'night & day'; QUERY PLAN --------------------------------------------------------------------------- Seq Scan on movies (cost=0.00..815.86 rows=3 width=171) Filter: (title @@ 'night & day'::text)
可以看到会进行全表扫描。
使用广义倒排索引GIN 为 lexemes value 创建索引
CREATE INDEX movies_title_searchable ON movies USING gin(to_tsvector(‘english’, title));
并在where子句指定使用ts_vector查询,再看一下explain结果:
EXPLAIN SELECT * FROM movies WHERE to_tsvector(‘english’,title) @@ ‘night & day’;
QUERY PLAN -------------------------------------------------------------------------------------------------- Bitmap Heap Scan on movies (cost=12.00..16.26 rows=1 width=68) Recheck Cond: (to_tsvector('english'::regconfig, title) @@ '''night'' & ''day'''::tsquery) -> Bitmap Index Scan on movies_title_searchable (cost=0.00..12.00 rows=1 width=0) Index Cond: (to_tsvector('english'::regconfig, title) @@ '''night'' & ''day'''::tsquery)
-
metaphones
我们努力匹配不太具体的输入。 像LIKE和正则表达式需要能够根据其格式精确匹配字符串的制作模式。 Levenshtein距离使您可以查找包含较小拼写错误但最终必须非常接近同一字符串的匹配项。 Trigram是查找合理的拼写错误的匹配的不错选择。 最后,全文搜索具有自然的语言灵活性,因为它可以忽略诸如a和the之类的次要单词,并且可以处理复数形式。 有时我们只是不知道如何正确拼写单词,但我们知道它们的发音。 我们爱布鲁斯·威利斯(Bruce Willis),很想看看他的电影。不幸的是,我们不记得确切地拼写他的名字,所以我们会尽力而为。
SELECT * FROM actors WHERE name = ‘Broos Wils’;
Even a trigram is no good here (using % rather than =).
SELECT * FROM actors WHERE name % ‘Broos Wils’;
输入metaphones,这是用于创建单词声音的字符串表示形式的算法。 您可以定义输出字符串中有多少个字符。 例如,名称为Aaron Eckhart的七个字符的变音素是ARNKHRT。 要查找所有演员的名字听起来像是布鲁斯·威尔斯的电影,我们可以查询metaphones输出。 请注意,NATURAL JOIN是一个INNER JOIN,它会自动将ON匹配的列名称连接起来(例如,movies.actor_id = movies_actors.actor_id)。
SELECT title FROM movies NATURAL JOIN movies_actors NATURAL JOIN actors
WHERE metaphone(name, 6) = metaphone(‘Broos Wils’, 6);
title ----------------------------- The Fifth Element Twelve Monkeys Armageddon Die Hard Pulp Fiction The Sixth Sense
如果您浏览在线文档,将会看到Fuzzystrmatch模块包含其他函数:dmetaphone()(双音素),dmetaphone_alt()(用于替代名称发音)和soundex()(这是1880年代真正的老算法 由美国人口普查比较常见的美国姓氏)。 您可以通过选择函数的输出来剖析函数的表示形式
SELECT name, dmetaphone(name), dmetaphone_alt(name), metaphone(name, 8), soundex(name) FROM actors;
name | dmetaphone | dmetaphone_alt | metaphone | soundex ----------------+------------+----------------+-----------+-------- 50 Cent | SNT | SNT | SNT | C530 Aaron Eckhart | ARNK | ARNK | ARNKHRT | A652 Agatha Hurle | AK0R | AKTR | AK0HRL | A236
-
组合使用字符串匹配 metaphone的最灵活的方面之一是它们的输出只是字符串。这使您可以与其他字符串匹配器混合和匹配。 例如,我们可以将trigram运算符用于metaphone()输出,然后按最低的Levenshtein距离对结果进行排序。 这意味着“按顺序输出让我听起来最像罗宾·威廉姆斯的名字。”
SELECT * FROM actors WHERE metaphone(name,8) % metaphone(‘Robin Williams’,8)
ORDER BY levenshtein(lower(‘Robin Williams’), lower(name));
-
作为多维超立方体的genres (题材,电影类型) 我们研究的最后一个贡献包是多维数据集(cube)。 我们将使用多维数据集(cube)数据类型将电影的类型映射为多维矢量。 然后,我们将使用一些方法来高效地查询超立方体边界内的最接近点,从而为我们提供类似电影的列表。 正如您可能在第3天开始时注意到的那样,我们创建了一个名为genres的cube类型的列。 每个值都是18维空间中的一个点,每个维代表一种电影题材。 为什么将电影题材表示为n维空间中的点? 电影归类并不是一门精确的科学,许多电影也不是100%喜剧或100%悲剧,它们介于两者之间。
在我们的系统中,根据电影在该genres中的强弱程度,将每种genres的得分(从任意数字中得出)从0到10,其中0表示不存在,而最强则为10。例如《星球大战》的风格向量为(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)。 genres表描述矢量中每个维度的位置。 我们可以通过使用每个genres.position提取cube_ur_coord(vector,dimension)来得到其genres值。 为了清楚起见,我们过滤掉得分为0的genres。
SELECT name, cube_ur_coord(‘(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)’, position) as scoreFROM genres g WHERE cube_ur_coord(‘(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)’, position) > 0;
name | score -----------+------- Adventure | 7 Fantasy | 7 SciFi | 10
通过找到最近的点,我们将找到类似的电影, 如下图所示
我们可以用更多的genres(2、3或18)将其外推到更多维度。原理是相同的:genres空间中最接近点的最近邻居匹配将产生最接近的genres匹配。 与geners向量最接近的匹配项可以通过cube_distance(point1,point2)发现。 在这里,我们可以找到所有电影与《星球大战》genres向量的距离,最接近的距离优先。
SELECT *, cube_distance(genre, ‘(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)’) dist FROM movies ORDER BY dist;
在创建表时我们已经创建了movies_genres_cube多维数据集(cube)索引。 但是,即使有索引,此查询仍然相对较慢,因为它需要全表扫描。 它计算每一行的距离,然后对它们进行排序。 与其计算每个点的距离,不如通过边界立方体专注于可能的点。 就像在地图上查找最近的五个城镇在州地图上比在世界地图上查找要快一样,边界减少了我们需要查看的点。 我们使用cube_enlarge(cube,radius,dimensions)来构建一个18维多维数据集,该多维数据集的长度(半径)比点宽。 让我们来看一个简单的例子。 如果我们在点(1,1)周围构建一个二维正方形,则正方形的左下点将在(0,0),右上点将在(2,2)。
SELECT cube_enlarge(‘(1,1)’,1,2);
cube_enlarge --------------- (0, 0),(2, 2)
相同的原理适用于任何数量的维度。 使用我们的边界超多维数据集,我们可以使用特殊的多维数据集运算符 @>,它表示包含。 此查询查找“星球大战” 的genre point所在的五单元立方体中包含的所有点的距离。
SELECT title, cube_distance(genre, ‘(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)’) dist FROM movies WHERE cube_enlarge(‘(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)’::cube, 5, 18) @> genre ORDER BY dist;
title | dist ------------------------------------------------+------------------ Star Wars | 0 Star Wars: Episode V - The Empire Strikes Back | 2 Avatar | 5 Explorers | 5.74456264653803 Krull | 6.48074069840786 E.T. The Extra-Terrestrial | 7.61577310586391
使用子选择,我们可以按电影名称获取类型,并使用表别名针对该类型执行计算。
SELECT m.movie_id, m.title FROM movies m, (SELECT genre, title FROM movies WHERE title = ‘Mad Max’) s
WHERE cube_enlarge(s.genre, 5, 18) @> m.genre AND s.title <> m.title ORDER BY cube_distance(m.genre, s.genre) LIMIT 10;
movie_id | title ----------+---------------------------- 1405 | Cyborg 1391 | Escape from L.A. 1192 | Mad Max Beyond Thunderdome 1189 | Universal Soldier 1222 | Soldier 1362 | Johnny Mnemonic 946 | Alive 418 | Escape from New York 1877 | The Last Starfighter 1445 | The Rocketeer
这种建议电影的方法并不完美,但这是一个很好的起点。 我们将在后面的章节中看到更多的维度查询,例如MongoDB中的二维地理搜索
-
总结
PostgreSQL的优势与任何关系模型一样多:数年的研究和生产在几乎每个计算领域中都有使用,灵活的可查询性以及非常一致且持久的数据。大多数编程语言都对Postgres进行了久经考验的驱动程序支持,并且许多编程模型(例如对象关系映射(ORM))都采用了基础关系数据库。 但是,问题的关键还是在于联接的灵活性。您不需要知道如何计划实际查询模型,因为您始终可以执行一些联接,过滤器,视图和索引,这很奇妙,因为您始终可以提取所需的数据。 PostgreSQL对于我们所谓的“ Stepford数据”(以“ Stepford妻子”命名,有关一个几乎每个人在风格和内容上都是一致的邻域的故事)来说都是很棒的,它是相当均匀的数据,并且非常符合结构化模式。 此外,PostgreSQL超越了常规的开源RDBMS产品,例如强大的模式约束机制。您可以编写自己的语言扩展,自定义索引,创建自定义数据类型,甚至覆盖传入查询的解析。在其他开源数据库可能具有复杂许可协议的地方,PostgreSQL是最纯粹的开源形式。没有人拥有代码。任何人都可以在该项目中做任何他们想做的事情(除了要求作者承担责任外)。开发和发行完全由社区支持。如果您是自由(dom)软件的忠实拥护者,则必须尊重他们普遍反对在出色产品上兑现的阻力。
PostgreSQL的弱点 尽管无可否认,关系数据库多年来一直是最成功的数据库风格,但在某些情况下它可能不太适合。
分区不是诸如PostgreSQL之类的关系数据库的优势之一。 如果您需要横向扩展而不是纵向扩展,多个并行数据库而不是单个强大的计算机或集群,寻找其他数据库可能会更好。 如果满足以下条件,则另一个数据库可能更合适: •您实际上并不需要整个数据库的开销(也许只需要像Redis这样的缓存)。 •您需要大量读取和写入作为键值。
•您只需要存储大的BLOB数据
离别的想法 关系数据库是提高查询灵活性的绝佳选择。 尽管PostgreSQL要求您预先设计数据,但不对如何使用这些数据做任何假设。 只要以相当规范的方式设计模式,而不会重复或存储可计算的值,则通常已经为任何查询做好了准备。 而且,如果您包括正确的模块,调整引擎并建立良好的索引,则它将以非常少的资源消耗对数TB的数据表现出惊人的性能。 最后,对于那些对数据安全至关重要的人来说,PostgreSQL的ACID兼容事务可确保您的提交是完全原子的,一致的,隔离的和持久的。