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

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

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

由于平台采用django系统实现,因此通过如下代码实现(代码low,也可以写成python文件,传参数执行):

简单思路:

对用户指定库的所有列值进行md5,并存储到本地数据库,每次定时执行,校对md5,并找出不匹配的进行判断

会自动找出新增、删除、变更表结构的表

# models.py

class MonitorSchema(models.Model):

table_schema = models.CharField(null=False, max_length=512)

table_name = models.CharField(null=False, max_length=512)

table_stru = models.TextField(null=False, default='')

md5_sum = models.CharField(null=False, max_length=256)

class Meta:

verbose_name = u'监控表结构变更表'

verbose_name_plural = verbose_name

permissions = ()

db_table = "dbaudit_monitor_schema"

# tasks.py

import datetime

import hashlib

import difflib

import mysql.connector as mdb

from celery import shared_task

from django.core.mail import EmailMessage

from django.template.loader import render_to_string

from auditdb.settings import EMAIL_FROM

@shared_task

def schema_modify_monitor(**kwargs):

check_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

conn = connect_db(**kwargs)

cursor = conn.cursor(dictionary=True)

query_info = "select table_schema,table_name,group_concat(COLUMN_NAME) as column_name," \

"group_concat(COLUMN_DEFAULT) as column_default,group_concat(IS_NULLABLE) as is_nullable," \

"group_concat(DATA_TYPE) as data_type,group_concat(CHARACTER_MAXIMUM_LENGTH) as char_length," \

"group_concat(COLUMN_TYPE) as column_type,group_concat(COLUMN_COMMENT) as column_comment " \

"from columns where table_schema='{schema}' " \

"group by table_schema,table_name".format(schema=kwargs['schema'])

cursor.execute(query_info)

source_info = []

table_list = []

diff_old_data = ''

diff_new_data = ''

table_change_data = []

for row in cursor.fetchall():

table_schema = row['table_schema']

table_name = row['table_name']

md5_source = ''.join(str(row.values()))

md5_sum = hashlib.md5(md5_source.encode('utf8')).hexdigest()

source_info.append({'table_schema': table_schema, 'table_name': table_name, 'md5_sum': md5_sum})

table_list.append(table_name)

# 如果当前库没有记录,则进行初始化全量同步

if MonitorSchema.objects.filter(table_schema=kwargs['schema']).first() is None:

for row in source_info:

table_schema = row['table_schema']

table_name = row['table_name']

query_table_stru = "show create table {}".format('.'.join((table_schema, table_name)))

cursor.execute(query_table_stru)

for i in cursor:

table_stru = i['Create Table']

row['table_stru'] = str(table_stru)

MonitorSchema.objects.create(**row)

else:

# 如果存在,开始核验数据

old_data = list(MonitorSchema.objects.filter(table_schema=kwargs['schema']).values_list('table_name', flat=True))

new_data = table_list

# 找出已删除的表,并处理

table_remove = list(set(old_data).difference(set(new_data)))

if table_remove:

table_change_data.append({'remove': table_remove})

# 从本地库中删除该表的记录

MonitorSchema.objects.filter(table_schema=kwargs['schema']).filter(table_name__in=table_remove).delete()

# 找出新增的表,并处理

table_add = list(set(new_data).difference(set(old_data)))

if table_add:

for i in table_add:

for j in source_info:

if i in j.values():

table_change_data.append({'add': j})

table_schema = j['table_schema']

table_name = j['table_name']

query_table_stru = "show create table {}".format('.'.join((table_schema, table_name)))

cursor.execute(query_table_stru)

for x in cursor:

table_stru = x['Create Table']

j['table_stru'] = str(table_stru)

MonitorSchema.objects.create(**j)

# 找出相同的表,并核验表结构

table_intersection = list(set(old_data).intersection(set(new_data)))

for row in source_info:

table_schema = row['table_schema']

table_name = row['table_name']

new_md5_sum = row['md5_sum']

if table_name in table_intersection:

old_table = MonitorSchema.objects.get(table_schema=table_schema, table_name=table_name)

if new_md5_sum != old_table.md5_sum:

query_table_stru = "show create table {}".format('.'.join((table_schema, table_name)))

cursor.execute(query_table_stru)

for i in cursor:

table_stru = i['Create Table']

diff_old_data += old_table.table_stru + '\n'*3

diff_new_data += table_stru + '\n'*3

# 更新新表表结构到本地

MonitorSchema.objects.update_or_create(table_schema=table_schema, table_name=table_name,

defaults={'table_stru': table_stru,

'md5_sum': new_md5_sum})

if (diff_old_data and diff_new_data) or table_change_data:

html_data = ''

if diff_old_data and diff_new_data:

diff_data = difflib.HtmlDiff(tabsize=2)

old_table_stru = list(diff_old_data.split('\n'))

new_table_stru = list(diff_new_data.split('\n'))

html_data = diff_data.make_file(old_table_stru, new_table_stru, '旧表-表结构', '新表-表结构', context=False,

numlines=5)

email_html_body = render_to_string('_monitor_table.html', {'html_data': html_data, 'table_change_data': table_change_data})

title = '{db}库表变更[来自:{host},检测时间:{check_time}]'.format(db=kwargs['schema'], host=kwargs['describle'], check_time=check_time)

msg = EmailMessage(subject=title,

body=email_html_body,

from_email=EMAIL_FROM,

to=kwargs['receiver'].split(','),

)

msg.content_subtype = "html"

msg.send()

cursor.close()

conn.close()

对应的html文件:

# _monitor_table.html

body {

font-family: Monaco, Menlo, Consolas, "Courier New", monospace;

font-size: 12px;

line-height: 1.42857143;

color: #333;

}

.box.box-primary {

border-top-color: #3c8dbc;

}

.box {

position: relative;

border-radius: 3px;

background: #ffffff;

border-top: 3px solid #d2d6de;

margin-bottom: 20px;

width: 100%;

box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);

}

.panel-danger > .panel-heading {

color: #a94442;

background-color: #f2dede;

border-color: #ebccd1;

}

.panel-info > .panel-heading {

color: #31708f;

background-color: #d9edf7;

border-color: #bce8f1;

}

.panel-success > .panel-heading {

color: #3c763d;

background-color: #dff0d8;

border-color: #d6e9c6;

}

.panel-heading {

padding: 6px 8px;

border-bottom: 1px solid transparent;

border-top-left-radius: 3px;

border-top-right-radius: 3px;

}

.panel-body {

padding: 6px;

color: #3c763d;

background-color: #f5f5f5;

}

各位同仁好:

  表结构变更如下,请查阅,谢谢。

{% if table_change_data %}

{% for row in table_change_data %}

{% if row.remove %}

删除的表

{% for j in row.remove %}

{{ j }}

{% endfor %}

{% endif %}

{% endfor %}

{% for row in table_change_data %}

{% if row.add %}

新增的表:{{ row.add.table_name }}_[表结构]

{{ row.add.table_stru }}

{% endif %}

{% endfor %}

{% endif %}

{% if html_data %}

变更的表结构[左侧为变更前表结构、右侧为变更后表结构、标色部分为差异]

{{ html_data|safe }}

{% endif %}

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

邮件输出结果:

版权声明:本文内容由互联网用户自发贡献,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:yqgroup@service.aliyun.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

原文链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容