MySQL Replication复制可以是基于一条语句(Statement level),也可以是基于一条记录(Row level),可以在MySQL的配置参数中设定这个复制级别,不同复制级别的设置会影响到Master端的bin-log记录成不同的形式。
Row Level:日志中会记录成每一行数据被修改的形式,然后在slave端再对相同的数据进行修改。
优点:在row level模式下,bin-log中可以不记录执行的sql语句的上下文相关的信息,仅仅只需要记录那一条记录被修改了,修改成什么样了。所以row level的日志内容会非常清楚的记录下每一行数据修改的细节,非常容易理解。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题。
缺点:row level下,所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容,比如有这样一条update语句:update product set owner_member_id = ‘b’ where owner_member_id = ‘a’,执行之后,日志中记录的不是这条update语句所对应额事件(MySQL以事件的形式来记录bin-log日志),而是这条语句所更新的每一条记录的变化情况,这样就记录成很多条记录被更新的很多个事件。自然,bin-log日志的量就会很大。尤其是当执行alter table之类的语句的时候,产生的日志量是惊人的。因为MySQL对于alter table之类的表结构变更语句的处理方式是整个表的每一条记录都需要变动,实际上就是重建了整个表。那么该表的每一条记录都会被记录到日志中。
Statement Level:每一条会修改数据的sql都会记录到 master的bin-log中。slave在复制的时候sql进程会解析成和原来master端执行过的相同的sql来再次执行。
优点:statement level下的优点首先就是解决了row level下的缺点,不需要记录每一行数据的变化,减少bin-log日志量,节约IO,提高性能。因为他只需要记录在Master上所执行的语句的细节,以及执行语句时候的上下文的信息。
缺点:由于他是记录的执行语句,所以,为了让这些语句在slave端也能正确执行,那么他还必须记录每条语句在执行的时候的一些相关信息,也就是上下文信息,以保证所有语句在slave端杯执行的时候能够得到和在master端执行时候相同的结果。另外就是,由于MySQL现在发展比较快,很多的新功能不断的加入,使MySQL得复制遇到了不小的挑战,自然复制的时候涉及到越复杂的内容,bug也就越容易出现。在statement level下,目前已经发现的就有不少情况会造成MySQL的复制出现问题,主要是修改数据的时候使用了某些特定的函数或者功能的时候会出现,比如:sleep()函数在有些版本中就不能真确复制,在存储过程中使用了last_insert_id()函数,可能会使slave和master上得到不一致的id等等。由于row level是基于每一行来记录的变化,所以不会出现类似的问题。
从官方文档中看到,之前的MySQL一直都只有基于statement的复制模式,直到5.1.5版本的MySQL才开始支持row level的复制。从5.0开始,MySQL的复制已经解决了大量老版本中出现的无法正确复制的问题。但是由于存储过程的出现,给MySQL Replication复制又带来了更大的新挑战。另外,看到官方文档说,从5.1.8版本开始,MySQL提供了除Statement Level和Row Level之外的第三种复制模式:Mixed,实际上就是前两种模式的结合。在Mixed模式下,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种。新版本中的Statment level还是和以前一样,仅仅记录执行的语句。而新版本的MySQL中队row level模式也被做了优化,并不是所有的修改都会以row level来记录,像遇到表结构变更的时候就会以statement模式来记录,如果sql语句确实就是update或者delete等修改数据的语句,那么还是会记录所有行的变更。
Note:
A.基于SQL语句的复制(statement-based replication, SBR)
B.基于行的复制(row-based replication, RBR)
C.混合模式复制(mixed-based replication, MBR)
相应地,binlog的格式也有三种:STATEMENT,ROW,MIXED。 MBR 模式中,SBR 模式是默认的。
在运行时可以动态改变binlog的格式,除了以下几种情况:
1. 存储过程或者触发器中间
2. 启用了NDB
3. 当前会话使用 RBR 模式,并且已打开了临时表
如果binlog采用了 MIXED 模式,那么在以下几种情况下会自动将binlog的模式由 SBR 模式改成 RBR 模式。
1. 当DML语句更新一个NDB表时
2. 当函数中包含 UUID() 时
3. 2个及以上包含 AUTO_INCREMENT 字段的表被更新时
4. 执行 INSERT DELAYED 语句时
5. 用 UDF(User-defined function) 时
6. 视图中必须要求使用 RBR 时,例如创建视图是使用了 UUID() 函数
设定主从复制模式的方法非常简单,只要在以前设定复制配置的基础上,再加一个参数:binlog_format="STATEMENT"
#binlog_format="ROW"
#binlog_format="MIXED"
当然了,也可以在运行时动态修改binlog的格式。例如mysql> SET SESSION binlog_format = 'STATEMENT';
mysql> SET SESSION binlog_format = 'ROW';
mysql> SET SESSION binlog_format = 'MIXED';
mysql> SET GLOBAL binlog_format = 'STATEMENT';
mysql> SET GLOBAL binlog_format = 'ROW';
mysql> SET GLOBAL binlog_format = 'MIXED';
SBR和RBR各自的优缺点
SBR 的优点:
1. 历史悠久,技术成熟
2. binlog文件较小
3. binlog中包含了所有数据库更改信息,可以据此来审核数据库的安全等情况
4. binlog可以用于实时的还原,而不仅仅用于复制
5. 主从版本可以不一样,从服务器版本可以比主服务器版本高
SBR 的缺点:
1. 不是所有的UPDATE语句都能被复制,尤其是包含不确定操作的时候。
2. 调用具有不确定因素的 UDF 时复制也可能出问题
3. 使用以下函数的语句也无法被复制:
* LOAD_FILE()
* UUID()
* USER()
* FOUND_ROWS()
* SYSDATE() (除非启动时启用了 –sysdate-is-now 选项)
4. INSERT … SELECT 会产生比 RBR 更多的行级锁
5. 复制需要进行全表扫描(WHERE 语句中没有使用到索引)的 UPDATE 时,需要比 RBR 请求更多的行级锁
6. 对于有 AUTO_INCREMENT 字段的 InnoDB表而言,INSERT 语句会阻塞其他 INSERT 语句
7. 对于一些复杂的语句,在从服务器上的耗资源情况会更严重,而 RBR 模式下,只会对那个发生变化的记录产生影响
8. 存储函数(不是存储过程)在被调用的同时也会执行一次 NOW() 函数,这个可以说是坏事也可能是好事
9. 确定了的 UDF 也需要在从服务器上执行
10. 数据表必须几乎和主服务器保持一致才行,否则可能会导致复制出错
11. 执行复杂语句如果出错的话,会消耗更多资源
RBR 的优点:
1. 任何情况都可以被复制,这对复制来说是最安全可靠的
2. 和其他大多数数据库系统的复制技术一样
3. 多数情况下,从服务器上的表如果有主键的话,复制就会快了很多
4. 复制以下几种语句时的行锁更少:
* INSERT … SELECT
* 包含 AUTO_INCREMENT 字段的 INSERT
* 没有附带条件或者并没有修改很多记录的 UPDATE 或 DELETE 语句
5. 执行 INSERT,UPDATE,DELETE 语句时锁更少
6. 从服务器上采用多线程来执行复制成为可能
RBR 的缺点:
1. binlog 大了很多
2. 复杂的回滚时 binlog 中会包含大量的数据
3. 主服务器上执行 UPDATE 语句时,所有发生变化的记录都会写到 binlog 中,而 SBR 只会写一次,这会导致频繁发生binlog 的并发写问题
4. UDF 产生的大 BLOB 值会导致复制变慢
5. 无法从 binlog 中看到都复制了写什么语句
6. 当在非事务表上执行一段堆积的SQL语句时,最好采用 SBR 模式,否则很容易导致主从服务器的数据不一致情况发生
另外,针对系统库 mysql 里面的表发生变化时的处理规则如下:
1. 如果是采用 INSERT,UPDATE,DELETE 直接操作表的情况,则日志格式根据 binlog_format 的设定而记录
2. 如果是采用 GRANT,REVOKE,SET PASSWORD 等管理语句来做的话,那么无论如何都采用 SBR 模式记录
注:采用 RBR 模式后,能解决很多原先出现的主键重复问题
mysql binlog格式与事务级别read committed的关系
binlog有三种格式,分别是STATEMENT、row、mixed。每种格式的区别可以去看复制那篇文章,那它分别与read committed 有什么关系呢。下面以例子来分析
1、数据库版本mysql> status
mysql Ver 14.14 Distrib 5.1.45, for unknown-linux-gnu (x86_64) using EditLine wrapper
Connection id: 2
Current database: xinying
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.1.45-VS-log XinYing
Protocol version: 10
Connection: Localhost via UNIX socket
Insert id: 2
Server characterset: latin1
Db characterset: latin1
Client characterset: latin1
Conn. characterset: latin1
UNIX socket: /tmp/mysql.sock
Uptime: 1 hour 40 min 14 sec
2、改变事务级别为read committedmysql>set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
3、改变二进制日志格式mysql>set binlog_format=STATEMENT;
Query OK, 0 rows affected (0.00 sec)
4、创建测试表mysql>CREATE TABLE `slevin` (
->`id` int(10) NOT NULL AUTO_INCREMENT,
->`book` char(10) DEFAULT NULL,
->PRIMARY KEY (`id`)
->) ENGINE=InnoDB;
Query OK, 0 rows affected (0.03 sec)
5、插入数据测试mysql>insert into slevin(book) values('wuli');
ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'
看到没,提示出错,那我们尝试把事务基本改为REPEATABLE READmysql>set session transaction isolation level REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
mysql>insert into slevin(book) values('wuli');
Query OK, 1 row affected (0.00 sec)
改个事务级别就成功了,那试试仍旧把它改为read committed,把binlog格式改了试试mysql>set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
mysql>set session binlog_format=row; 改为行格式
Query OK, 0 rows affected (0.00 sec)
mysql>insert into slevin(book) values('wuli');
Query OK, 1 row affected (0.00 sec)
mysql>set session binlog_format=mixed; 改为混合格式
Query OK, 0 rows affected (0.00 sec)
mysql>insert into slevin(book) values('wuli');
Query OK, 1 row affected (0.00 sec)
把上面改为两种格式都成功,唯独STATEMENT格式不行,所以以后要注意read committed与binlog格式的关系,否则会导致插入不了数据。为何会导致这种情况呢,那是因为read committed可能会导致不可重复读,也就是说可以读取到后面进入并提交的数据,如果基于STATEMENT格式的话,会导致主从数据不一样,因为STATEMENT是基于SQL语句的复制模式。另外设置innodb_locks_unsafe_for_binlog=1 ,binlog也要设为row格式。
UUID做主键分析
大概使用MySQL的人,百分之九九以上的人会使用Autoincrement ID做主键,这是可以理解的,因为MySQL的自增ID效率很高,使用也很方便。那么剩下的百分之一的人使用什么做主键呢?
可能是自己做的KeyGenerator,也可能是我们下面要说的UUID。
据说在Oracle的圈子里,如果谁用自增ID做主键是要被鄙视的,主键最自然的选择就是UUID。
那么我们先看看什么是UUID?
简单的说,UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。在UUID的算法中,可能会用到诸如网卡MAC地址,IP,主机名,进程ID等信息以保证其独立性。
如果你的MySQL版本不太老的话,键入 SELECT UUID(); 输出的就是UUID,如下:mysql> select uuid();
+--------------------------------------+
| uuid() |
+--------------------------------------+
| 54b4c01f-dce0-102a-a4e0-462c07a00c5e |
+--------------------------------------+
现在大家应该对UUID有一个比较直观的认识了,我们来看看UUID的优缺点分别是什么。
优点:
能够保证独立性,程序可以在不同的数据库间迁移,效果不受影响。
保证生成的ID不仅是表独立的,而且是库独立的,这点在你想切分数据库的时候尤为重要。
缺点:
比较占地方,和INT类型相比,存储一个UUID要花费更多的空间。
使用UUID后,URL显得冗长,不够友好。
下面针对上述UUID的缺点说说我的看法,比较占地方这个缺点我不是很在乎,现在最不值钱的就是硬盘了,略过此条缺点无妨,但需要注意的一点数据在索引的时候效率会随着体积的增加而降低。至于说使用UUID后,URL显得不友好,我觉得这多少是你的INT情结造成的惯性思维,其实,和INT类型相比,UUID才是最自然的主键选择,注意,我这里用的是自然这个形容词,仔细体会一下你能理解我的意思。另外,很多时候,URL本身就不需要友好,比如,一个电子商务网站,按照INT友好的URL说法,她的订单URL大概是下面这个形式的:/order.php/id/123,我要说明的是,这样是很友好,但是有些太友好了,友好的甚至不安全,比如说,我早晨下一个订单,发现URL是/order.php/id/1000,晚上再下一个订单发现URL是/order.php/id/2000,那么我就可以估计出此网站一天的订单数大致是1000左右,甚至能大体估计出它的销售额,而这些数据往往都是重要的商业秘密。使用UUID就没有这个顾虑。
如果上面说的UUID的所谓缺点都不成立的话,那么是否使用UUID做主键,唯一的问题就是效率了。据说在PostgreSQL等数据库里,都有专门的UUID类型,在这样的数据库里,使用UUID做主键,效率没有任何问题,可惜在MySQL里没有这样的字段,如果想在MySQL里保存UUID做主键,一般是使用CHAR(36)来模拟,因为不是一个原生的UUID类型,所以主键的效率到底如何有待测试,另外,UUID做主键的效率和UUID本身的算法实现也有很大关系。
另外,对于InnoDB这种聚集主键类型的引擎来说,数据会按照主键进行排序,由于UUID的无序性,InnoDB会产生巨大的IO压力,此时不适合使用UUID做物理主键,可以把它作为逻辑主键,物理主键依然使用自增ID。