标签归档:数据库

MySQL 5.6 如何给ibdata1瘦身

前不久刚给ibdata1瘦身,发篇文章总结下。

ibdata1是MySQL使用InnoDB引擎时所产生的文件,其一般存储数据、索引、结构、缓冲数据、共享数据和重做日志等。因为ibdata1只增不减,长期操作数据库,可能会使其越来越大,而浪费空间。

加上使用InnoDB引擎时,没有添加innodb_file_per_table参数也是导致ibdata1过大的原因。

但InnoDB只增不减,也导致给ibdata1瘦身是件比较麻烦的事。

最大的我见过的是40多G的ibdata1文件,实际数据库差不多是20多G,在做了优化后,ibdata1缩小至20多G,所以说减肥还是有必要的。

首先,先略微说下innodb_file_per_table参数,使用该参数可使得InnoDB引擎转变为独立表空间模式(默认为共享表空间),也就是每个数据库的每个表都会生成一个数据空间,就像MyISAM引擎一样。

 
独立表空间优点就是每个表有独立空间,数据和索引都会存在自已的表空间中,可以实现单表在不同的数据库中移动。重要的是空间可以回收,而且不管日常怎么操作,表空间的碎片不会太严重的影响性能,优化表的速度也快,表文件出现问题不会大动干戈,只要修复对应表即可。缺点是单表占用的空间比共享表空间方式稍大,共享表空间在Insert操作上有一些优势。

所以没增加innodb_file_per_table参数的同学,建议还是加上吧。因为增加innodb_file_per_table参数,与我们后续给ibdata1瘦身并无冲突,而且对以后也只有好处。

说了这么多废话,言归正传。谈谈怎么给ibdata1瘦身,唯一的方法是就是备份整个数据库,然后删掉ibdata1和ib_logfile*,再恢复数据库,以此达到瘦身目的。当然了,操作数据库肯定是有风险的,而且也需要生产环境允许MySQL暂停写或访问。

简单的总结就是以下这几点:

1.在/etc/my.cnf中添加“innodb_force_recovery=4”使InnoDB成为只读表,这其实应该说是第一个坑。另外确定“innodb_data_file_path”参数限定的初始ibdata1大小在合理范围,一般稍大于现有数据大小。
2.启动MySQL,使用我给的工具备份除了mysql、information_schema和performance_schema的整个数据库。为以后顺利恢复数据做准备。
3.删除除了mysql、information_schema和performance_schema的整个数据库,这3个排除在外的其实也删不掉。
4.停止MySQL,删除ibdata1和ib_logfile*文件。
5.删除数据库目录中的mysql目录的innodb_index_stats.*、innodb_table_stats.*、slave_master_info.*、slave_relay_log_info.*、slave_worker_info.*文件,这5个是InnoDB的基础表(状态表),这是MySQL 5.6的坑,删除ibdata1后不会自动重建这5个表,而且如果不删除这些旧文件,还不可创建或重建新的。
6.在/etc/my.cnf把“innodb_force_recovery=4”去除。
7.启动MySQL,将第2步备份的数据库还原,然后用从MySQL 5.6导出来的InnoDB基础表备份还原回去重建(第5步删掉的)。
8.搞定,重启下MySQL,确保没有错误,没有异常。

必要的工具,在文末的Github地址下载。

下面详细说说每个步骤,首先是设置InnoDB为只读表,这还是比较必要的。可确保你的数据完整性、安全性。为何是坑呢,因为我遇到过没这样设置,导致后续恢复备份时,数据有异常。

具体操作,在/etc/my.cnf中添加“innodb_force_recovery=4”


[mysqld]

innodb_force_recovery = 4

如此便可,innodb_file_per_table也是在[mysqld]下添加。

innodb_force_recovery的值可以设置为1-6,大的数字包含其前面所有数字的影响。

1. (SRV_FORCE_IGNORE_CORRUPT):忽略检查到的corrupt页。
2. (SRV_FORCE_NO_BACKGROUND):阻止主线程的运行,如主线程需要执行full purge操作,会导致crash。
3. (SRV_FORCE_NO_TRX_UNDO):不执行事务回滚操作。
4. (SRV_FORCE_NO_IBUF_MERGE):不执行插入缓冲的合并操作。
5. (SRV_FORCE_NO_UNDO_LOG_SCAN):不查看重做日志,InnoDB存储引擎会将未提交的事务视为已提交。
6. (SRV_FORCE_NO_LOG_REDO):不执行前滚的操作。

接着呢,启动MySQL,备份整个数据库,一般我们会用

mysqldump –lock-all-tables –all-databases > all-dbs.sql
来完整备份数据库。但这样的话,就包含了mysql、information_schema和performance_schema这3个数据库,而在后续还原数据库,带着这3个数据库可能会出错(我有几次遇到过)。

所以我提供了shell脚本工具mysql_dump_all_db.sh,因为怕大家不像我是免密码操作MySQL的,在使用工具前,请先修改脚本中的MySQL帐号和密码,然后再通过

sh mysql_dump_all_db.sh
执行备份操作,备份好的文件,会存放在脚本运行所在目录。

这个工具默认排除mysql、information_schema和performance_schema这3个数据库,如果你有其他想排除的,可以直接修改脚本,增加其他想要排除的数据库。

完了之后呢,删除所有数据库,可以通过phpMyadmin或直接在shell操作MySQL删除,在shell下删除,可以在/tmp/DatabasesToDump.txt查看到所有数据库,由“mysql_dump_all_db.sh”生成。我一般在phpMyadmin删除,简单,不怕错。如你所见,mysql、information_schema和performance_schema这3个数据库是删不掉的,所以要排除,免得麻烦。

 
搞定后,就可以停止MySQL了。删除数据库目录的ibdata1和ib_logfile*文件,一般是在/usr/local/mysql/data,看你怎么配置的了。

接着,在该目录下的mysql目录(/usr/local/mysql/data/mysql)中,把innodb_index_stats.*、innodb_table_stats.*、slave_master_info.*、slave_relay_log_info.*、slave_worker_info.*共计10个文件删除,这些文件已经无用了,而且占着茅坑不拉屎。MySQL 5.6很煞笔的不会重建这5个表,如果你不删除他们,待会将无法重建或恢复这5个表,接着log一直在报错,死循环。所以要把这5个表的10个文件干掉。所以这个是个坑。

在/etc/my.cnf把“innodb_force_recovery=4”去除后,就可以启动MySQL了,这时候ibdata1和ib_logfile*文件会重建。噢,上帝,胜利在望,不要激动,让我们继续吧。

把刚才备份的所有数据库还原,用从MySQL 5.6导出来的InnoDB基础表备份还原回去重建。

我在后面的Github地址有提供,从全新 MySQL 5.6导出的,名字为“mysql_innodb_basic_tables.sql”的备份文件,通过它可以重建刚才删掉的5个InnoDB基础表。

还原数据库文件非常简单,不过我还是略微写下,照顾下小白,在shell下:

mysql < all-dbs.sql
mysql < mysql_innodb_basic_tables.sql
这样就OK了,如没有免密码操作权限,请自行添加-u和-p参数。

好的,做完这些,重启下MySQL,确保没有错误即可。这样就完成了对ibdata1的瘦身。

以上操作环境为:CentOS 6.6 x64、MySQL 5.6.25。

工具存放在Github中(原谅我的渣英文),见: https://github.com/kn007/Reduce-Shrink-Purge-the-ibdata1-file-in-MySQL

写这篇文章就是为了大家少走一点弯路,也把自己遇到的坑说一下。算是个总结,也是给后人的一些经验。原则上不提供技术支持,有问题请自行解决。另外毕竟是数据库,瘦身有风险,操作需谨慎。

公司开发定的数据库MYSQL规范

我们公司相当多的项目用的是mysql数据库,但是大家在开发过程中对mysql的认识问题,往往在数据库设计时对字段的定义不一致,在开发时对sql语句的执行出现问题,特地把一些通用性的、值得注意的问题做一下总结
一、数据库的设计规范
1、必须使用InnoDB存储引擎
原因:支持事务安全、行级锁、并发性能更好(查询不加锁,完全不影响查询),内存缓存页优化使得资源利用率更高,mysql5.6版本开始支持 全文索引
2、必须使用utf-8的字符编码
原因:这个无需过多解释,和网站以及其他系统完全统一,避免转码带来不必要的麻烦,而且系统数据接口都是使用json格式。
3、数据库、表、字段名必须有意义并且必须加入中文注释
原因:避免自己遗忘,方便他人进行开发,要不然一段时间之后谁还知道这是用来干什么的
4、禁止使用存储过程、视图、触发器
原因:高并发大数据的互联网业务,架构设计思路是“解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引,计算还是使用程序来实现。使用存储过程等非常难于进行调试和测试。
5、禁止存储文件和图片
原因:存储路径在速度和空间方面会有更好的提升
6、数据库中表的数量不能高于500
原因:做好前期设计,尽量把一些相关度低的表进行分库处理
7、库名、表名、字段名的命名规则
所有的名字都使用小写并且间隔使用下划线风格,不超过32个字符,必须要见名知意,尽量使用英文,但是绝对禁止拼音英文混用命名。
 二、表的设计规则
8、表中的字段数不能超过30
原因:如果字段过多,就要把一些不常用的字段进行分表处理
9、表明和索引名统一
例如:表名table_xxx,非唯一索引名index_xxx,唯一索引名unique_xxx
10、所有表必须至少有一个主键,例如自增主键
原因:
a)主键递增,数据行写入可以提高插入性能,可以避免page分裂,减少表碎片提升空间和内存的使用
b)主键要选择较短的数据类型, Innodb引擎普通索引都会保存主键的值,较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率
c) 无主键的表删除,在row模式的主从架构,会导致备库夯住
11、禁止使用外键,如果要有外键完整性约束,必须使用程序进行控制
原因:外键会导致表之间耦合度增加,update与delete操作都会涉及相关联的表,非常影响sql 的性能,甚至会造成死锁。高并发情况下非常影响数据库性能,大数据高并发业务场景数据库使用以性能优先
三、字段的设计规范
12、所有字段都要定义为NOT NULL并提供默认值
原因:
1)null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化
2)null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多
3)null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识
4)对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号。如:where name!=’shenjian’,如果存在name为null值的记录,查询结果就不会包含name为null值的记录
13、在多字段的表中禁止使用TEXT、BLOB类型
原因:会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能
14、使用整数禁止使用小数存储货币
原因:价格乘以100来使用整数存储,小数在运算过程中会导致钱对不上
15、手机号必须使用varchar(20)进行存储
原因:
1)涉及到国家代号,可能出现类似+86
2)手机号会去做数学运算么?不会,所以不要使用int(11)
3)varchar可以支持模糊查询,例如:like“138%”
16、禁止使用ENUM,可使用TINYINT代替
原因:
1)增加新的ENUM值要做DDL操作
2)ENUM的内部实际存储就是整数,你以为自己定义的是字符串?
四、索引的设计规范
17、表中索引的数量最好控制在5个以内
原因:
1)、索引也占用很大的空间
2)、索引在创建修改数据的情况需要大量更新索引
18、一个索引关联的字段在5个以内
原因:字段超过5个时,实际已经起不到有效过滤数据的作用了
19、禁止在更新十分频繁、或者区分度不高的属性上建立索引
原因:
1)更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能
2)“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似
20、建立组合索引,必须把区分度高的字段放在前面
解读:能够更加有效的过滤数据
五、sql优化
21、禁止使用SELECT *,只获取必要的字段,需要显示说明列属性
原因:
1)读取不需要的列会增加CPU、IO、NET消耗
2)不能有效的利用覆盖索引
3)使用SELECT *容易在增加或者删除字段后出现程序BUG
22、禁止使用INSERT INTO t_xxx VALUES(yyy),必须显示指定插入的列属性
原因:容易在增加或者删除字段后出现程序BUG
23、禁止使用属性隐式转换
原因:SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描,而不能命中phone索引,
where 条件语句里,字段属性和赋给的条件,当数据类型不一样,这时候是没法直接比较的,需要进行一致转换,这种情况是无法使用索引的。
24、禁止在WHERE条件的属性上使用函数或者表达式
原因:SELECT uid FROM t_user WHERE from_unixtime(day)>=’2017-02-15′ 会导致全表扫描,而不能使用索引
正确的写法是:SELECT uid FROM t_user WHERE day>= unix_timestamp(‘2017-02-15 00:00:00’)
25、禁止负向查询,以及%开头的模糊查询
解读:
a)负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描,而不使用索引
b)%开头的模糊查询,同样会导致全表扫描,不能使用索引
26、禁止在大表中使用JOIN查询,禁止大表使用子查询
解读:会产生临时表,消耗较多内存与CPU,极大影响数据库性能
27、禁止使用OR条件,都改为IN查询
原因:旧版本Mysql的OR查询是不能命中索引的,即使新版能命中索引,为何要让数据库耗费更多的CPU呢?

28、应用程序必须捕获SQL异常的功能,并有相应处理

http://www.architecy.com/archives/456

MYSQL主从同步故障案例解决

公司里有两个mysql服务器做主从同步,某天Nagios发来报警短信,mysqla is down…赶紧联系机房,机房的人反馈来的信息是 HARDWARE ERROR 后面信息省略,让机房记下错误信息后让他们帮忙重启下看是不是能正常起来,结果竟然正常起来了,赶紧导出所有数据。

问题又出现了,nagios 又报警,mysql_AB error,检查从库

show slave status \G; 果然

Slave_IO_Running: Yes

Slave_SQL_Running: No

而且出现了1062错误,还提示

Last_SQL_Error: Error ‘Duplicate entry ‘1001-164761-0’ for key ‘PRIMARY” on query. Default database: ‘bug’. Query: ‘insert into misdata (uid,mid,pid,state,mtime) values (164761,1001,0,-1,1262623560)’

很显然,由于主库重启导致 从库数据不同步而且主键冲突。查看error 日志发现error日志文件变得好大,比以前大了将近好几倍,

tail -f mysql_error.log 最开始查看到的是这条信息

发现这条信息

[ERROR] Slave SQL: Error ‘Duplicate entry ‘1007-443786-0’ for key ‘PRIMARY” on query. Default database: ‘ufo’. Query: ‘insert into misdata (uid,mid,pid,sta

te,mtime) values (443786,1007,0,-1,1262598003)’, Error_code: 1062

100104 17:39:05 [Warning] Slave: Duplicate entry ‘1007-443786-0’ for key ‘PRIMARY’ Error_code: 1062

100104 17:39:05 [ERROR] Error running query, slave SQL thread aborted. Fix the problem, and restart the slave SQL thread with “SLAVE START”. We stopped at log ‘ufolog.000058

8′ position 55793296

报错和上面的意思差不多,

最先想到的就是首先手动同步一下,从库上首先 stop slave;停止同步

进入主库锁表,

FLUSH TABLES WITH READ LOCK;

mysql> show master status;

+——————-+———–+————–+——————+

| File              | Position  | Binlog_Do_DB | Binlog_Ignore_DB |

+——————-+———–+————–+——————+

| ufo.000063 | 159164526 |              |                  |

+——————-+———–+————–+——————+

1 row in set (0.00 sec)

进入从库

mysql>change master to master_host=’192.168.1.141′, master_user=’slave’,

master_password=’xxx’,

master_port=3306,

master_log_file=’ufo.000063′,

master_log_pos=159164526;

完成上面这些后

start slave;

回到主库

unlock tables; 解锁

回到从库 查看

show slave status \G;

发现正常了,长处了一口气。可是还没过一分钟,发现又开始报错了,还是最开始那个错误,这是怎么回事…

于是又想到了跳过错误的办法,(不过我不太喜欢用这种方法)马上进入从库

stop slave;

set global sql_slave_skip_counter=1; (1是指跳过一个错误)

slave start;

再show slave status \G;查看

还是报错 只不过 原来的 164761 变成了 165881,连续执行了几次后

除了上面的数值 在变,错误依然还在

郁闷了,看来只能先强制跳过 1062错误了,于是修改从库的/etc/my.cnf文件

在里面的[mysqld]下面加入了一行

slave-skip-errors = 1062 (忽略所有的1062错误)

重启下从库的mysql /etc/init.d/mysqld restart

再 show slave status \G;一下发现正常了,但是我知道这时的数据可能已经不同步了,

再次查看一下日志,让我感到意外的是tail -f mysql_error.log 出现大量的

…….

100106 16:54:21 [Warning] Statement may not be safe to log in statement format. Statement: delete from `system_message_1` where `to_uid` = 181464 ORDER BY `id` ASC LIMIT 1

………

日志里面有大量的这种警告,意思应该是statement 格式不安全,用vim 打开他看了一下,发现好多这类警告,我说为什么错误日志怎么变这么大了呢!!

statement format 应该是 binlog的一种格式,进入从库查看一下

show global variables like ‘binlog_format’;

果然当前的格式为statement

我需要把格式改为 mixed格式

修改从库的 my.cfg

在[mysqld]下面加入下面这行

binlog_format=mixed

然后重启mysql服务,发现错误日志里的 警告 都停止了。这回清静多了~~

我突然想起一件事,记得有朋友说过 RBR 模式可以解决很多因为主键冲突导致的主从无法同步情况,想到这里我就想要不要把 slave-skip-errors = 1062 去掉再试试,

于是就进入到my.cnf 里在注释掉了 slave-skip-errors = 1062

再次重新启动 mysql服务

进入从库

show slave status \G;

………

Slave_IO_Running: Yes

Slave_SQL_Running: Yes

……..

恢复了!!!有观察了一段时间没有出现问题这才放心,

看来导致 mysql 主从复制出错的原因还真不少修复的办法也不止一个,binlog的格式也是其中之一。

希望遇到和这次一样问题的朋友看到这篇文章后会得到 一些启发和解决问题的方法~~