En el entorno de producción, a menudo hay algunas conversiones de tipos implícitas que hacen que los índices SQL fallen y funcionen extremadamente mal, lo que a su vez afecta la carga del clúster y el negocio. Este artículo resume escenarios comunes de conversión implícita. Intente evitar la conversión implícita de SQL en producción.
Autor: Zhang Luodan es un apasionado de la tecnología de bases de datos y explora constantemente. ¡Espera escribir artículos más detallados y generar contenido más valioso en el futuro!
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 3000 palabras y se espera que demore 10 minutos en leerlo.
Los escenarios comunes en los que SQL genera conversiones implícitas incluyen:
- Conversión implícita de tipos de datos.
- Conversión implícita de juegos de caracteres.
Entre ellos, la conversión de juegos de caracteres, especialmente en escenarios de conexión de tablas y procedimientos almacenados, se pasa por alto fácilmente.
Nota: El juego de caracteres es la regla de codificación para datos de tipo de caracteres. Para los tipos numéricos, no es necesario convertir el juego de caracteres.
Conversión implícita de tipos de datos.
Estructura de la tabla de prueba
t1
Los campos de la tabla a
son de tipo VARCHAR y t2
los campos de la tabla a
son de tipo INT.
mysql> show create database test1\G
*************************** 1. row ***************************
Database: test1
Create Database: CREATE DATABASE `test1` /*!40100 DEFAULT CHARACTER SET utf8 */
1 row in set (0.00 sec)
mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL,
`a` varchar(20) DEFAULT NULL,
`b` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> show create table t2\G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`id` int(11) NOT NULL,
`a` int(11) DEFAULT NULL,
`b` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
Ejemplo de tabla única
Lo que hay que tener en cuenta aquí es que existen los siguientes dos tipos de conversiones:
- Cuando el tipo de campo es un tipo de cadena y el parámetro es un número entero, el índice fallará.
- Cuando el tipo de campo es un número entero y el parámetro entrante es un tipo de cadena, no provocará que el índice falle.
Esto se debe a que al comparar cadenas con números, MySQL convertirá el tipo de cadena en un número para comparar. Por lo tanto, cuando el tipo de campo es una cadena, se agregará una función al campo, lo que provocará que el índice falle.
La documentación oficial explica : Las cadenas se convierten automáticamente en números y los números en cadenas según sea necesario.
-- 字段类型为varchar,传参为整数,无法走到索引
mysql> explain select * from t1 where a=1000;
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | ALL | a | NULL | NULL | NULL | 498892 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)
mysql> show warnings;
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'a' due to type or collation conversion on field 'a' |
| Warning | 1739 | Cannot use range access on index 'a' due to type or collation conversion on field 'a' |
| Note | 1003 | /* select#1 */ select `test1`.`t1`.`id` AS `id`,`test1`.`t1`.`a` AS `a`,`test1`.`t1`.`b` AS `b` from `test1`.`t1` where (`test1`.`t1`.`a` = 1000) |
+---------+------+---------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)
-- 字段类型为int,传参为字符串,可以走到索引
mysql> explain select * from t2 where a='1000';
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t2 | NULL | ref | a | a | 5 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
¿En cuanto a por qué no se pueden convertir números en cadenas para comparar?
Resultados de la comparación a continuación:
- La comparación de cadenas consiste en comparar el tamaño de las cadenas una por una hasta que se encuentran diferentes caracteres. Los resultados de la comparación de esta comparación son diferentes de los resultados de la comparación de números.
mysql> select '2000' <'250';
+---------------+
| '2000' <'250' |
+---------------+
| 1 |
+---------------+
1 row in set (0.00 sec)
Conversión de tipos de datos en combinaciones de tablas
Cuando los tipos de campos de conexión de las dos tablas son inconsistentes, se producirá una conversión implícita ( cast()
se agrega la función interna de MySQL), no se puede alcanzar el índice del campo de conexión y no se puede utilizar el orden óptimo de conexión de la tabla.
La tabla que originalmente era una tabla controlada se puede usar como tabla controladora porque no se puede usar el índice.
Ejemplo:
- De la siguiente manera, en circunstancias normales,
t2
la tabla se seleccionará como tabla controladora, pero debido a los diferentes tipos de datos, el SQL real ejecutado es:select * from t1 join t2 on cast(t1.a as unsigned)=t2.a where t2.id<1000
- Si
t1
se usa como tabla controlada, no hay forma de ir alt1.a
índice de, por lo quet1
la tabla se selecciona como tabla controladora.
mysql> explain select * from t1 join t2 on t1.a=t2.a where t2.id<1000;
+----+-------------+-------+------------+------+---------------+------+---------+------------+--------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------------+--------+----------+-----------------------+
| 1 | SIMPLE | t1 | NULL | ALL | a | NULL | NULL | NULL | 498892 | 100.00 | Using where |
| 1 | SIMPLE | t2 | NULL | ref | PRIMARY,a | a | 5 | test1.t1.a | 1 | 5.00 | Using index condition |
+----+-------------+-------+------------+------+---------------+------+---------+------------+--------+----------+-----------------------+
2 rows in set, 2 warnings (0.00 sec)
mysql> show warnings;
+---------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'a' due to type or collation conversion on field 'a' |
| Note | 1003 | /* select#1 */ select `test1`.`t1`.`id` AS `id`,`test1`.`t1`.`a` AS `a`,`test1`.`t1`.`b` AS `b`,`test1`.`t2`.`id` AS `id`,`test1`.`t2`.`a` AS `a`,`test1`.`t2`.`b` AS `b` from `test1`.`t1` join `test1`.`t2` where ((`test1`.`t2`.`id` < 1000) and (`test1`.`t1`.`a` = `test1`.`t2`.`a`)) |
+---------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.01 sec)
Conversión implícita de juegos de caracteres.
Cuando el juego de caracteres del parámetro y el juego de caracteres del campo son diferentes, no se puede realizar una comparación directa y se requiere la conversión del juego de caracteres. Es posible que deba agregar convert()
una función al campo de conversión para convertir el juego de caracteres, lo que resulta en una falla del índice.
Estructura de la tabla de prueba
- El juego de caracteres de la base de datos es UTF8MB4
t1
El juego de caracteres de la tabla es UTF8t2
El juego de caracteres de la tabla es UTF8MB4.
mysql> show create database test\G
*************************** 1. row ***************************
Database: test
Create Database: CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET utf8mb4 */
mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL,
`a` varchar(20) DEFAULT NULL,
`b` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> show create table t2\G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`id` int(11) NOT NULL,
`a` varchar(20) DEFAULT NULL,
`b` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.01 sec)
Ejemplo de tabla única
-- 正常执行时,匹配字段的字符集(没有单独指定时继承表的字符集)
mysql> explain select * from t1 where a='1000';
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t1 | NULL | ref | a | a | 63 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
-- 将参数转换不同的字符集,无法走到索引,而是全表扫描
mysql> explain select * from t1 where a=convert('1000' using utf8mb4);
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 2000 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-- show warnings可以看到优化器进行了转换,在t1.a上加了convert函数,从而无法走到索引
mysql> show warnings;
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `test`.`t1`.`id` AS `id`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where (convert(`test`.`t1`.`a` using utf8mb4) = <cache>(convert('1000' using utf8mb4))) |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
Además, cabe señalar que:
MySQL dará prioridad internamente a la conversión de conjuntos de caracteres de bajo nivel a conjuntos de caracteres de nivel superior, como la conversión de UTF8 a UTF8MB4.
En el ejemplo anterior, convert()
la función se agregó t1.a
a , pero en el siguiente ejemplo, convert()
la función se agregó al parámetro en lugar del t2.a
campo. Esta situación no resultó en un rendimiento deficiente:
mysql> show create table t2\G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`id` int(11) NOT NULL,
`a` varchar(20) DEFAULT NULL,
`b` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
mysql> explain select * from t2 where a=convert('1000' using utf8);
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t2 | NULL | ref | a | a | 83 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> show warnings;
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `test`.`t2`.`id` AS `id`,`test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t2` where (`test`.`t2`.`a` = convert(convert('1000' using utf8) using utf8mb4)) |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
Para resumir:
- Cuando el juego de caracteres del campo de la tabla es un juego de caracteres de nivel inferior (como UTF8) y el valor entrante es un juego de caracteres de nivel superior (como UTF8MB4), el juego de caracteres del campo de la tabla se convertirá en este momento. lo que equivale a utilizar la Función, índice no válido.
- Cuando el campo de la tabla es un juego de caracteres de nivel superior (como UTF8MB4) y el valor entrante es un juego de caracteres de nivel inferior (como UTF8), el valor entrante se convertirá en el juego de caracteres y no provocará errores en el índice. .
Sin embargo, normalmente no utilizamos convert()
funciones para convertir manualmente el conjunto de caracteres de los parámetros. En los dos escenarios siguientes, puede haber conversiones de tipos implícitas que son fáciles de ignorar, lo que provoca problemas de producción.
Conversión de juegos de caracteres en uniones de tablas
Cuando los conjuntos de caracteres de los campos de conexión de las dos tablas son inconsistentes, se producirá una conversión implícita ( convert()
función agregada dentro de MySQL), no se puede alcanzar el índice del campo de conexión y no se puede utilizar la secuencia óptima de conexión de la tabla.
La tabla que originalmente era una tabla controlada se puede usar como tabla controladora porque no se puede usar el índice.
Ejemplo:
- En circunstancias normales, MySQL dará prioridad a las tablas con conjuntos de resultados pequeños como tablas de control. En este ejemplo, son
t2
la tabla de control yt1
la tabla de control. - Sin embargo, debido a los diferentes juegos de caracteres, el SQL ejecutado real es como
show warnings
se muestra en la figura. Sit1.a
se agrega una función al campoconvert()
para convertir el juego de caracteres,t1.a
no se puede alcanzar el índice del campo y se debe cambiar el orden de conexión. .
mysql> explain select * from t1 left join t2 on t1.a=t2.a where t2.id<1000;
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-----------------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 498649 | 100.00 | NULL |
| 1 | SIMPLE | t2 | NULL | ref | PRIMARY,a | a | 83 | func | 1 | 4.79 | Using index condition |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-----------------------+
2 rows in set, 1 warning (0.00 sec)
mysql> show warnings;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `test`.`t1`.`id` AS `id`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t2`.`id` AS `id`,`test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` join `test`.`t2` where ((`test`.`t2`.`id` < 1000) and (convert(`test`.`t1`.`a` using utf8mb4) = `test`.`t2`.`a`)) |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
-- 在下面示例中,虽然也发生了类型转换,但是效率并没有变差,因为原本最优的连接顺序就是t1作为驱动表
mysql> explain select * from t1 left join t2 on t1.a=t2.a where t1.id<1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 999 | 100.00 | Using where |
| 1 | SIMPLE | t2 | NULL | ref | a | a | 83 | func | 1 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
mysql> show warnings;
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `test`.`t1`.`id` AS `id`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t2`.`id` AS `id`,`test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` left join `test`.`t2` on((convert(`test`.`t1`.`a` using utf8mb4) = `test`.`t2`.`a`)) where (`test`.`t1`.`id` < 1000) |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
Conversión de juegos de caracteres en procedimientos almacenados
Este también es un escenario que es relativamente fácil de ignorar. El problema se descubrió cuando la clave principal se actualizó durante el proceso de almacenamiento en el entorno de producción, pero tardó más de 10 segundos en ejecutarse.
El juego de caracteres de las variables en el procedimiento almacenado se hereda del database
juego de caracteres de (también se puede especificar al crear). Cuando el juego de caracteres del campo de la tabla database
es diferente del juego de caracteres de (), la conversión de tipo de juego de caracteres implícita es similar a la anterior. ocurrirá uno.
Ejemplo:
database
El juego de caracteres es UTF8MB4.character_set_client
y son los valores de sesión ycollation_connection
al crear el procedimiento almacenadocharacter_set_client
collation_connection
- Se ha probado que el juego de caracteres de las variables en el procedimiento almacenado es consistente con el juego de caracteres a nivel de base de datos.
-- 存储过程信息: Database Collation: utf8mb4_general_ci
mysql> show create procedure update_data\G
*************************** 1. row ***************************
Procedure: update_data
sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
Create Procedure: CREATE DEFINER=`root`@`%` PROCEDURE `update_data`()
begin
declare j int;
declare n varchar(100);
select charset(n);
set j=1;
while(j<=2000)do
set n = cast(j as char);
select 1,now();
update t1 set b=concat(b,'1') where a=n;
select 2,now();
select sleep(1);
set j=j+1;
end while;
end
character_set_client: utf8mb4
collation_connection: utf8mb4_general_ci
Database Collation: utf8mb4_general_ci
1 row in set (0.00 sec)
如下,在执行存储过程后,看到打印的变量n的字符集是utf8mb4
mysql> call update_data();
+------------+
| charset(n) |
+------------+
| utf8mb4 |
+------------+
1 row in set (0.00 sec)
La declaración actualizada según el campo de índice a
en realidad se convierte en la siguiente, utilizando un escaneo completo de la tabla (tipo: índice, clave: principal).
mysql> explain update t1 set b=concat(b,'1') where a=convert('1000' using utf8mb4);
+----+-------------+-------+------------+-------+---------------+---------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+--------+----------+-------------+
| 1 | UPDATE | t1 | NULL | index | NULL | PRIMARY | 4 | NULL | 498649 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+--------+----------+-------------+
1 row in set (0.00 sec)
-- 而正常情况下,执行计划为:
mysql> explain update t1 set b=concat(b,'1') where a='1000';
+----+-------------+-------+------------+-------+---------------+------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+-------+------+----------+-------------+
| 1 | UPDATE | t1 | NULL | range | a | a | 63 | const | 1 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+------+---------+-------+------+----------+-------------+
1 row in set (0.00 sec)
El tiempo de actualización también cambió de 0,00 segundos a 0,60 segundos . Cuando la cantidad de datos de la tabla es grande, un escaneo completo de la tabla tendrá un mayor impacto en la producción.
mysql> update t1 set b=concat(b,'1') where a='1000';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update t1 set b=concat(b,'1') where a=convert('1000' using utf8mb4);
Query OK, 1 row affected (0.60 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Cómo evitar conversiones implícitas
Para conversión implícita de tipos de datos:
- Selección de tipo de datos estándar
- Coincidencia del tipo de datos del campo participante del pase SQL
Para la conversión implícita de juegos de caracteres: el juego de caracteres del cliente, el juego de caracteres del lado del servidor, el juego de caracteres de la base de datos, el juego de caracteres de la tabla y el juego de caracteres del campo permanecen consistentes.
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 |