当前位置:   article > 正文

自动监控MySQL表结构变更脚本

mysql数据库监听表结构变化
如何监控MySQL数据库表结构和表变更,并通知到相关的联系人、实现报警或通知?

由于平台采用django系统实现,因此通过如下代码实现(代码low,也可以写成python文件,传参数执行):
简单思路:
对用户指定库的所有列值进行md5,并存储到本地数据库,每次定时执行,校对md5,并找出不匹配的进行判断
会自动找出新增、删除、变更表结构的表

# models.py


  1. class MonitorSchema(models.Model):
  2. table_schema = models.CharField(null=False, max_length=512)
  3. table_name = models.CharField(null=False, max_length=512)
  4. table_stru = models.TextField(null=False, default='')
  5. md5_sum = models.CharField(null=False, max_length=256)
  6. class Meta:
  7. verbose_name = u'监控表结构变更表'
  8. verbose_name_plural = verbose_name
  9. permissions = ()
  10. db_table = "dbaudit_monitor_schema"



# tasks.py

  1. import datetime
  2. import hashlib
  3. import difflib
  4. import mysql.connector as mdb
  5. from celery import shared_task
  6. from django.core.mail import EmailMessage
  7. from django.template.loader import render_to_string
  8. from auditdb.settings import EMAIL_FROM
  9. @shared_task
  10. def schema_modify_monitor(**kwargs):
  11. check_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  12. conn = connect_db(**kwargs)
  13. cursor = conn.cursor(dictionary=True)
  14. query_info = "select table_schema,table_name,group_concat(COLUMN_NAME) as column_name," \
  15. "group_concat(COLUMN_DEFAULT) as column_default,group_concat(IS_NULLABLE) as is_nullable," \
  16. "group_concat(DATA_TYPE) as data_type,group_concat(CHARACTER_MAXIMUM_LENGTH) as char_length," \
  17. "group_concat(COLUMN_TYPE) as column_type,group_concat(COLUMN_COMMENT) as column_comment " \
  18. "from columns where table_schema='{schema}' " \
  19. "group by table_schema,table_name".format(schema=kwargs['schema'])
  20. cursor.execute(query_info)
  21. source_info = []
  22. table_list = []
  23. diff_old_data = ''
  24. diff_new_data = ''
  25. table_change_data = []
  26. for row in cursor.fetchall():
  27. table_schema = row['table_schema']
  28. table_name = row['table_name']
  29. md5_source = ''.join(str(row.values()))
  30. md5_sum = hashlib.md5(md5_source.encode('utf8')).hexdigest()
  31. source_info.append({'table_schema': table_schema, 'table_name': table_name, 'md5_sum': md5_sum})
  32. table_list.append(table_name)
  33. # 如果当前库没有记录,则进行初始化全量同步
  34. if MonitorSchema.objects.filter(table_schema=kwargs['schema']).first() is None:
  35. for row in source_info:
  36. table_schema = row['table_schema']
  37. table_name = row['table_name']
  38. query_table_stru = "show create table {}".format('.'.join((table_schema, table_name)))
  39. cursor.execute(query_table_stru)
  40. for i in cursor:
  41. table_stru = i['Create Table']
  42. row['table_stru'] = str(table_stru)
  43. MonitorSchema.objects.create(**row)
  44. else:
  45. # 如果存在,开始核验数据
  46. old_data = list(MonitorSchema.objects.filter(table_schema=kwargs['schema']).values_list('table_name', flat=True))
  47. new_data = table_list
  48. # 找出已删除的表,并处理
  49. table_remove = list(set(old_data).difference(set(new_data)))
  50. if table_remove:
  51. table_change_data.append({'remove': table_remove})
  52. # 从本地库中删除该表的记录
  53. MonitorSchema.objects.filter(table_schema=kwargs['schema']).filter(table_name__in=table_remove).delete()
  54. # 找出新增的表,并处理
  55. table_add = list(set(new_data).difference(set(old_data)))
  56. if table_add:
  57. for i in table_add:
  58. for j in source_info:
  59. if i in j.values():
  60. table_change_data.append({'add': j})
  61. table_schema = j['table_schema']
  62. table_name = j['table_name']
  63. query_table_stru = "show create table {}".format('.'.join((table_schema, table_name)))
  64. cursor.execute(query_table_stru)
  65. for x in cursor:
  66. table_stru = x['Create Table']
  67. j['table_stru'] = str(table_stru)
  68. MonitorSchema.objects.create(**j)
  69. # 找出相同的表,并核验表结构
  70. table_intersection = list(set(old_data).intersection(set(new_data)))
  71. for row in source_info:
  72. table_schema = row['table_schema']
  73. table_name = row['table_name']
  74. new_md5_sum = row['md5_sum']
  75. if table_name in table_intersection:
  76. old_table = MonitorSchema.objects.get(table_schema=table_schema, table_name=table_name)
  77. if new_md5_sum != old_table.md5_sum:
  78. query_table_stru = "show create table {}".format('.'.join((table_schema, table_name)))
  79. cursor.execute(query_table_stru)
  80. for i in cursor:
  81. table_stru = i['Create Table']
  82. diff_old_data += old_table.table_stru + '\n'*3
  83. diff_new_data += table_stru + '\n'*3
  84. # 更新新表表结构到本地
  85. MonitorSchema.objects.update_or_create(table_schema=table_schema, table_name=table_name,
  86. defaults={'table_stru': table_stru,
  87. 'md5_sum': new_md5_sum})
  88. if (diff_old_data and diff_new_data) or table_change_data:
  89. html_data = ''
  90. if diff_old_data and diff_new_data:
  91. diff_data = difflib.HtmlDiff(tabsize=2)
  92. old_table_stru = list(diff_old_data.split('\n'))
  93. new_table_stru = list(diff_new_data.split('\n'))
  94. html_data = diff_data.make_file(old_table_stru, new_table_stru, '旧表-表结构', '新表-表结构', context=False,
  95. numlines=5)
  96. email_html_body = render_to_string('_monitor_table.html', {'html_data': html_data, 'table_change_data': table_change_data})
  97. title = '{db}库表变更[来自:{host},检测时间:{check_time}]'.format(db=kwargs['schema'], host=kwargs['describle'], check_time=check_time)
  98. msg = EmailMessage(subject=title,
  99. body=email_html_body,
  100. from_email=EMAIL_FROM,
  101. to=kwargs['receiver'].split(','),
  102. )
  103. msg.content_subtype = "html"
  104. msg.send()
  105. cursor.close()
  106. conn.close()



对应的html文件:

# _monitor_table.html


  1. <html>
  2. <head>
  3. <meta charset="utf-8">
  4. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  5. <style>
  6. body {
  7. font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
  8. font-size: 12px;
  9. line-height: 1.42857143;
  10. color: #333;
  11. }
  12. .box.box-primary {
  13. border-top-color: #3c8dbc;
  14. }
  15. .box {
  16. position: relative;
  17. border-radius: 3px;
  18. background: #ffffff;
  19. border-top: 3px solid #d2d6de;
  20. margin-bottom: 20px;
  21. width: 100%;
  22. box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
  23. }
  24. .panel-danger > .panel-heading {
  25. color: #a94442;
  26. background-color: #f2dede;
  27. border-color: #ebccd1;
  28. }
  29. .panel-info > .panel-heading {
  30. color: #31708f;
  31. background-color: #d9edf7;
  32. border-color: #bce8f1;
  33. }
  34. .panel-success > .panel-heading {
  35. color: #3c763d;
  36. background-color: #dff0d8;
  37. border-color: #d6e9c6;
  38. }
  39. .panel-heading {
  40. padding: 6px 8px;
  41. border-bottom: 1px solid transparent;
  42. border-top-left-radius: 3px;
  43. border-top-right-radius: 3px;
  44. }
  45. .panel-body {
  46. padding: 6px;
  47. color: #3c763d;
  48. background-color: #f5f5f5;
  49. }
  50. </style>
  51. </head>
  52. <body>
  53. <div class="box box-primary">
  54. <p>各位同仁好:</p>
  55. <p>  表结构变更如下,请查阅,谢谢。</p>
  56. {% if table_change_data %}
  57. {% for row in table_change_data %}
  58. {% if row.remove %}
  59. <div class="panel panel-danger">
  60. <div class="panel-heading">删除的表</div>
  61. <div class="panel-body">
  62. {% for j in row.remove %}
  63. {{ j }}
  64. {% endfor %}
  65. </div>
  66. </div>
  67. {% endif %}
  68. {% endfor %}
  69. {% for row in table_change_data %}
  70. {% if row.add %}
  71. <div class="panel panel-info">
  72. <div class="panel-heading">新增的表:{{ row.add.table_name }}_[表结构]</div>
  73. <div class="panel-body">
  74. <pre>{{ row.add.table_stru }}</pre>
  75. </div>
  76. </div>
  77. {% endif %}
  78. {% endfor %}
  79. {% endif %}
  80. {% if html_data %}
  81. <div class="panel panel-success">
  82. <div class="panel-heading">变更的表结构[左侧为变更前表结构、右侧为变更后表结构、标色部分为差异]</div>
  83. <div class="panel-body">
  84. {{ html_data|safe }}
  85. </div>
  86. </div>
  87. {% endif %}
  88. </div>
  89. </body>
  90. </html>

最后在django后台添加定时任务或者轮询任务

邮件输出结果:
fbabeb503b7b334e82796db18be8463a403d1fd8
72310593bc9158613ee3796b72c823f9cbc18138


声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号