相比自动修正问题而言,了解为什么MySQL中的数据复制没有正常运行,显得更加重要。是因为错误?还是MySQL数据复制被其他原因中断,例如备份程序正在运行,又或者某个软件正在更新?Slave_SQL_Running=No标识符的出现不仅仅是个非预期错误结果,STOP SLAVE SQL_THREAD语句也能产生同样的情况。这是为什么在这种情况下警报不一定准确的原因之一。所以应该在Slave_SQL_Running=No标识符出现时产生警报。然而,我们明显不想每天早上的两点钟因为备份程序主动终止MySQL数据复制而被吵醒。只有当备份程序失效而且长时间没有重启时才应该启动这个警报。MySQL数据复制包括两个线程,在上面的SHOW SLAVE STATUS输出中我们已经提到过。另外,I/O这个线程仍然和SLAVE_IO_Running=Yes一起运行。同时监控两个线程的状态是十分重要的。
SHOW CREATE TABLE
SHOW SLAVE STATUS命令输出结果中的Last_Error列显示发生了重复键错误。这可以通过检查基础数据库表的结构来确定,然后查看以下当前数据:
slave> SHOW CREATE TABLE product_comment\G
*************************** 1. row ***************************
Table: product_comment
Create Table: CREATE TABLE 'product_comment' (
'comment_id' int(10) unsigned NOT NULL AUTO_INCREMENT,
'product_id' int(10) unsigned NOT NULL,
'user_id' int(10) unsigned NOT NULL,
'comment_dt' datetime NOT NULL,
'comment' varchar(1000) NOT NULL,
PRIMARY KEY ('comment_id'),
UNIQUE KEY 'user_id' ('user_id','comment_dt')
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
如你所见,在user_id和comment_dt列中有个叫做user_id的唯一键(UNIQUE KEY)。这个唯一键对应着错误信息中的编号。MySQL只能为唯一键或主键产出一条重复条目的错误信息。来自Last_Error列的值可以用来决定被插入数据的真实值。下面这条SQL语句可以用来验证那些导致错误的数据:
slave> SELECT * FROM product_comment
-> WHERE user_id = 42
-> AND comment_dt = '2011-04-16 00:00:00'\G
*************************** 1. row ***************************
comment_id: 1
product_id: 10
user_id: 42
comment_dt: 2011-04-16 00:00:00
comment: The packaging does not state this requires X
(1 row in set (0.01 sec)
实际上,导致重复键异常的MySQL数据复制错误的SQL语句本来是正确的。那说明什么呢?说明这条语句是无效的?我们应该忽视它吗?存在表格中的数据是错误的吗?应该把它删除吗?product_id和comment的值实际上是不同的,这意味着这条语句和上一条语句并不相同。只有被唯一键限制的user_id和comment_dt列是重复的。
此时,我们还没有足够的信息去做决定。其中一个选择就是审核MySQL的主数据库,并找出需要验证的数据:
master> SELECT * FROM product_comment
-> WHERE user_id = 42
-> AND comment_dt = '2011-04-16 00:00:00'\G
*************************** 1. row ***************************
comment_id: 1
product_id: 10
user_id: 42
comment_dt: 2011-04-16 00:00:00
comment: The packaging does not state this requires X
*************************** 2. row ***************************
comment_id: 2
product_id: 20
user_id: 42
comment_dt: 2011-04-16 00:00:00
comment: I found this very useful when used with product Y
(2 rows in set (0.01 sec)
我们发现在主节点数据和从节点数据中存在着差异。而这个差异并不在我们的预期内,因为这违反了已经定义的唯一键约束。通过验证主数据库中的表结构,我们可以得到:
master> SHOW CREATE TABLE product_comment\G
*************************** 1. row ***************************
Table: product_comment
Create Table: CREATE TABLE 'product_comment' (
'comment_id' int(10) unsigned NOT NULL AUTO_INCREMENT,
'product_id' int(10) unsigned NOT NULL,
'user_id' int(10) unsigned NOT NULL,
'comment_dt' datetime NOT NULL,
'comment' varchar(1000) NOT NULL,
PRIMARY KEY ('comment_id'),
KEY 'user_id' ('user_id','comment_dt')
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1
有一个键位于user_id和comment_dt这两个列中。然而,进一步审查就能发现这个键不再是唯一约束键。鉴于MySQL中是从主节点向从节点进行数据复制,我们可能会问这种情况是如何发生的。在MySQL中,从节点有不同的表结构,但是仍能正常操作,这样的可能性是存在的。在上面这个例子中,这个不同的结构后来导致了错误。现在有多种技术和工具可以用来对比不同MySQL实例的数据库对象,以区别它们之间的不同。在后面的第2章和第5章会讨论这一点。