Maison  >  Article  >  base de données  >  Une brève discussion sur le principe de communication de MySQL JDBC StreamResult

Une brève discussion sur le principe de communication de MySQL JDBC StreamResult

不言
不言avant
2018-10-19 16:33:332690parcourir

Le contenu de cet article concerne une brève discussion du principe de communication MySQL JDBC StreamResult. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Ceux qui ont utilisé MySQL JDBC pour lire de grandes quantités de données (par exemple, plus de 1 Go) doivent savoir que la mémoire est susceptible de déborder du tas Java lors de la lecture, et notre solution est déclaration . setFetchSize(Integer.MIN_VALUE) et assurez-vous que le curseur est en lecture seule et défile vers l'avant (la valeur par défaut du curseur). Vous pouvez également convertir le type en com.mysql.jdbc.StatementImpl, puis appeler sa méthode interne : activateStreamingResults(). ) De cette façon, la lecture de la mémoire de données ne se bloquera pas et l'effet obtenu par les deux est le même. Bien sûr, useCursorFetch peut également être utilisé, mais les performances des résultats de test de cette méthode sont beaucoup plus lentes que celles de StreamResult. Pourquoi ? Cet article expliquera ses principes généraux.

J'ai introduit dans certains articles et livres précédents que le code de traitement interne de MySQL JDBC est divisé en trois classes différentes à compléter, mais je n'ai jamais approfondi la relation entre la base de données et JDBC. Comment se déroule le processus de communication. entre eux. Pendant un moment, j'ai toujours pensé qu'il s'agissait d'un comportement côté serveur ou d'un comportement de coopération entre le client et le serveur, mais ce n'est pas le cas. Aujourd'hui, nous allons parler de ce qu'est ce comportement.

[Examinez d'abord la communication simple] :

La communication entre JDBC et la base de données est effectuée via Socket, nous pouvons donc traiter la base de données comme un fournisseur SocketServer Square, donc lorsque SocketServer renvoie des données (similaire au retour des jeux de résultats SQL), le processus est le suivant : Données du programme serveur (base de données) -> Kernel Socket Buffer -> Client Socket Buffer -> JDBC se trouve)

Jusqu'à présent, le JDBC que tout le monde a vu dans l'industrie informatique est : MySQL JDBC, SQL Server JDBC, PG JDBC, Oracle JDBC. Même pour les clients NoSQL : client Redis, client MongoDB, Memcached, le retour des données suit fondamentalement la même logique.

Une brève discussion sur le principe de communication de MySQL JDBC StreamResult

[Pourquoi se bloque-t-il lors de l'utilisation de MySQL JDBC pour lire directement les données par défaut ? 】

(1) L'ensemble de résultats SQL initié par le serveur MySQL génère tous les données via OutputStream, c'est-à-dire qu'il écrit les données dans le tampon de socket correspondant au Kennel local. Il s'agit d'une copie de mémoire (Memory. la copie n’est pas le sujet de cet article).

(2) Lorsque le Kennel's Buffer a des données, il renverra les données via le lien TCP (lien Socket activement initié par JDBC. À ce moment, les données seront renvoyées à la machine où JDBC). est localisé, il entrera d'abord dans la zone du chenil et entrera également dans une zone tampon.

(3) Une fois que JDBC a lancé l'opération SQL, le code Java se bloque sur l'opération inputStream.read() Lorsqu'il y a des données dans le tampon, il sera réveillé puis lira les données dans le tampon. Pour la mémoire Java, il s'agit d'une copie mémoire du côté JDBC.

(4) Ensuite, MySQL JDBC continuera à lire les données du tampon dans la mémoire Java et MySQL Server continuera à envoyer des données. Notez qu'avant que les données ne soient complètement assemblées, l'opération SQL initiée par le client ne répondra pas, ce qui signifie que cela vous donnera l'impression que le serveur MySQL n'a pas encore répondu. En fait, les données ont été envoyées localement et JDBC l'a fait. n'a pas encore renvoyé l'ensemble de résultats à l'endroit où la méthode d'exécution a été appelée le premier élément de données, mais lit en continu les données du tampon.

(5) La clé est que cet imbécile lira le contenu de la table entière dans la mémoire Java après avoir lu les données, qu'elles soient ou non stockées à la maison. Premièrement, FULL GC, La prochaine étape est la mémoire. débordement.

[Le réglage de useCursorFetch=true sur les paramètres JDBC peut résoudre le problème]

Cette solution combinée au paramètre FetchSize peut en effet résoudre le problème. Serveur MySQL que je veux Combien de données, combien de données sont nécessaires à chaque fois, le processus de communication est un peu comme ceci :

Une brève discussion sur le principe de communication de MySQL JDBC StreamResult

C'est tout comme dans nos vies, ce dont j'ai besoin, c'est d'aller au supermarché et d'acheter autant que nécessaire. Cependant, ce type d'interaction n'est pas comme les achats en ligne aujourd'hui. Vous pouvez vous faire livrer des choses à votre domicile tout en étant assis à la maison. Il faut marcher (lien réseau), ce qui signifie que cela nécessite une surcharge de temps réseau si les données contiennent 100 millions de données. , définissez FetchSize sur S'il est égal à 1 000, il y aura 100 000 communications aller-retour ; si le délai réseau est de 0,02 ms dans la même salle informatique, alors 100 000 communications ajouteront 2 secondes, ce qui n'est pas grave. Ainsi, si le temps de retard est de 2 ms dans les salles informatiques, il sera 200 secondes de plus (soit 3 minutes et 20 secondes). Si le temps de retard est de 10 à 40 ms dans les villes de Chine, alors le temps sera de 1 000 à 4 000 secondes. . Et si c'était 200 ~ 300 ms dans tous les pays ? Le temps sera plus de dix heures plus long.

Dans les calculs ici, nous n'avons pas inclus l'augmentation du nombre d'appels système, l'augmentation du nombre de contextes pour que les threads attendent et se réveillent, ni l'impact des retransmissions de paquets réseau sur les performances globales. Cela semble raisonnable, mais les performances ne sont effectivement pas très bonnes.

De plus, comme MySQL ne sait pas quand le client a fini de consommer les données et que sa table correspondante peut avoir des opérations d'écriture DML, MySQL doit créer un espace table temporaire pour stocker les données qui doivent être supprimées. Par conséquent, lorsque vous activez useCursorFetch pour lire une grande table, vous verrez plusieurs phénomènes sur MySQL :

(1) Les augmentations d'IOPS sont dues à une grande quantité de lecture d'E/S. S'il s'agit d'un disque dur ordinaire, il s'agit d'un disque dur ordinaire. peut provoquer une gigue dans l'écriture commerciale

(2) L'espace disque augmente. Cet espace temporaire peut être plus grand que la table d'origine. Si cette table occupe une grande proportion dans l'ensemble de la base de données, cela peut provoquer une écriture sur le disque de la base de données. plein, l'espace sera récupéré par MySQL après la lecture du jeu de résultats ou lorsque le client lance Result.close().

(3) Le CPU et la mémoire augmenteront d'un certain pourcentage, en fonction des capacités du CPU.

(4) Une fois que le client JDBC a lancé SQL, il attend longtemps les données de réponse SQL. Pendant ce temps, le serveur prépare les données. Cette attente est différente de la méthode JDBC d'origine consistant à n'en définir aucune. Paramètres.Il montre également l'attente.Les principes internes sont différents.Le premier continue de lire les données du tampon réseau et ne répond pas à l'entreprise.Maintenant, la base de données MySQL prépare l'espace de données temporaire et ne répond pas à JDBC.

[Données de lecture de flux]

Nous savons que la première méthode entraînera le blocage de Java, et la deuxième méthode est inefficace et a un plus grand impact sur la base de données MySQL , la réponse du client est également lente, cela ne peut que résoudre le problème, regardons maintenant la méthode de lecture Stream.

Comme mentionné précédemment, lorsque vous utilisez Statement.setFetchSize(Integer.MIN_VALUE) ou com.mysql.jdbc.StatementImpl.enableStreamingResults(), vous pouvez activer Stream pour que l'ensemble de résultats FetchSize ne puisse pas être utilisé avant de lancer. exécutez-le manuellement et assurez-vous que le curseur est FORWARD_ONLY.

Cette méthode est étonnante. Il semble que la mémoire ne soit pas bloquée, la réponse est plus rapide et l'impact sur MySQL est également plus faible. Au moins, les IOPS ne seront pas si importantes et l'utilisation du disque est également plus faible. disparu. Dans le passé, je ne voyais que du code séparé dans JDBC et je pensais qu'il s'agissait d'un autre protocole de communication entre MySQL et JDBC. Je ne savais pas qu'il s'agissait d'un "comportement du client". Oui, vous avez bien lu, c'est le client. Comportement.

Lorsqu'il lance activateStreamingResults(), il n'effectuera pratiquement aucune interaction avec le serveur. Autrement dit, le serveur renverra les données selon la méthode 1, puis le serveur poussera les données dans le tampon, et le client va Comment résister à la pression ?

Dans JDBC, lorsque vous activez le traitement de l'ensemble de résultats Stream, il ne lit pas toutes les données dans la mémoire Java en même temps, c'est-à-dire que les données de la figure 1 ne sont pas lues dans la mémoire Java en même temps. buffer lit un paquet à la fois (ce paquet peut être compris comme un tableau d'octets[] en Java). Il peut en lire autant à la fois, puis il verra s'il doit continuer à lire vers le bas pour garantir l'intégrité du paquet. données. . Le code métier est analysé en lignes basées sur les octets et utilisé par le côté métier.

Le serveur commence juste à pousser les données dans le tampon, et les données rempliront également le tampon du noyau du client lorsque les tampons des deux côtés sont pleins, un tampon sur le serveur essaie de passer via TCP lorsque le serveur commence à envoyer des données dans le tampon. les données sont envoyées au destinataire, le tampon du consommateur est également plein à ce moment-là, donc le thread de l'expéditeur sera bloqué, en attendant que l'autre partie en consomme une partie, elle peut pousser une partie des données vers. il. La connexion semble être qu'avant que les données du flux JDBC puissent être consommées, si les données du tampon sont pleines, alors le thread de MySQL envoyant les données sera bloqué, assurant ainsi un équilibre (pour cela, vous pouvez utiliser le Socket de Java pour essayer Est-ce le cas ci-dessous).

Pour le client JDBC, les données sont obtenues à chaque fois dans le tampon du noyau local, juste à une distance de la boîte à colis express de la communauté, elles sont donc naturellement plus chères que RT à chaque fois lorsqu'on se rend au supermarché. Il est beaucoup plus petit et ce processus est constitué de données préparées, il n'y a donc pas de processus de blocage des E/S (à moins que les données transmises par le serveur MySQL ne soient pas aussi rapides que le consommateur pour traiter les données, alors généralement seul le consommateur ne le fait pas. n'importe quelle entreprise et obtient Cela se produit uniquement lorsque les données sont directement supprimées dans le code de test). À ce stade, peu importe : entre les salles informatiques, entre les régions ou entre les pays, tant que le serveur commence à répondre, les données seront continuellement transmises. passée sous silence, et cette action même si elle est du premier type. La méthode est aussi un processus qui doit être vécu.

Par rapport à la première méthode, JDBC ne provoquera pas de débordement de mémoire lorsqu'il est utilisé Même si une grande table est lue sans débordement de mémoire, la réponse prendra cependant beaucoup de temps ; 1. L'impact sur la base de données est relativement important. Pendant le processus de transfert de données, les lignes de données correspondantes seront verrouillées (pour empêcher toute modification). L'utilisation d'InnoDB effectuera un traitement de verrouillage segmenté, tandis que l'utilisation de MyISAM ajoutera des verrous de table complets, ce qui peut provoquer une obstruction aux affaires.

[Théoriquement vous pouvez aller plus loin, tant que vous le souhaitez]

Théoriquement cette méthode est meilleure, mais en terme de perfectionnisme, on peut continuer Parlons-en Pour les paresseux, nous n'avons même pas la motivation d'aller à la boîte à colis express en bas de la communauté pour l'obtenir. Ce à quoi nous pensons, c'est si quelqu'un l'apporte chez moi et me le met dans la bouche, même dans. ma bouche. Ce serait génial si je la cassais.

Techniquement, cela peut effectivement être fait en théorie, car il faut du temps à JDBC pour copier la mémoire du noyau vers Java. Si une autre personne fait cela, cela ne fonctionnera pas pendant que je fais autre chose à la maison. à mon domicile. Quand j'en ai besoin, je viens directement de chez moi. Cela me fait gagner du temps. Chaque erreur est en effet enregistrée pour vous, mais la question est de savoir qui l'enverra ?

Un thread doit être ajouté au programme pour ce faire, copier les données du noyau dans la mémoire de l'application et même les analyser en lignes de données que l'application pourra utiliser directement, mais est-ce nécessairement parfait ? En fait, il y a un problème de coordination au milieu. Par exemple, si vous voulez cuisiner à la maison et qu'il vous manque un paquet d'assaisonnements, vous auriez pu les acheter vous-même en bas, mais vous devez demander à quelqu'un de les livrer. À ce moment-là, tous les autres plats sont cuits et il ne reste qu'un seul paquet d'assaisonnement, vous n'avez alors d'autre choix que d'attendre que le paquet d'assaisonnement soit livré à votre domicile avant de pouvoir procéder à la livraison. prochaine étape de la cuisson. Par conséquent, dans des circonstances idéales, cela peut permettre d'économiser beaucoup de temps de copie de la mémoire, mais augmentera la surcharge du verrouillage de coordination.

Alors, est-il possible de lire des données directement depuis le tampon du noyau ?

Théoriquement, c'est possible. Avant d'expliquer ce problème, comprenons d'abord ce qu'il y a d'autre à part cette copie mémoire :

JDBC lit les données du tampon du noyau au format binaire. Après Lors de la récupération, elles seront ensuite analysées en données structurées spécifiques, car à ce stade, les données structurées des lignes spécifiques du ResultSet doivent être renvoyées à l'entreprise, c'est-à-dire qu'il doit y avoir une copie des données générées par RowData, et JDBC renvoie certains types d'objets lorsqu'il s'agit de données (comme le tableau d'octets []), dans l'implémentation de certains scénarios, il ne souhaite pas que vous modifiiez le contenu de l'octet [] dans le résultat renvoyé (octet [1). ] = 0xFF) via l'ensemble de résultats pour modifier le contenu du ResultSet lui-même. Lors de l'utilisation du code métier, il y aura également des chaînes de caractères, une sortie réseau, etc., et il y en aura un tas. de copies de mémoire. Celles-ci sont inévitables au niveau métier. Par rapport à cette petite copie, c'est tout simplement trivial, donc nous ne l'avons pas fait, pensant que c'est presque trivial dans l'ensemble, à moins que le goulot d'étranglement de votre programme ne soit là. .

Par conséquent, la copie de mémoire est globalement inévitable. Cette fois, il ne s'agit que d'appels au niveau du système, et la surcharge sera techniquement plus importante, nous pouvons le faire directement à partir de l'état du noyau. ; mais à ce stade, les données du Buffer doivent être supprimées en fonction des octets pour permettre à davantage de données d'être transférées à distance. Il n'y a pas de troisième emplacement pour stocker le Buffer, sinon il reviendra à la copie mémoire du noyau vers. la demande.

Relativement parlant, le serveur peut optimiser et transférer des données directement via IO directe (mais le protocole de données de cette manière est cohérent avec le format de stockage des données, ce qui n'est évidemment que théorique Pour vraiment mettre en œuvre un protocole personnalisé). et envoyez des données directement via l'état du noyau, vous devez modifier le protocole du système de fichiers au niveau du système d'exploitation pour atteindre l'objectif de conversion.


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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer