Dans l'environnement de production, certaines conversions de type implicites entraînent souvent l'échec et les performances extrêmement médiocres des index SQL, ce qui affecte à son tour la charge et l'activité du cluster. Cet article résume les scénarios courants de conversion implicite. Essayez d’éviter la conversion implicite SQL en production.
Auteur : Zhang Luodan est passionné par la technologie des bases de données et explore constamment. Il espère écrire des articles plus approfondis et produire un contenu plus précieux à l'avenir !
Produit par la communauté open source Aikeson. Le contenu original ne peut être utilisé sans autorisation. Veuillez contacter l'éditeur et indiquer la source pour la réimpression.
Cet article compte environ 3 000 mots et sa lecture devrait prendre 10 minutes.
Les scénarios courants dans lesquels SQL génère des conversions implicites incluent :
- Conversion implicite des types de données
- Conversion implicite des jeux de caractères
Parmi eux, la conversion des jeux de caractères, en particulier dans les scénarios de connexion de tables et les procédures stockées, est facilement négligée.
Remarque : Le jeu de caractères est la règle de codage pour les données de type caractère. Pour les types numériques, il n'est pas nécessaire de convertir le jeu de caractères.
Conversion implicite des types de données
Structure de la table de test
t1
Les champs de la table a
sont de type VARCHAR et t2
les champs de la table a
sont de type 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)
Exemple de table unique
Ce qu’il faut noter ici, c’est qu’il existe les deux types de conversions suivants :
- Lorsque le type de champ est un type chaîne et que le paramètre est un entier, l'index échoue.
- Lorsque le type de champ est un entier et que le paramètre entrant est un type de chaîne, cela n’entraînera pas l’échec de l’index.
En effet, lors de la comparaison de chaînes avec des nombres, MySQL convertira le type de chaîne en nombre à des fins de comparaison. Par conséquent, lorsque le type de champ est une chaîne, une fonction sera ajoutée au champ, provoquant l'échec de l'index.
La documentation officielle explique : Les chaînes sont automatiquement converties en nombres et les nombres en chaînes si nécessaire.
-- 字段类型为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)
Quant à savoir pourquoi vous ne pouvez pas convertir de nombres en chaînes à des fins de comparaison ?
Résultats de comparaison ci-dessous :
- La comparaison des chaînes consiste à comparer la taille des chaînes une par une jusqu'à ce que différents caractères soient trouvés. Les résultats de cette comparaison sont différents des résultats de la comparaison des nombres.
mysql> select '2000' <'250';
+---------------+
| '2000' <'250' |
+---------------+
| 1 |
+---------------+
1 row in set (0.00 sec)
Conversion de type de données dans les jointures de tables
Lorsque les types de champs de connexion des deux tables sont incohérents, cela entraînera une conversion implicite ( cast()
une fonction interne MySQL est ajoutée), et l'index du champ de connexion ne peut pas être atteint et l'ordre optimal de connexion de la table peut ne pas être utilisé.
La table qui était à l'origine une table pilotée peut être utilisée comme table pilotante car l'index ne peut pas être utilisé.
Exemple:
- Comme suit, dans des circonstances normales,
t2
la table sera sélectionnée comme table de pilotage, mais en raison des différents types de données, le SQL réellement exécuté est :select * from t1 join t2 on cast(t1.a as unsigned)=t2.a where t2.id<1000
- Si
t1
elle est utilisée comme table pilotée, il n'y a aucun moyen d'accéder àt1.a
l'index de , donct1
la table est sélectionnée comme table pilote
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)
Conversion implicite des jeux de caractères
Lorsque le jeu de caractères du paramètre et le jeu de caractères du champ sont différents, une comparaison directe ne peut pas être effectuée et une conversion du jeu de caractères est requise. Vous devrez peut-être ajouter convert()
une fonction au champ de conversion pour convertir le jeu de caractères, ce qui entraînera un échec de l'index.
Structure de la table de test
- Le jeu de caractères de la base de données est UTF8MB4
t1
Le jeu de caractères du tableau est UTF8t2
Le jeu de caractères du tableau est 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)
Exemple de table unique
-- 正常执行时,匹配字段的字符集(没有单独指定时继承表的字符集)
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)
De plus, il convient de noter que :
MySQL donnera en interne la priorité à la conversion des jeux de caractères de bas niveau en jeux de caractères de niveau supérieur, comme la conversion d'UTF8 en UTF8MB4.
Dans l'exemple précédent, convert()
la fonction a été ajoutée t1.a
à , mais dans l'exemple suivant, convert()
la fonction a été ajoutée au paramètre au lieu du t2.a
champ. Cette situation n'a pas entraîné de mauvaises performances :
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)
Pour résumer:
- Lorsque le jeu de caractères du champ de table est un jeu de caractères de niveau inférieur (tel que UTF8) et que la valeur entrante est un jeu de caractères de niveau supérieur (tel que UTF8MB4), le jeu de caractères du champ de table sera converti à ce moment-là, ce qui équivaut à utiliser la fonction Function, index invalide.
- Lorsque le champ de la table est un jeu de caractères de niveau supérieur (tel que UTF8MB4) et que la valeur entrante est un jeu de caractères de niveau inférieur (tel que UTF8), la valeur entrante sera convertie en jeu de caractères et n'entraînera pas d'échec de l'index. .
Cependant, nous n'utilisons généralement pas convert()
la fonction pour convertir manuellement le jeu de caractères des paramètres. Dans les deux scénarios suivants, il peut y avoir des conversions de type implicites faciles à ignorer, provoquant des problèmes de production.
Conversion du jeu de caractères dans les jointures de tables
Lorsque les jeux de caractères des champs de connexion des deux tables sont incohérents, cela entraînera une conversion implicite ( convert()
fonction ajoutée dans MySQL), et l'index du champ de connexion ne pourra pas être atteint et la séquence de connexion de table optimale ne pourra pas être utilisée.
La table qui était à l'origine une table pilotée peut être utilisée comme table pilotante car l'index ne peut pas être utilisé.
Exemple:
- Dans des circonstances normales, MySQL donnera la priorité aux tables avec de petits ensembles de résultats comme tables pilotes. Dans cet exemple, il s'agit
t2
de la table pilote ett1
de la table pilotée. - Cependant, en raison des différents jeux de caractères, le SQL réellement exécuté est tel
show warnings
qu'indiqué sur la figure. Si une fonctiont1.a
est ajoutée au champconvert()
pour convertir le jeu de caractères,t1.a
l'index du champ ne peut pas être atteint et l'ordre de connexion doit être modifié. .
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)
Conversion de jeux de caractères dans les procédures stockées
Il s'agit également d'un scénario relativement facile à ignorer. Le problème a été découvert lors de la mise à jour de la clé primaire pendant le processus de stockage dans l'environnement de production, mais son exécution a pris plus de 10 s.
Le jeu de caractères des variables dans la procédure stockée est hérité du database
jeu de caractères de (peut également être spécifié lors de la création). Lorsque le jeu de caractères du champ de table database
est différent du jeu de caractères de (), conversion implicite du type de jeu de caractères similaire à la précédente. il y en aura un.
Exemple:
database
Le jeu de caractères est UTF8MB4character_set_client
et sont les valeurs de session etcollation_connection
lors de la création de la procédure stockéecharacter_set_client
collation_connection
- Il a été testé que le jeu de caractères des variables dans la procédure stockée est cohérent avec le jeu de caractères au niveau de la base de données.
-- 存储过程信息: 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)
L'instruction mise à jour en fonction du champ d'index a
devient en réalité la suivante, en utilisant une analyse complète de la table (type : index, clé : primaire).
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)
Le temps de mise à jour est également passé de 0,00 s à 0,60 s . Lorsque la quantité de données de table est importante, une analyse complète de la table aura un plus grand impact sur la production.
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
Comment éviter les conversions implicites
Pour la conversion implicite des types de données :
- Sélection du type de données standard
- Correspondance du type de données du champ participant de passe SQL
Pour la conversion implicite des jeux de caractères : le jeu de caractères client, le jeu de caractères côté serveur, le jeu de caractères de base de données, le jeu de caractères de table et le jeu de caractères de champ restent cohérents.
Pour des articles plus techniques, veuillez visiter : https://opensource.actionsky.com/
À propos de SQLE
SQLE est une plateforme complète de gestion de la qualité SQL qui couvre l'audit et la gestion SQL, du développement aux environnements de production. Il prend en charge les bases de données open source, commerciales et nationales grand public, fournit des capacités d'automatisation des processus pour le développement, l'exploitation et la maintenance, améliore l'efficacité en ligne et améliore la qualité des données.
SQLE obtenir
taper | adresse |
---|---|
Dépôt | https://github.com/actiontech/sqle |
document | https://actiontech.github.io/sqle-docs/ |
publier des nouvelles | https://github.com/actiontech/sqle/releases |
Documentation de développement du plug-in d'audit des données | https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse |