Maison > Article > base de données > Une introduction à la façon d'optimiser l'insertion par lots de données dans MYSQL
J'ai également vu plusieurs autres méthodes sur Internet, telles que le prétraitement SQL et la soumission par lots. Alors, comment fonctionnent ces méthodes ? Cet article fera une comparaison de ces méthodes
1. Quels problèmes avons-nous rencontrés
En SQL standard, nous écrivons généralement l'instruction d'insertion SQL suivante.
INSERT INTO TBL_TEST (id) VALUES(1);
Évidemment, cette méthode est également réalisable dans MYSQL. Mais lorsque nous devons insérer des données par lots, de telles instructions entraîneront des problèmes de performances. Par exemple, si vous devez insérer 100 000 éléments de données, vous avez besoin de 100 000 instructions d'insertion. Chaque instruction doit être soumise au moteur relationnel pour analyse et optimisation avant de pouvoir atteindre le moteur de stockage pour effectuer le travail d'insertion proprement dit.
C'est précisément en raison du problème de goulot d'étranglement des performances que la documentation officielle MYSQL mentionne également l'utilisation de l'insertion par lots, c'est-à-dire l'insertion de plusieurs valeursdans une instruction INSERT. Autrement dit,
INSERT INTO TBL_TEST (id) VALUES (1), (2), (3)
Cette approche peut en effet accélérer l'insertion par lots. La raison n'est pas difficile à comprendre puisqu'il y a moins d'instructions INSERT soumises au serveur. la charge du réseau est réduite. Le plus important est que le temps d'analyse et d'optimisation semble augmenter, mais en fait, davantage de lignes de données sont utilisées. Les performances globales sont donc améliorées. Selon certains avis sur Internet, cette méthode peut être améliorée des dizaines de fois.
Cependant, j'ai également vu plusieurs autres méthodes sur Internet, telles que le prétraitement SQL et la soumission par lots. Alors, comment fonctionnent ces méthodes ? Cet article fera une comparaison de ces méthodes.
2. Comparaison des environnements et des méthodes
Mon environnement est relativement difficile, essentiellement une machine virtuelle rétrograde. Il n'y a que 2 cœurs et 6 Go de mémoire. Le système d'exploitation est SUSI Linux et la version MYSQL est 5.6.15.
Comme vous pouvez l'imaginer, les performances de cette machine ont rendu mon TPS très faible, donc toutes les données ci-dessous n'ont aucun sens, mais la tendance est différente, ce qui peut montrer la tendance des performances de l'ensemble de l'insertion.
En raison des caractéristiques commerciales, la table que nous utilisons est très grande, avec un total de 195 champs, et lorsqu'elle est pleine (chaque champ est rempli, y compris varchar), la taille sera légèrement inférieure à 4 Ko , et d'une manière générale, la taille d'un enregistrement est également de 3 Ko.
Car, sur la base de notre expérience réelle, nous sommes presque sûrs que les performances peuvent être considérablement améliorées en soumettant un grand nombre d'instructions INSERT en une seule transaction. Par conséquent, tous les tests ci-dessous sont basés sur la pratique consistant à soumettre tous les 5 000 enregistrements insérés.
Enfin, il convient de noter que tous les tests ci-dessous sont réalisés à l'aide de l'API MYSQL C et utilisent le moteur de stockage INNODB.
3. Comparaison des méthodes
Test du type idéal (1) - Comparaison des méthodes
Objectif : découvrir ce qui est le plus approprié dans des circonstances idéales Mécanisme d'insertion
Méthodes clés :
1. Chaque fil/fil est inséré dans l'ordre de la clé primaire
2. Comparez les différentes méthodes d'insertion
3. Comparez l'impact de différents nombres d'entrées/threads sur l'insertion
* La « méthode ordinaire » fait référence à la situation où un INSERT n'insère qu'une seule VALEUR.
* « SQL prétraité » fait référence à l'utilisation de l'API MYSQL C prétraitée.
* "Valeurs de table multiples SQL (10 enregistrements)" est une situation dans laquelle 10 enregistrements sont insérés à l'aide d'une instruction INSERT. Pourquoi 10 ? Une vérification ultérieure nous indique que celui-ci présente les performances les plus élevées.
Conclusion, évidemment, à en juger par les tendances des trois méthodes, la méthode SQL à valeurs multi-tables (10 éléments) est la plus efficace.
Test idéal (2) - Comparaison du nombre d'entrées SQL avec plusieurs valeurs de table
Évidemment, comme la quantité de les données augmentent. Dans ce cas, il est plus efficace d'insérer 10 enregistrements pour chaque instruction INSERT.
Test idéal (3) - comparaison des numéros de connexion
Conclusion : La performance est le plus élevé lors de la connexion et du fonctionnement avec 2 fois le nombre de cœurs CPU
Test général - Test basé sur notre volume d'activité
Objectif : le meilleur mécanisme d'insertion est-il adapté aux situations de transaction ordinaires ?
Méthodes clés :
1. Simuler les données de production (chaque enregistrement fait environ 3 Ko)
2. commander
Évidemment, si l'insertion est effectuée dans le désordre en fonction de la clé primaire, les performances chuteront. Ceci est en fait cohérent avec le phénomène montré dans le principe de mise en œuvre interne d'INNODB. Mais il reste certain que le cas du SQL à valeurs multi-tables (10 entrées) est optimal.
Stress Test
Objectif : Meilleur mécanisme d'insertion pour les situations de trading extrêmes ?
Méthodes clés :
1. 将数据行的每一个字段填满(每条记录约为4KB)
2. 每个线程主键乱序插入
结果和我们之前的规律类似,性能出现了极端下降。并且这里验证了随着记录的增大(可能已经超过了一个page的大小,毕竟还有slot和page head信息占据空间),会有page split等现象,性能会下降。
四、结论
根据上面的测试,以及我们对INNODB的了解,我们可以得到如下的结论。
•采用顺序主键策略(例如自增主键,或者修改业务逻辑,让插入的记录尽可能顺序主键)
•采用多值表(10条)插入方式最为合适
•将进程/线程数控制在2倍CPU数目相对合适
五、附录
我发现网上很少有完整的针对MYSQL 预处理SQL语句的例子。这里给出一个简单的例子。
--建表语句 CREATE TABLE tbl_test ( pri_key varchar(30), nor_char char(30), max_num DECIMAL(8,0), long_num DECIMAL(12, 0), rec_upd_ts TIMESTAMP );
c代码
#include <string.h> #include <iostream> #include <mysql.h> #include <sys/time.h> #include <sstream> #include <vector> using namespace std; #define STRING_LEN 30 char pri_key [STRING_LEN]= "123456"; char nor_char [STRING_LEN]= "abcabc"; char rec_upd_ts [STRING_LEN]= "NOW()"; bool SubTimeval(timeval &result, timeval &begin, timeval &end) { if ( begin.tv_sec>end.tv_sec ) return false; if ( (begin.tv_sec == end.tv_sec) && (begin.tv_usec > end.tv_usec) ) return false; result.tv_sec = ( end.tv_sec - begin.tv_sec ); result.tv_usec = ( end.tv_usec - begin.tv_usec ); if (result.tv_usec<0) { result.tv_sec--; result.tv_usec+=1000000;} return true; } int main(int argc, char ** argv) { INT32 ret = 0; char errmsg[200] = {0}; int sqlCode = 0; timeval tBegin, tEnd, tDiff; const char* precompile_statment2 = "INSERT INTO `tbl_test`( pri_key, nor_char, max_num, long_num, rec_upd_ts) VALUES(?, ?, ?, ?, ?)"; MYSQL conn; mysql_init(&conn); if (mysql_real_connect(&conn, "127.0.0.1", "dba", "abcdefg", "TESTDB", 3306, NULL, 0) == NULL) { fprintf(stderr, " mysql_real_connect, 2 failed\n"); exit(0); } MYSQL_STMT *stmt = mysql_stmt_init(&conn); if (!stmt) { fprintf(stderr, " mysql_stmt_init, 2 failed\n"); fprintf(stderr, " %s\n", mysql_stmt_error(stmt)); exit(0); } if (mysql_stmt_prepare(stmt, precompile_statment2, strlen(precompile_statment2))) { fprintf(stderr, " mysql_stmt_prepare, 2 failed\n"); fprintf(stderr, " %s\n", mysql_stmt_error(stmt)); exit(0); } int i = 0; int max_num = 3; const int FIELD_NUM = 5; while (i < max_num) { //MYSQL_BIND bind[196] = {0}; MYSQL_BIND bind[FIELD_NUM]; memset(bind, 0, FIELD_NUM * sizeof(MYSQL_BIND)); unsigned long str_length = strlen(pri_key); bind[0].buffer_type = MYSQL_TYPE_STRING; bind[0].buffer = (char *)pri_key; bind[0].buffer_length = STRING_LEN; bind[0].is_null = 0; bind[0].length = &str_length; unsigned long str_length_nor = strlen(nor_char); bind[1].buffer_type = MYSQL_TYPE_STRING; bind[1].buffer = (char *)nor_char; bind[1].buffer_length = STRING_LEN; bind[1].is_null = 0; bind[1].length = &str_length_nor; bind[2].buffer_type = MYSQL_TYPE_LONG; bind[2].buffer = (char*)&max_num; bind[2].is_null = 0; bind[2].length = 0; bind[3].buffer_type = MYSQL_TYPE_LONG; bind[3].buffer = (char*)&max_num; bind[3].is_null = 0; bind[3].length = 0; MYSQL_TIME ts; ts.year= 2002; ts.month= 02; ts.day= 03; ts.hour= 10; ts.minute= 45; ts.second= 20; unsigned long str_length_time = strlen(rec_upd_ts); bind[4].buffer_type = MYSQL_TYPE_TIMESTAMP; bind[4].buffer = (char *)&ts; bind[4].is_null = 0; bind[4].length = 0; if (mysql_stmt_bind_param(stmt, bind)) { fprintf(stderr, " mysql_stmt_bind_param, 2 failed\n"); fprintf(stderr, " %s\n", mysql_stmt_error(stmt)); exit(0); } cout << "before execute\n"; if (mysql_stmt_execute(stmt)) { fprintf(stderr, " mysql_stmt_execute, 2 failed\n"); fprintf(stderr, " %s\n", mysql_stmt_error(stmt)); exit(0); } cout << "after execute\n"; i++; } mysql_commit(&conn); mysql_stmt_close(stmt); return 0; }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!