Apache Ignite 2.17 C++客户端:SQL表与KV API数据互通及游标问题
我使用Apache Ignite 2.17 C++瘦客户端写入记录,已创建关联缓存SQL_PUBLIC_NPV_RESULTS的npv_results表。最初通过SqlFieldsQuery插入数据时,SQL表可正常查询数据,但循环插入8万条记录时出现「Too many open cursors」错误;转而使用KV API的putAll进行批量插入后,缓存大小正常增长,但SQL表为空,无法通过GridGain控制台或DBeaver查询到数据。
我认为原因是KV API插入的是字符串格式的键值对,与表结构不兼容,且C++客户端无Binary Object API可注册对象模板。现需解决两个核心问题:
- 若退回
SqlFieldsQuery,如何解决游标未自动关闭的问题? - 若继续使用KV API的
putAll批量插入,如何让数据同步到SQL表中?
附相关代码片段
1. SqlFieldsQuery插入代码
// 示例SqlFieldsQuery插入逻辑 IgniteClientConfiguration cfg; cfg.SetEndpoints({"127.0.0.1:10800"}); IgniteClient client = IgniteClient::Start(cfg); CacheClient<int, std::string> cache = client.GetOrCreateCache<int, std::string>("SQL_PUBLIC_NPV_RESULTS"); for (int i = 0; i < 80000; ++i) { SqlFieldsQuery query("INSERT INTO npv_results(id, value) VALUES(?, ?)"); query.SetArgs(i, std::to_string(i * 1.5)); cache.Query(query); }
2. KV API putAll批量插入代码
// 示例KV API putAll批量插入逻辑 IgniteClientConfiguration cfg; cfg.SetEndpoints({"127.0.0.1:10800"}); IgniteClient client = IgniteClient::Start(cfg); CacheClient<std::string, std::string> cache = client.GetOrCreateCache<std::string, std::string>("SQL_PUBLIC_NPV_RESULTS"); std::map<std::string, std::string> batchData; for (int i = 0; i < 80000; ++i) { batchData.insert({std::to_string(i), std::to_string(i * 1.5)}); } cache.PutAll(batchData);
3. Java创建npv_results表的代码
// 示例Java创建表逻辑 Ignite ignite = Ignition.start(); CacheConfiguration<Integer, NpvResult> cacheCfg = new CacheConfiguration<>("SQL_PUBLIC_NPV_RESULTS"); cacheCfg.setIndexedTypes(Integer.class, NpvResult.class); IgniteCache<Integer, NpvResult> cache = ignite.getOrCreateCache(cacheCfg); // 执行建表SQL cache.query(new SqlFieldsQuery( "CREATE TABLE npv_results (" + "id INT PRIMARY KEY, " + "value DOUBLE) " + "WITH \"CACHE_NAME=SQL_PUBLIC_NPV_RESULTS\"" )).getAll();
解决方案
问题1:退回SqlFieldsQuery解决游标泄漏问题
「Too many open cursors」是因为每次调用cache.Query(query)后,返回的QueryCursor未被显式关闭,导致资源泄漏。C++瘦客户端中QueryCursor是RAII对象,但循环中频繁创建未及时释放仍会累积打开的游标。
解决方法:
- 显式关闭游标:每次调用
Query后获取游标对象,使用完毕后主动关闭,或利用RAII特性确保对象生命周期结束时自动释放。 - 批量插入优化:将单条插入改为批量SQL插入(一次插入多条数据),大幅减少游标创建次数。
示例优化后的SqlFieldsQuery批量插入代码:
IgniteClientConfiguration cfg; cfg.SetEndpoints({"127.0.0.1:10800"}); IgniteClient client = IgniteClient::Start(cfg); CacheClient<int, std::string> cache = client.GetOrCreateCache<int, std::string>("SQL_PUBLIC_NPV_RESULTS"); const int BATCH_SIZE = 1000; for (int i = 0; i < 80000; i += BATCH_SIZE) { std::string sql = "INSERT INTO npv_results(id, value) VALUES "; std::vector<SqlField> args; for (int j = 0; j < BATCH_SIZE && (i + j) < 80000; ++j) { if (j > 0) sql += ", "; sql += "(?, ?)"; args.push_back((i + j)); args.push_back(std::to_string((i + j) * 1.5)); } SqlFieldsQuery query(sql); query.SetArgs(args); auto cursor = cache.Query(query); cursor.Close(); // 显式关闭游标 }
关键说明:
- 批量插入将1000条数据合并为一次SQL请求,大幅降低游标创建数量。
- 显式调用
cursor.Close()确保资源及时释放,避免游标泄漏。
问题2:使用KV API putAll让数据同步到SQL表
KV API插入的数据无法被SQL查询到,是因为插入的键值对格式不符合SQL表对应的缓存索引结构:SQL表关联的缓存要求键和值必须与表定义的类型匹配,且值需要是符合Ignite二进制格式的对象(而非简单字符串)。
由于C++瘦客户端没有Binary Object API,可通过以下两种方式解决:
方式1:使用缓存的键值类型与SQL表定义严格匹配
确保缓存的键类型对应SQL表的主键类型,值类型使用与表字段匹配的复合类型(如自定义结构体,实现Ignite二进制序列化)。
示例代码:
// 定义与SQL表对应的结构体 struct NpvResult { int id; double value; // 实现二进制序列化/反序列化逻辑 friend BinaryWriter& operator<<(BinaryWriter& writer, const NpvResult& obj) { writer.WriteInt32("id", obj.id); writer.WriteDouble("value", obj.value); return writer; } friend BinaryReader& operator>>(BinaryReader& reader, NpvResult& obj) { obj.id = reader.ReadInt32("id"); obj.value = reader.ReadDouble("value"); return reader; } }; // 使用匹配的缓存类型 IgniteClientConfiguration cfg; cfg.SetEndpoints({"127.0.0.1:10800"}); IgniteClient client = IgniteClient::Start(cfg); CacheClient<int, NpvResult> cache = client.GetOrCreateCache<int, NpvResult>("SQL_PUBLIC_NPV_RESULTS"); std::map<int, NpvResult> batchData; for (int i = 0; i < 80000; ++i) { NpvResult res; res.id = i; res.value = i * 1.5; batchData.insert({i, res}); } cache.PutAll(batchData);
关键说明:
- 结构体
NpvResult必须实现Ignite二进制序列化的运算符重载,确保对象能被Ignite正确解析。 - 缓存的键类型
int对应SQL表的主键id INT,值类型NpvResult对应表的字段结构,插入的数据会自动被SQL索引识别。
方式2:通过SQL语句预定义缓存的键值映射
如果无法使用结构体,可在创建SQL表时明确指定键的字段,确保KV API插入的键对应主键,值以MAP格式存储对应字段:
修改Java建表语句,指定键值类型:
cache.query(new SqlFieldsQuery( "CREATE TABLE npv_results (" + "id INT PRIMARY KEY, " + "value DOUBLE) " + "WITH \"CACHE_NAME=SQL_PUBLIC_NPV_RESULTS, KEY_TYPE=java.lang.Integer, VALUE_TYPE=java.util.Map\"" )).getAll();
然后C++端使用int作为键,std::map<std::string, IgniteVariant>作为值插入:
IgniteClientConfiguration cfg; cfg.SetEndpoints({"127.0.0.1:10800"}); IgniteClient client = IgniteClient::Start(cfg); CacheClient<int, std::map<std::string, IgniteVariant>> cache = client.GetOrCreateCache<int, std::map<std::string, IgniteVariant>>("SQL_PUBLIC_NPV_RESULTS"); std::map<int, std::map<std::string, IgniteVariant>> batchData; for (int i = 0; i < 80000; ++i) { std::map<std::string, IgniteVariant> valueMap; valueMap["id"] = IgniteVariant(i); valueMap["value"] = IgniteVariant(static_cast<double>(i * 1.5)); batchData.insert({i, valueMap}); } cache.PutAll(batchData);
关键说明:
- 建表时指定
VALUE_TYPE=java.util.Map,允许值以Map格式存储字段。 - C++端使用
IgniteVariant兼容不同数据类型,确保字段类型与SQL表定义一致。
内容的提问来源于stack exchange,提问作者Haleema Khan

