使用Python cx_Oracle时 - 我们设置参数cursor.arraysize = 10000。我假设这意味着服务器上运行的Python客户端从Oracle数据库“顺序”接收批量为10K的数据。假设我们正在提取 5 亿条记录,数据库可以处理该负载,并且运行 Python 的服务器有足够的资源来处理,并且网络带宽没有问题。我们如何仍将其保持为 10k,但并行化获取以并发拉取数据,假设并发性为 15。拉取 10k 记录后,我们创建文件并将文件通过 ftp 传输到对象存储中。我想要弄清楚的是如何在从数据库获取数据的同时实现并发。
arraysize 设置获取大小 - 每个获取操作中的行数。每次获取都需要与数据库进行一次往返(来回几个 TCP 数据包),这会累积网络延迟。如果提取大小太小并且您的网络速度相当慢,您最终可能会将大部分时间花在网络延迟上,而不是花在有用的工作上。
但是,您仍然受到客户端消耗数据流的单个进程的速度的限制。如果要获取的数据量确实很大,那么并发获取可能是提高吞吐量的答案。这样做需要同时运行多个 python 进程,每个进程提取一部分数据。然而,您需要使用自己的编程控件来对此进行检测 - 分叉子进程,为每个子进程分配一个线程号,从父进程中观察它们以了解它们何时完成,检查状态并处理其中一个子进程中的错误等。这需要一些管道工作,但绝对可以完成。
但是,人们常常没有对数据库方面给予足够的考虑。如果你是单线程的,一旦你的 SQL 执行完成并且客户端开始发出 fetch 调用,数据库就非常空闲 - 所有时间都在网络和客户端进程上。但如果您有并发进程,这种情况可能会改变。然而,更大的问题不是获取,而是 SQL 执行本身。您不希望 15 个单独的数据库会话都扫描相同的表并执行相同的连接,从而导致每个线程低效地重复所有其他线程已经在执行的工作。这很容易导致数据库负载过大,特别是如果每个会话都在数据库中进行并行查询 (PQ)。
处理此问题的最佳方法是按线程号或每个线程可以与之关联的某个值(例如定义为数字 PK 列上的 MOD 的虚拟列)对表进行分区。然后让每个线程发出其 SQL 并将其表扫描隔离(分区修剪)到有问题的线程值。如果每个会话正在读取单独的分区,则不会发生重复工作,并且您可以大幅提高并发性以获得最大吞吐量,而不会导致数据库过载。
如果你做不到这一点,下一个最好的办法是使用 create-table-as-select (CTAS) 并使用 SQL 填充工作表(假设它具有连接并且除了简单转储一个表之外还可以工作) 。定义分区以与 CTAS 操作本身中的线程保持一致,以便它创建一个正确分区的表并在一个操作中加载它。另请务必使用
parallel (degree ...)
属性来利用数据库中的 PX。在分叉子线程之前,请在父进程中预先执行此操作。然后断开并释放所有子线程,每个子线程都会访问工作表的一个分区。缺点是您必须将数据写入两次(一次在数据库中,另一次在客户端上),但至少您没有重复 SQL 工作,这意味着您可以提高并发性而不会使数据库面临风险。
如果做得正确,您可以获得比单个进程更高的吞吐量。但你必须做对。如果您只是在每个线程中重复相同的繁重 SQL 工作,您的 DBA 将关闭您并告诉您限制并发性。上述技术之一可以避免这种情况并使每个人都满意。