成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

MySQL 擴(kuò)展字段長(zhǎng)度報(bào)錯(cuò) Specified key was too long

數(shù)據(jù)庫(kù) MySQL
MySQL 5.5 中引入 innodb_large_prefix 參數(shù),5.5 與 5.6 中該參數(shù)默認(rèn)關(guān)閉,5.7 中默認(rèn)開(kāi)啟。innodb_large_prefix 參數(shù)用于控制行格式 ?DYNAMIC or COMPRESSED 中的索引最大長(zhǎng)度。

引言

本文主要分析一套 MySQL 分庫(kù)分表擴(kuò)展字段長(zhǎng)度時(shí)其中一個(gè)實(shí)例報(bào)錯(cuò)索引超長(zhǎng)的案例,其中失敗實(shí)例的版本是 5.7.21,而成功實(shí)例的版本都是 5.7.24。因此懷疑與版本有關(guān),最終通過(guò)測(cè)試與分析判斷是一個(gè) bug,官方文檔顯示在 5.7.23 中修復(fù)。

現(xiàn)象

首先介紹三個(gè)案例,都是字段長(zhǎng)度擴(kuò)展時(shí)報(bào)錯(cuò)索引超長(zhǎng)。

案例 1

時(shí)間:2023-09-08 21:31:02

數(shù)據(jù)庫(kù)版本:5.6.39

SQL

ALTER TABLE sign_bill_return_image_audit_result 
MODIFY COLUMN image_name VARCHAR(250) COMMENT '圖片名稱';

日志顯示 pt-osc 執(zhí)行期間報(bào)錯(cuò)索引長(zhǎng)度超過(guò) 767。

EXECUTE START AT 2023-09-08 21:31:02
Error altering new table `station_manager`.`_sign_bill_return_image_audit_result_new`: DBD::mysql::db do failed: Specified key was too long; max key length is 767 bytes [for Statement "ALTER TABLE `station_manager`.`_sign_bill_return_image_audit_result_new` MODIFY COLUMN image_name VARCHAR(250) COMMENT '圖片名稱';"] at /usr/bin/pt-online-schema-change line 9194.

EXECUTE FAIL AT 2023-09-08 21:31:03

查看表結(jié)構(gòu),顯示字符集為 utf8mb4,索引類型為單列唯一索引,image_name 字段長(zhǎng)度從 50 擴(kuò)展到 250。

mysql> show create table station_manager.sign_bill_return_image_audit_result \G
*************************** 1. row ***************************
       Table: sign_bill_return_image_audit_result
Create Table: CREATE TABLE `sign_bill_return_image_audit_result` (
  `image_name` varchar(50) NOT NULL DEFAULT '' COMMENT '圖片名稱',
  UNIQUE KEY `idx_img_name` (`image_name`) USING BTREE,
) ENGINE=InnoDB AUTO_INCREMENT=27756774 DEFAULT CHARSET=utf8mb4 COMMENT='簽單返還圖片審核結(jié)果表'
1 row in set (0.00 sec)

查看參數(shù),顯示未開(kāi)啟 innodb_large_prefix。

mysql> show variables like 'innodb_large_prefix';
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| innodb_large_prefix | OFF   |
+---------------------+-------+
1 row in set (0.00 sec)

案例 2

時(shí)間:2024-08-19 14:18:31

現(xiàn)象:上游字段擴(kuò)展,因此下游修改,但是執(zhí)行報(bào)錯(cuò)聯(lián)合索引超長(zhǎng)

數(shù)據(jù)庫(kù)版本:5.7.33

SQL

alter table worker_board_quota_counting 
modify column  `business_id` varchar(1456) NOT NULL COMMENT '業(yè)務(wù)id';

日志顯示 pt-osc 執(zhí)行期間報(bào)錯(cuò)索引長(zhǎng)度超過(guò) 3072。

Error altering new table `dms_offline`.`_worker_board_quota_counting_new`: DBD::mysql::db do failed: Specified key was too long; max key length is 3072 bytes [for Statement "ALTER TABLE `dms_offline`.`_worker_board_quota_counting_new` modify column `business_id` varchar(1456) NOT NULL COMMENT '業(yè)務(wù)id';"] at /usr/bin/pt-online-schema-change line 9194.

查看表結(jié)構(gòu),顯示字符集為 utf8mb4,索引類型為聯(lián)合唯一索引,business_id 字段長(zhǎng)度從 456 擴(kuò)展到 1456。

mysql> show create table dms_offline.worker_board_quota_counting \G
*************************** 1. row ***************************
       Table: worker_board_quota_counting
Create Table: CREATE TABLE `worker_board_quota_counting` (
  `business_id` varchar(456) NOT NULL COMMENT '業(yè)務(wù)id',
  UNIQUE KEY `idx_source_businessid` (`source`,`business_id`),
) ENGINE=InnoDB AUTO_INCREMENT=19747573 DEFAULT CHARSET=utf8mb4 COMMENT='人員看板計(jì)提表'
1 row in set (0.00 sec)

查看參數(shù),顯示已開(kāi)啟 innodb_large_prefix。

mysql> select @@innodb_large_prefix;
+-----------------------+
| @@innodb_large_prefix |
+-----------------------+
|                     1 |
+-----------------------+
1 row in set (0.00 sec)

案例 3

時(shí)間:2024-01-22 23:59:12

工單類型:分庫(kù)分表

數(shù)據(jù)庫(kù)版本:5.7.21 報(bào)錯(cuò),5.7.24 不報(bào)錯(cuò)

SQL

ALTER TABLE mst_sku
modify `upc_code` varchar(1000) DEFAULT NULL COMMENT '69碼';

日志顯示 pt-osc 執(zhí)行期間報(bào)錯(cuò)索引長(zhǎng)度超過(guò) 767。

Error altering new table `wms3`.`__mst_sku_new`: DBD::mysql::db do failed: Index column size too large. The maximum column size is 767 bytes. [for Statement "ALTER TABLE `wms3`.`__mst_sku_new` MODIFY COLUMN `upc_code` varchar(1000) DEFAULT NULL COMMENT '69碼';"] at /usr/bin/pt-online-schema-change line 9194.

查看表結(jié)構(gòu),顯示字符集為 utf8,索引類型為單列非唯一索引,upc_code 字段長(zhǎng)度從 64 擴(kuò)展到 1000,注意其中行格式為 COMPACT。

mysql> show create table `wms3`.`mst_sku` \G
*************************** 1. row ***************************
       Table: mst_sku
Create Table: CREATE TABLE `mst_sku` (
  `upc_code` varchar(64) DEFAULT NULL COMMENT '69碼',
  KEY `idx_sku_upccode` (`upc_code`),
) ENGINE=InnoDB AUTO_INCREMENT=12952734 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='商品信息表表'
1 row in set (0.00 sec)

查看參數(shù),顯示已開(kāi)啟 innodb_large_prefix。

mysql> select @@innodb_large_prefix;
+-----------------------+
| @@innodb_large_prefix |
+-----------------------+
|                     1 |
+-----------------------+
1 row in set (0.00 sec)

由于分庫(kù)分表工單中只有一個(gè)實(shí)例報(bào)錯(cuò),因此查看每個(gè)實(shí)例的數(shù)據(jù)庫(kù)版本與執(zhí)行結(jié)果。

ysql> select a.instance_version, t.execute_status from inception_job as t 
inner join assets_instance as a on t.mysql_ip=a.instance_ip 
where t.xbp_id =9334073;
+------------------+----------------+
| instance_version | execute_status |
+------------------+----------------+
| MySQL5.7.24      |              4 |
| MySQL5.7.24      |              4 |
| MySQL5.7.24      |              4 |
| MySQL5.7.24      |              4 |
| MySQL5.7.24      |              4 |
| MySQL5.7.24      |              4 |
| MySQL5.7.24      |              4 |
| MySQL5.7.24      |              4 |
| MySQL5.7.21      |              3 |
+------------------+----------------+
9 rows in set (0.00 sec)

其中:

  • execute_status = 4 表示成功,3 表示失敗;
  • 顯示有一個(gè)實(shí)例失敗,版本是 5.7.21,其他實(shí)例都是 5.7.24,都執(zhí)行成功,這一點(diǎn)很反常。

因此盡管上面三個(gè)案例都是報(bào)錯(cuò)索引超長(zhǎng),但是其中第三個(gè)案例中 5.7.21 報(bào)錯(cuò)的現(xiàn)象比較反常,因此進(jìn)行分析。

分析

索引最大長(zhǎng)度

其中對(duì)于 InnoDB 存儲(chǔ)引擎,單列索引的最大長(zhǎng)度是 767 字節(jié),聯(lián)合索引的最大長(zhǎng)度是 3072 字節(jié)。

不同版本的索引最大長(zhǎng)度也不同。

其中:

  • 5.5 中引入 innodb_large_prefix 參數(shù),5.5 與 5.6 中該參數(shù)默認(rèn)關(guān)閉,5.7 中默認(rèn)開(kāi)啟。其中:

參數(shù)關(guān)閉時(shí)單列索引的最大長(zhǎng)度為 767 字節(jié);

參數(shù)開(kāi)啟時(shí)單列索引的最大長(zhǎng)度為 3072 字節(jié)。

  • 8.0 中移除 innodb_large_prefix 參數(shù)。

innodb_large_prefix

參考官方文檔,innodb_large_prefix 參數(shù)用于控制行格式 DYNAMIC or COMPRESSED 中的索引最大長(zhǎng)度。

When this option is enabled, index key prefixes longer than 767 bytes (up to 3072 bytes) are allowed for InnoDB tables that use DYNAMIC or COMPRESSED row format.

同時(shí)滿足以下三個(gè)條件時(shí)允許創(chuàng)建 large index(索引最大長(zhǎng)度為 3072 字節(jié)):

  • ROW_FORMAT = DYNAMIC or COMPRESSED
  • innodb_file_format = Barracuda
  • innodb_large_prefix = 1

其中有一個(gè)條件不滿足時(shí)索引最大長(zhǎng)度為 767,且超長(zhǎng)數(shù)據(jù)將被截?cái)唷?/p>

innodb_large_prefix is enabled by default in MySQL 5.7. This change coincides with the default value change for。innodb_file_format, which is set to Barracuda by default in MySQL 5.7. Together, these default value changes allow larger index key prefixes to be created when using DYNAMIC or COMPRESSED row format. If either option is set to a non-default value, index key prefixes larger than 767 bytes are silently truncated.

innodb_large_prefix is deprecated; expect it to be removed in a future release. innodb_large_prefix was introduced to disable large index key prefixes for compatibility with earlier versions of InnoDB that do not support large index key prefixes.

因此對(duì)于案例 3,第一個(gè)條件不滿足,原因是行格式指定為 COMPACT,因此索引最大長(zhǎng)度為 766 字節(jié),那么超長(zhǎng)時(shí)會(huì)報(bào)錯(cuò)嗎?

測(cè)試

5.7.24

測(cè)試環(huán)境 5.7.24 執(zhí)行報(bào)錯(cuò),與官方文檔描述一致,因此報(bào)錯(cuò)是正常現(xiàn)象。

mysql> create table _mst_sku_new (
`upc_code` varchar(64) DEFAULT NULL COMMENT '69碼',
KEY `idx_sku_upccode` (`upc_code`)
) ENGINE=InnoDB CHARSET=utf8 ROW_FORMAT=COMPACT;
Query OK, 0 rows affected (0.02 sec)

mysql> alter table `_mst_sku_new` MODIFY COLUMN `upc_code` varchar(1000) DEFAULT NULL;
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
 
mysql> select version();
+------------+
| version()  |
+------------+
| 5.7.24-log |
+------------+
1 row in set (0.00 sec)

測(cè)試環(huán)境 5.7.33 執(zhí)行成功,但是有警告,原因是 sql_mode 為空,表明 sql_mode 的優(yōu)先級(jí)高于 ROW_FORMAT。

圖片圖片

線上環(huán)境 5.7.24 執(zhí)行成功,原因是 sql_mode = NO_ENGINE_SUBSTITUTION,因此將報(bào)錯(cuò)降級(jí)為警告。

mysql> set session sql_mode='STRICT_TRANS_TABLES';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> alter table `_mst_sku_new` MODIFY COLUMN `upc_code` varchar(1000) DEFAULT NULL;
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes

mysql> set session sql_mode='NO_ENGINE_SUBSTITUTION';
Query OK, 0 rows affected (0.00 sec)

mysql> alter table `_mst_sku_new` MODIFY COLUMN `upc_code` varchar(1000) DEFAULT NULL;
Query OK, 0 rows affected, 1 warning (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 1

線上環(huán)境 5.7.21 執(zhí)行失敗,可是同樣 sql_mode = NO_ENGINE_SUBSTITUTION,原因是什么呢?

5.7.21

線上環(huán)境 5.7.21 與 5.7.24 配置相同但是報(bào)錯(cuò)。

mysql> create table _mst_sku_new (
`upc_code` varchar(64) DEFAULT NULL COMMENT '69碼',
KEY `idx_sku_upccode` (`upc_code`)
) ENGINE=InnoDB CHARSET=utf8 ROW_FORMAT=COMPACT;
Query OK, 0 rows affected (0.01 sec)

mysql> select @@sql_mode;
+------------------------+
| @@sql_mode             |
+------------------------+
| NO_ENGINE_SUBSTITUTION |
+------------------------+
1 row in set (0.00 sec)

mysql> alter table `_mst_sku_new` MODIFY COLUMN `upc_code` varchar(1000) DEFAULT NULL;
ERROR 1709 (HY000): Index column size too large. The maximum column size is 767 bytes.

mysql> alter table _mst_sku_new ROW_FORMAT=dynamic;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table `_mst_sku_new` MODIFY COLUMN `upc_code` varchar(1000) DEFAULT NULL;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

其中:

  • 報(bào)錯(cuò)不同,Index column size too large. The maximum column size is 767 bytes;
  • ROW_FORMAT = COMPACT 報(bào)錯(cuò),理論上不報(bào)錯(cuò),不合理,原因是 sql_mode = NO_ENGINE_SUBSTITUTION;
  • ROW_FORMAT = DYNAMIC 不報(bào)錯(cuò),理論上不報(bào)錯(cuò),合理;

因此懷疑 5.7.21 中存在 bug,比如判斷是否支持 create larger index 時(shí)沒(méi)有判斷 sql_mode。

5.7.23

查看 release notes,顯示 5.7.23 中修復(fù)了一個(gè) bug,bug 的現(xiàn)象是對(duì)于 COMPACT 或 REDUNDANT:

  • 嚴(yán)格模式下不報(bào)錯(cuò)
  • 非嚴(yán)格模式下不告警

For attempts to increase the length of a VARCHAR column of an InnoDB table using ALTER TABLE with the INPLACE algorithm, the attempt failed if the column was indexed.

If an index size exceeded the InnoDB limit of 767 bytes for COMPACT or REDUNDANT row format, CREATE TABLE and ALTER TABLE did not report an error (in strict SQL mode) or a warning (in nonstrict mode). (Bug #26848813)

對(duì)應(yīng) commit 為 MySQL Commit 913071c,下面表格中展示修復(fù)后的行格式與索引長(zhǎng)度,以及嚴(yán)格模式與非嚴(yán)格模式下返回報(bào)錯(cuò)還是告警,其中 IL 表示 Index Limit。

Row Format

INDEX LIMIT

STRICT MODE (>IL)

NON-STRICT MODE (>IL)

Compact/Redundant (Non Unique Index)

767 bytes

Error

Index truncation (767) and warning

Compact/Redundant (Unique/Primary Index)

767 bytes

Error

Error

Dynamic/Compressed (Non Unique Index)

3072 bytes

Error

Index truncation (3072) and warning

Dynamic/Compressed (Unique/Primary Index)

3072 bytes

Error

Error

其中當(dāng)索引超長(zhǎng)時(shí),返回報(bào)錯(cuò)還是告警由索引類型與 sql_mode 共同決定:

  • 唯一索引,對(duì)于嚴(yán)格模式與非嚴(yán)格模式,均返回報(bào)錯(cuò);
  • 非唯一索引,對(duì)于嚴(yán)格模式,返回報(bào)錯(cuò),對(duì)于非嚴(yán)格模式,返回警告,并將索引值截?cái)酁榍熬Y索引。

因此,判斷該現(xiàn)象對(duì)應(yīng)該 bug,表現(xiàn)為 5.7.21 非嚴(yán)格模式中,非唯一索引超長(zhǎng)后返回報(bào)錯(cuò),而不是警告。

debug

debug 數(shù)據(jù)庫(kù)版本為 5.7.33,測(cè)試索引超長(zhǎng)返回警告的堆棧見(jiàn)下圖。

其中有以下兩個(gè)函數(shù):

  • mysql_prepare_create_table
  • push_warning_printf

commit 中顯示修改 ha_innobase::max_supported_key_part_length 函數(shù)。

因此給以上三個(gè)函數(shù)設(shè)置斷點(diǎn)。

測(cè)試顯示行記錄為 COMPACT 時(shí),返回索引最大長(zhǎng)度為 767。

圖片圖片

在判斷索引長(zhǎng)度超長(zhǎng)(1000 * 3 = 3000 > 767)后,判斷返回報(bào)錯(cuò)還是警告。

圖片圖片

其中:

  • 如果是唯一索引,返回報(bào)錯(cuò);
  • 如果是非唯一索引,繼續(xù)判斷 sq_mode,如果是嚴(yán)格模式,返回報(bào)錯(cuò),否則返回警告,并且將索引長(zhǎng)度自動(dòng)截?cái)鄬?shí)現(xiàn)字節(jié)對(duì)齊。變量 key_part_length 從 767 改為 765 字節(jié),對(duì)應(yīng) utf8 字符集 255 字符。

相關(guān)代碼如下所示。

// 如果不是唯一索引,也就是二級(jí)非唯一索引,根據(jù) sql_mode 判斷是否返回報(bào)錯(cuò)
 if (key->type == KEYTYPE_MULTIPLE)
 {
   /* not a critical problem */
    // 警告
   push_warning_printf(thd, Sql_condition::SL_WARNING,
                              ER_TOO_LONG_KEY, ER(ER_TOO_LONG_KEY),
                              key_part_length);
          /* Align key length to multibyte char boundary */
          // 索引長(zhǎng)度自動(dòng)截?cái)啵热?767 // 3 = 255
          // 將 key_part_length 減少到最接近的整數(shù)倍數(shù),使得它不超過(guò)當(dāng)前字符集中最多的多字節(jié)字符長(zhǎng)度
          key_part_length-= key_part_length % sql_field->charset->mbmaxlen;
          /*
            If SQL_MODE is STRICT, then report error, else report warning
            and continue execution.
          */
          // 對(duì)于嚴(yán)格模式,將警告升級(jí)為錯(cuò)誤
          if (thd->is_error())
            DBUG_RETURN(true);
 }

其中枚舉類型變量 keytype 的定義如下所示,沒(méi)有區(qū)分單列索引與聯(lián)合索引,因此判斷 KEYTYPE_MULTIPLE 表示非唯一索引。

enum keytype {
  KEYTYPE_PRIMARY,
  KEYTYPE_UNIQUE,
  KEYTYPE_MULTIPLE,
  KEYTYPE_FULLTEXT,
  KEYTYPE_SPATIAL,
  KEYTYPE_FOREIGN
};

而在 5.7.21 中,返回的索引最大長(zhǎng)度等于 3072,大于當(dāng)前字段的長(zhǎng)度 3000,因此判斷結(jié)果是索引不超長(zhǎng)。

圖片圖片

而在創(chuàng)建索引的時(shí)候還會(huì)二次檢查判斷索引長(zhǎng)度是否超長(zhǎng)。

/* Even though we've defined max_supported_key_part_length, we
 still do our own checking using field_lengths to be absolutely
 sure we don't create too long indexes. */

 error = convert_error_code_to_mysql(
  row_create_index_for_mysql(index, trx, field_lengths, handler),
  flags, NULL);

其中:

  • create_index 函數(shù)中調(diào)用 row_create_index_for_mysql 函數(shù)創(chuàng)建索引;
  • row_create_index_for_mysql 函數(shù)中檢查索引的長(zhǎng)度與行格式對(duì)應(yīng)的索引最大長(zhǎng)度,其中通過(guò)宏 DICT_MAX_FIELD_LEN_BY_FORMAT 獲取索引長(zhǎng)度;
/* Column or prefix length exceeds maximum column length */
  if (len > (ulint) DICT_MAX_FIELD_LEN_BY_FORMAT(table)) {
   err = DB_TOO_BIG_INDEX_COL;

   dict_mem_index_free(index);
   goto error_handling;
  }
 }
  • DICT_MAX_FIELD_LEN_BY_FORMAT 宏中根據(jù)行格式返回索引最大長(zhǎng)度,COMPACT 對(duì)應(yīng) 767;
/** Find out maximum indexed column length by its table format.
For ROW_FORMAT=REDUNDANT and ROW_FORMAT=COMPACT, the maximum
field length is REC_ANTELOPE_MAX_INDEX_COL_LEN - 1 (767). For
Barracuda row formats COMPRESSED and DYNAMIC, the length could
be REC_VERSION_56_MAX_INDEX_COL_LEN (3072) bytes */
#define DICT_MAX_FIELD_LEN_BY_FORMAT(table)    \
  ((dict_table_get_format(table) < UNIV_FORMAT_B)  \
   ? (REC_ANTELOPE_MAX_INDEX_COL_LEN - 1)  \
   : REC_VERSION_56_MAX_INDEX_COL_LEN)
  • 由于 3000 > 767,因此判斷索引超長(zhǎng),最終返回報(bào)錯(cuò);
  • 但是為什么非嚴(yán)格模式下沒(méi)有將報(bào)錯(cuò)降級(jí)為警告的原因暫時(shí)沒(méi)查到。

處理

時(shí)間:2024-11-24 02:00:27

10個(gè)月以后,這套該分庫(kù)分表給其他字段擴(kuò)展長(zhǎng)度時(shí)再次觸發(fā)該問(wèn)題,因此決定進(jìn)行處理,具體是將數(shù)據(jù)庫(kù)從 5.7.21 升級(jí)到 5.7.24。

而在升級(jí)后發(fā)現(xiàn)兩個(gè)現(xiàn)象:

  • 索引中字符長(zhǎng)度自動(dòng)調(diào)整為 255,正常現(xiàn)象;
  • 不小心又踩坑了,先升級(jí)的主庫(kù),執(zhí)行 DDL 后導(dǎo)致從庫(kù)復(fù)制中斷,異常現(xiàn)象。

如下所示,對(duì)比執(zhí)行失敗與執(zhí)行成功時(shí)的索引長(zhǎng)度。

# 失敗后
KEY `idx_dispatch_no` (`dispatch_no`)

# 成功后
KEY `idx_dispatch_no` (`dispatch_no`(255)),

官方文檔顯示,從 5.7.17 版本開(kāi)始:

  • 對(duì)于非唯一索引,如果是非嚴(yán)格模式,索引超長(zhǎng)后返回警告,并自動(dòng)截?cái)嗟街С值乃饕畲箝L(zhǎng)度;
  • 對(duì)于唯一索引,索引超長(zhǎng)后直接報(bào)錯(cuò),不會(huì)發(fā)生截?cái)啵蚴墙財(cái)嗪罂赡軐?dǎo)致唯一性約束失效。

As of MySQL 5.7.17, if a specified index prefix exceeds the maximum column data type size, CREATE INDEX handles the index as follows:

For a nonunique index, either an error occurs (if strict SQL mode is enabled), or the index length is reduced to lie within the maximum column data type size and a warning is produced (if strict SQL mode is not enabled).

For a unique index, an error occurs regardless of SQL mode because reducing the index length might enable insertion of nonunique entries that do not meet the specified uniqueness requirement.

如下所示,進(jìn)行測(cè)試。

其中:

  • 嚴(yán)格模式,非唯一索引,索引超長(zhǎng)后報(bào)錯(cuò);
  • 非嚴(yán)格模式,非唯一索引,索引超長(zhǎng)后警告,并自動(dòng)截?cái)啵?/li>
  • 非嚴(yán)格模式,唯一索引,索引超長(zhǎng)后報(bào)錯(cuò)。

主庫(kù)升級(jí)后使用 pt-osc 執(zhí)行 DDL 導(dǎo)致從庫(kù)復(fù)制中斷,原因是從庫(kù)未升級(jí)。

重試時(shí)發(fā)生異常,日志顯示執(zhí)行暫停。

2024-11-25T11:35:07 Copying approximately 764 rows...
Replica MSS-2hbqmzhk2m is stopped. Waiting. 
Killed

查看復(fù)制,顯示復(fù)制中斷,原因是從庫(kù)執(zhí)行 DDL 報(bào)錯(cuò),pt-osc 延遲檢測(cè)期間發(fā)現(xiàn)復(fù)制中斷后執(zhí)行暫停。

mysql> SHOW SLAVE STATUS \G
*************************** 1. row ***************************
               Last_SQL_Errno: 1709
               Last_SQL_Error: Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 1 failed executing transaction '965c7418-175f-11ee-b6d3-fa163eae0649:12102' at master log mysql-bin.146487, end_log_pos 8137214. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any.
     Last_SQL_Error_Timestamp: 241125 11:35:07
            Executed_Gtid_Set: 965c7418-175f-11ee-b6d3-fa163eae0649:1-12101
                Auto_Position: 1
1 row in set (0.00 sec)

mysql> select * from performance_schema.replication_applier_status_by_worker  limit 1 \G
*************************** 1. row ***************************
         CHANNEL_NAME: 
            WORKER_ID: 1
            THREAD_ID: NULL
        SERVICE_STATE: OFF
LAST_SEEN_TRANSACTION: 965c7418-175f-11ee-b6d3-fa163eae0649:12102
    LAST_ERROR_NUMBER: 1709
   LAST_ERROR_MESSAGE: Worker 1 failed executing transaction '965c7418-175f-11ee-b6d3-fa163eae0649:12102' at master log mysql-bin.146487, end_log_pos 8137214; Error 'Index column size too large. The maximum column size is 767 bytes.' on query. Default database: 'wms3'. Query: 'ALTER TABLE `wms3`.`_task_group_new` MODIFY COLUMN dispatch_no varchar(500) NULL COMMENT '派車單號(hào)''
 LAST_ERROR_TIMESTAMP: 2024-11-25 11:35:07
1 row in set (0.01 sec)

知識(shí)點(diǎn)

ROW_FORMAT

innodb_default_row_format 參數(shù)用于控制默認(rèn)行格式,取值與版本有關(guān):

  • 5.0.3 版本之前,僅支持一種行格式 REDUNDANT;
  • 5.0.3 - 5.7.8,默認(rèn)行格式為 COMPACT;
  • 從 5.7.9 版本開(kāi)始,默認(rèn)行格式為 DYNAMIC,包括 8.0。

行格式 COMPACT 與 DYNAMIC 的主要區(qū)別是行溢出(一個(gè)列中存儲(chǔ)的數(shù)據(jù)大于等于8098個(gè)字節(jié))數(shù)據(jù)的保存方式不同,其中:

  • COMPACT,在記錄的真實(shí)數(shù)據(jù)處存儲(chǔ)字段真實(shí)數(shù)據(jù)的前 768 個(gè)字節(jié),剩余數(shù)據(jù)保存在其他頁(yè)中,并在真實(shí)數(shù)據(jù)中保存溢出頁(yè)地址;
  • DYNAMIC,把所有的字節(jié)都存儲(chǔ)到其他頁(yè)面中,只在記錄的真實(shí)數(shù)據(jù)處存儲(chǔ)其他頁(yè)面的地址。

圖片圖片

因此在數(shù)據(jù)庫(kù)升級(jí)過(guò)程中也需要關(guān)注行格式。

故障分析 | ERROR 1709: Index column size too large 引發(fā)的思考 文章中分享了一個(gè)案例,現(xiàn)象是數(shù)據(jù)庫(kù)重啟后有張表無(wú)法訪問(wèn),SELECT、DML 和 DDL 執(zhí)行均報(bào)錯(cuò) ERROR 1709 (HY000): Index column size too large. The maximum column size is 767 bytes.。

復(fù)現(xiàn)流程如下所示:

  • 數(shù)據(jù)庫(kù)從 5.6.21 原地升級(jí)到 8.0.21,升級(jí)之前創(chuàng)建的一個(gè)表未指定行格式,因此使用默認(rèn)行格式 COMPACT;
  • 升級(jí)后添加字段并創(chuàng)建索引,索引超長(zhǎng)但是沒(méi)有報(bào)錯(cuò),也沒(méi)有警告;
  • 數(shù)據(jù)庫(kù)重啟前,表可以正常訪問(wèn);
  • 數(shù)據(jù)庫(kù)重啟后,表無(wú)法訪問(wèn),報(bào)錯(cuò)索引超長(zhǎng)。

最終定位到也是一個(gè) bug,具體表現(xiàn)為非顯式定義的 redundant 行格式表允許創(chuàng)建的索引列大小超 767 bytes,并在 8.0.22 版本中修復(fù)。

因此建議在數(shù)據(jù)庫(kù)升級(jí)前檢查隱式創(chuàng)建行格式為 compact/redundant 的表,并顯式指定。

相關(guān)案例

下面引申一個(gè)話題,SQL 工單中遇到過(guò) goinception 語(yǔ)法校驗(yàn)通過(guò),但是執(zhí)行時(shí)報(bào)錯(cuò)行超長(zhǎng)的現(xiàn)象,因此分別測(cè)試 goinception 是否可以識(shí)別字段超長(zhǎng)與行超長(zhǎng)。

已知:

  • 對(duì)于VARCHAR(M)類型的列最多可以占用65535個(gè)字節(jié)。其中的M代表該類型最多存儲(chǔ)的字符數(shù)量;
  • MySQL對(duì)一條記錄占用的最大存儲(chǔ)空間是有限制的,除了BLOB或者TEXT類型的列之外,其他所有的列(不包括隱藏列和記錄頭信息)占用的字節(jié)長(zhǎng)度加起來(lái)不能超過(guò)65535個(gè)字節(jié)。

字段超長(zhǎng)

SQL

create table,155355 * 3 > 65535,因此字段長(zhǎng)度超長(zhǎng)。

create table ttt(
  id int primary key auto_increment comment 'id', 
  a varchar(155355) default '' comment 'a'
) comment 'ttt';

goinception 返回報(bào)錯(cuò)字段超長(zhǎng),建議使用大字段替換 varchar。

Column length too big for column 'a' (max = 21845); use BLOB or TEXT instead. Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs.

add column;

alter table t1 add column aa varchar(155355) default '' comment 'a';

goinception;

Column length too big for column 'aa' (max = 21845); use BLOB or TEXT instead.

modify column;

alter table t1 modify column a varchar(155355) default '' comment 'a';

goinception;

Column length too big for column 'a' (max = 21845); use BLOB or TEXT instead.

因此,測(cè)試顯示 goinception 可以驗(yàn)證字段超長(zhǎng),包括建表與改表時(shí),那么是否可以驗(yàn)證行超長(zhǎng)?

行超長(zhǎng)

create table,15535 * 3 * 2 = 93210 > 65535,因此雖然單個(gè)字段不超長(zhǎng),但是行超長(zhǎng)。

create table ttt(
  id int primary key auto_increment comment 'id', 
  a varchar(15535) default '' comment 'a',
  b varchar(15535) default '' comment 'b'
) comment 'ttt';

goinception 返回校驗(yàn)通過(guò),當(dāng)然實(shí)際執(zhí)行會(huì)失敗。

{
    "id": 1, 
    "stage": "CHECKED", 
    "errlevel": 0, 
    "stagestatus": "Audit Completed", 
    "errormessage": "", 
    "sql": "USE `cctest`", 
    "affected_rows": 0, 
    "sequence": "0_0_00000000", 
    "backup_dbname": "", 
    "execute_time": "0", 
    "sqlsha1": "", 
    "backup_time": "0", 
    "actual_affected_rows": ""
}

因此結(jié)論是 goinception 可以發(fā)現(xiàn)單字段超長(zhǎng),但是無(wú)法發(fā)現(xiàn)多字段導(dǎo)致的行超長(zhǎng)。

因此,SQL 工單中自定義行超長(zhǎng)校驗(yàn),調(diào)用接口返回報(bào)錯(cuò)。

{
    "code": 16, 
    "message": "SQLCheckMaxRowSizeError", 
    "error": "Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs. 庫(kù):cctest,表:ttt,行大小為:93214,超過(guò)最大行大小65535字節(jié),請(qǐng)修改字段長(zhǎng)度或類型"
}

原因是代碼中自行實(shí)現(xiàn)行超長(zhǎng)檢測(cè),并自定義異常類。

class SQLCheckMaxRowSizeError(BaseError):  
    def __init__(self, db_name="", table="", row_size=""):  
        BaseError.__init__(  
            self, code=SQL_Check_MAX_ROW_SIZE_Error, message="SQLCheckMaxRowSizeError",  
            error="Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. "  
                  "This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs."                  " 庫(kù):%s,表:%s,行大小為:%s,超過(guò)最大行大小65535字節(jié),請(qǐng)修改字段長(zhǎng)度或類型" % (db_name, table, row_size)  
        )

結(jié)論

MySQL 5.5 中引入 innodb_large_prefix 參數(shù),5.5 與 5.6 中該參數(shù)默認(rèn)關(guān)閉,5.7 中默認(rèn)開(kāi)啟。

innodb_large_prefix 參數(shù)用于控制行格式  DYNAMIC or COMPRESSED 中的索引最大長(zhǎng)度。

5.7 中同時(shí)滿足以下三個(gè)條件時(shí)索引最大長(zhǎng)度為 3072 字節(jié):

  • ROW_FORMAT = DYNAMIC or COMPRESSED
  • innodb_file_format = Barracuda
  • innodb_large_prefix = 1

其中有一個(gè)條件不滿足時(shí)索引最大長(zhǎng)度等于 767 字節(jié)。因此對(duì)于行格式 COMPACT,索引最大長(zhǎng)度為 767 字節(jié)。

當(dāng)索引超長(zhǎng)時(shí),返回報(bào)錯(cuò)還是告警由索引類型與 sql_mode 共同決定:

  • 唯一索引,對(duì)于嚴(yán)格模式與非嚴(yán)格模式,均返回報(bào)錯(cuò),注意不允許截?cái)啵駝t可能導(dǎo)致索引失效;
  • 非唯一索引,對(duì)于嚴(yán)格模式,返回報(bào)錯(cuò),對(duì)于非嚴(yán)格模式,返回警告,并將索引值截?cái)酁榍熬Y索引。

而本文中 5.7.21 版本中的現(xiàn)象與上述描述不符,非嚴(yán)格模式中,非唯一索引超長(zhǎng)后返回報(bào)錯(cuò),而不是警告。因此判斷該現(xiàn)象是 bug。

分析代碼后發(fā)現(xiàn),有兩次索引長(zhǎng)度檢查,但是索引最大長(zhǎng)度的判斷條件不一致:

  • 第一次,索引最大長(zhǎng)度由 innodb_large_prefix 決定,參數(shù)開(kāi)啟時(shí)返回 3072;
  • 第二次,索引最大長(zhǎng)度由行格式?jīng)Q定,COMPACT 對(duì)應(yīng) 767。

因此在 5.7.21 中當(dāng) COMPACT 開(kāi)啟 innodb_large_prefix 時(shí),將導(dǎo)致第一次檢查通過(guò),第二次檢查報(bào)錯(cuò),但是具體為什么沒(méi)有將報(bào)錯(cuò)降級(jí)為警告的原因暫未查到。

而在 8.0 中移除了 innodb_large_prefix 參數(shù),索引最大長(zhǎng)度統(tǒng)一由行格式?jīng)Q定,這樣也就避免了該問(wèn)題。

回過(guò)頭來(lái)分析最初的三個(gè)案例,其中:

  • 案例 1,5.6.39,隱式 COMPACT,單列唯一索引報(bào)錯(cuò)超長(zhǎng) 767,報(bào)錯(cuò)正常;
  • 案例 2,5.7.33,隱式 COMPACT,聯(lián)合唯一索引報(bào)錯(cuò)超長(zhǎng) 3072,報(bào)錯(cuò)正常;
  • 案例 3,5.7.21,顯式 COMPACT,單列非唯一索引報(bào)錯(cuò)超長(zhǎng) 767,報(bào)錯(cuò)不正常,正常應(yīng)該是警告。

注意都是非嚴(yán)格模式。

責(zé)任編輯:武曉燕 來(lái)源: 丹柿小院
相關(guān)推薦

2009-12-11 14:16:13

PHP獲取字段長(zhǎng)度

2010-11-22 11:55:23

MySQL字段

2010-11-01 14:30:47

db2擴(kuò)充表空間

2010-10-08 14:59:00

MySql字段

2024-01-07 20:05:33

2014-04-15 11:22:24

2024-04-15 10:30:22

MySQL存儲(chǔ)引擎

2023-12-25 14:47:14

2023-11-13 10:55:09

MySQL數(shù)據(jù)庫(kù)

2023-04-10 08:28:35

CharVarchar

2024-05-31 09:31:00

2010-04-23 16:18:36

Oracle存取

2023-02-07 09:01:30

字符串類型MySQL

2010-09-25 10:48:59

SQL字段類型長(zhǎng)度

2016-09-20 23:44:43

2024-03-14 08:11:45

模型RoPELlama

2022-12-05 14:05:26

MySQL最大取值存儲(chǔ)

2010-08-16 13:25:41

DB2數(shù)據(jù)庫(kù)操作

2019-06-18 15:20:01

MySQL連接錯(cuò)誤數(shù)據(jù)庫(kù)

2024-07-15 08:32:34

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 日韩一二三区视频 | 免费在线视频精品 | 国产精品国产精品 | 中文字幕第十五页 | 福利一区二区在线 | 成人一区二区视频 | 国产三区视频在线观看 | 羞羞网站免费观看 | 国产精品一区在线观看 | 99精品一区二区三区 | 自拍 亚洲 欧美 老师 丝袜 | 国产在线观看一区二区三区 | 福利视频1000 | 国产精品美女久久久久aⅴ国产馆 | 亚洲精品高清视频 | 国产视频二区 | 欧美精品久久 | 日韩中文字幕久久 | 日韩激情在线 | 亚洲免费人成在线视频观看 | 国产精品不卡 | 一级大黄| 欧美另类日韩 | 精品在线一区 | 亚洲欧美一区二区三区视频 | 99热碰| 久久精品视频99 | 蜜桃在线播放 | 成人污污视频 | 精品久久久一区 | 免费视频一区 | 欧美日韩成人在线 | 一区二区免费视频 | 欧美一区二区免费 | av高清| 亚洲视频在线一区 | 久久久久精 | 天天插天天操 | 在线观看成人免费视频 | 日韩av电影院 | 国产精品a久久久久 |