如何在R中异步查询多个数据库

时间:2019-09-09 20:16:20

标签: r postgresql asynchronous

我目前正在使用存储的连接列表和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循环更改为单个执行语句,以在多个数据库中爆发相同的查询并返回结果。

2 个答案:

答案 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应该很好地传输到节点,因为它只是listmydb(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