ArangoDB基础概念教程

我会试着放下往事,管它过去有多美

Posted by yishuifengxiao on 2021-12-08

一 快速入门

1.1 认证授权

ArangoDB 允许限制某些用户访问数据库。 系统数据库的所有用户都被视为管理员。 在安装过程中会创建一个默认用户 root,该用户可以访问所有数据库。

您应该为您的应用程序创建一个数据库以及对该数据库具有访问权限的用户。 请参阅管理用户。

使用 arangosh 创建新的数据库和用户。

该文档由原始官方文档 https://www.arangodb.com/docs/devel/graphs.html 翻译整合而来

1.2 基础概念

数据库是集合的集合。集合存储记录,称为文档。集合相当于 RDBMS 中的表,文档可以被认为是表中的行。不同之处在于您没有预先定义将有哪些列(或更确切地说是属性)。任何集合中的每个文档都可以具有任意的属性键和值。然而,单个集合中的文档在实践中可能具有类似的结构,但数据库系统本身不会强加它,无论您的数据看起来如何,它都会稳定快速地运行。

在数据模型概念一章中阅读更多内容。

现在,您可以坚持使用默认的 _system 数据库并使用 Web 界面来创建集合和文档。首先单击集合菜单项,然后单击添加集合磁贴。给它一个名字,例如用户,保持其他设置不变(我们希望它是一个文档集合)并保存它。应显示一个标记为用户的新磁贴,您可以单击以将其打开。

将没有文件。单击右侧带有白色加号的绿色圆圈以创建此集合中的第一个文档。一个对话框将要求您输入 _key。您可以将该字段留空并单击创建,让数据库系统分配一个自动生成的(唯一)键。请注意,_key属性是不可变的,这意味着一旦创建文档就无法更改它。命名约定中描述了可以用作文档键的内容。

自动生成的密钥可能是“9883”(_key始终是字符串!),在这种情况下,文档_id将是“users/9883”。除了一些系统属性外,本文档中还没有任何内容。让我们通过单击(空对象)左侧的图标,然后附加来添加自定义属性。两个输入字段将变为可用,FIELD(属性键)和 VALUE(属性值)。键入名称作为键,您的姓名作为值。附加另一个属性,将其命名为年龄并将其设置为您的年龄。单击保存以保留更改。如果您单击 ArangoDB 徽标右侧顶部的 Collection: users,文档浏览器将显示用户集合中的文档,您将在列表中看到您刚刚创建的文档。

1.3 查询数据库

是时候使用 AQL(ArangoDB 的查询语言)检索我们的文档了。 我们可以通过_id直接查找我们创建的文档,但也有其他选项。 单击 QUERIES 菜单项以调出查询编辑器并键入以下内容(调整文档 ID 以匹配您的文档):

1
RETURN DOCUMENT("users/9883")

然后单击执行以运行查询。 结果显示在查询编辑器下方:

1
2
3
4
5
6
7
8
9
[
{
"_key": "9883",
"_id": "users/9883",
"_rev": "9883",
"age": 32,
"name": "John Smith"
}
]

如您所见,返回了包括系统属性在内的整个文档。DOCUMENT() 是检索单个文档或您知道 _keys_ids的文档列表的函数。 我们将函数调用的结果作为我们的查询结果返回,这是我们在结果数组中的文档(我们可以使用不同的查询返回多个结果,但即使对于单个文档作为结果,我们仍然得到一个数组 在顶层)。

这种类型的查询称为数据访问查询。 不会创建、更改或删除任何数据。 还有另一种类型的查询称为数据修改查询。 让我们使用修改查询插入第二个文档:

1
INSERT { name: "Katie Foster", age: 27 } INTO users

查询是不言自明的:INSERT关键字告诉 ArangoDB 我们想要插入一些东西。 接下来要插入什么,在这种情况下是一个具有两个属性的文档。 花括号 { } 表示文档或对象。 当谈论集合中的记录时,我们称它们为文档。 编码为 JSON,我们称它们为对象。 对象也可以嵌套。 下面是一个例子:

1
2
3
4
5
6
{
"name": {
"first": "Katie",
"last": "Foster"
}
}

INTO 是每个 INSERT 操作的必需部分,后面是我们要在其中存储文档的集合名称。请注意,集合名称周围没有引号。

如果您运行上面的查询,结果将是一个空数组,因为我们没有使用 RETURN 关键字指定要返回的内容。 它在修改查询中是可选的,但在数据访问查询中是必需的。 即使使用RETURN,返回值仍然可以是空数组,例如 如果未找到指定的文档。 尽管结果为空,但上述查询仍然创建了一个新的用户文档。 您可以使用文档浏览器验证这一点。

让我们添加另一个用户,但这次返回新创建的文档:

1
2
INSERT { name: "James Hendrix", age: 69 } INTO users
RETURN NEW

NEW是一个伪变量,指的是INSERT创建的文档。 查询结果如下所示:

1
2
3
4
5
6
7
8
9
[
{
"_key": "10074",
"_id": "users/10074",
"_rev": "10074",
"age": 69,
"name": "James Hendrix"
}
]

现在我们的集合中有 3 个用户,如何通过单个查询检索所有用户? 以下不起作用:

1
2
3
RETURN DOCUMENT("users/9883")
RETURN DOCUMENT("users/9915")
RETURN DOCUMENT("users/10074")

这里只能有一个 RETURN语句,如果您尝试执行它,则会引发语法错误。 DOCUMENT() 函数提供了一个辅助签名来指定多个文档句柄,所以我们可以这样做:

1
RETURN DOCUMENT( ["users/9883", "users/9915", "users/10074"] )

一个包含所有 3 个文档的 _id的数组被传递给函数。 数组用方括号 [ ] 表示,其元素用逗号分隔。

但是如果我们添加更多用户呢? 我们还必须更改查询以检索新添加的用户。 对于我们的查询,我们只想说:“对于集合 users 中的每个用户,返回用户文档”。 我们可以用 FOR 循环来表述:

1
2
FOR user IN users
RETURN user

它表示遍历users中的每一个文档,并使用user作为变量名,我们可以用它来引用当前的用户文档。 它也可以称为 doc、u 或 ahuacatlguacamole,这取决于您。 但是,建议使用简短且具有自我描述性的名称。

循环体告诉系统返回变量 user 的值,这是一个单一的用户文档。 所有用户文档都以这种方式返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[
{
"_key": "9915",
"_id": "users/9915",
"_rev": "9915",
"age": 27,
"name": "Katie Foster"
},
{
"_key": "9883",
"_id": "users/9883",
"_rev": "9883",
"age": 32,
"name": "John Smith"
},
{
"_key": "10074",
"_id": "users/10074",
"_rev": "10074",
"age": 69,
"name": "James Hendrix"
}
]

您可能已经注意到,返回文档的顺序不一定与插入的顺序相同。 除非您明确对它们进行排序,否则无法保证顺序。 我们可以很容易地添加一个SORT 操作:

1
2
3
FOR user IN users
SORT user._key
RETURN user

这仍然没有返回所需的结果:James (10074) 在 John (9883) 和 Katie (9915) 之前返回。 原因是 _key 属性在 ArangoDB 中是一个字符串,而不是一个数字。 比较字符串的各个字符。 1 小于 9,因此结果是“正确的”。 如果我们想使用 _key 属性的数值来代替,我们可以将字符串转换为数字并使用它进行排序。 然而,有一些影响。 我们最好整理别的东西。 年龄如何,按降序排列?

1
2
3
FOR user IN users
SORT user.age DESC
RETURN user

用户将按以下顺序返回:James (69)、John (32)、Katie (27)。 ASC 可用于升序,而不是用于降序的 DESC。 ASC 是默认值,可以省略。

我们可能希望将结果集限制为用户的子集,例如基于年龄属性。 让我们只返回 30 岁以上的用户:

1
2
3
4
FOR user IN users
FILTER user.age > 30
SORT user.age
RETURN user

这将返回 John 和 James(按此顺序)。 Katie 的年龄属性不符合标准(大于 30),她只有 27 岁,因此不属于结果集。 我们可以使用修改查询使她的年龄再次返回她的用户文档:

1
2
UPDATE "9915" WITH { age: 40 } IN users
RETURN NEW

UPDATE允许部分编辑现有文档。 还有REPLACE,它将删除所有属性(除了 _key_id,它们保持不变)并只添加指定的属性。 另一方面,UPDATE仅替换指定的属性并保持其他所有内容不变。

UPDATE关键字后跟文档键(或带有_key属性的文档/对象)以标识要修改的内容。 要更新的属性在WITH关键字之后写为对象。IN表示在哪个集合中执行此操作,就像INTO一样(这里两个关键字实际上可以互换)。 如果我们使用 NEW伪变量,则返回应用了更改的完整文档:

1
2
3
4
5
6
7
8
9
[
{
"_key": "9915",
"_id": "users/9915",
"_rev": "12864",
"age": 40,
"name": "Katie Foster"
}
]

如果我们改用 REPLACE,name 属性就会消失。 使用 UPDATE 时,属性被保留(如果我们有其他属性,这同样适用于它们)。

让我们再次运行我们的 FILTER查询,但这次只返回用户名:

1
2
3
4
FOR user IN users
FILTER user.age > 30
SORT user.age
RETURN user.name

这将返回所有 3 个用户的姓名:

1
2
3
4
5
[
"John Smith",
"Katie Foster",
"James Hendrix"
]

如果仅返回属性的子集,则称为投影。 另一种投影是改变结果的结构:

1
2
FOR user IN users
RETURN { userName: user.name, age: user.age }

该查询定义了每个用户文档的输出格式。 用户名返回为 userName 而不是 name,年龄在本例中保留属性键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"userName": "James Hendrix",
"age": 69
},
{
"userName": "John Smith",
"age": 32
},
{
"userName": "Katie Foster",
"age": 40
}
]

也可以计算新值,例如通过连接:

1
2
FOR user IN users
RETURN CONCAT(user.name, "'s age is ", user.age)

CONCAT()是一个可以将元素连接到一个字符串的函数。 我们在这里使用它来为每个用户返回一个语句。 如您所见,结果集并不总是必须是对象数组:

1
2
3
4
5
[
"James Hendrix's age is 69",
"John Smith's age is 32",
"Katie Foster's age is 40"
]

现在让我们做一些疯狂的事情:对于 users 集合中的每个文档,再次迭代所有用户文档并返回用户对,例如 约翰和凯蒂。 我们可以在循环内使用循环来获得叉积(所有用户记录的所有可能组合,3 * 3 = 9)。 然而,我们不想要像 John + John 这样的配对,所以让我们用过滤条件消除它们:

1
2
3
4
FOR user1 IN users
FOR user2 IN users
FILTER user1 != user2
RETURN [user1.name, user2.name]

我们得到 6 对。 像 James + John 和 John + James 这样的配对基本上是多余的,但足够公平:

1
2
3
4
5
6
7
8
[
[ "James Hendrix", "John Smith" ],
[ "James Hendrix", "Katie Foster" ],
[ "John Smith", "James Hendrix" ],
[ "John Smith", "Katie Foster" ],
[ "Katie Foster", "James Hendrix" ],
[ "Katie Foster", "John Smith" ]
]

我们可以计算两个年龄的总和并通过这种方式计算新的东西:

1
2
3
4
5
6
7
FOR user1 IN users
FOR user2 IN users
FILTER user1 != user2
RETURN {
pair: [user1.name, user2.name],
sumOfAges: user1.age + user2.age
}

我们引入了一个新属性 sumOfAges 并将值的两个年龄相加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[
{
"pair": [ "James Hendrix", "John Smith" ],
"sumOfAges": 101
},
{
"pair": [ "James Hendrix", "Katie Foster" ],
"sumOfAges": 109
},
{
"pair": [ "John Smith", "James Hendrix" ],
"sumOfAges": 101
},
{
"pair": [ "John Smith", "Katie Foster" ],
"sumOfAges": 72
},
{
"pair": [ "Katie Foster", "James Hendrix" ],
"sumOfAges": 109
},
{
"pair": [ "Katie Foster", "John Smith" ],
"sumOfAges": 72
}
]

如果我们想对新属性进行后过滤以仅返回总和小于 100 的对,我们应该定义一个变量来临时存储总和,以便我们可以在 FILTER 语句和 RETURN语句中使用它 :

1
2
3
4
5
6
7
8
9
FOR user1 IN users
FOR user2 IN users
FILTER user1 != user2
LET sumOfAges = user1.age + user2.age
FILTER sumOfAges < 100
RETURN {
pair: [user1.name, user2.name],
sumOfAges: sumOfAges
}

LET关键字后跟指定的变量名称 (sumOfAges),然后是 = 符号和值或表达式来定义变量应该具有的值。 我们在这里重新使用我们的表达式来计算总和。 然后我们有另一个过滤器来跳过不需要的配对并使用我们之前声明的变量。 我们返回一个包含用户名数组和计算出的年龄的投影,我们再次使用变量:

1
2
3
4
5
6
7
8
9
10
[
{
"pair": [ "John Smith", "Katie Foster" ],
"sumOfAges": 72
},
{
"pair": [ "Katie Foster", "John Smith" ],
"sumOfAges": 72
}
]

专业提示:在定义对象时,如果所需的属性键和用于属性值的变量相同,您可以使用简写符号:{ sumOfAges } 而不是 { sumOfAges: sumOfAges }。

最后,让我们删除用户文档之一:

1
REMOVE "9883" IN users

它删除用户 John (_key: “9883”)。 我们还可以在循环中删除文档(插入、更新和替换也是如此):

1
2
3
FOR user IN users
FILTER user.age >= 30
REMOVE user IN users

该查询删除所有年龄大于或等于 30 的用户。

1.4 来自 SQL

如果您使用过关系数据库管理系统 (RDBMS),例如 MySQL、MariaDB 或 PostgreSQL,您就会熟悉它的查询语言,即 SQL(结构化查询语言)的一种方言。

ArangoDB 的查询语言称为 AQL。 尽管数据库系统的数据模型不同,但两种语言之间仍有一些相似之处。 最显着的区别可能是 AQL 中循环的概念,这让它感觉更像是一种编程语言。 它更自然地适合无模式模型,并使查询语言非常强大,同时保持易于阅读和编写。

要开始使用 AQL,请查看我们对 SQL 和 AQL 的详细比较。 它还将帮助您在迁移到 ArangoDB 时将 SQL 查询转换为 AQL。

选择列表如何转换为 AQL 查询?

在传统 SQL 中,您可以使用 SELECT * FROM table 逐行获取表的所有列,或者选择列的子集。 要获取的表列列表通常称为选择列表:

1
SELECT columnA, columnB, columnZ FROM table

由于文档不是二维的,也不希望局限于返回二维列表,对查询语言的要求更高。 因此,AQL 起初比普通 SQL 稍微复杂一点,但从长远来看,它提供了更大的灵活性。 它让您以方便的方式处理任意结构的文档,主要依赖于 JavaScript 中使用的语法。

编写要退回的文件

AQL RETURN语句为它处理的每个文档返回一个项目。 您可以返回整个文档,也可以只返回其中的一部分。 鉴于 oneDocument 是一个文档(例如像 LET oneDocument = DOCUMENT("myusers/3456789")那样检索),它可以按原样返回,如下所示:

1
RETURN oneDocument
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[
{
"_id": "myusers/3456789",
"_key": "3456789",
"_rev": "14253647",
"firstName": "John",
"lastName": "Doe",
"address": {
"city": "Gotham",
"street": "Road To Nowhere 1"
},
"hobbies": [
{ "name": "swimming", "howFavorite": 10 },
{ "name": "biking", "howFavorite": 6 },
{ "name": "programming", "howFavorite": 4 }
]
}
]

仅返回爱好子结构:

1
RETURN oneDocument.hobbies
1
2
3
4
5
6
7
[
[
{ "name": "swimming", "howFavorite": 10 },
{ "name": "biking", "howFavorite": 6 },
{ "name": "programming", "howFavorite": 4 }
]
]

返回爱好和地址:

1
2
3
4
RETURN {
hobbies: oneDocument.hobbies,
address: oneDocument.address
}
1
2
3
4
5
6
7
8
9
10
11
12
13
[
{
"hobbies": [
{ "name": "swimming", "howFavorite": 10 },
{ "name": "biking", "howFavorite": 6 },
{ "name": "programming", "howFavorite": 4 }
],
"address": {
"city": "Gotham",
"street": "Road To Nowhere 1"
}
}
]

只返回第一个爱好:

1
RETURN oneDocument.hobbies[0].name
1
2
3
[
"swimming"
]

返回所有爱好字符串的列表:

1
RETURN { hobbies: oneDocument.hobbies[*].name }
1
2
3
[
{ "hobbies": ["swimming", "biking", "programming"] }
]

二 数据模型和概念

本章介绍了 ArangoDB 的核心概念,涵盖了

  • 它的数据模型(或分别为数据模型)
  • 整个数据库系统和本文档中使用的术语

您还将找到有关如何使用 arangosh 与数据库系统交互的使用示例,例如如何创建和删除数据库/集合,或如何保存、更新、替换和删除文档。您也可以使用 Web 界面完成所有这些操作,因此初学者可以跳过这些部分。

数据库交互
ArangoDB 是一个为客户提供文档的数据库。这些文档通过 TCP 连接使用 JSON 传输,使用 HTTP 协议。提供了一个 REST API 来与数据库系统进行交互。

ArangoDB 附带的 Web 界面称为 Aardvark,提供易于使用的图形用户界面。还提供了一个名为 arangosh 的交互式 shell。此外,还有所谓的驱动程序,可以轻松地在各种环境和编程语言中使用数据库系统。所有这些工具都使用服务器的 HTTP 接口,并且在大多数情况下不需要为基本通信滚动自己的低级代码。

数据模型
您可以在 ArangoDB 中存储的文档严格遵循 JSON 格式,尽管它们以称为 VelocyPack 的二进制格式存储。一个文档包含零个或多个属性,这些属性中的每一个都有一个值。值可以是原子类型,即。 e.数字、字符串、布尔值或空值,或复合类型,即数组或嵌入的文档/对象。数组和子对象可以包含所有这些类型,这意味着可以在单个文档中表示任意嵌套的数据结构。

文档被分组到集合中。一个集合包含零个或多个文档。如果您熟悉关系数据库管理系统 (RDBMS),那么将集合与表进行比较,将文档与行进行比较是安全的。不同之处在于,在传统的 RDBMS 中,您必须先定义列,然后才能将记录存储到表中。此类定义也称为模式。 ArangoDB 默认是无模式的,这意味着不需要定义文档可以具有的属性。每个文档都可以具有完全不同的结构,并且仍然与其他文档一起存储在一个集合中。在实践中,集合中的文档之间会有共同点,但数据库系统本身并不会强迫您将自己限制在某种数据结构中。为了检查和/或强制执行通用结构,ArangoDB 支持对集合级别的文档进行可选的模式验证。

有两种类型的集合:文档集合(在图的上下文中也称为顶点集合)以及边集合。 Edge 集合也存储文档,但它们包含两个特殊属性,_from 和 _to,用于创建文档之间的关系。通常,存储在文档集合中的两个文档(顶点)由存储在边集合中的文档(边)链接。这是 ArangoDB 的图数据模型。它遵循有向标记图的数学概念,不同之处在于边不仅有标签,而且是完整的文档。

集合存在于数据库内部。可以有一个或多个数据库。不同的数据库通常用于多租户设置,因为其中的数据(集合、文档等)彼此隔离。默认数据库_system 是特殊的,因为它不能被删除。数据库用户在此数据库中进行管理,其凭据对服务器实例的所有数据库均有效。

类似地,数据库也可能包含视图实体。最简单形式的 View 可以被视为只读数组或文档集合。视图概念与大多数关系数据库管理系统 (RDBMS) 中可用的类似名称的概念非常匹配。每个视图实体通常将一些实现特定的文档转换(可能是身份)映射到来自零个或多个集合的文档。

数据检索
查询用于根据特定条件过滤文档、计算新数据以及操作或删除现有文档。查询可以像“示例查询”一样简单,也可以像使用许多集合或遍历图结构的“连接”一样复杂。它们是用 ArangoDB 查询语言 (AQL) 编写的。

游标用于迭代查询的结果,以便您获得易于处理的批次而不是一大块。

索引用于加快搜索速度。有多种类型的索引,例如持久性索引和地理空间索引。

2.1 Databases

这是在 JavaScript 中管理 ArangoDB 中的数据库的介绍。

当您与 ArangoDB 建立了连接时,可以使用db._useDatabase() 方法显式更改当前数据库。这将切换到指定的数据库(前提是它存在并且用户可以连接到它)。从现在开始,除非另有说明,否则同一 shell 或连接中的任何后续操作都将使用指定的数据库。

注意:如果数据库发生更改,客户端驱动程序也需要在其一侧存储当前数据库名称。这是因为 ArangoDB 中的连接不包含任何状态信息。所有状态信息都包含在 HTTP 请求/响应数据中。

要在 arangosh 启动后连接到特定数据库,请使用上述命令。也可以在调用 arangosh 时指定数据库名称。为此,请使用命令行参数 --server.database,例如

arangosh —server.database 测试

请注意,命令、操作、脚本或 AQL 查询不应访问多个数据库,即使它们存在。 ArangoDB 中唯一预期和支持的方式是一次使用一个数据库来执行命令、操作、脚本或查询。在一个数据库中开始的操作不得稍后切换数据库并继续在另一个数据库中操作。

2.1.1 使用数据库

以下方法可用于通过 JavaScript 管理数据库。 请注意,其中几种方法只能从 _system 数据库中使用。

2.1.1.1 Name

返回数据库名称 db._name()

以字符串形式返回当前数据库的名称。

1
arangosh> require("@arangodb").db._name();
1
_system

2.2.1.2 ID

返回数据库 id db._id()

以字符串形式返回当前数据库的 id。

1
arangosh> require("@arangodb").db._id();
1
1

2.2.1.3 Path

返回数据库文件的路径 db._path()

以字符串形式返回当前数据库的文件系统路径。

1
arangosh> require("@arangodb").db._path();
1
none

2.2.1.4 isSystem

返回数据库类型 db._isSystem()

返回当前使用的数据库是否为_system 数据库。 系统数据库具有一些特殊的权限和属性,例如创建或删除等数据库管理操作只能在该数据库内部执行。 此外,不能删除_system数据库本身。

2.1.1.5 Properties

返回数据库文件的路径 db._properties()

将当前数据库的属性作为具有以下属性的对象返回:

  • id:数据库ID
  • name:数据库名称
  • isSystem:数据库类型
  • path:数据库文件的路径
  • sharding:用于新集合的分片方法(仅限集群)
  • replicationFactor:新集合的默认复制因子(仅限集群)
  • writeConcern:如果少于此数量的同步副本,分片将拒绝写入(仅限集群)
1
arangosh> require("@arangodb").db._properties();

2.1.1.6 使用数据库

更改当前数据库 db._useDatabase(name)

将当前数据库更改为名称指定的数据库。 请注意,名称指定的数据库必须已经存在。

在某些情况下可能不允许更改数据库,例如服务器端操作(包括 Foxx)。

从 arangosh 执行此命令时,将重新使用当前凭据(用户名和密码)。 这些凭据可能无法连接到名称指定的数据库。 此外,只能从某些端点访问数据库。 在这种情况下,切换数据库可能不起作用,连接/会话应该关闭并使用不同的用户名和密码凭据和/或端点数据重新启动。

2.1.1.7 列出数据库

返回所有现有数据库的列表 db._databases()

返回所有数据库的列表。 此方法只能在 _system数据库中使用。

2.1.1.8 创建数据库

创建一个新的数据库 db._createDatabase(name, options, users)

使用 name 指定的名称创建一个新数据库。 数据库名称有限制(请参阅 DatabaseNames)。

请注意,即使数据库创建成功,也不会将当前数据库更改为新数据库。 必须使用db._useDatabase方法显式请求更改当前数据库。

options 属性可用于为将在新数据库中创建的集合设置默认值(仅限集群):

  • sharding:要使用的分片方法。 有效值为:”” 或 “single”。 将此选项设置为“单个”将启用企业版中的 OneShard 功能。

  • replicationFactor:默认复制因子。 特殊值包括“satellite”,它将集合复制到每个 DB-Server,以及 1,它禁用复制。

  • writeConcern:每个分片的多少副本需要在不同的 DB-Server 上同步。 如果集群中的副本少于这么多副本,分片将拒绝写入。 writeConcern 的值不能大于replicationFactor。

可选的 users 属性可用于为新数据库创建初始用户。 如果指定,它必须是用户对象列表。 每个用户对象可以包含以下属性:

  • username: 用户名作为字符串。 此属性是必需的。
  • passwd: 用户密码作为字符串。 如果未指定,则默认为空字符串。
  • active: 指示用户帐户是否应处于活动状态的布尔标志。 默认值是true。
  • extra: 带有额外用户信息的可选 JSON 对象。 extra 中包含的数据将为用户存储,但不会被 ArangoDB 进一步解释。

如果未指定初始用户,则将使用空字符串密码创建默认用户 root。 这可确保新数据库在创建后可通过 HTTP 访问。

如果未指定初始用户,则可以在数据库中创建用户。 切换到新数据库(用户名和密码必须与当前会话相同)并使用以下命令添加或修改用户。

1
2
3
require("@arangodb/users").save(username, password, true);
require("@arangodb/users").update(username, password, true);
require("@arangodb/users").remove(username);

或者,您可以直接指定用户数据。 例如:

1
db._createDatabase("newDB", {}, [{ username: "newUser", passwd: "123456", active: true}])

这些方法只能在_system 数据库中使用。

2.1.1.9 删除数据库

删除现有数据库db._dropDatabase(name)

删除按名称指定的数据库。 名称指定的数据库必须存在。

注意:只能从_system 数据库中删除数据库。_system数据库本身不能删除。

数据库被异步删除,如果所有客户端都已断开连接并且引用已被垃圾收集,则数据库将被物理删除。

2.1.1.10 版本信息

1
db._version()

返回服务器版本字符串。 请注意,这不是数据库的版本。

1
arangosh> require("@arangodb").db._version();

2.1.2 关于数据库的注意事项

请记住,每个数据库都包含自己的系统集合,需要在创建数据库时进行设置。 这将使创建数据库需要一段时间。

复制是在每个数据库级别或在服务器级别配置的。 在每个数据库设置中,必须在创建新数据库后显式配置任何复制日志记录或申请新数据库,而在使用全局复制应用程序的服务器级设置的情况下,所有数据库都会自动复制。

Foxx 应用程序也仅在安装它们的数据库的上下文中可用。新数据库将只提供对 ArangoDB 附带的系统应用程序(即目前的 Web 界面)的访问,并且在它们被安装之前不会提供其他 Foxx 应用程序 为特定数据库显式安装。

2.2 Collections

这是对 ArangoDB 的集合接口以及如何处理来自 JavaScript shell arangosh 的集合的介绍。 对于其他语言,请参阅相应的语言 API。

最重要的调用是创建新集合的调用。

集合地址

ArangoDB 中的所有集合都具有唯一标识符和唯一名称。 集合的命名空间与视图共享,因此在同一个数据库中不能存在同名的集合和视图。 ArangoDB 在内部使用集合的唯一标识符来查找集合。 然而,这个标识符由 ArangoDB 管理,用户无法控制它。 为了让用户可以使用自己的名字,每个集合也有一个由用户指定的唯一名称。 要从用户角度访问集合,应使用集合名称,即:

集合:

1
db._collection(collection-name)

集合由db._create调用创建。

例如:假设集合标识为7254820,名称为demo,则可以通过以下方式访问集合:

1
db._collection("demo")

如果不存在具有此类名称的集合,则返回 null。

有一个捷径可用于非系统集合:

Collection name

1
db.collection-name

此调用将返回名为db.collection-name 的集合,或者使用该名称和一组默认属性创建一个新集合。

注意:不推荐使用 db.collection-name 动态创建集合,并且在 arangosh 中不起作用。 要创建新集合,请使用

Create

1
db._create(collection-name)

此调用将创建一个名为 collection-name 的新集合。 此方法是一种数据库方法,在数据库方法中有详细记录

2.2.1 同步复制

从 ArangoDB 3.0 开始,分布式版本提供同步复制,这意味着可以选择在 ArangoDB 集群内自动复制所有数据。这是通过在创建集合时指定“复制因子”来为每个集合的分片集合配置的。 k 的复制因子意味着每个分片的 k 个副本在集群中保存在 k 个不同的服务器上,并且保持同步。也就是说,每个写操作都会自动复制到所有副本上。

这是使用领导者/追随者模型组织的。在任何时候,保存分片副本的服务器之一是“领导者”,所有其他服务器都是“追随者”,此配置保存在代理中(有关 ArangoDB 集群架构的详细信息,请参阅集群)。每个写操作都由协调器之一发送给领导者,然后在报告操作成功之前复制到所有追随者。领导者记录当前同步的追随者。如果网络出现问题或follower故障,leader可以并且会在3秒后暂时丢弃follower,以便恢复服务。在适当的时候,追随者会自动与领导者重新同步以恢复弹性。

如果领导者失败,集群代理会在大约 15 秒后自动启动故障转移程序,将其中一个追随者提升为领导者。其他追随者(以及前任领导者,当它回来时)会自动与新领导者重新同步以恢复弹性。通常,整个故障转移过程可以对协调器透明地处理,这样用户代码甚至看不到错误消息。

显然,这种容错是以增加延迟为代价的。每个写操作都需要额外的网络往返来同步复制追随者,但所有追随者的所有复制操作都是并发发生的。这就是为什么默认复制因子是 1,这意味着没有复制。

有关如何为集合打开同步复制的详细信息,请参阅数据库方法部分中的数据库方法 db._create(collection-name)

2.2.2 操作方法

2.2.2.1 Drop

删除一个集合 collection.drop(options)

删除集合及其所有索引和数据。 为了删除系统集合,必须指定一个属性 isSystem设置为 true的选项对象。

禁止在集群中删除集合,这是在其他集合中共享的原型。 为了能够删除这样的集合,必须首先删除所有依赖的集合。

示例

1
2
3
4
5
arangosh> col = db.example;
arangosh> col.drop();
arangosh> col;
[ArangoCollection 70596, "example" (type document, status loaded)]
[ArangoCollection 70596, "example" (type document, status deleted)]

2.2.1.2 Rename

重命名集合collection.rename(new-name)

使用新名称重命名集合。 新名称不得已用于不同的集合。new-name也必须是有效的集合名称。 有关有效集合名称的更多信息,请参阅命名约定。

如果由于任何原因重命名失败,则会引发错误。 如果重命名集合成功,则该集合也会在当前数据库中 _graphs集合内的所有图形定义中重命名。

rename() 方法不能在集群中使用。

示例

1
2
3
4
5
arangosh> c = db.example;
arangosh> c.rename("better-example");
arangosh> c;
[ArangoCollection 70727, "example" (type document, status loaded)]
[ArangoCollection 70727, "better-example" (type document, status loaded)]

其他方法参见 https://www.arangodb.com/docs/stable/data-modeling-collections-collection-methods.html


2.2.3 使用方法

2.2.3.1 Create

创建一个新的文档或边缘集合 db._create(collection-name)

创建一个名为collection-name的新文档集合。 如果集合名称已存在或名称格式无效,则会引发错误。 有关有效集合名称的更多信息,请参阅命名约定。

1
db._create(collection-name, properties)
1
db._create(collection-name, properties, type)

指定集合的可选类型,它可以是文档或边缘。 默认情况下它是文档。 除了提供类型,您还可以使用 db._createEdgeCollection 或 db._createDocumentCollection。

2.2.3.2 Drop

删除一个集合 db._drop(collection)

删除集合及其所有索引和数据。

1
db._drop(collection-identifier)

删除由集合标识符标识的集合及其所有索引和数据。 如果没有这样的集合,则不会抛出错误。

1
db._drop(collection-name)

删除名为 collection-name 的集合及其所有索引。 如果没有这样的集合,则不会抛出错误。

1
db._drop(collection-name, options)

为了删除系统集合,必须指定一个选项对象,其属性 isSystem 设置为 true。 否则无法删除系统集合。

注意:集群集合是带有distributeShardsLike参数的集合的原型,不能删除。


其他方法参见 https://www.arangodb.com/docs/stable/data-modeling-collections-database-methods.html#drop


三 管理用户

需要在服务器上启用身份验证才能使用用户权限。 默认情况下,ArangoDB 中的身份验证是打开的。 但是,您应该确保它没有被手动关闭。 检查配置文件(通常命名为/etc/arangodb.conf)并确保它在 [server]部分包含以下行:

1
authentication = true

这将使 ArangoDB 要求对每个请求进行身份验证(包括对 Foxx 应用程序的请求,具体取决于以下选项)。 如果您想在没有 HTTP 身份验证的情况下运行 Foxx 应用程序,但为内置服务器 API 激活 HTTP 身份验证,您可以在配置的 [server] 部分添加以下行:

1
authentication-system-only = true

以上将绕过对 Foxx 应用程序请求的身份验证。

完成更改后,您需要重新启动 ArangoDB,例如:

1
service arangodb restart

登录到_system数据库和 arangosh 时,可以在 Web 界面中进行用户管理,也可以通过 HTTP API。

有一个无法删除的内置用户帐户 root。 请注意,默认情况下它的密码为空,因此请确保立即设置强密码。 可以创建其他用户并授予不同的操作和访问级别。 ArangoDB 用户帐户在整个服务器实例(跨数据库)中都是有效的。

3.1 操作和访问级别

ArangoDB 服务器包含用户列表。它还定义了可以分配给用户(有关详细信息,请参见下文)以及执行某些操作所需的各种访问级别。这些操作可以分为三类:

  • 服务器操作
  • 数据库操作
  • 收集行动

服务器操作是

  • 创建用户:允许创建一个新用户。

  • 更新用户:允许更改现有用户的访问级别和详细信息。

  • drop user:允许删除现有用户。

  • 创建数据库:允许创建一个新的数据库。

  • drop database:允许删除现有的数据库。

  • 关闭服务器:从集群中删除服务器并关闭

数据库操作与给定的数据库相关联,并且必须为每个数据库单独设置访问级别。对于给定的数据库,操作是

  • 创建集合:允许在给定的数据库中创建一个新集合。

  • 更新集合:允许更新现有集合的属性。

  • 删除集合:允许删除现有集合。

  • 创建索引:允许为给定数据库中的现有集合创建索引。

  • 删除索引:允许删除给定数据库中现有集合的索引。

集合操作与给定数据库的给定集合相关联,并且必须为每个集合单独设置访问级别。对于给定的集合,操作是

  • 读取文档:读取给定集合的文档。

  • 创建文档:在给定的集合中创建一个新文档。

  • 修改文档:修改给定集合的现有文档,这可以是更新或替换操作。

  • drop document:删除给定集合的现有文档。

  • 截断集合:删除给定集合的所有文档。

要在服务器级别执行操作,用户至少需要以下访问级别。访问级别为管理和无访问权限:

server action server level
create a database Administrate
drop a database Administrate
create a user Administrate
update a user Administrate
update user access level Administrate
drop a user Administrate
shutdown server Administrate

要在特定数据库中执行操作(如创建或删除集合),用户至少需要以下访问级别。 数据库的可能访问级别为管理、访问和无访问权限。 集合的访问级别为读/写、只读和无访问权限。

database action database level collection level
create collection Administrate Read/Write
list collections Access Read Only
rename collection Administrate Read/Write
modify collection properties Administrate Read/Write
read properties Access Read Only
drop collection Administrate Read/Write
create an index Administrate Read/Write
drop an index Administrate Read/Write
see index definition Access Read Only

请注意,要对该数据库中的集合执行任何操作,始终需要数据库的访问级别 Access。

对于集合,用户需要对给定数据库和给定集合具有以下访问级别。 数据库的访问级别为管理、访问和无访问权限。 集合的访问级别为读/写、只读和无访问权限。

action collection level database level
read a document Read/Write or Read Only Administrate or Access
create a document Read/Write Administrate or Access
modify a document Read/Write Administrate or Access
drop a document Read/Write Administrate or Access
truncate a collection Read/Write Administrate or Access

例子

例如,给定

  • a database example
  • a collection data in the database example
  • a user JohnSmith

如果为用户 JohnSmith 分配了数据库示例的访问权限级别和集合数据的读取/写入级别,则允许用户读取、创建、修改或删除集合数据中的文档。 但是,例如,不允许用户为集合数据创建索引,也不允许在数据库示例中创建新集合。

四 索引

4.1 索引基础知识

索引允许快速访问文档,前提是在查询中使用了索引属性。虽然 ArangoDB 会自动索引一些系统属性,但用户可以自由地为文档的非系统属性创建额外的索引。

可以在集合级别创建用户定义的索引。大多数用户定义的索引可以通过指定索引属性的名称来创建。一些索引类型只允许索引一个属性(例如全文索引),而其他索引类型允许同时索引多个属性。

通过学习 ArangoDB 性能课程,了解如何有效地使用不同的索引。

系统属性 和 由 ArangoDB 自动索引,无需用户为它们创建额外的索引。和被集合的主键覆盖,并且被边缘集合的边缘索引自动覆盖。_id _key _from _to _id _key _from _to

在用户定义的索引中使用系统属性是不可能的,但是索引 和 _id _key _rev _from _to是可以的。

默认情况下,创建新索引是在排他集合锁下完成的。创建索引时集合不可用。如果您必须在没有专用维护窗口的实时系统上执行此“前台”索引创建,则可能不受欢迎。

对于可能长时间运行的索引创建操作,RocksDB 存储引擎还支持在“后台”创建索引。该集合在索引创建期间保持(大部分)可用,请参阅在后台创建索引部分以获取更多信息。

ArangoDB 提供以下索引类型:

4.2 一级索引

对于每个集合,总会有一个主索引,它是集合中所有文档的文档键(属性)的持久索引。 主索引允许使用 或 属性快速选择集合中的文档。 当对 _key _key _id _key _id 执行相等查找时,它将在 AQL 查询中自动使用

也有专门的函数来查找给定文档的文档,或者总是使用主索引:_key _id

1
2
db.collection.document("<document-key>");
db._document("<document-id>");

主索引可用于范围查询和排序,因为对持久索引进行排序。

集合的主索引不能被删除或更改,并且没有创建用户定义的主索引的机制。

4.3 边缘索引

每个边集合也有一个自动创建的边索引。边缘索引通过文档或属性提供对文档的快速访问。因此可用于快速查找顶点文档之间的连接,并在查询顶点的连接边时调用。_from _to

当对边集合中的值或值执行相等查找时,在 AQL 中使用边索引。还有专门的函数来查找给定的边或值,这些函数将始终使用边索引:_from _to _from _to

1
2
3
4
5
6
db.collection.edges("<from-value>");
db.collection.edges("<to-value>");
db.collection.outEdges("<from-value>");
db.collection.outEdges("<to-value>");
db.collection.inEdges("<from-value>");
db.collection.inEdges("<to-value>");

边索引存储所有和属性的并集。它可用于相等查找,但不能用于范围查询或排序。边索引是自动为边集合创建的。_from _to

无法创建用户定义的边缘索引。但是,可以在用户定义的索引中自由使用 和 属性。_from _to

不能删除或更改边缘索引。

4.4 持久索引

持久化索引是具有持久性的排序索引。存储或更新文档时,索引条目会写入磁盘。这意味着当服务器重新启动或索引集合最初加载时,不需要从集合数据重建索引条目。因此,使用持久索引可以减少集合加载时间。

持久化索引类型目前可以用于二级索引。这意味着持久性索引目前不能成为集合的唯一索引,因为除了集合的内存中主索引之外,总会有更多的索引(例如边缘集合的边缘索引)。

索引实现使用 RocksDB 引擎,它为插入、更新和删除操作提供对数复杂度。由于持久索引不是内存索引,它不像所有内存索引那样将指针存储到主索引中,而是存储文档的主键。要通过索引值查找通过持久索引检索文档,因此将有一个额外的 O(1) 查找到主索引以获取实际文档。

由于持久索引已排序,因此它可用于点查找、范围查询和排序操作,但前提是查询中提供了所有索引属性,或者指定了索引属性的最左侧前缀。

4.5 TTL(生存时间)索引

ArangoDB 提供的 TTL 索引类型可用于自动从集合中删除过期文档。

TTL 索引是通过设置值并选择包含文档创建日期和时间的单个文档属性来设置的。文档在创建时间后几秒钟后过期。创建时间指定为数字时间戳(Unix 时间戳)或格式的日期字符串,可选在格式中的小数点后的毫秒数和可选的时区偏移量。所有没有时区偏移的日期字符串将被解释为 UTC 日期。expireAfter expireAfter YYYY-MM-DDTHH:MM:SS YYYY-MM-DDTHH:MM:SS.MMM

例如,如果设置为 600 秒(10 分钟)并且索引属性为“creationDate”并且有以下文档:expireAfter

1
{ "creationDate" : 1550165973 }

此文档将使用 的创建日期时间值编制索引,该值将转换为人类可读的日期。该文档将在 600 秒后过期,即时间戳(或人类可读版本)。1550165973 2019-02-14T17:39:33.000Z 1550166573 2019-02-14T17:49:33.000Z

过期文件的实际删除不一定会立即发生。过期文档最终将被后台线程删除,该线程定期检查所有 TTL 索引并删除过期文档。可以使用启动选项配置调用此后台线程的频率。--ttl.frequency

无法保证何时确切地执行过期文档的删除,因此查询可能仍会找到并返回已过期的文档。当后台线程启动并有能力删除过期文档时,这些最终将被删除。然而,可以保证只有超过其到期时间的文件才会被实际删除。

请注意,索引属性的数字日期时间值必须以自 1970 年 1 月 1 日(Unix 时间戳)以来的秒数为单位指定。要以这种格式从 JavaScript 计算当前时间戳,有 ;要从任意 Date 实例计算它,有 .在 AQL 中,您可以将任意 Unix 时间戳(以毫秒为单位)除以 1000 以将其转换为 seconds. Date.now() / 1000Date.getTime() / 1000DATE_NOW() / 1000