python-pooledDB连接池性能问题

最近写工具时需要快速执行大量sql,之前因为db操作不是很多,所以底层实现是每次执行sql的时候,都会去新建立一个链接,这次就直接拿来用了,最后发现大概每秒只能执行300-400个的sql(还可以更高,因为只起了10个线程,但是如果线程数太高db可能会拒绝访问),所以想要改用连接池看看速度能有多块,但是换成链接池后,反而速度立马变成了100个sql/秒,当时还以为是连接池的配置不对,但是试了很多次都是这个速度,最后看了pooledDB的代码才知道对于高线程多并发的场景,pooledDB支持度有限。

并发时性能变差的原因

取出db链接时需要加锁测试存活性

首先,为了保证多线程不出问题,在存取链接到池子里面的时候,会对池子加锁。

而为了保证链接的可用性,在连接池取链接(pool.connection())的时候,pooledDB还会用该链接ping一下(ping_check()),来测试是否链接还可用,确认可用后再将链接返回并解锁队列,此时下一个线程才能来队列中拿链接。

因此在高并发的场景下,耗时一堆叠,就会越来越慢。

def connection(self, shareable=True):
self._lock.acquire()
try:
while (self._maxconnections
and self._connections >= self._maxconnections):
self._wait_lock()
con = self._idle_cache.pop(0)
except IndexError:
con = self.steady_connection()
else:
con._ping_check()
con = PooledDedicatedDBConnection(self, con)
self._connections += 1
finally:
self._lock.release()
return con

因此,只要不让db去检查链接存活性就可以大幅提升取连接的速度(可以通过初始化时指定ping=0来避免对连接的可用性进行检查), 减少阻塞时间, 但是同时也会导致可能出现不可用的链接,需要逻辑对这种场景进行兼容。

归还db连接时,会对没关机的链接进行回滚

除了取的时候会有问题,归还的时候,也会对pool的队列进行上锁,然后进行归还,pooledDB有个默认的操作,归还时进行reset,也就是回滚链接。

def cache(self, con):
self._lock.acquire()
...
if not self._maxcached or len(self._idle_cache) < self._maxcached:
con._reset(force=self._reset)
self._idle_cache.append(con)
...
self._lock.release()

def _reset(self, force=False):
if not self._closed and (force or self._transaction):
try:
self.rollback()
except Exception:
pass

如果大量的链接同时归还,也会出现耗时越来越长的问题,最终速度上不去。

对于这个问题,可以在初始化时指定reset=False,避免其强制性回滚。

解决办法

无论是ping=0,还是reset=False,虽然能解决速度问题,但是牺牲了pooledDB为连接带来的健壮性。

因此最好的办法是创建多个链接池。把压力均摊到每个连接池上这样就可以在大量并发的同时,拥有更好的性能。