1、簡單介紹該錯誤發生的背景:
1) 數據庫版本:MySQL5.7.19
2) 對一個大表修改字段類型DDL(將主鍵id int變為bigint),為了不影響主庫業務,先在從庫上執行DDL操作,然后通過主從切換完成最終的大表DDL;在從庫執行完DDL后,這時發現復制中斷了,報錯信息:
Last_SQL_Errno: 1677 Last_SQL_Error: Column 0 of table 'darren.conversions' cannot be converted from type 'int' to type 'bigint(20)'
3)錯誤重現:
在主庫上,建表如下:
CREATE TABLE `conversions` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` char(10) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB
在從庫上,修改字段類型,將id int 變成bigint:
alter table conversions modify id bigint not null;
最后在主庫上向該表插入數據,這時從庫就報1677錯誤了。
2、解決方案
參考MySQL文檔中的方法,在從庫上設置slave_type_conversions
="ALL_NON_LOSSY",重啟復制即可解決
MySQL> set global slave_type_conversions ='ALL_NON_LOSSY'; MySQL> stop slave;start slave;
3、關於該錯誤的詳細介紹
【不同數據類型的主從復制】:正常情況下,主庫和從庫每個表的各個列數據類型都是一致的,但是MySQL並不強制這樣。有些特殊情況下,是存在主從列數據類型不一致,比如上面的大表DDL操作,先在從庫上修改,然后切換主從,最后再修改老主庫的結構,當然,可能存在其他多種情況,不一一列舉。
【屬性升級和降級(attribute promotion and demotion)】:主從復制下,MySQL只支持同類小數據類型和較大類型之間的屬性提升和降級,比如將主庫上int在從庫上轉為bigint,視為屬性升級,相反將bigint轉為int就是屬性降級。屬性升降級可用於基於語句和基於行的復制格式,並且不依賴於存儲引擎。但是,日志格式的選擇對允許的類型轉換有影響,這里主要討論row格式。
【有損和無損轉換(Lossy and non-lossy conversions)】:主從數據庫同一表同一列數據類型不一致,會產生數據類型轉換。為了符合和適應目標列類型,截斷(或其他修改)源值,稱為有損轉換;不需要截斷或類似的修改以適應目標列中的源列值的轉換是一個非有損的轉換。這兩種轉換模式主要由slave_type_conversions系統變量控制,該變量的值如下:
Mode | 影響描述 |
ALL_LOSSY(有損轉換) | 該模式下,列類型轉換允許丟失一些信息。如果只設置該模式,只允許同類的大數據類型轉換為小數據類型,其他模式的轉換都會發生1677錯誤。生產環境不建議設置該值,很容易導致主從不一致。 |
ALL_NON_LOSSY(無損轉換) | 該模式下,不會導致數據丟失和截斷,因為該值只允許同類的小數據類型轉換為大數據類型,其他模式的轉換都會發生1677錯誤。生產環境建議設置該值 |
ALL_LOSSY,ALL_NON_LOSSY(兩種情況並存) | 上面兩種模式都支持,生產環境不建議設置該值 |
[empty] (空值) | 不設置任何值,表示不允許任何模式的類型轉換,如果發現類型轉換,都會產生1677錯誤,導致復制中斷 |
4、測試
4-1、slave_type_conversions='ALL_LOSSY'
主庫id是int類型,slave修改為bigint,當主庫插入數據后,從庫直接報錯1677;當從庫修改為tinyint,由於是ALL_LOSSY模式,從庫數據被截斷,這時主從數據是不一致的。
----------------------------------------當從庫修改為bigint----------------------------------------------------------------------------
mysql> show global variables like 'slave_type_conversions'; +------------------------+-----------+ | Variable_name | Value | +------------------------+-----------+ | slave_type_conversions | ALL_LOSSY | +------------------------+-----------+ 1 row in set (0.00 sec) mysql> show slave status\G *************************** 1. row *************************** Last_Errno: 1677 #直接報錯了 Last_Error: Column 0 of table 'darren.conversions' cannot be converted from type 'int' to type 'bigint(20)'
----------------------------------------當從庫修改為tinyint---------------------------------------------------------------------------- master插入11111: mysql> insert into conversions(id,name) values(11111,'a'); Query OK, 1 row affected (0.00 sec) mysql> mysql> select * from conversions; +-------+------+ | id | name | +-------+------+ | 1 | a | | 11111 | a | +-------+------+ slave的值127: mysql> select * from darren.conversions; +-----+------+ | id | name | +-----+------+ | 1 | a | | 127 | a | #11111被截斷為127 +-----+------+
4-2、slave_type_conversions='ALL_NON_LOSSY'

slave> show global variables like 'slave_type_conversions'; +------------------------+---------------+ | Variable_name | Value | +------------------------+---------------+ | slave_type_conversions | ALL_NON_LOSSY | +------------------------+---------------+ slave> show create table conversions; +-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | conversions | CREATE TABLE `conversions` ( `id` tinyint(4) NOT NULL AUTO_INCREMENT, `name` char(10) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=127 DEFAULT CHARSET=utf8 | +-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ------------------------------------主庫插入值----------------------------- master> insert into conversions(id,name) values(2,'a'); Query OK, 1 row affected (0.01 sec) master> select * from conversions; +-------+------+ | id | name | +-------+------+ | 1 | a | | 2 | a | | 11111 | a | +-------+------+ 從庫報錯了,因為ALL_NON_LOSSY 只支持小數據類型轉為大數據類型 mysql> show slave status\G *************************** 1. row *************************** Last_Errno: 1677 Last_Error: Column 0 of table 'darren.conversions' cannot be converted from type 'int' to type 'tinyint(4)'
4-3、slave_type_conversions=''
設置為空值,從庫不支持任何的類型轉換,直接報錯。
5、支持哪些類型轉換?
5.1) 支持整型 TINYINT
, SMALLINT
, MEDIUMINT
, INT
, and BIGINT之間任意兩者轉換
5.2) 支持浮點數 DECIMAL
, FLOAT
, DOUBLE
, and NUMERIC之間任意兩者轉換
5.3) 支持 CHAR
, VARCHAR
, and TEXT之間任意兩者轉換
5.4) 支持BINARY
, VARBINARY
, and BLOB之間任意兩者轉換
5.5) 支持bit之間轉換
其他不在上述的都不支持轉換。
【總結】設置空值是最嚴格的模式,不允許列類型不一致,如果發生不一致,從庫復制就中斷報錯了,能夠最大程度上維護主從數據一致性。
如果日常運維,如大表DDL需要在從庫上進行的,可以先設置slave_type_conversions='ALL_NON_LOSSY',能夠保證從庫不會產生1677錯誤,但是對於整型字段,一定要注意主從unsigned或者signed問題,如果主庫signed,從庫上是unsigned,主從數據有可能不一致;當主庫上unsigned,從庫signed的,一定要保證從庫字段足夠大能存下主庫字段值,否則也會導致數據不一致。
參考文檔:https://dev.mysql.com/doc/refman/5.7/en/replication-features-differing-tables.html