clear_locks.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. #!/usr/bin/env python3
  2. """
  3. 清理 PostgreSQL 数据库锁和阻塞会话
  4. """
  5. import os
  6. import sys
  7. import psycopg2
  8. from psycopg2.extras import RealDictCursor
  9. from dotenv import load_dotenv
  10. # 加载环境变量
  11. _script_dir = os.path.dirname(os.path.abspath(__file__))
  12. _project_root = os.path.normpath(os.path.join(_script_dir, '..', '..'))
  13. load_dotenv(os.path.join(_project_root, '.env'))
  14. def get_connection():
  15. """建立数据库连接"""
  16. host = os.getenv('KNOWHUB_DB')
  17. port = int(os.getenv('KNOWHUB_PORT', 5432))
  18. user = os.getenv('KNOWHUB_USER')
  19. password = os.getenv('KNOWHUB_PASSWORD')
  20. dbname = os.getenv('KNOWHUB_DB_NAME')
  21. print(f"连接到 {host}:{port}/{dbname} as {user} ...")
  22. conn = psycopg2.connect(
  23. host=host,
  24. port=port,
  25. user=user,
  26. password=password,
  27. database=dbname,
  28. connect_timeout=10
  29. )
  30. conn.autocommit = True
  31. print("连接成功。\n")
  32. return conn
  33. def show_locks(cursor):
  34. """显示当前的锁信息"""
  35. print("=" * 80)
  36. print("当前数据库锁信息:")
  37. print("=" * 80)
  38. cursor.execute("""
  39. SELECT
  40. l.pid,
  41. l.locktype,
  42. l.relation::regclass AS table_name,
  43. l.mode,
  44. l.granted,
  45. a.usename,
  46. a.application_name,
  47. a.state,
  48. a.query_start,
  49. a.state_change,
  50. LEFT(a.query, 100) AS query
  51. FROM pg_locks l
  52. LEFT JOIN pg_stat_activity a ON l.pid = a.pid
  53. WHERE l.relation IS NOT NULL
  54. ORDER BY l.granted, a.query_start;
  55. """)
  56. locks = cursor.fetchall()
  57. if not locks:
  58. print("✓ 没有发现表级锁\n")
  59. return []
  60. for lock in locks:
  61. print(f"\nPID: {lock['pid']}")
  62. print(f" 表: {lock['table_name']}")
  63. print(f" 锁类型: {lock['locktype']} / {lock['mode']}")
  64. print(f" 已授予: {'是' if lock['granted'] else '否(等待中)'}")
  65. print(f" 用户: {lock['usename']}")
  66. print(f" 应用: {lock['application_name']}")
  67. print(f" 状态: {lock['state']}")
  68. print(f" 查询开始: {lock['query_start']}")
  69. print(f" 查询: {lock['query']}")
  70. return locks
  71. def show_blocking(cursor):
  72. """显示阻塞关系"""
  73. print("\n" + "=" * 80)
  74. print("阻塞关系:")
  75. print("=" * 80)
  76. cursor.execute("""
  77. SELECT
  78. blocked_locks.pid AS blocked_pid,
  79. blocked_activity.usename AS blocked_user,
  80. blocking_locks.pid AS blocking_pid,
  81. blocking_activity.usename AS blocking_user,
  82. blocked_activity.query AS blocked_query,
  83. blocking_activity.query AS blocking_query,
  84. blocking_activity.state AS blocking_state
  85. FROM pg_catalog.pg_locks blocked_locks
  86. JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
  87. JOIN pg_catalog.pg_locks blocking_locks
  88. ON blocking_locks.locktype = blocked_locks.locktype
  89. AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
  90. AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
  91. AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
  92. AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
  93. AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
  94. AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
  95. AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
  96. AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
  97. AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
  98. AND blocking_locks.pid != blocked_locks.pid
  99. JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
  100. WHERE NOT blocked_locks.granted;
  101. """)
  102. blocking = cursor.fetchall()
  103. if not blocking:
  104. print("✓ 没有发现阻塞关系\n")
  105. return []
  106. for block in blocking:
  107. print(f"\n被阻塞的会话 PID: {block['blocked_pid']} (用户: {block['blocked_user']})")
  108. print(f" 查询: {block['blocked_query'][:100]}")
  109. print(f"\n阻塞者 PID: {block['blocking_pid']} (用户: {block['blocking_user']})")
  110. print(f" 状态: {block['blocking_state']}")
  111. print(f" 查询: {block['blocking_query'][:100]}")
  112. return blocking
  113. def show_active_connections(cursor):
  114. """显示活跃连接"""
  115. print("\n" + "=" * 80)
  116. print("活跃连接:")
  117. print("=" * 80)
  118. cursor.execute("""
  119. SELECT
  120. pid,
  121. usename,
  122. application_name,
  123. client_addr,
  124. state,
  125. query_start,
  126. state_change,
  127. LEFT(query, 100) AS query
  128. FROM pg_stat_activity
  129. WHERE state != 'idle'
  130. AND pid != pg_backend_pid()
  131. ORDER BY query_start;
  132. """)
  133. connections = cursor.fetchall()
  134. if not connections:
  135. print("✓ 没有其他活跃连接\n")
  136. return []
  137. for conn in connections:
  138. print(f"\nPID: {conn['pid']}")
  139. print(f" 用户: {conn['usename']}")
  140. print(f" 应用: {conn['application_name']}")
  141. print(f" 客户端: {conn['client_addr']}")
  142. print(f" 状态: {conn['state']}")
  143. print(f" 查询开始: {conn['query_start']}")
  144. print(f" 查询: {conn['query']}")
  145. return connections
  146. def kill_session(cursor, pid):
  147. """终止指定的会话"""
  148. try:
  149. cursor.execute("SELECT pg_terminate_backend(%s)", (pid,))
  150. result = cursor.fetchone()
  151. if result and result[0]:
  152. print(f"✓ 成功终止会话 PID: {pid}")
  153. return True
  154. else:
  155. print(f"✗ 无法终止会话 PID: {pid}")
  156. return False
  157. except Exception as e:
  158. print(f"✗ 终止会话失败: {e}")
  159. return False
  160. def main():
  161. if len(sys.argv) > 1 and sys.argv[1] == '--kill-all':
  162. kill_all = True
  163. else:
  164. kill_all = False
  165. conn = get_connection()
  166. cursor = conn.cursor(cursor_factory=RealDictCursor)
  167. # 显示锁信息
  168. locks = show_locks(cursor)
  169. # 显示阻塞关系
  170. blocking = show_blocking(cursor)
  171. # 显示活跃连接
  172. connections = show_active_connections(cursor)
  173. # 如果有阻塞,询问是否终止
  174. if blocking or kill_all:
  175. print("\n" + "=" * 80)
  176. if kill_all:
  177. print("将终止所有活跃连接...")
  178. pids_to_kill = [c['pid'] for c in connections]
  179. else:
  180. print("发现阻塞关系,建议终止阻塞者会话")
  181. pids_to_kill = list(set([b['blocking_pid'] for b in blocking]))
  182. if pids_to_kill:
  183. print(f"\n准备终止的 PID: {pids_to_kill}")
  184. confirm = input("确认终止这些会话?(yes/no): ")
  185. if confirm.lower() in ['yes', 'y']:
  186. for pid in pids_to_kill:
  187. kill_session(cursor, pid)
  188. print("\n清理完成!")
  189. else:
  190. print("\n已取消操作")
  191. else:
  192. print("\n没有需要终止的会话")
  193. cursor.close()
  194. conn.close()
  195. if __name__ == '__main__':
  196. print("PostgreSQL 锁清理工具")
  197. print("用法:")
  198. print(" python clear_locks.py # 检查锁并选择性终止")
  199. print(" python clear_locks.py --kill-all # 终止所有活跃连接")
  200. print()
  201. main()