我目前正在使用存储的连接列表和for循环查询多个数据库,但是我想通过异步查询所有数据库来加快过程
我在线阅读了有关promises功能的信息,但是它没有按预期工作。
for(connection in databases)
{
temp <- data.table(dbGetQuery(connection, "super secret sql query"))
results <- rbind(results, temp)
dbDisconnect(connection)
}
results$sum <- as.numeric(results$sum)
return(results)}
我想将此for循环更改为单个执行语句,以在多个数据库中爆发相同的查询并返回结果。
答案 0 :(得分:4)
因为future
将尝试将变量自动传输到节点中,并且不传输具有外部指针的变量(包括数据库连接对象),因此需要来为您做到这一点的包装。这是一个建议,虽然测试不足,但可以为您提供一个开始。
更新:我认为陈旧的连接最好在驱动程序级别完成,因此我建议使用pool
。 (如果您最了解如何在没有tryCatch(DBI::dbGetQuery(...), error=function(e) "expired")
的情况下终止连接,那么我就会耳熟能详...大多数ODBC驱动程序和odbc
本身对“有效连接”的意思。)
cred <- list(drv = odbc::odbc(), server = "server.address", user = "me", password = "secret")
mydb <- function(cred) {
library(DBI)
library(odbc)
library(pool)
if (exists(".cred") && !is.null(.cred) && !identical(.cred, cred)) {
if (exists(".pool") && !is.null(.pool)) {
pool::poolClose(.pool)
.pool <<- NULL
}
.cred <<- NULL
}
if (!exists(".pool") || is.null(.pool)) {
.pool <<- do.call(pool::dbPool, cred)
.cred <<- cred
}
conn <- pool::poolCheckout(.pool)
# hack to always return the pool object, don't "leak" it
do.call(on.exit, list(substitute(suppressWarnings(pool::poolReturn(conn)))),
envir = parent.frame())
conn
}
它做出了一个草率的决定,将一个可行的连接(及其凭据)存储在一个点变量中的每个节点的全局环境中,该点变量旨在不与其他任何冲突。 cred
应该很好地传输到节点,因为它只是list
。 mydb(cred)
将在不存在的情况下创建新连接,在存在且具有相同凭据的情况下通过旧连接,或者在凭据由于某些原因而更改的情况下删除旧连接并创建新连接。
POC:
library(DBI)
library(odbc)
library(pool)
library(future)
library(future.apply) # only required for this demo, future_lapply
cl <- parallel::makeCluster(3)
plan(cluster, workers=cl)
cred <- list(driver = odbc::odbc(), server = "sqlserver.ip.address", user = "me", password = "secret")
DBI::dbGetQuery(mydb(cred), paste("select", Sys.getpid(), " as R_pid"))
# R_pid
# 1 7500
DBI::dbGetQuery(mydb(cred), paste("select", Sys.getpid(), " as R_pid"))
# R_pid
# 1 7500
### single future call
a %<-% DBI::dbGetQuery(mydb(cred), paste("select", Sys.getpid(), " as R_pid"))
a
# R_pid
# 1 9732
### multiple future calls
future_lapply(1:4, function(ign) DBI::dbGetQuery(mydb(cred), paste("select", Sys.getpid(), " as R_pid")))
# [[1]]
# R_pid
# 1 9732
# [[2]]
# R_pid
# 1 6132
# [[3]]
# R_pid
# 1 6132
# [[4]]
# R_pid
# 1 8480
虽然尝试不泄漏数据库对象,但由于某些原因,我仍然会收到有关泄漏对象的警告...这表明我喜欢的on.exit(..., envir=parent.frame())
并没有尽我所愿。我认为警告是相对良性的,但是它确实暗示了连接管理中的一些草率行为。
我对加载软件包有点露骨,因为否则我会看到以下形式的错误:
# Error in (function (classes, fdef, mtable) :
# unable to find an inherited method for function 'dbGetQuery' for signature '"Microsoft SQL Server", "character"'
当我针对mssql进行测试并且您正在使用postgresql时,我认为问题无关紧要。通过对程序包的显式控制和代码中的其他一些细微差别来纠正。
答案 1 :(得分:0)
此问题的公认答案不正确。无论您使用 <-
、<<-
还是 assign(...)
,future 内运行的代码都不能修改该 future 外的 R 变量。这是因为 future 中的代码完全在不同的 R 进程中运行,并且仅通过套接字与主 R 进程通信。 “副作用”是不可能的——只有返回值才能让它回到原来的 R 进程。
例如:
a <- 1
b <- 1
f <- future::future({
a <<- 2
assign('b', 3, envir = .GlobalEnv)
})
future::value(f) # waits for the future to complete
print(a)
# [1] 1
print(b)
# [1] 1
如果你没有得到这个结果,你可能在交互式 RStudio 会话中使用 plan('multicore') 运行 future,它实际上并没有并行化。尝试 plan('multisession') 或 run with Rscript
。
正如 HenrikB(future package 的作者)在评论中指出的那样,不可能在 R 中的进程之间“共享”数据库连接——future 软件包制作的副本(包括从子进程复制到父进程的副本)过程,通过从未来返回)do not function。
db_conn <- DBI::dbConnect(...)
f <- future::future({
dplyr::tbl(db_conn, 'table_name')
})
future::value(f)
# Error: Invalid connection
connection_future <- future::future({
DBI::dbConnect(...)
})
db_conn <- future::value(connection_future)
dplyr::tbl(db_conn, 'table_name')
# Error: Invalid connection