【问题标题】:Fetching big SQL table in the web app session在 Web 应用会话中获取大 SQL 表
【发布时间】:2022-01-06 10:35:42
【问题描述】:

我是网络应用程序的新手,所以如果我的问题有点基本,我深表歉意。我正在开发一个带有 R 闪亮的 Web 应用程序,其中输入是来自 Azure SQL 服务器的非常大的表。它们是 20 个表,每个表按十万行和数百列包含数字、字符等。我调用它们没有问题,我的主要问题是从 Azure SQL 服务器获取所有内容需要花费大量时间。大约需要 20 分钟。因此,Web 应用程序的用户需要等待相当长的时间。 我使用 DBI 包如下:

db_connect <- function(database_config_name){
  dbConfig <- config::get(database_config_name)
  connection <- DBI::dbConnect(odbc::odbc(),
                        Driver = dbConfig$driver,
                        Server = dbConfig$server,
                        UID    = dbConfig$uid,
                        PWD    = dbConfig$pwd,
                        Database = dbConfig$database,
                        encoding = "latin1"
  )

  return(connection)
}

然后通过以下方式获取表格:

connection <- db_connect(db_config_name)
table <- dplyr::tbl(con, dbplyr::in_schema(fetch_schema_name(db_config_name,table_name,data_source_type), fetch_table_name(db_config_name,table_name,data_source_type)))

我搜索了很多但没有找到一个好的解决方案,我很感激任何解决方案都可以解决这个问题。

【问题讨论】:

  • This link 可能是最好的起点。有一些功能可以让您的数据库进行过滤,而您的 Shiny 应用程序可以只带回这些结果(而不是整个数据库)。传输数据可能会花费最多时间,所以这应该会对您有所帮助。
  • @p0bs 不幸的是,这并没有多大帮助。我已经厌倦了不同的功能和驱动程序。我的主机是 Azure SQL 服务器。你知道如果我尝试其他 SQL 服务器(如 Mysql、Postgress 等)或非 SQL 数据库,它是否可以更快地获取表?

标签: sql r azure shiny azure-sql-database


【解决方案1】:

我每天使用 R 访问 SQL Server(不是 Azure)。对于较大的数据(如您的示例),我总是恢复使用命令行工具sqlcmd,它明显更快。对我来说唯一的痛点是学习参数并解决它没有返回正确的 CSV 的事实,需要查询后处理。您可能还有一个额外的痛点,即必须调整我的示例以连接到您的 Azure 实例(我没有帐户)。

为了在闪亮的环境中使用它并保持其交互性,我使用processx 包在后台启动进程,然后定期轮询其退出状态以确定它何时完成。

前面:这主要是一个“松散的指南”,我不假装这是一个功能齐全的解决方案。您可能需要自己解决一些粗糙的问题。例如,虽然我说您可以异步执行此操作,但您可以将轮询过程和延迟数据可用性处理到闪亮的应用程序中。我在这里的回答提供了启动过程并在完成后读取文件。最后,如果encoding= 对您来说是个问题,我不知道sqlcmd 是否正确地使用非拉丁语,我不知道是否或如何通过其非常有限甚至过时的论点来解决这个问题。

步骤:

  1. 将查询保存到文本文件中。可以在命令行上提供简短的查询,但是过了某个时间点(128 个字符?我不知道它的定义是否明确,并且最近看起来还不够)它只是失败了。使用查询-文件很简单,而且总是有效,所以我总是使用它。

    我总是为每个查询使用临时文件,而不是硬编码文件名;这很有意义。为方便起见(对我而言),我使用相同的临时文件基本名称,并为查询附加.sql,为返回的数据附加.csv,这样在临时文件中匹配查询到数据要容易得多。这是我使用的约定,仅此而已。

    tf <- tempfile()
    # using the same tempfile base name for both the query and csv-output temp files
    querytf <- paste0(tf, ".sql")
    writeLines(query, querytf)
    csvtf <- paste0(tf, ".csv")
    # these may be useful in troubleshoot, but not always [^2]
    stdouttf <- paste0(tf, ".stdout")
    stderrtf <- paste0(tf, ".stderr")
    
  2. 拨打电话。我建议你先看看同步方式有多快,看看你是否需要在闪亮的界面中添加异步查询和轮询。

    exe <- "/path/to/sqlcmd" # or "sqlcmd.exe"
    args <- c("-W", "b", "-s", "\037", "-i", querytf, "-o", csvtf,
              "-S", dbConfig$server, "-d", dbConfig$database,
              "-U", dbConfig$uid, "-P", dbConfig$pwd)
    ## as to why I use "\037", see [^1]
    ## note that the user id and password will be visible on the shiny server
    ## via a `ps -fax` command-line call
    proc <- processx::process$new(command = exe, args = args,
                                  stdout = stdouttf, stderr = stderrtf) # other args exist
    # this should return immediately, and should be TRUE until
    # data retrieval is done (or error)
    proc$is_alive()
    # this will hang (pause R) until retrieval is complete; if/when you
    # shift to asynchronous queries, do not do this
    proc$wait()
    

    可以使用processx::run 代替process$newproc$wait(),但我想我会从这条路开始,以防您想要/需要异步。

  3. 如果您使用异步操作,则定期检查(可能每 3 或 10 秒一次)proc$is_alive()。一旦返回FALSE,您就可以开始处理文件了。在此期间,shiny 将继续正常运行。 (如果你不去异步并因此选择proc$wait(),那么闪亮将挂起,直到查询完成。)

    如果你犯了一个错误并且没有proc$wait() 并尝试继续阅读该文件,那就是一个错误。该文件可能不存在,在这种情况下它会报错No such file or directory。该文件可能存在,也可能为空。它可能存在并且数据不完整。所以说真的,要下定决心保持同步并因此调用proc$wait(),或者进行异步并定期轮询,直到proc$is_alive() 返回FALSE

  4. 读取文件。使用sqlcmd 的三个“乐趣”需要对文件进行特殊处理。

    1. 它不会始终如一地使用嵌入式引号,这就是我选择使用"\037" 作为分隔符的原因。 (参见 [^1]。)
    2. 它会在列名下添加一行破折号,这会在 R 读取数据时破坏数据的自动分类。为此,我们对文件进行两步读取。
    3. 数据库中的空值是数据中的文字NULL 字符串。为此,我们在读取文件时更新了na.strings= 参数。
    exitstat <- proc$get_exit_status()
    if (exitstat == 0) {
      ## read #1: get the column headers
      tmp1 <- read.csv(csvtf, nrows = 2, sep = "\037", header = FALSE)
      colnms <- unlist(tmp1[1,], use.names = FALSE)
      ## read #2: read the rest of the data
      out <- read.csv(csvtf, skip = 2, header = FALSE, sep = "\037",
                      na.strings = c("NA", "NULL"), quote = "")
      colnames(out) <- colnms
    } else {
      # you should check both stdout and stderr files, see [^2]
      stop("'sqlcmd' exit status: ", exitstat)
    }
    

注意:

  1. 在经历了几个问题(sqlcmd.exe 中的一些,data.table::fread 和其他读者中的一些,都处理 CSV 格式的不合规问题)之后,有一次我选择停止使用逗号分隔返回,而不是选择"\037" 字段Delimiter。它适用于所有 CSV 阅读工具,并解决了很多问题(有些问题在这里没有提到)。如果您不担心,请随时将 args 更改为 "-s", ","(同时调整读取)。

  2. sqlcmd 似乎在出现问题时以不同的方式使用 stdout 或 stderr。我确信某处有理由,但关键是如果有问题,请检查两个文件。

    我添加了stdout=stderr= 的使用,因为我做了很多故障排除,如果我遇到查询,请继续这样做。使用它们并不是严格要求,但如果您忽略这些选项,您可能会谨慎行事。

  3. 顺便说一句,如果您选择对所有查询使用sqlcmd,则无需在 R 中创建连接对象。也就是说,db_connect 可能不会必要的。在我的使用中,我倾向于使用“真正的”R DBI 连接来处理已知的小查询,而使用批量 sqlcmd 来处理大约 10K 行以上的任何内容。有一个权衡;我没有在我的环境中对其进行充分测量,无法知道临界点在哪里,而且在你的情况下可能会有所不同。

【讨论】:

  • 哇,答案不错!
  • @GregorThomas 感觉更像是教程……
  • 知识似乎来之不易,我相信它不仅能帮助 OP。
  • @GregorThomas,谢谢。 “来之不易”是一个贴切的标签……虽然我没想到我在文档中误读或遗漏了某些东西,但有很多谷歌搜索和搜索这么多……”bcp 引用字段”和“bcp 列名”等。太多次我认为我已经拥有它然后去“生产”查看不同的表(和类类型等),却发现另一个极端情况。在我的私人包裹中,我有整个 roxygen 部分被视为“反对违反标准的咆哮”。叹息。
  • 我很惊讶你使用\037fortunes::fortune("james bond") 让我相信\031 是最好的选择(除了\007)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-27
  • 1970-01-01
  • 2011-11-24
  • 1970-01-01
  • 2021-10-31
  • 2023-01-17
相关资源
最近更新 更多