当前位置: 首页 > 工具软件 > RavenDB > 使用案例 >

[原创] RavenDB 安装 使用

廉高邈
2023-12-01

RavenDB 安装/使用

上一篇文章介绍了 CouchDB, 这篇文章介绍一下 RavenDB安装使用.
RavenDB 也是支持对记录做版本管理的数据库. 默认是不支持的, 需要手动设置.
官网: https://ravendb.net
RavenDB Github: https://github.com/ravendb/ravendb
RavenDB-python Github: https://github.com/ravendb/RavenDB-Python-Client

文档极少, 几乎没有社区讨论, 官网对 HTTP API 的文档几乎没有. (版本2.5有一些 HTTP API 文档).
SDK 示例代码以 c# 为主, 其余几乎为空.
文中测试的 API 都是在浏览器 UI 上操作抓包获取. 有些 API 是靠感觉猜出来的.
到底还是.net写的 … 安装目录就一个 Server 文件夹, 里面放着所有的 .dll, .so, *.json …
和 couchdb 相比, 易用性好一些. 但从整体上看, 不如 couchdb.

下载/安装

下载地址: https://ravendb.net/download
支持: WINDOWS, LINUX, MACOS, RASPBERRY PI, DOCKER, NUGET

这里是在 Macbook 上安装的

$ wget https://daily-builds.s3.amazonaws.com/RavenDB-4.0.2-osx-x64.tar.bz2
$ tar zxvf RavenDB-4.0.2-osx-x64.tar.bz2
$ cd RavernDB
$ bash run.sh
# 需要安装 libsodium 模块
# brew install libsodium

启动/配置

可以看到启动成功. 默认端口是5984

# 如果没有异常, 会看到如下提示
Using GC in server concurrent mode retaining memory from the OS.
Could not start browser: No such file or directory
Server available on: http://127.0.0.1:57606
Tcp listening on 127.0.0.1:57607
Server started, listening to requests...
TIP: type 'help' to list the available commands.
Running non-interactive.


$ curl -v http://127.0.0.1:57606
* Rebuilt URL to: http://127.0.0.1:57606/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 57606 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:57606
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< Date: Tue, 10 Apr 2018 09:12:44 GMT
< Content-Type: application/json; charset=utf-8
< Server: Kestrel
< Content-Length: 0
< Location: /studio/index.html
< 
* Connection #0 to host 127.0.0.1 left intact

直接打开浏览器, 访问控制台提示的地址, 会引导进行一些配置, 安全配置等等信息.
在安装目录 Server 中,有一个 setting.json, 默认如下

{
    "ServerUrl": "http://127.0.0.1:0",
    "Setup.Mode": "Initial",
    "DataDir": "RavenData"
}

可以替换为如下, 略过引导配置

{
  "DataDir": "RavenData",
  "License.Eula.Accepted": true,
  "Setup.Mode": "Unsecured",
  "Security.UnsecuredAccessAllowed": "PublicNetwork",
  "ServerUrl": "http://127.0.0.1:8080",
  "ServerUrl.Tcp": "tcp://127.0.0.1:38888"
}

ServerUrl 监听 http 请求
DataDir 配置数据文件目录
Setup.Mode 启动模式
ServerUrl.Tcp 是用于集群模式节点之间通讯. - Indicates the IP addresses or host addresses with ports and protocols that the server should listen on for incoming TCP connections, are used for inter-node communication

更多配置可以查看官方文档: https://ravendb.net/docs/article-page/4.0/csharp/server/configuration/core-configuration

使用测试

测试可以使用浏览器直接测试, 这里不做说明

使用 curl 测试

创建 DB
$ curl -X PUT "http://127.0.0.1:8080/admin/databases?name=td&replicationFactor=1" -d '{"DatabaseName":"td"}'

{
    "RaftCommandIndex": 19,
    "Name": "td",
    "Topology": {
        "Members": [
            "A"
        ],
        "Promotables": [],
        "Rehabs": [],
        "Stamp": {
            "Index": -1,
            "Term": 1,
            "LeadersTicks": 113011146
        },
        "PromotablesStatus": {},
        "DemotionReasons": {},
        "DynamicNodesDistribution": false,
        "ReplicationFactor": 1
    },
    "NodesAddedTo": [
        "http://127.0.0.1:8080"
    ]
}
$ curl -X PUT "http://127.0.0.1:8080/admin/databases?name=td&replicationFactor=1" -d '{
    "DatabaseName": "td",
    "Settings": {
        "DataDir": null
    },
    "Disabled": false,
    "Encrypted": false,
    "Topology": {
        "DynamicNodesDistribution": false
    }
}'

{"Url":"/admin/databases?name=td&replicationFactor=1","Type":"Raven.Client.Exceptions.ConcurrencyException","Message":"Database 'td' already exists!","Error":"Raven.Client.Exceptions.ConcurrencyException: Database 'td' already exists!\n   at Raven.Server.Web.System.AdminDatabasesHandler.<CreateDatabase>d__6.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Web\\System\\AdminDatabasesHandler.cs:line 326\n--- End of stack trace from previous location where exception was thrown ---\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n   at Raven.Server.Web.System.AdminDatabasesHandler.<Put>d__4.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Web\\System\\AdminDatabasesHandler.cs:line 233\n--- End of stack trace from previous location where exception was thrown ---\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n   at Raven.Server.Routing.RequestRouter.<HandlePath>d__6.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Routing\\RequestRouter.cs:line 104\n--- End of stack trace from previous location where exception was thrown ---\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\n   at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()\n   at Raven.Server.RavenServerStartup.<RequestHandler>d__11.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\RavenServerStartup.cs:line 159"}
支持 revision
# 查看当前数据库配置的 revision
$ curl http://127.0.0.1:8080/databases/qa/revisions/config
{
    "Default": null,
    "Collections": {
        "@empty": {
            "Disabled": false,
            "MinimumRevisionsToKeep": null,
            "MinimumRevisionAgeToKeep": null,
            "PurgeOnDelete": false
        }
    }
}
# 配置 revision
curl -XPOST http://127.0.0.1:8080/databases/qa/admin/revisions/config -d '{
    "Collections": {
        "@empty": {
            "Disabled": false,
            "MinimumRevisionsToKeep": 100,
            "MinimumRevisionAgeToKeep": null,
            "PurgeOnDelete": false
        }
    }
}'

{"RaftCommandIndex":21}

Disabled, true: 关闭 revision, false: 开启 revision.
MinimumRevisionsToKeep: 保留最近多少个版本, 可以设置为 null
MinimumRevisionAgeToKeep: 保留最近多少天的版本, 可以设置为 null
PurgeOnDelete: 删除的时候是否彻底清除. 建议 false

添加记录时, 如果不指定 @matadata.@collection, 会默认添加到 @empty collection
RavenDB 默认不支持 revision, 需要手动开启. revision 作用域是某个 database 中的 collection

特别注意: 配置 revision 时, 会覆盖原先的设置, 所以每次提交都必须将以前设置的 revision 也添加进去. 否则会丢失原先的配置.

添加一条记录 (_id 可以自己定义, 也可以不填, 会默认生成 GUID)
$ curl -XPUT "http://127.0.0.1:8080/databases/qa/docs?id=1" -d '{"name": "zhipeng"}'
{"Id":"1","ChangeVector":"A:22-5YM16pKww0W6G+UxDC7ViQ"}
$ curl -XPUT "http://127.0.0.1:8080/databases/qa/docs?id=2" -d '{
    "name": "zhipeng",
    "@metadata": {},
    "books":["aa", "bb"],
    "school": [{"type": "h", "name": "high school"}, {"type": "u", "name": "university"}],
    "other": {"age": 18, "first_name": "zhang"},
    "desc": "hello world."
}'

{"Id":"2","ChangeVector":"A:42-5YM16pKww0W6G+UxDC7ViQ"}

# 也可以使用 bulk 操作
# 插入一条 id 为2的, 里面包含 list, dictionary 两种类型. ["", ""], [{}, {}], {}
$ curl -XPOST http://127.0.0.1:8080/databases/qa/bulk_docs -d '{
    "Commands": [
        {
            "Document": {
                "info": {
                    "a": 123,
                    "b": 456
                },
                "name": "zhipeng",
                "@metadata": {
                    "@collection": "qa",
                    "@id": ""
                }
            },
            "Id": "",
            "Type": "PUT"
        },
        {
            "Document": {
                "name": "zhipeng",
                "books": [
                    "aa",
                    "bb"
                ],
                "school": [
                    {
                        "type": "h",
                        "name": "high school"
                    },
                    {
                        "type": "u",
                        "name": "university"
                    }
                ],
                "other": {
                    "age": 18,
                    "first_name": "zhang"
                },
                "desc": "hello world.",
                "@metadata": {
                    "@id": "2"
                }
            },
            "Id": "2",
            "Type": "PUT"
        }
    ]
}'

{
    "Results": [
        {
            "Type": "PUT",
            "@id": "2",
            "@collection": "@empty",
            "@change-vector": "A:42-5YM16pKww0W6G+UxDC7ViQ",
            "@last-modified": "2018-04-10T11:41:17.1278590",
            "@flags": "HasRevisions"
        },
        {
            "Type": "PUT",
            "@id": "269639cf-0852-430c-b256-3cdee38b2519",
            "@collection": "qa",
            "@change-vector": "A:14-5YM16pKww0W6G+UxDC7ViQ",
            "@last-modified": "2018-04-10T10:27:33.9939300",
            "@flags": "HasRevisions"
        }
    ]
}

$ curl -XPUT "http://127.0.0.1:8080/databases/qa/docs" -d '{"name": "zhipeng"}'
{"Url":"/databases/qa/docs?id=%20","Type":"System.ArgumentException","Message":"Query string value 'id' must have a non empty value","Error":"System.ArgumentException: Query string value 'id' must have a non empty value\n   at Raven.Server.Web.RequestHandler.InvalidEmptyValue(String name) in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Web\\RequestHandler.cs:line 341\n   at Raven.Server.Web.RequestHandler.GetQueryStringValueAndAssertIfSingleAndNotEmpty(String name) in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Web\\RequestHandler.cs:line 333\n   at Raven.Server.Documents.Handlers.DocumentHandler.<Put>d__7.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Documents\\Handlers\\DocumentHandler.cs:line 240\n--- End of stack trace from previous location where exception was thrown ---\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n   at Raven.Server.Routing.RequestRouter.<HandlePath>d__6.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Routing\\RequestRouter.cs:line 97\n--- End of stack trace from previous location where exception was thrown ---\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\n   at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()\n   at Raven.Server.RavenServerStartup.<RequestHandler>d__11.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\RavenServerStartup.cs:line 159"}

如果不使用 bulk, 必须指定 id, 否则无法自动生成 id.

查询 - 获取所有记录
$ curl  "http://127.0.0.1:8080/databases/qa/docs?start=0&pageSize=10"

{
    "Results": [
        {
            "name": "zhipeng",
            "@metadata": {
                "@change-vector": "A:40-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions",
                "@id": "1",
                "@last-modified": "2018-04-10T11:29:39.6150230Z"
            }
        },
        {
            "name": "zhipeng",
            "desc": "test collection.x'x'x",
            "@metadata": {
                "@collection": "qa",
                "@change-vector": "A:12-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions",
                "@id": "zhipeng",
                "@last-modified": "2018-04-10T09:41:57.0446820Z"
            }
        }
    ]
}

# 也可以用这个 API, 可以返回查询到的数据所包含的字段
curl "http://127.0.0.1:8080/databases/qa/studio/collections/preview?start=0&pageSize=10"

{
    "Results": [
        {
            "@metadata": {
                "$a": [],
                "$o": [],
                "$t": [],
                "@change-vector": "A:40-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions",
                "@id": "1",
                "@last-modified": "2018-04-10T11:29:39.6150230Z"
            }
        },
        {
            "@metadata": {
                "$a": [],
                "$o": [],
                "$t": [],
                "@collection": "qa",
                "@change-vector": "A:12-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions",
                "@id": "zhipeng",
                "@last-modified": "2018-04-10T09:41:57.0446820Z"
            }
        }
    ],
    "TotalResults": 2,
    "AvailableColumns": [
        "name",
        "desc"
    ]
}
查询 - 根据 id 查询
$ curl  "http://127.0.0.1:8080/databases/qa/docs?id=1"
{
    "Results": [
        {
            "name": "zhipeng",
            "@metadata": {
                "@change-vector": "A:40-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions",
                "@id": "1",
                "@last-modified": "2018-04-10T11:29:39.6150230Z"
            }
        }
    ],
    "Includes": {}
}
$ curl  "http://127.0.0.1:8080/databases/qa/docs?id=2&id=1" 
{
    "Results": [
        {
            "name": "zhipeng",
            "books": [
                "aa",
                "bb"
            ],
            "school": [
                {
                    "type": "h",
                    "name": "high school"
                },
                {
                    "type": "u",
                    "name": "university"
                }
            ],
            "other": {
                "age": 18,
                "first_name": "zhang"
            },
            "desc": "hello world.",
            "@metadata": {
                "@change-vector": "A:42-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions",
                "@id": "2",
                "@last-modified": "2018-04-10T11:41:17.1278590Z"
            }
        },
        {
            "name": "zhipeng",
            "@metadata": {
                "@change-vector": "A:40-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions",
                "@id": "1",
                "@last-modified": "2018-04-10T11:29:39.6150230Z"
            }
        }
    ],
    "Includes": {}
}
# 等同下面的效果
$ curl -XPOST "http://127.0.0.1:8080/databases/qa/docs" -d '{"Ids":[2,1, "zhipeng"]}'

如果需要查询多个 id, 可以在参数中拼接. 例如 id=1&id=2

查询 - 查询语法

RavenDB 将 SQL 称之为 RQL. 后面 RQL 都用 SQL 表示.
查询地址: /databases/qa/queries?query=SQL&start=0&pageSize=10
平常的 SQL 是 select fields from TABLE where WHERE\_SYNTAX;
RavenDB SQL 稍有不同, 是 from TABLE where WHERE\_SYNTAX select fields

注意行尾不要加 ;

$ curl "http://127.0.0.1:8080/databases/qa/queries?query=from%20qa%20%20%0D%0Awhere%20name%20%3D%20%22zhipeng%22%0D%0Aselect%20ID()%20as%20id%2C%20name&start=0&pageSize=10"

{
    "TotalResults": 2,
    "SkippedResults": 0,
    "DurationInMs": 0,
    "IncludedPaths": null,
    "IndexName": "Auto/qa/Bybooks.CountAndbooks[]AndnameAndother.ageAndschool[].type",
    "Results": [
        {
            "id": "zhipeng",
            "name": "zhipeng",
            "@metadata": {
                "@projection": true,
                "@change-vector": "A:12-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions",
                "@id": "zhipeng",
                "@index-score": 0.594534814357758,
                "@last-modified": "2018-04-10T09:41:57.0446820Z"
            }
        },
        {
            "id": "2",
            "name": "zhipeng",
            "@metadata": {
                "@projection": true,
                "@change-vector": "A:52-5YM16pKww0W6G+UxDC7ViQ",
                "@id": "2",
                "@index-score": 0.594534814357758,
                "@last-modified": "2018-04-11T03:04:48.1657180Z"
            }
        }
    ],
    "Includes": {},
    "IndexTimestamp": "2018-04-11T04:06:00.7646740",
    "LastQueryTime": "2018-04-11T04:17:41.3646580",
    "IsStale": false,
    "ResultEtag": -6847100035392847000
}

后面只写 SQL, 不再写 curl, 将 SQL 用 urlEncode 后放入参数 query 即可

基础查询
from qa where name = "zhipeng" select ID() as id, name
from qa group by name where name = "zhipeng" select name, count() as count

需要注意, 不能直接使用如果要使用 COUNT(), 必须使用聚合函数.
主键 id 必须使用 ID() 才可以正常返回. 否则只能从 @metadata 中获取

list 相关查询
from qa where books.Count = 2 select books, ID() as ID
// 这里是可以使用一部分 js 语法的
from qa where books.length = 2 select books, ID() as ID

SQL: from qa as c where c.books.length = 2 select { book: c.books, metadata: c["@metadata"], _source: c, id: c["@metadata"]["@id"]}


// 查询 books 中存在 "aa" 这本书的所有记录
from qa where books[] = "aa" select *
//result
{
    "TotalResults": 1,
    "SkippedResults": 0,
    "DurationInMs": 0,
    "IncludedPaths": null,
    "IndexName": "Auto/qa/Bybooks.CountAndbooks[]",
    "Results": [
        {
            "name": "zhipeng",
            "books": [
                "aa",
                "bb"
            ],
            "school": [
                {
                    "type": "h",
                    "name": "high school"
                },
                {
                    "type": "u",
                    "name": "university"
                }
            ],
            "other": {
                "age": 18,
                "first_name": "zhang"
            },
            "desc": "hello world.",
            "@metadata": {
                "@collection": "qa",
                "@change-vector": "A:52-5YM16pKww0W6G+UxDC7ViQ",
                "@id": "2",
                "@index-score": 0.3068528175354,
                "@last-modified": "2018-04-11T03:04:48.1657180Z"
            }
        }
    ],
    "Includes": {},
    "IndexTimestamp": "2018-04-11T03:45:00.7649240",
    "LastQueryTime": "2018-04-11T03:59:53.3999770",
    "IsStale": false,
    "ResultEtag": 3604079527690140700
}
dictionary 查询
from qa where other.age=18 select ID() as id, name, other.age as age
// 查询上过高中的人名
from qa where school.type  = 'h' select name

需要注意, 如果使用 js 语法查询, 是没有办法获取到 id 的, 从能后续从@ metadata 中获取. 不知道是 bug 还是原因. 如果直接获取这行记录, 是可以看到@metadata.@id 的, 但如果直接获取[“@metadata”]只能看到@ collection. 所以 id 也就无法获取.
并且试了 load 语法, 似乎也不行, 获取不到结果. 可能是 bug 吧, 用不到这个, 所以不细研究了.

from qa as q
load q.other as o
select {
    name: q.name.toUpperCase(),
    other: o.age,
    bookCount: q.books.length
}
// result
{
    "TotalResults": 2,
    "SkippedResults": 0,
    "DurationInMs": 0,
    "IncludedPaths": null,
    "IndexName": "collection/qa",
    "Results": [
        {
            "name": "ZHIPENG",
            "other": null,
            "bookCount": null,
            "q": {
                "@collection": "qa"
            },
            "@metadata": {
                "@projection": true,
                "@change-vector": "A:12-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions",
                "@id": "zhipeng",
                "@index-score": 0,
                "@last-modified": "2018-04-10T09:41:57.0446820Z"
            }
        },
        {
            "name": "ZHIPENG",
            "other": null,
            "bookCount": 2,
            "q": {
                "@collection": "qa"
            },
            "@metadata": {
                "@projection": true,
                "@change-vector": "A:52-5YM16pKww0W6G+UxDC7ViQ",
                "@id": "2",
                "@index-score": 0,
                "@last-modified": "2018-04-11T03:04:48.1657180Z"
            }
        }
    ],
    "Includes": {},
    "IndexTimestamp": "0001-01-01T00:00:00.0000000",
    "LastQueryTime": "0001-01-01T00:00:00.0000000",
    "IsStale": false,
    "ResultEtag": 6893458203660772000
}
更新
更新记录和插入记录接口一样, 已存在的记录会自己做版本管理

更新的时候, @metadata 和原记录保持一致. @collection 不可以更改.

删除
$ curl -XDELETE "http://127.0.0.1:8080/databases/qa/docs?id=1"

# 也可以使用 bulk 进行批量删除, 
$ curl -X POST http://127.0.0.1:8080/databases/qa/bulk_docs -d '{"Commands":[{"Id":"0910b0f3-3878-4667-811f-3c0cff067f44","Type":"DELETE"},{"Id":"269639cf-0852-430c-b256-3cdee38b2519","Type":"DELETE"}]}'
{
    "Results": [
        {
            "Id": "0910b0f3-3878-4667-811f-3c0cff067f44",
            "Type": "DELETE",
            "Deleted": true
        },
        {
            "Id": "269639cf-0852-430c-b256-3cdee38b2519",
            "Type": "DELETE",
            "Deleted": true
        }
    ]
}

删除的时候不需要指定版本号, couchdb 必须指定.
删除只是删除当前版本, 并且会生成出一个被删除的版本. 旧版本是可以继续查到的.
如果再创建一条记录, _id 存在过, 版本号会在最后一次删除的版本基础上 +1.

查看版本记录
$ curl "http://127.0.0.1:8080/databases/qa/revisions?id=1&start=0&pageSize=100&metadataOnly=false"
{
    "Results": [
        {
            "name": "11",
            "desc": "1",
            "@metadata": {
                "@collection": "qa",
                "@change-vector": "A:58-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions, Revision",
                "@id": "1",
                "@last-modified": "2018-04-11T06:50:46.7506050Z"
            }
        },
        {
            "@metadata": {
                "@collection": "@empty",
                "@change-vector": "A:53-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions, DeleteRevision",
                "@id": "1",
                "@last-modified": "2018-04-11T06:48:46.4398030Z"
            }
        },
        {
            "Name": "...",
            "@metadata": {
                "@change-vector": "A:36-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions, Revision",
                "@id": "1",
                "@last-modified": "2018-04-10T10:45:11.6156140Z"
            }
        },
        {
            "name": "zhipeng",
            "id": "1",
            "@metadata": {
                "@change-vector": "A:16-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions, Revision",
                "@id": "1",
                "@last-modified": "2018-04-10T10:36:22.3838210Z"
            }
        }
    ],
    "TotalResults": 11
}

metadataOnly=true 只返回 metadata 信息. 默认为 false

查看某个版本记录
$ curl "http://127.0.0.1:8080/databases/qa/revisions?changeVector=A%3A58-5YM16pKww0W6G%2BUxDC7ViQ"
{
    "Results": [
        {
            "name": "11",
            "desc": "1",
            "@metadata": {
                "@collection": "qa",
                "@change-vector": "A:58-5YM16pKww0W6G+UxDC7ViQ",
                "@flags": "HasRevisions, Revision",
                "@id": "1",
                "@last-modified": "2018-04-11T06:50:46.7506050Z"
            }
        }
    ]
}

查看某条记录的某个版本, 只需要用 @change-vector 即可, 不需要 id.
特别注意, 某条记录如果已经设置了 collection, 是不能再更换 collection 的. 即使第一次是 @empty, 也不能更换.

如果想更换 collection, 只能将其删除, 重新创建, 并指定 collection.
切换过 collection 的数据, 短时间内不能再删除, 只能更新. 比如上面 id=1 的 revision, collection 就是从 @empty 切换到 qa, 继续操作删除, 就会报错.

$ curl POST http://127.0.0.1:8080/databases/qa/bulk_docs -d '{"Commands":[{"Id":"1","Type":"DELETE","ChangeVector":null}]}'
$ curl -v -XDELETE "http://127.0.0.1:8080/databases/qa/docs?id=1"

Voron.Exceptions.ConcurrencyException: Value already exists, but requested NewOnly

{"Url":"/databases/qa/bulk_docs","Type":"Voron.Exceptions.ConcurrencyException","Message":"Value already exists, but requested NewOnly","Error":"Voron.Exceptions.ConcurrencyException: Value already exists, but requested NewOnly\n   at Voron.Data.BTrees.Tree.ThrowConcurrencyException() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Voron\\Data\\BTrees\\Tree.cs:line 411\n   at Voron.Data.BTrees.Tree.DirectAdd(Slice key, Int32 len, TreeNodeFlags nodeType, Byte*& ptr) in C:\\Builds\\RavenDB-Stable-4.0\\src\\Voron\\Data\\BTrees\\Tree.cs:line 292\n   at Voron.Data.Tables.Table.InsertIndexValuesFor(Int64 id, TableValueReader& value) in C:\\Builds\\RavenDB-Stable-4.0\\src\\Voron\\Data\\Tables\\Table.cs:line 611\n   at Voron.Data.Tables.Table.Insert(TableValueBuilder builder) in C:\\Builds\\RavenDB-Stable-4.0\\src\\Voron\\Data\\Tables\\Table.cs:line 479\n   at Raven.Server.Documents.DocumentsStorage.CreateTombstone(DocumentsOperationContext context, Slice lowerId, Int64 documentEtag, CollectionName collectionName, String docChangeVector, Int64 lastModifiedTicks, String changeVector, DocumentFlags flags) in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Documents\\DocumentsStorage.cs:line 1320\n   at Raven.Server.Documents.DocumentsStorage.Delete(DocumentsOperationContext context, Slice lowerId, String id, LazyStringValue expectedChangeVector, Nullable`1 lastModifiedTicks, String changeVector, CollectionName collectionName, NonPersistentDocumentFlags nonPersistentFlags, DocumentFlags documentFlags) in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Documents\\DocumentsStorage.cs:line 1129\n   at Raven.Server.Documents.DocumentsStorage.Delete(DocumentsOperationContext context, String id, String expectedChangeVector) in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Documents\\DocumentsStorage.cs:line 1028\n   at Raven.Server.Documents.Handlers.BatchHandler.MergedBatchCommand.Execute(DocumentsOperationContext context) in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Documents\\Handlers\\BatchHandler.cs:line 305\n--- End of stack trace from previous location where exception was thrown ---\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n   at Raven.Server.Documents.Handlers.BatchHandler.<BulkDocs>d__0.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Documents\\Handlers\\BatchHandler.cs:line 59\n--- End of stack trace from previous location where exception was thrown ---\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n   at Raven.Server.Routing.RequestRouter.<HandlePath>d__6.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\Routing\\RequestRouter.cs:line 97\n--- End of stack trace from previous location where exception was thrown ---\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\n   at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()\n   at Raven.Server.RavenServerStartup.<RequestHandler>d__11.MoveNext() in C:\\Builds\\RavenDB-Stable-4.0\\src\\Raven.Server\\RavenServerStartup.cs:line 159"}

大概2分钟左右, 就可以删除成功. 应该是数据同步有一定延时造成的.

通过这个现象, 我认为 RavenDB collection 不等同于 mongodb 中的 collection, mongodb 中的 collection 可以理解为传统数据库中的 table, 但 RavenDB 中, 应该将 database 理解为表.

参考

RavenDB Server Revisionhttps://ravendb.net/docs/article-page/4.0/Csharp/server/revisions
System Properties Comparison CouchDB vs. RavenDBhttps://db-engines.com/en/system/CouchDB%3BRavenDB

 类似资料: