大家好。 在五月假期的第二部分开始之前,我们与您分享我们翻译的材料,以期在课程中推出新的课程
应用程序开发人员花费大量时间比较多个操作数据库,以选择最适合预期工作负载的数据库。 需求可能包括简化的数据建模、事务保证、读/写性能、水平扩展和容错。 传统上,选择从数据库类别(SQL 或 NoSQL)开始,因为每个类别都提出了一组明确的权衡。 低延迟和高吞吐量方面的高性能通常被视为不可权衡的要求,因此对于任何示例数据库都是至关重要的。
本文的目的是帮助应用程序开发人员在应用程序数据建模的背景下在 SQL 和 NoSQL 之间做出正确的选择。 我们将介绍一个 SQL 数据库(即 PostgreSQL)和两个 NoSQL 数据库(Cassandra 和 MongoDB),以涵盖数据库设计的基础知识,例如创建表、填充表、从表中读取数据以及删除表。 在下一篇文章中,我们一定会了解索引、事务、JOIN、TTL 指令和基于 JSON 的数据库设计。
SQL 和 NoSQL 有什么区别?
SQL 数据库通过 ACID 事务保证以及在现有规范化关系数据库模型之上以意想不到的方式使用 JOIN 查询数据的能力,提高了应用程序的灵活性。
鉴于其整体/单节点架构和使用主从复制模型来实现冗余,传统 SQL 数据库缺乏两个重要功能 - 线性写入可扩展性(即跨多个节点的自动分区)和自动/零数据丢失。 这意味着接收的数据量不能超过单个节点的最大写入吞吐量。 此外,容错(在无共享架构中)必须考虑一些临时数据丢失。 这里您需要记住,最近的提交尚未反映在从属副本中。 SQL数据库中也很难实现非停机更新。
NoSQL 数据库通常本质上是分布式的,即其中,数据被分为多个部分并分布在多个节点上。 它们需要非规范化。 这意味着输入的数据还必须复制多次才能响应您发送的特定请求。 总体目标是通过减少读取期间可用的分片数量来获得高性能。 这意味着 NoSQL 要求您对查询建模,而 SQL 要求您对数据建模。
NoSQL 专注于在分布式集群中实现高性能,这是许多数据库设计权衡的基本原理,包括 ACID 事务丢失、JOIN 和一致的全局二级索引。
有一种观点认为,虽然 NoSQL 数据库提供线性写入可扩展性和高容错能力,但事务保证的丧失使它们不适合任务关键型数据。
下表显示了 NoSQL 中的数据建模与 SQL 的不同之处。
SQL 和 NoSQL:为什么两者都需要?
拥有大量用户的现实世界应用程序(例如 Amazon.com、Netflix、Uber 和 Airbnb)的任务是执行复杂的多方面任务。 例如,像 Amazon.com 这样的电子商务应用程序需要存储轻量级、高关键性的数据,例如用户信息、产品、订单、发票,以及大量、不太敏感的数据,例如产品评论、支持消息、用户活动、用户评论和建议。 当然,这些应用程序依赖于至少一个 SQL 数据库以及至少一个 NoSQL 数据库。 在跨区域和全球系统中,NoSQL 数据库充当地理分布式缓存,用于存储在单个区域中运行的可信源 SQL 数据库中的数据。
YugaByte DB 如何结合 SQL 和 NoSQL?
YugaByte DB 基于面向日志的混合存储引擎、自动分片、分片分布式共识复制和 ACID 分布式事务(受 Google Spanner 启发)构建,是世界上第一个同时兼容 NoSQL(Cassandra 和 Redis)和SQL(PostgreSQL)。 如下表所示,兼容 Cassandra 的 YugaByte DB API YCQL 在 NoSQL API 中添加了单键、多键 ACID 事务和全局二级索引的概念,从而开启了事务型 NoSQL 数据库时代。 此外,YCQL(与 PostgreSQL 兼容的 YugaByte DB API)在 SQL API 中添加了线性写入扩展和自动容错的概念,将分布式 SQL 数据库带入世界。 由于 YugaByte DB 本质上是事务性的,因此 NoSQL API 现在可以在关键任务数据的上下文中使用。
正如之前文章中所说
- 如果您的主要工作负载是多键 JOIN 操作,那么在选择 YSQL 时,请了解您的键可能分布在多个节点上,从而导致比 NoSQL 更高的延迟和/或更低的吞吐量。
- 否则,请选择两个 NoSQL API 之一,请记住,由于一次从一个节点提供查询,您将获得更好的性能。 YugaByte DB 可以用作需要同时管理多个工作负载的现实复杂应用程序的单一操作数据库。
下一节中的数据建模实验室基于 PostgreSQL 和 Cassandra API 兼容的 YugaByte DB 数据库,而不是本机数据库。 这种方法强调与同一数据库集群的两个不同 API(在两个不同端口上)进行交互的便捷性,而不是使用两个不同数据库的完全独立的集群。
在以下部分中,我们将了解数据建模实验室,以说明所涉及数据库的差异和一些共同点。
数据建模实验室
数据库安装
鉴于对数据模型设计(而不是复杂的部署架构)的重视,我们将在本地计算机上的 Docker 容器中安装数据库,然后使用它们各自的命令行 shell 与它们进行交互。
PostgreSQL 和 Cassandra 兼容 YugaByte DB 数据库
mkdir ~/yugabyte && cd ~/yugabyte
wget https://downloads.yugabyte.com/yb-docker-ctl && chmod +x yb-docker-ctl
docker pull yugabytedb/yugabyte
./yb-docker-ctl create --enable_postgres
MongoDB的
docker run --name my-mongo -d mongo:latest
命令行访问
让我们使用相应 API 的命令行 shell 连接到数据库。
PostgreSQL的
docker exec -it yb-postgres-n1 /home/yugabyte/postgres/bin/psql -p 5433 -U postgres
卡桑德拉
cqlsh
在目录中 bin
.
请注意,CQL 受到 SQL 的启发,并且具有类似的表、行、列和索引概念。 然而,作为一种 NoSQL 语言,它增加了一定的限制,其中大部分我们也将在其他文章中介绍。
docker exec -it yb-tserver-n1 /home/yugabyte/bin/cqlsh
MongoDB的
docker exec -it my-mongo bash
cd bin
mongo
创建一个表
现在我们可以使用命令行与数据库交互来执行各种操作。 让我们首先创建一个表来存储有关不同艺术家创作的歌曲的信息。 这些歌曲可能是专辑的一部分。 歌曲的可选属性还有发行年份、价格、流派和评级。 我们需要通过“标签”字段考虑将来可能需要的其他属性。 它可以以键值对的形式存储半结构化数据。
PostgreSQL的
CREATE TABLE Music (
Artist VARCHAR(20) NOT NULL,
SongTitle VARCHAR(30) NOT NULL,
AlbumTitle VARCHAR(25),
Year INT,
Price FLOAT,
Genre VARCHAR(10),
CriticRating FLOAT,
Tags TEXT,
PRIMARY KEY(Artist, SongTitle)
);
卡桑德拉
在 Cassandra 中创建表与 PostgreSQL 非常相似。 主要区别之一是缺乏完整性约束(例如 NOT NULL),但这是应用程序的责任,而不是 NoSQL 数据库的责任。 主键由一个分区键(下例中的 Artist 列)和一组集群列(下例中的 SongTitle 列)组成。 分区键确定应将行放入哪个分区/分片,而集群列指示应如何在当前分片内组织数据。
CREATE KEYSPACE myapp;
USE myapp;
CREATE TABLE Music (
Artist TEXT,
SongTitle TEXT,
AlbumTitle TEXT,
Year INT,
Price FLOAT,
Genre TEXT,
CriticRating FLOAT,
Tags TEXT,
PRIMARY KEY(Artist, SongTitle)
);
MongoDB的
MongoDB 将数据组织到数据库(Database)(类似于 Cassandra 中的 Keyspace),其中包含包含 Document(类似于表中的行)的 Collection(类似于表)。 在MongoDB中,基本上不需要定义初始模式。 团队 “使用数据库”如下所示,在第一次调用时实例化数据库并更改新创建的数据库的上下文。 甚至集合也不需要显式创建;只需将第一个文档添加到新集合中,它们就会自动创建。 请注意,MongoDB 默认使用测试数据库,因此任何不指定特定数据库的集合级操作都会默认在该数据库上运行。
use myNewDatabase;
获取有关表的信息
PostgreSQL的
d Music
Table "public.music"
Column | Type | Collation | Nullable | Default
--------------+-----------------------+-----------+----------+--------
artist | character varying(20) | | not null |
songtitle | character varying(30) | | not null |
albumtitle | character varying(25) | | |
year | integer | | |
price | double precision | | |
genre | character varying(10) | | |
criticrating | double precision | | |
tags | text | | |
Indexes:
"music_pkey" PRIMARY KEY, btree (artist, songtitle)
卡桑德拉
DESCRIBE TABLE MUSIC;
CREATE TABLE myapp.music (
artist text,
songtitle text,
albumtitle text,
year int,
price float,
genre text,
tags text,
PRIMARY KEY (artist, songtitle)
) WITH CLUSTERING ORDER BY (songtitle ASC)
AND default_time_to_live = 0
AND transactions = {'enabled': 'false'};
MongoDB的
use myNewDatabase;
show collections;
将数据输入表中
PostgreSQL的
INSERT INTO Music
(Artist, SongTitle, AlbumTitle,
Year, Price, Genre, CriticRating,
Tags)
VALUES(
'No One You Know', 'Call Me Today', 'Somewhat Famous',
2015, 2.14, 'Country', 7.8,
'{"Composers": ["Smith", "Jones", "Davis"],"LengthInSeconds": 214}'
);
INSERT INTO Music
(Artist, SongTitle, AlbumTitle,
Price, Genre, CriticRating)
VALUES(
'No One You Know', 'My Dog Spot', 'Hey Now',
1.98, 'Country', 8.4
);
INSERT INTO Music
(Artist, SongTitle, AlbumTitle,
Price, Genre)
VALUES(
'The Acme Band', 'Look Out, World', 'The Buck Starts Here',
0.99, 'Rock'
);
INSERT INTO Music
(Artist, SongTitle, AlbumTitle,
Price, Genre,
Tags)
VALUES(
'The Acme Band', 'Still In Love', 'The Buck Starts Here',
2.47, 'Rock',
'{"radioStationsPlaying": ["KHCR", "KBQX", "WTNR", "WJJH"], "tourDates": { "Seattle": "20150625", "Cleveland": "20150630"}, "rotation": Heavy}'
);
卡桑德拉
整体表现 INSERT
Cassandra 中的看起来与 PostgreSQL 中的非常相似。 然而,语义上存在很大差异。 在卡桑德拉 INSERT
实际上是一个操作 UPSERT
,如果该行已存在,则将最后一个值添加到该行。
数据录入类似于PostgreSQL
INSERT
更高
.
MongoDB的
尽管 MongoDB 与 Cassandra 一样是 NoSQL 数据库,但其插入操作与 Cassandra 的语义行为没有任何共同之处。 在 MongoDB 中 UPSERT
,这使得它类似于 PostgreSQL。 添加默认数据,无需 _idspecified
将导致新文档添加到集合中。
db.music.insert( {
artist: "No One You Know",
songTitle: "Call Me Today",
albumTitle: "Somewhat Famous",
year: 2015,
price: 2.14,
genre: "Country",
tags: {
Composers: ["Smith", "Jones", "Davis"],
LengthInSeconds: 214
}
}
);
db.music.insert( {
artist: "No One You Know",
songTitle: "My Dog Spot",
albumTitle: "Hey Now",
price: 1.98,
genre: "Country",
criticRating: 8.4
}
);
db.music.insert( {
artist: "The Acme Band",
songTitle: "Look Out, World",
albumTitle:"The Buck Starts Here",
price: 0.99,
genre: "Rock"
}
);
db.music.insert( {
artist: "The Acme Band",
songTitle: "Still In Love",
albumTitle:"The Buck Starts Here",
price: 2.47,
genre: "Rock",
tags: {
radioStationsPlaying:["KHCR", "KBQX", "WTNR", "WJJH"],
tourDates: {
Seattle: "20150625",
Cleveland: "20150630"
},
rotation: "Heavy"
}
}
);
表查询
也许 SQL 和 NoSQL 在查询构造方面最显着的区别是所使用的语言 FROM
и WHERE
。 SQL允许after表达式 FROM
选择多个表,并使用表达式 WHERE
可以是任何复杂性(包括操作 JOIN
表之间)。 然而,NoSQL 往往对以下方面施加了严格的限制: FROM
,并且仅使用一个指定的表,并且在 WHERE
,必须始终指定主键。 这与我们之前讨论过的 NoSQL 性能提升有关。 这种愿望导致任何跨表和跨键交互的一切可能的减少。 当响应请求时,它可能会在节点间通信中引入很大的延迟,因此通常最好避免。 例如,Cassandra 要求查询仅限于某些运算符(仅 =, IN, <, >, =>, <=
) 在分区键上,除非请求二级索引(此处仅允许使用 = 运算符)。
PostgreSQL的
下面是 SQL 数据库可以轻松执行的三个查询示例。
- 显示艺术家的所有歌曲;
- 显示与标题第一部分匹配的艺术家的所有歌曲;
- 显示某个艺术家的所有标题中包含特定单词且价格低于 1.00 的歌曲。
SELECT * FROM Music
WHERE Artist='No One You Know';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle LIKE 'Call%';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle LIKE '%Today%'
AND Price > 1.00;
卡桑德拉
在上面列出的 PostgreSQL 查询中,只有第一个查询可以在 Cassandra 中保持不变,因为运算符 LIKE
不能应用于聚类列,例如 SongTitle
。 在这种情况下,仅允许操作员 =
и IN
.
SELECT * FROM Music
WHERE Artist='No One You Know';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle IN ('Call Me Today', 'My Dog Spot')
AND Price > 1.00;
MongoDB的
如前面的示例所示,在 MongoDB 中创建查询的主要方法是 music
在下面的示例中),因此禁止查询多个集合。
db.music.find( {
artist: "No One You Know"
}
);
db.music.find( {
artist: "No One You Know",
songTitle: /Call/
}
);
读取表的所有行
读取所有行只是我们之前看到的查询模式的一个特例。
PostgreSQL的
SELECT *
FROM Music;
卡桑德拉
与上面的 PostgreSQL 示例类似。
MongoDB的
db.music.find( {} );
编辑表中的数据
PostgreSQL的
PostgreSQL 提供说明 UPDATE
更改数据。 她没有机会 UPSERT
,因此如果该行不再存在于数据库中,则该语句将失败。
UPDATE Music
SET Genre = 'Disco'
WHERE Artist = 'The Acme Band' AND SongTitle = 'Still In Love';
卡桑德拉
卡桑德拉有 UPDATE
类似于 PostgreSQL。 UPDATE
具有相同的语义 UPSERT
, 喜欢 INSERT
.
与上面的 PostgreSQL 示例类似。
MongoDB的
手术 UPSERT
。 更新多个文档和类似行为 UPSERT
可以通过为操作设置附加标志来应用。 例如,在下面的示例中,特定艺术家的流派根据他的歌曲进行更新。
db.music.update(
{"artist": "The Acme Band"},
{
$set: {
"genre": "Disco"
}
},
{"multi": true, "upsert": true}
);
从表中删除数据
PostgreSQL的
DELETE FROM Music
WHERE Artist = 'The Acme Band' AND SongTitle = 'Look Out, World';
卡桑德拉
与上面的 PostgreSQL 示例类似。
MongoDB的
MongoDB 有两种类型的删除文档的操作 -
db.music.deleteMany( {
artist: "The Acme Band"
}
);
删除表
PostgreSQL的
DROP TABLE Music;
卡桑德拉
与上面的 PostgreSQL 示例类似。
MongoDB的
db.music.drop();
结论
关于在 SQL 和 NoSQL 之间进行选择的争论已经持续了 10 多年。 这场争论有两个主要方面:数据库引擎架构(整体式事务性 SQL 与分布式非事务性 NoSQL)和数据库设计方法(使用 SQL 建模数据与使用 NoSQL 建模查询)。
有了像 YugaByte DB 这样的分布式事务数据库,关于数据库架构的争论就可以轻松平息。 随着数据量变得大于可以写入单个节点的数据量,支持具有自动分片/重新平衡的线性写入可扩展性的完全分布式架构变得必要。
此外,正如其中一篇文章所述
回到数据库设计讨论,可以公平地说,这两种设计方法(SQL 和 NoSQL)对于任何复杂的现实应用程序都是必要的。 SQL“数据建模”方法使开发人员能够更轻松地满足不断变化的业务需求,而NoSQL“查询建模”方法允许相同的开发人员以低延迟和高吞吐量操作大量数据。 正是出于这个原因,YugaByte DB 在一个公共核心中提供 SQL 和 NoSQL API,而不是推广其中一种方法。 此外,通过提供与 PostgreSQL 和 Cassandra 等流行数据库语言的兼容性,YugaByte DB 确保开发人员无需学习另一种语言即可使用分布式、高度一致的数据库引擎。
在本文中,我们研究了 PostgreSQL、Cassandra 和 MongoDB 之间的数据库设计基础知识有何不同。 在以后的文章中,我们将深入探讨高级设计概念,例如索引、事务、JOIN、TTL 指令和 JSON 文档。