Oracle 11g - 随着数据的增长,系统性能受到影响

时间:2015-03-12 03:16:19

标签: java performance oracle11g

我们的系统具有相当高的并发性,多个Java线程一次从给定的Oracle 11g表中获取一条记录,该表通常包含大约两百万条记录。 总有很多记录准备好被处理。准备处理的记录是基于相对复杂的SQL语句选择的,但是一旦选择,处理顺序就基于FIFO算法(ID顺序)。 至关重要的是,相同的记录不会被两个不同的线程拾取。因此,我们有一个锁定机制。

从高级别的角度来看,它现在的工作方式是java线程调用一个存储过程,然后它将打开一个RECORD_READY_KEYS游标然后通过该游标迭代并尝试获取一个记录上的锁带有该键的锁定表。使用SELECT FOR UPDATE SKIP LOCKED完成锁定尝试。如果锁成功,则要处理的记录将返回到java线程进行处理。

只要准备处理的记录不是太多,一切都能正常工作。但是,当这个数字增长超过一个限制时(从超过15K时的观察结果),用于获取RECORD_READY_KEYS游标的SQL语句的性能开始下降。尽管它已经完全优化,但它开始运行接近0.2秒,这意味着每个java线程每秒最多只能处理5条记录。实际上,考虑到获取锁定,通过网络传输,实际进行处理,提交事务等所需的时间将导致处理速度更慢。

增加java线程的数量是一种选择,但是我们不能超过某个限制,因为它们将开始对数据库/应用程序服务器/系统资源等施加压力。

真正的问题是我们运行一个SQL语句来获取包含一万两千万个密钥中的一万五千个密钥的RECORD_READY_KEYS然后我们从顶部获取第一个可用记录,然后我们通过关闭游标来丢弃其余的

我的想法是在包级别定义一个KEYS_CACHE嵌套表,并将RECORD_READY_KEYS选择的结果存储在该嵌套表中。一旦键被锁定,它将从KEYS_CACHE中删除它并将其返回到java线程。这个过程可以这样直到整个KEYS_CACHE被消耗掉,当这种情况发生时,它会再次填充它。

现在我的问题将是:

Q1。你能用这种方法看到任何弱点吗? 我可以看到多个线程试图同时锁定同一条记录,这样浪费了一点时间。在java方面,我们可以使存储过程调用同步到给定的扩展,因为调用将从多个JVM发生。但是,我不认为这是一个重大问题。 另一个问题是当发生不太可能的回滚时,因为没有简单的方法来放回已删除的密钥。下一个RECORD_READY_KEYS选项会再次回来,延迟几分钟并不重要。

Q2。随着嵌套表越来越少的记录,它将变得非常稀疏。你能看到这成为一个问题吗?如果是这样,我应该将初始大小限制为5000键,或者它并不重要。

Q3。你能看到一个包级别的问题KEYS_CACHE嵌套表被这么多线程同时访问(我们有25到100个)

Q4。您能否看到一种不需要重新设计整个系统的替代方法。

提前谢谢

我认为在解释我的情况时我并不是很好。我们不会锁定要在两百万个记录表中处理的记录,但是我们锁定密钥而不是保存在不同的锁定表中。

说我有200万条记录表叫做消息: messages table

并且只有Key-A,Key-B和Key-C的消息准备好处理,密钥锁定表的可能内容可能是:

Keys table

注意,即使没有准备好为该密钥处理的消息,Key-X也在那里,因为具有这样一个密钥的消息刚刚完成处理并且清理线程尚未启动。这是好的,甚至是可取的,如果有更多新的消息,Key-X将在短时间内进入系统,同时它将保存一个新的插入。

因此我们的select(完全优化)将按此顺序获得一个包含Key-A,Key-C和Key-B的列表(Key-C在Key-B之前,因为它有一个Id = 2的消息,小于Id = 6的第一个Key-B消息 非常简化我们在这里做的事实是

SELECT key FROM messages WHERE ready = ‘Y’ GROUP BY key ORDER BY min(id)

一旦我们在游标中获得选择,我们逐个获取密钥并尝试将其锁定在key_locckings表中。一旦锁定成功,密钥就会被分配给一个线程(这里有线程表),并且将继续处理该线程处理为该密钥准备好的所有消息。正如我在第一篇文章中提到的,具有相同密钥的消息由与密钥相同的线程处理是至关重要的是我们如何链接必须按顺序处理的相关消息。

当选择的键数达到几千时,上面的SELECT就会立即生效。当它获得10000个密钥时它仍然表现良好。一旦检索到的密钥数超过15000,性能就开始降低。运行SELECT的时间仍然正常(大约0.2秒),并且我们在此选择中涉及的所有字段上都有索引。只需将WHERE,GROUP,ORDER BY应用于从占用时间的200万条记录中选择15000个密钥。

因此,对我们来说问题是每个线程都会运行相同的SELECT并获得15000条记录只是为了获取其中一条。我正在考虑的想法是,而不是关闭光标并抛弃努力工作,因为我们现在尝试将这些密钥存储在包级别嵌套表中,并在我们将它们分配给线程时从那里删除密钥。我的前三个问题只是想捕捉其他人对这种方法的看法,而最后一个问题是关于找到一些替代想法(例如有人会说使用高级队列等)

1 个答案:

答案 0 :(得分:0)

嗯......我正在GLOC(大湖甲骨文会议,俄亥俄州克里夫兰市)谈论索引,我的一个例子(我认为)非常相似。

你有一个包含大量行的表,并且你有多个消费者进程(JAVA线程),他们都希望处理该表的内容。

首先,我建议避免使用SKIP LOCKED。如果您认为自己需要,请考虑是否已将INITRANS设置得足够高。请注意SKIP LOCKED表示Oracle将跳过锁定的 资源 ,而不仅仅是锁定的行。如果某个块的ITL已满,SKIP LOCKED将跳过它,即使该块中有未锁定的行!

有关此内容的更详细讨论,请参阅此处: https://markjbobak.wordpress.com/2010/04/06/unintended-consequences/

所以,根据我的建议。对于每个并发的JAVA线程,定义一个线程号。因此,假设您有10个并发线程,请为每个线程分配一个线程号或线程ID 0-9。现在,如果您可以灵活地修改表,则可以添加一列THREAD_ID,然后在从表中进行选择时在select语句中使用该列。每个并发的JAVA线程将仅选择与其匹配的线程ID的那些行。通过这种方式,您可以保证您避免碰撞。如果你没有能力在表中添加一个列,那么你希望有一个漂亮的,数字的,序列驱动的主键?如果是这样,您可以通过查询MOD(PRIMARY_KEY_COLUMN, 10) = :client_thread_id来获得相同的效果。

此外,您是否有指定某种状态的列,或类似的东西,您将使用它来确定表中的哪些行有资格由JAVA线程处理?如果是这样,特别是如果该标准显着提高了选择性,那么创建仅为您感兴趣的值填充的虚拟列可能非常有用,如果该列随后被添加到索引中。例如(THREAD_ID, STATUS)

最后,您提到了按特定顺序处理。如果THREAD_ID, STATUS是您的选择标准,那么PRIORITYSTATUS_DATE列可能是您的订购要求。在这种情况下,继续构建索引,添加指定所需顺序的列,并使用表的主键加上它可能很有用。

使用精心构造的索引,并使用THREAD_ID的想法,应该可以构建一个索引,允许您:   - 避免冲突(在主键上使用THREAD_ID或MOD())   - 最小化索引的大小(vurtual列)   - 避免任何ORDER BY操作(按列添加顺序到索引)   - 避免任何TABLE ACCESS BY ROWID操作(将主键列添加到索引结尾)

我做了一些可能适用或可能不适用的假设。希望至少我的一些想法适用于您的情况。如果您提供更多详细信息,代码示例等,您可能会得到更具体的答案。

希望有所帮助。