为什么 R 在从 RODBC 检索数据时会崩溃?

Why does R crash when retreiving data from RODBC?

提问人:MikeB 提问时间:8/9/2023 最后编辑:MikeB 更新时间:8/10/2023 访问量:39

问:

我正在使用 RGui 在 Windows 10 上运行 R 4.3.1。如果我连接到我的数据库:

library(RODBC)

connstr = sprintf('driver={ODBC Driver 17 for SQL Server};server=%s;database=%s;uid=%s;pwd=%s',
    /* my parameters */)
    
dbhandle <- odbcDriverConnect(connstr)

我可以取回一列:

tbl1 <- sqlQuery(dbhandle, sprintf('select runID from myTable WHERE runID = 34'))

但是如果我尝试获取两列:

tbl2 <- sqlQuery(dbhandle, sprintf('select runID, fit from myTable WHERE runID = 34'))

RGui 只是崩溃。它不显示消息;它的窗户刚刚关闭,就这样结束了。

命令行中的 R.EXE 几乎相同:它结束并返回到命令提示符,没有错误消息。

为什么这么微不足道的练习会让 R 崩溃?如何在不崩溃的情况下使用 R?

同样在 Ubuntu 上

在 Ubuntu 20.04 上使用 R 3.6.3,我遇到了同样的问题,但至少我收到了一条段错误消息:

> tbl2 <- sqlQuery(dbhandle, sprintf('select fit from myTable WHERE runID = 34'))

给出这个:

 *** caught segfault ***
address 0x560b5bbef000, cause 'memory not mapped'

Traceback:
 1: odbcFetchRows(channel, max = max, buffsize = buffsize, nullstring = nullstring,     believeNRows = believeNRows)
 2: sqlGetResults(channel, errors = errors, ...)
 3: sqlQuery(dbhandle, sprintf("select fit from myTable WHERE runID = 34"))

如何使用 R 成功从 ODBC 查询数据?

R 崩溃 rodbc sql-server-2022

评论


答:

0赞 MikeB 8/10/2023 #1

在解决了这个问题和相关问题之后,我认为很明显,RODBC 中存在一个围绕 SQL Server 上二进制数据类型处理的错误。

如果我从连接开始:

# install.packages('RODBC')

library(RODBC)

connstr = sprintf('driver={ODBC Driver 17 for SQL Server};server=%s;database=%s;uid=%s;pwd=%s', 
    /* my parameters */)
dbhandle <- odbcDriverConnect(connstr)

在该目标数据库中,我创建了一个小表:

create table mikeb.repocase (runid int, bindata varbinary(max));
insert into mikeb.repocase (runid, bindata) values (33, 0x0102030405);

我应该能够选择数据。在这里,数据只有五个字节,因此遇到内存损坏问题的可能性很低,但不是零。使用我在 R 中的连接:

tbl1 <- sqlQuery(dbhandle, 'select bindata from mikeb.repocase WHERE runID = 33')

返回数据。但这是不正确的:

> tbl1$bindata[[1]]
[1] 00 00 00 00 00

都是零,什么时候应该返回。01 02 03 04 05

将数据转换为将在 Linux (Ubuntu) 上返回正确的结果:image

> tbl2 <- sqlQuery(dbhandle, 'select CAST(bindata as image) as bindata from mikeb.repocase WHERE runID = 33')
> tbl2$bindata[[1]]
[1] 01 02 03 04 05

但是,在 Windows RGui 客户端上,相同的代码失败,并显示有关分配不可能数量的内存的错误:

> tbl2 <- sqlQuery(dbhandle, 'select cast(bindata as image) as bindata from mikeb.repocase WHERE runID = 33')
Error in odbcQuery(channel, query, rows_at_time) : 
  'R_Calloc' could not allocate memory (214748364800 of 1 bytes)

在 Linux 上,从列中获取大量数据(在我的例子中约为 13 MB)有时会导致段错误失败,其余时间返回具有正确长度但未初始化数据的对象。(这是安全问题吗?然而,使用演员表似乎几乎总是有效的。varbinary(max)IMAGE

有问题的代码位于 RODBC 函数中,该函数将进行列说明检查并设置数据绑定:cachenbind()

case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
{
    /* should really use SQLCHAR (unsigned) */
    SQLLEN datalen = thisHandle->ColData[i].ColSize;
    thisHandle->ColData[i].datalen = datalen;
    thisHandle->ColData[i].pData = R_Calloc(nRows * (datalen + 1), char);
    BIND(SQL_C_BINARY, pData, datalen);
}

调用 传递的大小为 1,计算为 的数量为 。 来自此专栏的 API。R_CallocnRows * (datalen + 1)datalenSQLDescribeCol()

对于数据类型,我们得到类型,并且是 -1。通过这种计算及其粗心的符号管理,加上忽略溢出,请求的数量是214748364800,与我们在 Windows 上看到的错误消息相匹配。(由于我现在还没有准备好在 Linux 上进行 C 开发,所以我无法评论那里发生了什么。也许 Linux 设法映射了一些内存来响应此调用,或者它最终从同一驱动程序的 Linux 版本中获得不同的数字。或者,可能是发生了其他事情。IMAGESQL_LONGVARBINARYdatalenR_CallocR_Calloc

对于类型,我们得到类型和 0 的数据。那么,请求的字节数比预期的要小得多——看起来是 100 字节。VARBINARY(max)SQL_VARBINARYR_CallocnRows

奇怪的是,这里使用的不是计算的.nRowsNROWS

无论如何,我们似乎很幸运 RODBC 可以返回二进制数据,因为它没有正确地处理数据的绑定或绑定内存的分配。

我今天已经写信给软件包维护者,但我还没有收到回复。