db.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import os
  2. import pymysql
  3. import threading
  4. import time
  5. import atexit
  6. from collections import deque
  7. from pymysql.cursors import DictCursor
  8. # ============================================================
  9. # 数据库配置
  10. # ============================================================
  11. DB_HOST = "120.24.26.108"
  12. DB_PORT = 3307
  13. DB_USER = "collect_user"
  14. DB_PASSWORD = "collect123456"
  15. DB_NAME = "drug_retrieve" # 正式库;测试库改 drug_retrieve_test
  16. DB_CHARSET = "utf8mb4"
  17. # ============================================================
  18. # 连接池
  19. # ============================================================
  20. MYSQL_POOL = None
  21. POOL_LOCK = threading.Lock()
  22. class PooledConnection:
  23. def __init__(self, conn, pool):
  24. self._conn = conn
  25. self._pool = pool
  26. self._closed = False
  27. def cursor(self, *args, **kwargs):
  28. return self._conn.cursor(*args, **kwargs)
  29. def commit(self):
  30. return self._conn.commit()
  31. def rollback(self):
  32. return self._conn.rollback()
  33. def ping(self, *args, **kwargs):
  34. return self._conn.ping(*args, **kwargs)
  35. def close(self):
  36. if self._closed:
  37. return
  38. self._closed = True
  39. try:
  40. self._pool.putconn(self._conn)
  41. except Exception:
  42. try:
  43. self._conn.close()
  44. except Exception:
  45. pass
  46. def __getattr__(self, item):
  47. return getattr(self._conn, item)
  48. class MySQLConnectionPool:
  49. def __init__(self, minconn=1, maxconn=10, **connect_kwargs):
  50. self.minconn = int(minconn)
  51. self.maxconn = int(maxconn)
  52. self.connect_kwargs = dict(connect_kwargs)
  53. self._lock = threading.Lock()
  54. self._pool = deque()
  55. self._total = 0
  56. for _ in range(self.minconn):
  57. conn = pymysql.connect(**self.connect_kwargs)
  58. self._pool.append(conn)
  59. self._total += 1
  60. def getconn(self, timeout=None):
  61. start = time.time()
  62. while True:
  63. conn = None
  64. with self._lock:
  65. if self._pool:
  66. conn = self._pool.popleft()
  67. elif self._total < self.maxconn:
  68. conn = pymysql.connect(**self.connect_kwargs)
  69. self._total += 1
  70. if conn is None:
  71. if timeout is not None and (time.time() - start) >= timeout:
  72. raise TimeoutError("获取数据库连接超时")
  73. time.sleep(0.05)
  74. continue
  75. # 尝试 ping 确认连接有效,若无效则关闭并尝试重建
  76. try:
  77. conn.ping(reconnect=True)
  78. return PooledConnection(conn, self)
  79. except Exception:
  80. try:
  81. conn.close()
  82. except Exception:
  83. pass
  84. with self._lock:
  85. self._total = max(0, self._total - 1)
  86. # 尝试新建连接并返回
  87. try:
  88. new_conn = pymysql.connect(**self.connect_kwargs)
  89. with self._lock:
  90. self._total += 1
  91. return PooledConnection(new_conn, self)
  92. except Exception:
  93. # 若重建失败且超时则抛出,否则继续重试
  94. if timeout is not None and (time.time() - start) >= timeout:
  95. raise TimeoutError("获取数据库连接超时(重建失败)")
  96. time.sleep(0.1)
  97. continue
  98. def putconn(self, conn):
  99. try:
  100. conn.ping(reconnect=True)
  101. with self._lock:
  102. self._pool.append(conn)
  103. except Exception:
  104. try:
  105. conn.close()
  106. except Exception:
  107. pass
  108. with self._lock:
  109. self._total = max(0, self._total - 1)
  110. def closeall(self):
  111. with self._lock:
  112. while self._pool:
  113. c = self._pool.popleft()
  114. try:
  115. c.close()
  116. except Exception:
  117. pass
  118. self._total = 0
  119. def _pool_kwargs():
  120. """连接池参数"""
  121. return {
  122. "host": DB_HOST,
  123. "port": DB_PORT,
  124. "user": DB_USER,
  125. "password": DB_PASSWORD,
  126. "database": DB_NAME,
  127. "charset": DB_CHARSET,
  128. "cursorclass": pymysql.cursors.Cursor,
  129. }
  130. def get_mysql():
  131. """获取数据库连接(优先连接池,失败则直连)"""
  132. global MYSQL_POOL
  133. with POOL_LOCK:
  134. if MYSQL_POOL is None:
  135. try:
  136. MYSQL_POOL = MySQLConnectionPool(minconn=1, maxconn=20, **_pool_kwargs())
  137. except Exception:
  138. MYSQL_POOL = None
  139. if MYSQL_POOL is None:
  140. return pymysql.connect(**_pool_kwargs())
  141. return MYSQL_POOL.getconn()
  142. def get_connection():
  143. """直连数据库(兼容旧脚本,DictCursor 模式)"""
  144. return pymysql.connect(
  145. host=DB_HOST,
  146. port=DB_PORT,
  147. user=DB_USER,
  148. password=DB_PASSWORD,
  149. database=DB_NAME,
  150. charset=DB_CHARSET,
  151. autocommit=False,
  152. cursorclass=DictCursor,
  153. )
  154. # 在程序退出时确保池中所有物理连接被关闭
  155. try:
  156. atexit.register(lambda: MYSQL_POOL.closeall() if MYSQL_POOL is not None else None)
  157. except Exception:
  158. pass