DataStax Bulk Loader教程(一)
DataStax Bulk Loader系列教程共分为六篇。
通过作者Brian Hess对概念的清晰解释再辅以丰富的示例代码,用户可以较为全面细致地了解DataStax Bulk Loader这种高效易用的批量加载工具,从而实现轻松在数据模型、DSE系统或者其它数据系统之间的数据迁移。
另外,我们也建议您在使用dsbulk时参考dsbulk 文档页面,从而了解所有的相关参数和选项。
DataStax Bulk Loader - dsbulk是在DSE 6 引入的一种新的批量加载方法。(点击这里下载DataStax Bulk Loader)。
它提供了将数据加载(load)到DataStax Enterprise和Apache Cassandra,以及将数据从DSE和Apache Cassandra中卸载(unload)并对数据进行计数的更有效率的方案,不需要编写任何自定义的代码或是有其他组件,如Apache Spark。除了含有批量加载 (Bulk Load)以及批量卸载(bulk unload)的功能,dsbulk 也可以帮助将数据迁移到新的数据模型和在DSE系统或者其他数据系统之间的数据迁移。
dsbulk 有以下优点:
-
容易使用
-
支持常见的输入数据的格式
-
将数据导出为常见的输出数据格式
-
支持多种字段的格式,像是日期和时间
-
支持所有的DSE数据类型,包括了用户自定义的类型
-
支持高级的安全配置
-
能妥善处理解析错误的数据和数据库插入错误
-
能报告加载任务的状态和完成情况,包括总结统计信息(如加载率 load rate)
-
高效且快捷
我比较喜欢通过范例来学习,在本系列的博客中,我将展示一些运用dsbulk完成一些常见的任务的方法。
$ cqlsh -e "SELECT COUNT(*) FROM dsbulkblog.iris_with_id;" count ------- 150 (1 rows) Warnings : Aggregation query used without partition key
设置(Setting)
在开始这些范例之前,让我们先开始用cqlsh创建一些要用到的表格:
将被用到的数据文件存放在这个公开的Gist里。您需要单独将文件下载到你本地机器。为了简单起见,我们假设您将他们下载到了/tmp/dsbulkblog,您将看到以下这些文件:
$ ls -lh /tmp/dsbulkblog/* -rw-rw-r-- 1 digger digger 5.0K Jun 13 14:55 /tmp/dsbulkblog/iris.csv -rw-rw-r-- 1 digger digger 5.0K Jun 13 14:55 /tmp/dsbulkblog/iris_no_header.csv -rw-rw-r-- 1 digger digger 5.0K Jun 13 14:56 /tmp/dsbulkblog/iris_with_comment.csv -rw-rw-r-- 1 digger digger 3.1K Jun 13 14:56 /tmp/dsbulkblog/iris_with_nulls.csv -rw-rw-r-- 1 digger digger 3.7K Jun 13 14:56 /tmp/dsbulkblog/iris_with_null_string.csv -rw-rw-r-- 1 digger digger 4.5K Jun 13 14:57 /tmp/dsbulkblog/iris_without_id.csv -rw-rw-r-- 1 digger digger 289 Jun 13 14:57 /tmp/dsbulkblog/president_birthdates.psv -rw-rw-r-- 1 digger digger 562 Jun 13 14:57 /tmp/dsbulkblog/sportsteams.csv $ wc /tmp/dsbulkblog/* 151 151 5101 /tmp/dsbulkblog/iris.csv 150 150 5040 /tmp/dsbulkblog/iris_no_header.csv 152 155 5120 /tmp/dsbulkblog/iris_with_comment.csv 151 151 3101 /tmp/dsbulkblog/iris_with_nulls.csv 151 151 3701 /tmp/dsbulkblog/iris_with_null_string.csv 151 151 4608 /tmp/dsbulkblog/iris_without_id.csv 7 26 289 /tmp/dsbulkblog/president_birthdates.psv 6 61 562 /tmp/dsbulkblog/sportsteams.csv 919 996 27522 total
现在我们已经准备好开始展示范例了!
加载范例 (Loading Examples)
范例1.1 简易的加载
第一个范例是最简单的。让我们将iris.csv 的数据载入dsbulkblog.iris_with_id 表格。
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id
在我们看输出和结果之前,我们将这条命令行拆解成以下几部分:
-
load: 在dsbulk后面,我们有load。dsbulk现在有三种模式(dsbulk 1.2)中,加载(load),卸载(unload)和 计数(count)。就正如您可能猜测的,加载是将数据从文件中导入Cassandra,卸载是将数据从Cassandra导出到文件中。计数会给Cassandra中的数据进行计数以及报告各种指标。
-
-url: 接下来的是 -url 参数。它加载的是一个文件名或者文件的位置(或者如 HTML URL 的资源连接)。它可以是一个单独的文件,一个一个文件的地址,一个URL或者标准输入stdin(默认值)。在本范例中,这指向我们下载的 /tmp/dsbulkblog/iris.csv 文件。
-
-k: 接下来的是-k参数,这是使用的键空间(keyspace)名。
-
-t: 接着是-t参数,这是使用的表(table)名。
-
我们将 -k 设为 dsbulkblog,将 -t设为 iris_with_id 意味着我们将把数据加载到 dsbulkblog.iris_with_id 的表中。
以下是我们执行此命令的输出:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id Operation directory: /tmp/logs/LOAD_20190314-161940-640593 total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms | batches 150 | 0 | 329 | 0.02 | 0.05 | 9.84 | 39.58 | 45.88 | 1.00 Operation LOAD_20190314-161940-640593 completed successfully in 0 seconds. Last processed positions can be found in positions.txt
我们可以看到 dsbulk 已经将一些信息写入了stderr。让我回顾一下最终的统计表,该表表明总共有150条记录而且所有记录都成功。failed的列是未成功加载的行数。其余的统计信息的是load的吞吐量和延迟指标。
我们可以确认数据库有150条记录包含了一个COUNT 的查询(备注:我们在这里通过cqlsh 进行COUNT 的查询是为了展示的目的。我们会在之后的文章中进一步讨论dsbulk 的计数操作)。
同时,在这消息里也包含了很多其他的信息,包括了日志目录的信息。我在 /tmp 的目录下运行里这条指令,dsbulk在默认情况下会在运行命令的目录下创建一个叫做 logs 的子目录, 每当一个dsbulk指令运行时,会在本子目录下再创建一个日志目录。
加载的指令会有一个LOAD_ 开头的日志目录,卸载会有一个UNLOAD_ 开头的目录,计数会有一个COUNT_ 开头的。从输出我们可以看到:
操作地址: /tmp/logs/LOAD_20190314-155621-436426.
敏锐的眼睛会注意到,dsbulk 运行产生的文件名包含是的年、月、日、小时、分钟、秒。这个日志目录包含了两封文件: operation.log 和 positions.txt。
operation.log 是主要的日志文件,包含了使用的设置以及主要操作的日志。position.txt用于记录上次停止运行的地方并继续开始加载数据。如果在dsbulk运行中有其他的错误(我们之后会看到一些), 我们将会在本目录看到其他文件。
范例2.1 从标准输入Stdin 加载(Loading From Stdin)
Dsbulk旨在像其他命令行工具一样工作,并将数据从标准输入加载到数据库以及将数据卸载到标准输出stdout。事实上,默认的 -url 是标准输入来加载而标准输出则适用于unload。
例如:
$ cat /tmp/dsbulkblog/iris.csv | sed 's/Iris-//g' | dsbulk load -k dsbulkblog -t iris_with_id
我们可以快速通过一个SELECT 查询来检查数据并没有Iris- 前缀。
$ cqlsh -e "SELECT * FROM dsbulkblog.iris_with_id LIMIT 3;" id | petal_length | petal_width | sepal_length | sepal_width | species -----+--------------+-------------+--------------+-------------+------------ 23 | 1.7 | 0.5 | 5.1 | 3.3 | setosa 114 | 5.1 | 2.4 | 5.8 | 2.8 | virginica 53 | 4 | 1.3 | 5.5 | 2.3 | versicolor (3 rows)
我们也可以向没有标题的数据加一个标题行。比如,iris_no_header.csv没有一个标题行(header),但是与iris.csv相同,我们可以用awk来自定义一个标题行,如:
$ cat /tmp/dsbulkblog/iris_no_header.csv | awk 'BEGIN{print "sepal_length,sepal_width,petal_length,petal_width,species,id"} {print $0}' | dsbulk load -k dsbulkblog -t iris_with_id
这是一种方式,还有其他方法。
我们也可以用命令行添加缺失的列。比如,iris_without_id.csv 文件就像 iris.csv,但没有id列。因为id是主键列(Primary Key)的一部分,我们需要创建此列,awk 可以帮我们完成。
$ cat /tmp/dsbulkblog/iris_without_id.csv | awk 'BEGIN{id=-1} {if (id == -1) {printf("%s,id\n", $0);} else {printf("%s,%d\n", $0, id);} id++;}' | dsbulk load -k dsbulkblog -t iris_with_id
所以很明显我喜欢awk,sed还有cut。但是你可以用任何你想用的命令行工具:perl, python, 等。或你可以用任何你喜欢的编程语言编写程序写入stdout 如,Java, C 等。
范例2.2: 从一个URL加载(Loading From A URL)
Dsbulk 也将可以从一个URL加载,如使通过iris.csv 的HTTP 地址:
$ dsbulk load -k dsbulkblog -t iris_with_id -url https://gist.github.com/brianmhess/8864cf0cb0ce9ea1fd64e579e9f41100/raw/522a1f564a381d7eacf6955c490bb6331d4369b2/iris.csv
范例2.3 从一个文件目录加载(Loading A Directory Of Files)
Dsbulk 也可以从一个文件目录加载数据。让我们创建一个目录,并把iris.csv加入本目录:
$ mkdir /tmp/dsbulkblog/iris $ cp /tmp/dsbulkblog/iris.csv /tmp/dsbulkblog/iris
现在让我们用以下命令行将/tmp/dsbulkblog/iris 目录中的文件导入:
$ dsbulk load -url /tmp/dsbulkblog/iris -k dsbulkblog -t iris_with_id
范例2.4:在目录中只加载一些文件(Loading Only Some Files In A Directory)
我们也可以在目录中只加载一些文件。让我们将president_birthdates.psv 放在 /tmp/dsbulkblog/iris 目录下,同时告诉dsbulk 只加载 .csv 类型文件。
$ cp /tmp/dsbulkblog/president_birthdates.psv /tmp/dsbulkblog/iris
我们会通过指定--connector.csv.fileNamePattern 参数告诉dsbulk只加载* .csv 文件(**/*.csv 文件是CSV文件的默认值,但我们在这里只是为了展示):
$ dsbulk load -url /tmp/dsbulkblog/iris -k dsbulkblog -t iris_with_id --connector.csv.fileNamePattern "**/*.csv"
范例3: 映射(Mapping)
现在如何让dsbulk 知道将哪个输入域映射到dsbulkblog.iris_with_id 表上的哪一列呢?它会先寻找输入数据第一行或者标题行。/tmp/dsbulkblog/iris.csv 文件的第一行是:
sepal_length,sepal_width,petal_length,petal_width,species,id
这些列名刚好与 dsbulkblog.iris_with_id 的名称完全匹配。dsbulk 默认用这此标题行学习如何将输入字段与列表名称匹配。很幸运的是它们可以匹配。
如果列名与数据的输入值不匹配呢?好吧,那我们可以将输入字段单独映射到列表名字。有几种方法可以做到这一点。首先是列出从文件所具有的内容到表所具有的内容的映射。例如:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "sepal_length = sepal_length, sepal_width = sepal_width, petal_length = petal_length, petal_width = petal_width, species = species, id = id"
不过,这个例子有一点傻,因为标题中的字段标题行与列表名相同。借用上面我们运用到标题行的例子,让我们用一个不同的标题行来进行映射。
$ cat /tmp/dsbulkblog/iris_no_header.csv | awk 'BEGIN{print "sepal_l,sepal_w,petal_l,petal_w,species_name,id_number"} {print $0}' | dsbulk load -k dsbulkblog -t iris_with_id -m "sepal_l = sepal_length, sepal_w = sepal_width, petal_l = petal_length, petal_w = petal_width, species_name = species, id_number = id"
您可以看到, -m包含了从标题行中指定的输入字段标题行到数据库模式中指定的列名称的映射列表。因此,名为sepal_l的输入字段将加载到名为sepal_length的列中。
你也可以在 -m 中写简写, *=*。这表示所有匹配的名称都将会匹配(如果没有提供,则是默认映射)。所以以下两种方法是对等的:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "sepal_length = sepal_length, sepal_width = sepal_width, petal_length = petal_length, petal_width = petal_width, species = species, id = id"
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "*=*"
我们也可以指定我们应该加载除一个以外所有匹配的列。例如,如果我们不想加载species 列,则可以执行以下操作:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "* = -species"
同时我们如果不想加载species 或者petal_length 列, 则可以:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "* = [-species, -petal_length]"
范例 3.1: 按索引映射
通过索引来映射是除了名字以外的另外一种方法。因此,考虑到 iris_no_header.csv 文件没有标题行,我们则可以映射输入列表的索引:
$ dsbulk load -url /tmp/dsbulkblog/iris_no_header.csv -k dsbulkblog -t iris_with_id -header false -m "0=sepal_length, 1=sepal_width, 2=petal_length, 3=petal_width, 4=species, 5=id"
范例3.2: 映射一个列表
我们也可以告诉dsbulk 忽略原本标题行,而用一个我们想让dsbulk 阅读的特定列表来替换。
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "sepal_length,sepal_width,petal_length,petal_width,species,id"
这将会按顺序映射列表。也就是说,第一个字段映射到sepal_length 列,而第二个字段映射到sepal_width 列, 以此类推。 如果输入文具有field1, field2, 等等的标题行,则等同于:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "field1=sepal_length,field2=sepal_width,field3=petal_length,field4=petal_width,field5=species,field6=id"
类似的,如果输入文件没有标题行,则等效于:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -header false -m "0=sepal_length,1=sepal_width,2=petal_length,3=petal_width,4=species,5=id"
范例 3.3: 跳过记录
请注意我们不会将标题行-header 设置为false。 如果我们这么做,我们则需要跳过第一行,不然我们就会尝试将第一行加载成数据。我们可以通过 -skipRecords 参数来处理:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "sepal_length,sepal_width,petal_length,petal_width,species,id" -header false -skipRecords 1
我们也可以跳过包含标题行的前100行数据,只加载最后50行:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "sepal_length,sepal_width,petal_length,petal_width,species,id" -header false -skipRecords 101
我们可以看到我们只加载了50行:
total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms | batches 50 | 0 | 122 | 0.01 | 0.05 | 10.13 | 23.33 | 23.33 | 1.00
范例 3.4: 只加载一些的记录
当讨论跳过记录的功能时,我们也应该讨论只加载一些记录的情况。我们可以用-maxRecords 将加载数记为前20条记录。
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -maxRecords 20
我们可以看到我们只加载了20条记录:
total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms | batches 20 | 0 | 53 | 0.00 | 0.05 | 5.86 | 12.26 | 12.26 | 1.00
我们同时也可以跳过和限制加载数据:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -skipRecords 100 -maxRecords 20
范例 3.5: 错误的映射或者错误的解析
现在,假设我们做了了些什么,在之前例子中少加载一列。比如我们忘记了id 列。
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -header false -m "sepal_length,sepal_width,petal_length,petal_width,species"
Operation directory: /tmp/logs/LOAD_20190314-162512-798132
Operation LOAD_20190314-162512-798132 failed: Missing required primary key column id from schema.mapping or schema.query.
Last processed positions can be found in positions.txt
我们看到会产生一个错误,因为id 列是一个主键列,但dsbulk需要有指定所有的主键列。
然而,如果我们包含了所有的列但是调换了列之间的顺序,例如将id 和 species 列调换顺序,我们将会得到一个不同的报错:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -k dsbulkblog -t iris_with_id -m "0=sepal_length,1=sepal_width,2=petal_length,3=petal_width,4=id,5=species" Operation directory: /tmp/logs/LOAD_20190314-162539-255317 Operation LOAD_20190314-162539-255317 aborted: Too many errors, the maximum allowed is 100. total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms | batches 102 | 101 | 0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 Rejected records can be found in the following file(s): mapping.bad Errors are detailed in the following file(s): mapping-errors.log Last processed positions can be found in positions.txt
这里我们可以看到产生了100多个报错。 但是这是什么类型的报错呢?为了了解详情,我们需要检查/tmp/logs/LOAD_20190314-162539-255317 文件,特别是mapping-errors.log。 这报错文件的开头看起来像:
Resource: file:/tmp/dsbulkblog/iris.csv Position: 129 Source: 6.4,2.8,5.6,2.1,Iris-virginica,128\u000a java.lang.IllegalArgumentException: Could not parse 'Iris-virginica'; accepted formats are: a valid number (e.g. '1234.56'), a valid Java numeric format (e.g. '-123.45e6'), a valid date-time pattern (e.g. '2019-03-14T16:25:41.267Z'), or a valid boolean word
at com.datastax.dsbulk.engine.internal.codecs.util.CodecUtils.parseNumber(CodecUtils.java:119)
at com.datastax.dsbulk.engine.internal.codecs.string.StringToNumberCodec.parseNumber(StringToNumberCodec.java:72)
at com.datastax.dsbulk.engine.internal.codecs.string.StringToIntegerCodec.externalToInternal(StringToIntegerCodec.java:55)
at com.datastax.dsbulk.engine.internal.codecs.string.StringToIntegerCodec.externalToInternal(StringToIntegerCodec.java:26)
at com.datastax.dsbulk.engine.internal.codecs.ConvertingCodec.serialize(ConvertingCodec.java:50)
Suppressed: java.time.format.DateTimeParseException: Text 'Iris-virginica' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1819)
at com.datastax.dsbulk.engine.internal.codecs.util.SimpleTemporalFormat.parse(SimpleTemporalFormat.java:41)
at com.datastax.dsbulk.engine.internal.codecs.util.ZonedTemporalFormat.parse(ZonedTemporalFormat.java:45)
at com.datastax.dsbulk.engine.internal.codecs.util.CodecUtils.parseNumber(CodecUtils.java:106)
Suppressed: java.lang.NumberFormatException: For input string: "Iris-virginica"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.lang.Double.valueOf(Double.java:502)
at com.datastax.dsbulk.engine.internal.codecs.util.CodecUtils.parseNumber(CodecUtils.java:101)
Suppressed: java.lang.NumberFormatException: null
at java.math.BigDecimal.(BigDecimal.java:494)
at java.math.BigDecimal.(BigDecimal.java:383)
at java.math.BigDecimal.(BigDecimal.java:806)
at com.datastax.dsbulk.engine.internal.codecs.util.CodecUtils.parseNumber(CodecUtils.java:96)
at com.datastax.dsbulk.engine.internal.codecs.string.StringToNumberCodec.parseNumber(StringToNumberCodec.java:72)
Suppressed: java.text.ParseException: Invalid number format: Iris-virginica
at com.datastax.dsbulk.engine.internal.codecs.util.CodecUtils.parseNumber(CodecUtils.java:247)
at com.datastax.dsbulk.engine.internal.codecs.util.CodecUtils.parseNumber(CodecUtils.java:92)
at com.datastax.dsbulk.engine.internal.codecs.string.StringToNumberCodec.parseNumber(StringToNumberCodec.java:72)
at com.datastax.dsbulk.engine.internal.codecs.string.StringToIntegerCodec.externalToInternal(StringToIntegerCodec.java:55)
at com.datastax.dsbulk.engine.internal.codecs.string.StringToIntegerCodec.externalToInternal(StringToIntegerCodec.java:26)
我们可以看到dsbulk尝试将Iris-setosa 转换成数字。这是因为它认为这个列是id 列,包含INT值。如果我们仔细看文件,我们就会看到一次又一次相同的错误。我们显然搞砸了映射。在100条错误(这个是默认值,但是可以通过设置 -maxErrors参数复写)之后,dsbulk 退出尝试加载的进程。
当dsbulk 找到了一个不能解析的输入值,它会将那行加入mapping.bad 文件。这是dsbulk在导入时看到的输入行。如果一或两行出现乱码或者和其它行有不同的格式,dsbulk会将其加入mapping.bad文件,将错误记录到mapping-errors.log的日志中,但是会继续执行加载直到最大容错数为止。这样,一些坏行不会阻碍整个加载过程,而且用户也可以处理这些坏行,像手动插入它们甚至使用不同参数在mapping.bad文件重新运行dsbulk。
例如,我们可以通过以下方法重新加载这些坏行:
$ dsbulk load -url /tmp/logs/LOAD_20190314-162539-255317/mapping.bad -k dsbulkblog -t iris_with_id -header false -m "0=sepal_length,1=sepal_width,2=petal_length,3=petal_width,4=species, 5=id" Operation directory: /tmp/logs/LOAD_20190314-163033-590156 total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms | batches 101 | 0 | 231 | 0.01 | 0.05 | 8.50 | 17.17 | 18.22 | 1.00 Operation LOAD_20190314-163033-590156 completed successfully in 0 seconds. Last processed positions can be found in positions.txt
范例 3.6: 缺失字段和额外字段
有些时候我们只加载了一些列。当表格的列比输入数据的列多时,dsbulk默认会报错。现在尽管有必要指定所有主键列,但也可以允许其它列保持未定义或者未设置的状态。
例如,我们假设我们没有定义sepal_length 列,我们可以像下面例子用awk模拟这一情况:
$ cat /tmp/dsbulkblog/iris.csv | awk -F, '{printf("%s,%s,%s,%s,%s\n", $2, $3, $4, $5, $6)}' | dsbulk load -k dsbulkblog -t iris_with_id
因为我们没有提供sepal_length列,这最终会产生一个错误:
Operation directory: /tmp/logs/LOAD_20190314-163121-694645 At least 1 record does not match the provided schema.mapping or schema.query. Please check that the connector configuration and the schema configuration are correct. Operation LOAD_20190314-163121-694645 aborted: Too many errors, the maximum allowed is 100. total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms | batches 102 | 101 | 0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 Rejected records can be found in the following file(s): mapping.bad Errors are detailed in the following file(s): mapping-errors.log Last processed positions can be found in positions.txt
为了解决这个问题,我们可以添加 --schema.allowMissingFields:
$ cat /tmp/dsbulkblog/iris.csv | awk -F, '{printf("%s,%s,%s,%s,%s\n", $2, $3, $4, $5, $6)}' | dsbulk load -k dsbulkblog -t iris_with_id --schema.allowMissingFields true
同理,我们可以允许输入文件有额外列的情况。
$ cat /tmp/dsbulkblog/iris.csv | awk -F, '{printf("%s,%s,%s,%s,%s,%s,extra\n", $1, $2, $3, $4, $5, $6)}' | dsbulk load -k dsbulkblog -t iris_with_id
这样将会正常加载列,但额外字段会被忽视。但是如果我们希望严格限制输入有额外字段的情况,可以让dsbulk报错:
$ cat /tmp/dsbulkblog/iris.csv | awk -F, '{printf("%s,%s,%s,%s,%s,%s,extra\n", $1, $2, $3, $4, $5, $6)}' | dsbulk load -k dsbulkblog -t iris_with_id --schema.allowExtraFields false
这个错误是因为我们没有说明如何映射额外的字段:
Operation directory: /tmp/logs/LOAD_20190314-163305-346514 At least 1 record does not match the provided schema.mapping or schema.query. Please check that the connector configuration and the schema configuration are correct. Operation LOAD_20190314-163305-346514 aborted: Too many errors, the maximum allowed is 100. total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms | batches 102 | 101 | 0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 Rejected records can be found in the following file(s): mapping.bad Errors are detailed in the following file(s): mapping-errors.log Last processed positions can be found in positions.txt
范例 3.7: 空格
有些时候在text string 的开头会有空格。我们有时候想保留这个空格但有时候并不想保留。 这可以通过参数--connector.csv.ignoreLeadingWhitespaces 进行控制,默认为false(空格被保留)。比如:
$ cat /tmp/dsbulkblog/iris.csv | sed "s/Iris/ Iris/g" | dsbulk load -k dsbulkblog -t iris_with_id
我们可以通过dsbulk unload 命令检查被保留的前导空格(我们会在之后的博客文章进行讨论):
$ dsbulk unload -query "SELECT species FROM dsbulkblog.iris_with_id" | head Operation directory: /tmp/logs/UNLOAD_20190321-144112-777452 total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms 150 | 0 | 392 | 0.01 | 0.02 | 9.49 | 41.68 | 41.68 Operation UNLOAD_20190321-144112-777452 completed successfully in 0 seconds. species Iris-setosa Iris-setosa Iris-virginica Iris-versicolor Iris-virginica Iris-versicolor Iris-virginica Iris-virginica Iris-virginica
要剥离前导空格,我们将--connector.csv.ignoreLeadingWhitespaces 设为true,再运行一次 load 指令:
$ cat /tmp/dsbulkblog/iris.csv | sed "s/Iris/ Iris/g" | dsbulk load -k dsbulkblog -t iris_with_id --connector.csv.ignoreLeadingWhitespaces true
同理,我们可以通过dsbulk unload 指令来检查:
$ dsbulk unload -query "SELECT species FROM dsbulkblog.iris_with_id" | head Operation directory: /tmp/logs/UNLOAD_20190321-144510-244786 total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms 150 | 0 | 416 | 0.01 | 0.01 | 8.16 | 36.96 | 36.96 Operation UNLOAD_20190321-144510-244786 completed successfully in 0 seconds. species Iris-setosa Iris-setosa Iris-virginica Iris-versicolor Iris-virginica Iris-versicolor Iris-virginica Iris-virginica Iris-virginica
范例 4:提供查询
范例 4.1: 自己提供查询
我们可以实际提供一个INSERT 语句本身来指定映射:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -query "INSERT INTO dsbulkblog.iris_with_id(id,petal_width,petal_length,sepal_width,sepal_length,species) VALUES (:id, :petal_width, :petal_length, :sepal_width, :sepal_length, :species)"
请注意我们这里不需要指定 -k 或者 -t的参数,因为它已经包含在查询语句里了。
范例 4.2: 提供一个含有位置映射的查询
我们可以实际提供一个INSERT 语句本身来指定映射:
$ dsbulk load -url /tmp/dsbulkblog/iris_no_header.csv -query "INSERT INTO dsbulkblog.iris_with_id(petal_width,petal_length,sepal_width,sepal_length,species,id) VALUES (?,?,?,?,?,?)" -header false
请注意我需要将id 列移到列表的最后。因为这里的顺序很重要,而且必须将顺序与输入数据匹配。在我们数据中,id列放在最后。
范例 4.3: 提供一个含有常量的查询
我们也可以指定一个将常量值放入列的映射。例如,让我们把所有species 列设为相同的值:
$ dsbulk load -url /tmp/dsbulkblog/iris.csv -query "INSERT INTO dsbulkblog.iris_with_id(id,petal_width,petal_length,sepal_width,sepal_length,species) VALUES (:id, :petal_width, :petal_length, :sepal_width, :sepal_length, 'some kind of iris')"
案例 4.4: 通过一个查询删除数据
这看起来似乎违反直觉,但是我们可以使用disbulk load 命令删除数据。除了使用INSERT 语句,我们也可以发出DELETE 语句。 例如,让我们删除我们表中与iris.csv 文件对应的前10行 (11 如果您包含了标题行)。
$ head -11 /tmp/dsbulkblog/iris.csv | dsbulk load -query "DELETE FROM dsbulkblog.iris_with_id WHERE id=:id" Operation directory: /tmp/logs/LOAD_20190320-180959-025572 total | failed | rows/s | mb/s | kb/row | p50ms | p99ms | p999ms | batches 10 | 0 | 32 | 0.00 | 0.00 | 4.59 | 5.96 | 5.96 | 1.00 Operation LOAD_20190320-180959-025572 completed successfully in 0 seconds. Last processed positions can be found in positions.txt
我们可以cqlsh里检查,发现这些行已经被删除了:
$ cqlsh -e "SELECT * FROM dsbulkblog.iris_with_id WHERE id IN (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)" id | petal_length | petal_width | sepal_length | sepal_width | species ----+--------------+-------------+--------------+-------------+------------- 10 | 1.5 | 0.2 | 5.4 | 3.7 | Iris-setosa 11 | 1.6 | 0.2 | 4.8 | 3.4 | Iris-setosa 12 | 1.4 | 0.1 | 4.8 | 3 | Iris-setosa 13 | 1.1 | 0.1 | 4.3 | 3 | Iris-setosa 14 | 1.2 | 0.2 | 5.8 | 4 | Iris-setosa 15 | 1.5 | 0.4 | 5.7 | 4.4 | Iris-setosa (6 rows)
显然,我们可以在这里做其它所有种类的查询。我们可以做计数更新(counter updates)、集合更新(collection updates)等等。
点击这里下载 DataStax Bulk Loader。
本系列所有文章: