朋友遭遇「數據被竄改」疑雲,排查後發現是 無符號整型減法溢出導致靜默回繞。MySQL 5.1 與 5.5+ 對溢出處理機制截然不同,若主從版本混用(主 5.1、從 5.5)更會導致複製中斷。本文實測對比並給出升級建議。
| 類型 | 有符號範圍 | 無符號範圍 | 存儲字節 |
|---|---|---|---|
| TINYINT | -128 ~ 127 | 0 ~ 255 | 1 |
| SMALLINT | -32,768 ~ 32,767 | 0 ~ 65,535 | 2 |
| MEDIUMINT | -8,388,608 ~ 8,388,607 | 0 ~ 16,777,215 | 3 |
| INT | -2,147,483,648 ~ 2,147,483,647 | 0 ~ 4,294,967,295 | 4 |
| BIGINT | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | 0 ~ 18,446,744,073,709,551,615 | 8 |
注意:MySQL 內部運算統一提升至 BIGINT,故以 BIGINT 為例驗證。
mysql> SELECT CAST(0 AS UNSIGNED) - 1;
+-------------------------+
| CAST(0 AS UNSIGNED) - 1 |
+-------------------------+
| 18446744073709551615 | -- 回繞到 UNSIGNED MAX
+-------------------------+
mysql> SELECT 9223372036854775807 + 1;
+-------------------------+
| 9223372036854775807 + 1 |
+-------------------------+
| -9223372036854775808 | -- 回繞到 SIGNED MIN
+-------------------------+
mysql> SELECT CAST(0 AS UNSIGNED) - 1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0 as unsigned) - 1)'
mysql> SELECT 9223372036854775807 + 1;
ERROR 1690 (22003): BIGINT value is out of range in '(9223372036854775807 + 1)'
UPDATE account SET balance = balance - 100 WHERE id=1,餘額不足時 5.1 靜默變巨款,5.5+ 直接報錯保護數據。SELECT ... FOR UPDATE 校驗餘額 ≥ 扣減額,再執行更新。STRICT_TRANS_TABLES(5.5+ 默認開啟),強制截斷/溢出報錯。結語:MySQL 5.1 時代的「靜默回繞」是數據完整性的隱形殺手。業務涉及金額/積分/計數的系統,強烈建議升級至 5.5+ 並開啟嚴格模式,將溢出風險從運行時轉移到編譯/驗證階段。