【问题标题】:Why does MySQL keep changing my VARCHAR to a TEXT?为什么 MySQL 不断将我的 VARCHAR 更改为 TEXT?
【发布时间】:2021-03-02 21:36:27
【问题描述】:

我正在尝试改进公司数据库中某个表的性能。此表有 7.9 690 万行,格式为:

mysql> show fields from BroadcastLog;
+---------------+------------------+------+-----+---------+----------------+
| Field         | Type             | Null | Key | Default | Extra          |
+---------------+------------------+------+-----+---------+----------------+
| id            | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| broadcast_id  | int(10) unsigned | YES  | MUL | NULL    |                |
| author_id     | int(10) unsigned | YES  | MUL | NULL    |                |
| type          | int(11)          | NO   | MUL | NULL    |                |
| origin        | int(11)          | YES  | MUL | NULL    |                |
| date_created  | datetime         | NO   | MUL | NULL    |                |
| date_modified | datetime         | NO   |     | NULL    |                |
| old_status    | int(10) unsigned | YES  | MUL | NULL    |                |
| new_status    | int(10) unsigned | YES  | MUL | NULL    |                |
| json_data     | text             | YES  |     | NULL    |                |
| log_text      | text             | YES  |     | NULL    |                |
+---------------+------------------+------+-----+---------+----------------+
11 rows in set (0.01 sec)

我首先想改进的地方之一是将两个 text 字段更改为 varchar 字段,我知道这通常更有效。于是我试了一下:

mysql> alter table BroadcastLog modify log_text varchar(2048);
Query OK, 0 rows affected, 1 warning (1 min 13.08 sec)
Records: 0  Duplicates: 0  Warnings: 1

mysql> show warnings;
+-------+------+---------------------------------------------------+
| Level | Code | Message                                           |
+-------+------+---------------------------------------------------+
| Note  | 1246 | Converting column 'log_text' from VARCHAR to TEXT |
+-------+------+---------------------------------------------------+
1 row in set (0.01 sec)

它没有转换!

我试图变得聪明。让我们创建一个新(临时)列,复制数据,删除旧列,然后重命名新列:

mysql> alter table BroadcastLog add column log_text_vc varchar(2048);
Query OK, 0 rows affected, 1 warning (1 min 13.08 sec)
Records: 0  Duplicates: 0  Warnings: 1

mysql> show warnings;
+-------+------+---------------------------------------------------+
| Level | Code | Message                                           |
+-------+------+---------------------------------------------------+
| Note  | 1246 | Converting column 'log_text' from VARCHAR to TEXT |
+-------+------+---------------------------------------------------+
1 row in set (0.01 sec)

甚至无法创建新列!

我试图变得更聪明。创建一个新,复制数据,删除旧列,将数据复制回来:

mysql> create table tmp (id INT UNSIGNED PRIMARY KEY, json_data VARCHAR(1024), log_text VARCHAR(2048));
Query OK, 0 rows affected (0.04 sec)

mysql> insert into tmp (id, json_data, log_text) select id, json_data, log_text from BroadcastLog;
Query OK, 6939076 rows affected (5 min 28.12 sec)
Records: 6939076  Duplicates: 0  Warnings: 0

mysql> alter table BroadcastLog drop column json_data;
Query OK, 0 rows affected (1 min 12.36 sec)
Records: 0  Duplicates:  0 Warnings: 0

mysql> alter table BroadcastLog drop column log_text;
Query OK, 0 rows affected (1 min 9.10 sec)
Records: 0  Duplicates:  0 Warnings: 0

mysql> alter table BroadcastLog add column json_data varchar(1024);
Query OK, 0 rows affected (1 min 11.52 sec)
Records: 0  Duplicates:  0 Warnings: 0

mysql> alter table BroadcastLog add column log_text varchar(2048);
Query OK, 0 rows affected (1 min 15.41 sec)
Records: 0  Duplicates:  0 Warnings: 1

mysql> show warnings;
+-------+------+---------------------------------------------------+
| Level | Code | Message                                           |
+-------+------+---------------------------------------------------+
| Note  | 1246 | Converting column 'log_text' from VARCHAR to TEXT |
+-------+------+---------------------------------------------------+
1 row in set (0.01 sec)

mysql> show fields from BroadcastLog;
+---------------+------------------+------+-----+---------+----------------+
| Field         | Type             | Null | Key | Default | Extra          |
+---------------+------------------+------+-----+---------+----------------+
| id            | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| broadcast_id  | int(10) unsigned | YES  | MUL | NULL    |                |
| author_id     | int(10) unsigned | YES  | MUL | NULL    |                |
| type          | int(11)          | NO   | MUL | NULL    |                |
| origin        | int(11)          | YES  | MUL | NULL    |                |
| date_created  | datetime         | NO   | MUL | NULL    |                |
| date_modified | datetime         | NO   |     | NULL    |                |
| old_status    | int(10) unsigned | YES  | MUL | NULL    |                |
| new_status    | int(10) unsigned | YES  | MUL | NULL    |                |
| json_data     | varchar(1024)    | YES  |     | NULL    |                |
| log_text      | mediumtext       | YES  |     | NULL    |                |
+---------------+------------------+------+-----+---------+----------------+
11 rows in set (0.01 sec)

因此,一个字段已正确创建,但另一个字段仍然转换为 TEXT,尽管该字段完全为空且其中没有数据

我一直在谷歌搜索试图找到答案,但到目前为止我什么也没找到。

创建表语句

根据 cmets,这是 create table 语句(在我进行上述更改后,我本地数据库上的 log_text 列和 json_data 列可能与我今天早上从生产数据库中提取的原始数据不匹配):

mysql> show create table BroadcastLog\G
*************************** 1. row ***************************
       Table: BroadcastLog
Create Table: CREATE TABLE `BroadcastLog` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `broadcast_id` int(10) unsigned DEFAULT NULL,
  `author_id` int(10) unsigned DEFAULT NULL,
  `type` int(11) NOT NULL,
  `origin` int(11) DEFAULT NULL,
  `date_created` datetime NOT NULL,
  `date_modified` datetime NOT NULL,
  `old_status` int(10) unsigned DEFAULT NULL,
  `new_status` int(10) unsigned DEFAULT NULL,
  `log_text` mediumtext,
  PRIMARY KEY (`id`),
  KEY `old_status` (`old_status`),
  KEY `new_status` (`new_status`),
  KEY `broadcast_id` (`broadcast_id`),
  KEY `author_id` (`author_id`),
  KEY `log_type_and_origin` (`type`,`origin`),
  KEY `log_origin` (`origin`),
  KEY `bl_date_created` (`date_created`),
  CONSTRAINT `fk_BroadcastLog_author_id` FOREIGN KEY (`author_id`) REFERENCES `User` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `fk_BroadcastLog_broadcast_id` FOREIGN KEY (`broadcast_id`) REFERENCES `Broadcast` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=6941898 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

MySQL 版本

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.31    |
+-----------+
1 row in set (0.01 sec)

更新

我更新了 MySQL 并得到了相同的结果:

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.23    |
+-----------+
1 row in set (0.00 sec)

mysql> show fields from BroadcastLog;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| id            | int unsigned | NO   | PRI | NULL    | auto_increment |
| broadcast_id  | int unsigned | YES  | MUL | NULL    |                |
| author_id     | int unsigned | YES  | MUL | NULL    |                |
| type          | int          | NO   | MUL | NULL    |                |
| origin        | int          | YES  | MUL | NULL    |                |
| date_created  | datetime     | NO   |     | NULL    |                |
| date_modified | datetime     | NO   |     | NULL    |                |
| log_text      | text         | NO   |     | NULL    |                |
| json_data     | text         | YES  |     | NULL    |                |
| old_status    | int unsigned | YES  | MUL | NULL    |                |
| new_status    | int unsigned | YES  | MUL | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
11 rows in set (0.00 sec)

mysql> alter table BroadcastLog modify log_text varchar(2048);
Query OK, 6939076 rows affected, 1 warning (3 min 22.64 sec)
Records: 6939076  Duplicates: 0  Warnings: 1

mysql> show warnings;
+-------+------+---------------------------------------------------+
| Level | Code | Message                                           |
+-------+------+---------------------------------------------------+
| Note  | 1246 | Converting column 'log_text' from VARCHAR to TEXT |
+-------+------+---------------------------------------------------+
1 row in set (0.01 sec)

mysql> show fields from BroadcastLog;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| id            | int unsigned | NO   | PRI | NULL    | auto_increment |
| broadcast_id  | int unsigned | YES  | MUL | NULL    |                |
| author_id     | int unsigned | YES  | MUL | NULL    |                |
| type          | int          | NO   | MUL | NULL    |                |
| origin        | int          | YES  | MUL | NULL    |                |
| date_created  | datetime     | NO   |     | NULL    |                |
| date_modified | datetime     | NO   |     | NULL    |                |
| log_text      | mediumtext   | YES  |     | NULL    |                |
| json_data     | text         | YES  |     | NULL    |                |
| old_status    | int unsigned | YES  | MUL | NULL    |                |
| new_status    | int unsigned | YES  | MUL | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
11 rows in set (0.02 sec)

我会注意到我在输出中看到的一个差异,这可能有另一种解释:它现在是“6939076 行受影响”而不是“0 行受影响”。尽管我花了几个小时试图理解这种行为,但在我开始这个 SO 线程之前,我已经运行了 multiple ALTER TABLE 语句。第一次尝试更改列时,您可能只会影响行,而我只是错过了它。也有可能 MySQL 8 只是对“受影响的行”使用了不同的度量标准并且具有不同的输出。

无论如何,由于某种原因仍未转换为 VARCHAR

【问题讨论】:

  • 你能显示完整的SHOW CREATE TABLE 版本而不是DESCRIBE 的摘要吗?提示:使用\G 获得干净的输出。如果这是一个 MyISAM 表,那可能会解释很多。
  • 我用我的 CREATE TABLE 语句更新了帖子。这是一个 InnoDB 表,而不是 MyISAM。
  • 下一个逻辑问题:什么 MySQL 版本?支持 >255 个字符是“新”事物,因此如果您使用的是 5.6 或更早版本,它可能无法正常工作。
  • @tadman 再次更新 MySQL 版本。现在是 5.7.31
  • @tadman 今天早上我用 MySQL 8.0 进行了测试——仍然无法转换为 VARCHAR。没有解释为什么

标签: mysql


【解决方案1】:

经过进一步的实验和研究,我弄清楚了为什么 MySQL 不允许我转换这些字段类型。我的特定案例仅尝试创建VARCHAR(2048) 的问题是由配置不当的 MySQL 实例引起的,但问题一般 /strong> 可以适用于尝试使用默认配置创建 VARCHAR(16378) 或更大的任何人。

VARCHARTEXT 之间的主要区别在于 VARCHAR 与表中的所有其他数据存储在磁盘上的同一文件中,而 TEXT 存储在磁盘上其他地方的单独文件。这意味着当您从磁盘读取页面时,您隐式读取VARCHAR字段的数据,但TEXT字段的数据只有在您显式请求这些字段(例如,通过SELECT * 或在您的 SELECT 语句中命名该字段)。这就是VARCHARTEXT性能提升的源泉。

因为VARCHAR 存储在磁盘上的表文件中,所以如果字段适合页面文件,则只能将字段设置为VARCHAR。虽然 MySQL 的文档声称 VARCHAR 字段的最大长度为 65535,但它强制页面文件长度为 65535 字节。事实上,这甚至不是默认值:

https://dev.mysql.com/doc/refman/8.0/en/innodb-init-startup-configuration.html

对第一个系统表空间数据文件实施最小文件大小,以确保有足够的空间用于双写缓冲区页面。下表显示了每个 InnoDB 页面大小的最小文件大小。 默认 InnoDB 页面大小为 16384 (16KB)。 [强调我的]

在不修改页面大小的情况下,您不能使 VARCHAR 字段大于 16384(实际上,您不能使字段大于 16377,因为 VARCHAR 是如何存储在磁盘上的:4 个字节用于指针, 2个字节表示一个长度,1个字节布尔值声明是否为null,然后实际数据存储在页面的“可变长度”部分)

我的问题来自于我们配置的页面尺寸要小得多。

结论

如果您尝试创建 VARCHAR 字段并且 MySQL 自动将其转换为 TEXT 字段,请检查您的 InnoDB 页面大小:

SHOW VARIABLES LIKE "%page_size%";

您可能正在尝试创建一个不适合页面的字段。

很遗憾,更改页面大小并非易事。您必须创建一个全新的数据库并将数据复制过来。

【讨论】:

  • 您的表/数据库的页面大小有多大?当您总结其他列的大小时,您是否接近您的页面大小?
  • @Progman 我的数据库配置为 2 KB 页面大小。
【解决方案2】:

较小的VARCHARsTEXT 有一些不明显的优势,尤其是TINYTEXT

但是,既然您是 2K 字符,那么 VARCHAR(2048)TEXT 相比没有任何优势,除了一件事 - 如果您尝试插入超过 2048 个字符,则会抱怨。

DESCRIBE 似乎说log_textTEXT。但是SHOW CREATE TABLE(我更信任)说它是MEDIUMTEXT(限制为16MBytes)。

【讨论】:

  • 它最初是TEXT,但是当我尝试将其转换为VARCHAR(2048) 时,MySQL 自动将其转换为MEDIUMTEXT。所以DESCRIBE 现在也显示MEDIUMTEXT。就优势而言,我根据一般经验法则进行操作,即“CHAR 比 VARCHAR 快,而 VARCHAR 比 TEXT 快”,但我总是通过实验来确认我的假设(我现在正在尝试这样做)。当我用谷歌搜索它时,我读到 VARCHAR 严格来说性能更高,最多可达 65535 个字符。也就是说,您的“答案”根本没有解决我的问题 - 为什么 MySQL 不允许我更改字段类型?
  • CHAR 在非常有限的情况下使用 MyISAM 比 VARCHAR 快。也就是说,那是一个“老妇人的故事”。 VARCHAR 在少数情况下比 TEXT 快,但仅适用于较小的值。我会争辩说,那些 Google 网站关于“严格来说性能更高”是错误的。
  • 至于“为什么”改了?我不知道答案[还]。 2048 --> MEDIUM 不应该有可测量的差异。它确实失去了“最大 2048”的测试。
  • 我看到我对这个答案反复抱怨。但我坚持我所说的。我欢迎有针对性的批评。
  • 我个人对答案投了反对票,因为它没有回答任何内容。它提供了一些琐事(“VARCHAR(2048)TEXT 相比没有任何优势,除了一件事——如果您尝试插入超过 2048 个字符时会抱怨”),但没有t 解决我提出的问题:为什么 MySQL 不允许我更改字段类型?如果您的帖子没有回答问题,则更适合发表评论而不是答案。
猜你喜欢
  • 2011-02-03
  • 2016-05-15
  • 2011-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多