¿Su VARCHAR tiene la longitud correcta?
Autor: Guan Yongqiang, miembro del equipo de DBA de Aikesheng, tiene buenas habilidades de operación y mantenimiento de MySQL. Le encanta aprender nuevos conocimientos y también es un otaku al que le encanta jugar.
Autor: Li Fuqiang, miembro del equipo de Aikesheng DBA, familiarizado con MySQL, TiDB, OceanBase y otras bases de datos. Creo que si continúas haciendo bien las cosas correctas, obtendrás diferentes recompensas.
Producido por la comunidad de código abierto de Aikeson. El contenido original no se puede utilizar sin autorización. Comuníquese con el editor e indique la fuente para la reimpresión.
Este artículo tiene aproximadamente 2200 palabras y se espera que su lectura demore 8 minutos.
Descripción de fondo
Algunos clientes informaron que ampliaron la longitud de un campo de tipo VARCHAR . La primera vez puedes modificarlo rápidamente, pero la segunda vez tarda mucho en ejecutarse. Estoy confundido porque la cantidad de datos en la tabla es casi la misma. ¿Por qué es más rápido VARCHAR(20)
ajustar de a , pero lleva mucho tiempo ajustar de a ?VARCHAR(50)
VARCHAR(50)
VARCHAR(100)
Entonces reproducimos la situación y realizamos un análisis del problema.
información ambiental
Los productos y la información de versión involucrados en esta verificación son los siguientes:
producto | Versión |
---|---|
mysql | 5.7.Servidor comunitario MySQL de 25 registros (GPL) |
Banco del sistema | banco de sistemas 1.0.17 |
Recurrencia de escena
3.1 Preparación de datos
mysql> show create table test.sbtest1;
+---------+----------------------------------------+
| Table | Create Table |
+---------+----------------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+----------------------------------------+
1 row in set (0.00 sec)
mysql> select count(*) from test.sbtest1;
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (0.10 sec)
3.2 Verificación de problemas
Para simular la descripción del cliente, c
modificamos el campo, VARCHAR(20)
lo modificamos VARCHAR(50)
y luego lo modificamos VARCHAR(100)
y observamos el tiempo requerido para su ejecución. Los siguientes son los comandos de operación relevantes y los resultados de ejecución:
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(50);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+-------------------------------+
| Table | Create Table |
+---------+-------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+--------------------------------------------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(100);
Query OK, 1000000 rows affected (4.80 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+---------------------------+
| Table | Create Table |
+---------+---------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+------------------------------------------------------------------------+
1 row in set (0.00 sec)
A través de la verificación, se descubrió que el problema reaparecería de manera estable, por lo que continuamos intentando modificarlo. Finalmente, descubrimos que tomó mucho tiempo modificarlo VARCHAR(63)
, VARCHAR(64)
pero después de 64, continuamos expandiendo la longitud y descubrimos que. podría completarse rápidamente.
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(63);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(64);
Query OK, 1000000 rows affected (4.87 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+---------------+
| Table | Create Table |
+---------+---------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(65);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(66);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
3.3 Análisis de problemas
Analice la situación en la que lleva mucho tiempo VARCHAR(63)
modificarla . VARCHAR(64)
Al consultar la documentación oficial , descubrimos que VARCHAR
el tipo de carácter que puede almacenar caracteres cuando la longitud del byte es 1 es 0 ~ 255. El tipo de juego de caracteres actual es UTF8MB4. Dado que UTF8MB4 es un juego de caracteres codificado de cuatro bytes, es decir, una longitud de byte puede almacenar 63,75 (255/4) caracteres, por lo que cuando lo modificamos aVARCHAR(63)
, debemos agregar un byte para los datos. VARCHAR(64)
Para el procesamiento, es necesario completar esta expansión de longitud estableciendo una tabla temporal, por lo que lleva mucho tiempo.
Verificación extendida
4.1 Preparación de datos
mysql> show create table test_utf8.sbtest1;
+---------+----------------------------------------+
| Table | Create Table |
+---------+----------------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(20) NOT NULL DEFAULT '',
`pad` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8 |
+---------+------------------+
1 row in set (0.00 sec)
mysql> select count(*) from test_utf8.sbtest1;
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (0.10 sec)
4.2 Verificación de escena UTF8
Dado que UTF8 es un conjunto de caracteres de codificación de tres bytes, un byte puede almacenar 85 (255/3 = 85) caracteres.
La secuencia de esta modificación es: VARCHAR(20)→VARCHAR(50)→VARCHAR(85), y observe el tiempo requerido para su ejecución. Los siguientes son los comandos de operación relevantes y los resultados de la ejecución:
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(50) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(85) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table test_utf8.sbtest1;
+---------+-------------------------------+
| Table | Create Table |
+---------+-------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(85) DEFAULT NULL,
`pad` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8 |
+---------+--------------------------------------------------+
1 row in set (0.00 sec)
Secuencia de modificación: VARCHAR (85) → VARCHAR (86) → VARCHAR (100). En este momento, observaremos que la declaración SQL ejecutada devuelve directamente un error. Entonces eliminamos algorithm=inplace ,lock=none
estos dos parámetros, lo que permite que este SQL cree una tabla temporal y bloquee la tabla de destino, y luego vuelva a ejecutar el SQL. Los siguientes son los comandos de operación relevantes y los resultados de la ejecución:
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(86) ,algorithm=inplace,lock=none;
ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY.
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(86);
Query OK, 1000000 rows affected (4.94 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
mysql> show create table test_utf8.sbtest1;
+---------+-------------------------------+
| Table | Create Table |
+---------+-------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(86) DEFAULT NULL,
`pad` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8 |
+---------+--------------------------------------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(100) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
4.3 Verificación del escenario UTF8MB4
Dado que UTF8MB4 es un conjunto de caracteres de codificación de cuatro bytes, un byte puede almacenar 63 (255/4 = 63,75) caracteres.
La secuencia de esta modificación es: VARCHAR(20)→VARCHAR(50)→VARCHAR(63), y observe el tiempo requerido para su ejecución. Los siguientes son los comandos de operación relevantes y los resultados de la ejecución:
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(50) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(63) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+-------------------------+
| Table | Create Table |
+---------+-------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(63) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+-------------------------------------------------------------------------+
1 row in set (0.00 sec)
La secuencia de esta modificación es: VARCHAR (63) → VARCHAR (64) → VARCHAR (100). En este momento, observaremos que la declaración SQL ejecutada devuelve directamente un error. Entonces eliminamos algorithm=inplace, lock=none
estos dos parámetros, lo que permite que este SQL cree una tabla temporal y bloquee la tabla de destino, y luego vuelva a ejecutar el SQL. Los siguientes son los comandos de operación relevantes y los resultados de la ejecución:
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(64) ,algorithm=inplace,lock=none;
ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY.
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(64) ;
Query OK, 1000000 rows affected (4.93 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+--------------------------+
| Table | Create Table |
+---------+--------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+-------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(100) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
4.4 Análisis comparativo
Modificación de la longitud de los caracteres | UTF8 (MB3) | UTF8MB4 |
---|---|---|
20->50 | ddl en línea (in situ) | ddl en línea (in situ) |
50->100 | ddl en línea (copia) | ddl en línea (copia) |
X->Y | Cuando Y*3<256, en el lugar <br> Cuando X*3>=256, en el lugar | Cuando Y*4<256, en el lugar <br> Cuando X*4>=256, en el lugar |
Observación | Un carácter ocupa un máximo de 3 bytes | Un carácter ocupa un máximo de 4 bytes |
en conclusión
Cuando la longitud máxima en bytes de un campo es >= 256 caracteres, se necesitan 2 bytes para representar la longitud del campo.
Ejemplo usando UTF8MB4:
- Para la longitud máxima de bytes del campo que varía dentro de los 256 caracteres (es decir, x*4<256 e Y*4<256), el ddl en línea utiliza el modo in situ, que es muy eficiente.
- Para los campos cuya longitud máxima en bytes varía más allá de los 256 caracteres (es decir, x*4>=256 e Y*4>=256), el ddl en línea utiliza el modo in situ, que es muy eficiente.
- De lo contrario, el ddl en línea utiliza el modo de copia, lo cual es ineficaz.
- Lo mismo ocurre con UTF8 (MB3).
sugerencia
Para evitar una expansión posterior de la longitud del campo, ddl en línea adopta el modo de copia ineficiente. Se recomienda:
- Para el tipo de carácter UTF8(MB3):
- El número de caracteres es inferior a 50. Se recomienda establecer la longitud de los caracteres en VARCHAR(50) o menos.
- El número de caracteres es cercano a 84 (256/3 = 83,33). Se recomienda configurarlo en varchar (84) o una longitud de caracteres mayor.
- Para el tipo de carácter UTF8MB4:
- Si el número de caracteres es inferior a 50, se recomienda establecerlo en VARCHAR(50), o una longitud de caracteres menor.
- El número de caracteres es cercano a 64 (256/4 = 64). Se recomienda configurarlo en VARCHAR (64) o una longitud de caracteres mayor.
Los resultados de esta verificación son solo como referencia. Si necesita operar en un entorno de producción, defina razonablemente la longitud de VARCHAR en función de la situación real para evitar pérdidas económicas.
Para obtener más artículos técnicos, visite: https://opensource.actionsky.com/
Acerca de SQLE
SQLE es una plataforma integral de gestión de calidad de SQL que cubre la auditoría y gestión de SQL desde los entornos de desarrollo hasta los de producción. Admite bases de datos nacionales, comerciales y de código abierto convencionales, proporciona capacidades de automatización de procesos para el desarrollo, operación y mantenimiento, mejora la eficiencia en línea y mejora la calidad de los datos.
obtener SQLE
tipo | DIRECCIÓN |
---|---|
Repositorio | https://github.com/actiontech/sqle |
documento | https://actiontech.github.io/sqle-docs/ |
noticias de lanzamiento | https://github.com/actiontech/sqle/releases |
Documentación de desarrollo del complemento de auditoría de datos | https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse |