原始网页:https://www.cockroachlabs.com/docs/stable/frequently-asked-questions.html#do-transactions-in-cockroachdb-guarantee-acid-semantics


SQL常见问题

如何批量导入数据到CockroachDB?

目前,我们能够使用一批INSERT语句(每条语句包含数据的大小不超过几MB)来实现批量导入。一行数据的大小决定了一个批量INSERT适合一次性处理多少行,一般来说1,000到10,000行表现最佳。更多细节可以查看导入数据

如何在CockroachDB中自动生成唯一的行ID?

自动生成唯一的行ID的方式是使用UUID列,并设置get_random_uuid()函数作为默认值

CREATE TABLE t1 (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name STRING);
INSERT INTO t1 (name) VALUES ('a'), ('b'), ('c');
SELECT * FROM t1;
+--------------------------------------+------+
|                  id                  | name |
+--------------------------------------+------+
| 60853a85-681d-4620-9677-946bbfdc8fbc | c    |
| 77c9bc2e-76a5-4ebc-80c3-7ad3159466a1 | b    |
| bd3a56e1-c75e-476c-b221-0da9d74d66eb | a    |
+--------------------------------------+------+
(3 rows)

此外,用户也可以使用BYTES类型的列,并设置uuid_v4()函数作为默认值。

CREATE TABLE t2 (id BYTES PRIMARY KEY DEFAULT uuid_v4(), name STRING);
INSERT INTO t2 (name) VALUES ('a'), ('b'), ('c');
SELECT * FROM t2;
+---------------------------------------------------+------+
|                        id                         | name |
+---------------------------------------------------+------+
| "\x9b\x10\xdc\x11\x9a\x9cGB\xbd\x8d\t\x8c\xf6@vP" | a    |
| "\xd9s\xd7\x13\n_L*\xb0\x87c\xb6d\xe1\xd8@"       | c    |
| "\uac74\x1dd@B\x97\xac\x04N&\x9eBg\x86"           | b    |
+---------------------------------------------------+------+
(3 rows)

上述两种情况,生成的ID大小都是128bit。数值范围足够大,因此几乎没有机会生成不唯一的值。同时一旦表数据增长达到一个KV Range(默认情况下超过64MB)阈值,新的ID将会分布到表的所有Range,并随着这些Range尽量分布到不同的节点。这意味着负载将由很多个节点分担。

如果用户必须要求生成的ID存储在同一个键值Range内,则可以使用SERIAL数据类型,此时相当于使用unique_rowid作为默认值的INT类型。

CREATE TABLE t3 (id SERIAL PRIMARY KEY, name STRING);
INSERT INTO t3 (name) VALUES ('a'), ('b'), ('c');
SELECT * FROM t3;
+--------------------+------+
|         id         | name |
+--------------------+------+
| 293807573840855041 | a    |
| 293807573840887809 | b    |
| 293807573840920577 | c    |
+--------------------+------+
(3 rows)

在插入过程中,unique_rowid函数根据时间戳和节点ID生成的默认值。像这样按时间有序的值能够保证全局唯一性,除非每个节点在一秒内生成了超量的ID(100,000+)。

如何在CockroachDB中生成唯一且递增的有序数列?

在CockroachDB中使用unique_rowid()内置函数或SQL序列可以生成的有序数列。然而用户需要注意以下几个方面:

UUID、sequences和unique_rowid()的区别是?

属性 uuid_v4()生成的UUID unique_rowid()生成的INT 序列
大小 16字节 8字节 1到8字节
属性有序性 无序 高度时间有序 高度时间有序
生成的性能开销 小,可伸缩 小,可伸缩 随机,可能导致竞争
值分布 均匀分布
(128 bits)
包含时间和位置(节点ID)的两部分 分布紧密,生成值较小
数据局部性 最大程度地分散数值的分布 短时间内生成的值在同一数值范围内 高度局部性
作为主键的INSERT时延 小,对并发度不敏感 小,但是时延会随着并发度提高而增加 较大
作为主键的INSERT吞吐量 最高 受限于一个节点的最大吞吐量 受限于一个节点的最大吞吐量
作为主键时的读吞吐量 最高(最大限度的并发度) 受限 受限

如何根据CockroachDB的时间,对表的写入操作进行排序?

大多数要求严格基于时间保证写入顺序的用例,可以替换成其他的分布式友好的解决方案来解决。

例如,可以考虑使用CockroachDB的Time Travel查询AS OF SYSTEM TIME,它支持以下特性:

此外,还可以考虑使用在上一个FAQ中unique_rowid(),生成大致时间有序的值。

然而,如果你的应用程序确实是需要严格基于时间保证写入顺序,CockroachDB能够提供一个严格的单调计数器,顺序递增:

#初始化
CREATE TABLE cnt(val INT PRIMARY KEY); INSERT INTO cnt(val) VALUES(1);
#在每次事务中
INSERT INTO cnt(val) SELECT max(val)+1 FROM cnt RETURNING val;

这将会导致INSERT事务相互之间产生冲突,强制限定集群中事务在同一时刻内只允许一个事务运行,从而保证这种方式生成的值是严格有序且无缝的。此时,性能将受到严重的影响。

如果您对这个问题感兴趣,欢迎联系我们,描述你应用场景。我们很乐意帮助你寻找替代的方案,以便在以后的CockroachDB版本中更好地满足你的需求。

如何获取插入到表当中的最后的ID/SERIAL值?

CockroachDB目前没有办法获取最后插入的值,但是用户可以选择使用带RETURNING子句的INSERT语句。 例如,使用RETURNING子句返回通过SERIALunique_rowid()自动生成的值的SQL语句:

CREATE TABLE users (id SERIAL, name STRING);
INSERT INTO users (name) VALUES ('mike') RETURNING id;

什么是事务冲突?

当多个客户端在同一时间内提交多个事务操作同一个数据的时候,会发生事务冲突。这会导致事务之间相互进行等待,性能降低,就像是在商店里很多人尝试通过同一个收银员进行结账。

更多关于冲突的更多细节,可以查看理解并避免事务冲突

CockroachDB是否支持JOIN

CockroachDB支持JOIN操作,我们也正在改进JOIN操作的性能。目前尚不支持Correlated Join(例如LATERALJOIN)。

什么时候使用交错表?

交错表能够通过优化密切关联的表的键值结构来提高查询的效率,CockroachDB会尝试将交错表的数据存放在同一个键值Range,以便它们一起进行读写操作。

用户在以下场景使用交错表将受益:

CockroachDB是否支持JSON或Protobuf数据类型?

是的,v2.0以上的版本已经支持JSONB数据类型。

如何知道CockroachDB在一个查询中选择了哪些索引?

可以使用EXPLAIN语句打印查询计划(其中包括索引使用信息),来查看给定查询在CockroachDB中使用哪些索引:

EXPLAIN SELECT col1 FROM tbl1;

如果需要显示地指定查询计划器针对某个查询使用哪个索引,可以使用带索引提示的特殊语法

SELECT col1 FROM tbl1@idx1;

如何记录慢查询?

有几种方式来记录SQL查询,记录类型取决于用户的需求。

SQL审计日志

WARNING: 这是实验性的功能,该功能的接口和输出可能会发生改变。

如果用户需要记录发生在特定表上的所有查询,SQL审计日志将会非常有用。

集群范围的执行日志

对于生产集群,记录所有查询的最佳方式是打开集群配置项sql.trace.log_statement_execute

SET CLUSTER SETTING sql.trace.log_statement_execute = true;

一旦该配置项打开,集群中的每个节点将输出其执行的所有查询到节点日志文件。在不需要该功能的时候则可以关闭该配置项:

SET CLUSTER SETTING sql.trace.log_statement_execute = false;

单节点执行日志

如果用户需要本地测试CockroachDB,想要记录指定节点执行的查询,则用户可以在节点启动的时候传递CLI Flag,也可以在一个已经运行的节点上执行一个SQL函数打开单节点日志记录功能。

使用CLI启动一个新的节点,需要在使用cockroach start命令启动节点时指定--vmoduleFlag,例如执行以下命令启动一个本地节点并记录该节点执行的所有查询:

cockroach start --insecure --host=localhost --vmodule=exec_log=2

另一种方式是在一个运行中节点的SQL提示符处,执行crdb_internal.set_vmodule()函数

SELECT crdb_internal.set_vmodule('exec_log=2');

+---------------------------+
| crdb_internal.set_vmodule |
+---------------------------+
|                         0 |
+---------------------------+
(1 row)

一旦日志打开,该节点的所有查询将通过以下格式输出到CockroachDB日志文件中:

I180402 19:12:28.112957 394661 sql/exec_log.go:173  [n1,client=127.0.0.1:50155,user=root] exec "psql" {} "SELECT version()" {} 0.795 1 ""

CockroachDB是否支持UUID类型?

是的,更多细节可以查看UUID

不使用ORDER BY的时候,CockroachDB如何排序结果?

若一个查询没有使用ORDER BY子句,行的处理或返回将以非确定的顺序进行。“非确定”意味着实际的顺序受到逻辑计划、硬盘上存储的数据的顺序、CockroachDB集群的拓扑结构的影响,而以上影响因子会在系统运行过程中随时变化。

为什么在Javascript中INT类型的列,对应的返回结果是字符串类型?

在CockroachDB当中,所有INT都将采用64 bits的精度,但是在JavaScript当中只有53 bits的精度。这就意味着CockroachDB中大数可能无法匹配到一个正确的JavaScript数值。例如,JavaScript将会把235191684988928001取整到最接近的值235191684988928000。在使用unique_rowid()函数的时候问题非常突出,unique_rowid()函数很可能返回超过53 bits精度的整数。

为了避免精度上的丢失,节点的pg驱动在默认的情况下会将CockroachDB的INT类型数据以字符串的形式返回。

// Schema: CREATE TABLE users (id INT DEFAULT unique_rowid(), name STRING);
pgClient.query("SELECT id FROM users WHERE name = 'Roach' LIMIT 1", function(err, res) {
  var idString = res.rows[0].id;
  // idString === '235191684988928001'
  // typeof idString === 'string'
});

如果需要通过该idString执行另一个查询,用户可以直接使用该idStringidString字符串在驱动内部将自动强制转换为CockroachDB中INT类型的值。

pgClient.query("UPDATE users SET name = 'Ms. Roach' WHERE id = $1", [idString], function(err, res) {
  // All should be well!
});

如果需要在JavaScript当中使用INT做算术计算,请勿直接使用内置的parseInt函数,需要使用像Long.js这样的支持大数的JavaScript库。

parseInt(idString, 10) + 1; // WRONG: returns 235191684988928000
require('long').fromString(idString).add(1).toString(); // GOOD: returns '235191684988928002'