MariaDB
 sql >> Datenbank >  >> RDS >> MariaDB

Maximierung der Effizienz von Datenbankabfragen für MySQL – Teil Zwei

Dies ist der zweite Teil einer zweiteiligen Blogreihe zur Maximierung der Effizienz von Datenbankabfragen in MySQL. Den ersten Teil können Sie hier lesen.

Einspaltigen, zusammengesetzten, Präfix- und abdeckenden Index verwenden

Tabellen, die häufig hohen Datenverkehr erhalten, müssen ordnungsgemäß indiziert werden. Es ist nicht nur wichtig, Ihre Tabelle zu indizieren, sondern Sie müssen auch bestimmen und analysieren, welche Arten von Abfragen oder Abruftypen Sie für die jeweilige Tabelle benötigen. Es wird dringend empfohlen, dass Sie analysieren, welche Art von Abfragen oder Abrufen von Daten Sie für eine bestimmte Tabelle benötigen, bevor Sie entscheiden, welche Indizes für die Tabelle erforderlich sind. Sehen wir uns diese Arten von Indizes an und wie Sie sie verwenden können, um Ihre Abfrageleistung zu maximieren.

Einspaltiger Index

InnoD-Tabelle kann maximal 64 sekundäre Indizes enthalten. Ein einspaltiger Index (oder ganzspaltiger Index) ist ein Index, der nur einer bestimmten Spalte zugewiesen ist. Das Erstellen eines Index zu einer bestimmten Spalte, die unterschiedliche Werte enthält, ist ein guter Kandidat. Ein guter Index muss eine hohe Kardinalität und Statistik aufweisen, damit der Optimierer den richtigen Abfrageplan auswählen kann. Um die Verteilung der Indizes anzuzeigen, können Sie mit der SHOW INDEXES-Syntax genau wie unten nachsehen:

root[test]#> SHOW INDEXES FROM users_account\G

*************************** 1. row ***************************

        Table: users_account

   Non_unique: 0

     Key_name: PRIMARY

 Seq_in_index: 1

  Column_name: id

    Collation: A

  Cardinality: 131232

     Sub_part: NULL

       Packed: NULL

         Null: 

   Index_type: BTREE

      Comment: 

Index_comment: 

*************************** 2. row ***************************

        Table: users_account

   Non_unique: 1

     Key_name: name

 Seq_in_index: 1

  Column_name: last_name

    Collation: A

  Cardinality: 8995

     Sub_part: NULL

       Packed: NULL

         Null: 

   Index_type: BTREE

      Comment: 

Index_comment: 

*************************** 3. row ***************************

        Table: users_account

   Non_unique: 1

     Key_name: name

 Seq_in_index: 2

  Column_name: first_name

    Collation: A

  Cardinality: 131232

     Sub_part: NULL

       Packed: NULL

         Null: 

   Index_type: BTREE

      Comment: 

Index_comment: 

3 rows in set (0.00 sec)

Sie können auch die Tabellen information_schema.index_statistics oder mysql.innodb_index_stats untersuchen.

Zusammengesetzte (zusammengesetzte) oder mehrteilige Indizes

Ein zusammengesetzter Index (allgemein zusammengesetzter Index genannt) ist ein mehrteiliger Index, der aus mehreren Spalten besteht. MySQL erlaubt bis zu 16 Spalten, die für einen bestimmten zusammengesetzten Index begrenzt sind. Das Überschreiten des Limits gibt einen Fehler wie unten zurück:

ERROR 1070 (42000): Too many key parts specified; max 16 parts allowed

Ein zusammengesetzter Index verbessert Ihre Abfragen, erfordert jedoch, dass Sie genau wissen, wie Sie die Daten abrufen. Zum Beispiel eine Tabelle mit einer DDL von...

CREATE TABLE `user_account` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `last_name` char(30) NOT NULL,

  `first_name` char(30) NOT NULL,

  `dob` date DEFAULT NULL,

  `zip` varchar(10) DEFAULT NULL,

  `city` varchar(100) DEFAULT NULL,

  `state` varchar(100) DEFAULT NULL,

  `country` varchar(50) NOT NULL,

  `tel` varchar(16) DEFAULT NULL

  PRIMARY KEY (`id`),

  KEY `name` (`last_name`,`first_name`)

) ENGINE=InnoDB DEFAULT CHARSET=latin1

...das aus dem zusammengesetzten Index `name` besteht. Der zusammengesetzte Index verbessert die Abfrageleistung, sobald diese Schlüssel als verwendete Schlüsselteile referenziert werden. Siehe beispielsweise Folgendes:

root[test]#> explain format=json select * from users_account where last_name='Namuag' and first_name='Maximus'\G

*************************** 1. row ***************************

EXPLAIN: {

  "query_block": {

    "select_id": 1,

    "cost_info": {

      "query_cost": "1.20"

    },

    "table": {

      "table_name": "users_account",

      "access_type": "ref",

      "possible_keys": [

        "name"

      ],

      "key": "name",

      "used_key_parts": [

        "last_name",

        "first_name"

      ],

      "key_length": "60",

      "ref": [

        "const",

        "const"

      ],

      "rows_examined_per_scan": 1,

      "rows_produced_per_join": 1,

      "filtered": "100.00",

      "cost_info": {

        "read_cost": "1.00",

        "eval_cost": "0.20",

        "prefix_cost": "1.20",

        "data_read_per_join": "352"

      },

      "used_columns": [

        "id",

        "last_name",

        "first_name",

        "dob",

        "zip",

        "city",

        "state",

        "country",

        "tel"

      ]

    }

  }

}

1 row in set, 1 warning (0.00 sec

Die used_key_parts zeigen, dass der Abfrageplan unsere gewünschten Spalten, die in unserem zusammengesetzten Index abgedeckt sind, perfekt ausgewählt hat.

Die zusammengesetzte Indizierung hat auch ihre Grenzen. Bestimmte Bedingungen in der Abfrage können nicht alle Spalten in den Schlüssel aufnehmen.

In der Dokumentation heißt es:"Der Optimierer versucht, zusätzliche Schlüsselteile zu verwenden, um das Intervall zu bestimmen, solange der Vergleichsoperator =, <=> oder IS NULL ist. Wenn der Operator> ist , <,>=, <=, !=, <>, BETWEEN oder LIKE, der Optimierer verwendet sie, berücksichtigt aber keine weiteren Schlüsselteile. Für den folgenden Ausdruck verwendet der Optimierer =vom ersten Vergleich. Er verwendet auch>=aus dem zweiten Vergleich, berücksichtigt aber keine weiteren Schlüsselteile und verwendet den dritten Vergleich nicht zur Intervallkonstruktion…" . Grundsätzlich bedeutet dies, dass unabhängig davon, ob Sie einen zusammengesetzten Index für zwei Spalten haben, eine Beispielabfrage unten nicht beide Felder abdeckt:

root[test]#> explain format=json select * from users_account where last_name>='Zu' and first_name='Maximus'\G

*************************** 1. row ***************************

EXPLAIN: {

  "query_block": {

    "select_id": 1,

    "cost_info": {

      "query_cost": "34.61"

    },

    "table": {

      "table_name": "users_account",

      "access_type": "range",

      "possible_keys": [

        "name"

      ],

      "key": "name",

      "used_key_parts": [

        "last_name"

      ],

      "key_length": "60",

      "rows_examined_per_scan": 24,

      "rows_produced_per_join": 2,

      "filtered": "10.00",

      "index_condition": "((`test`.`users_account`.`first_name` = 'Maximus') and (`test`.`users_account`.`last_name` >= 'Zu'))",

      "cost_info": {

        "read_cost": "34.13",

        "eval_cost": "0.48",

        "prefix_cost": "34.61",

        "data_read_per_join": "844"

      },

      "used_columns": [

        "id",

        "last_name",

        "first_name",

        "dob",

        "zip",

        "city",

        "state",

        "country",

        "tel"

      ]

    }

  }

}

1 row in set, 1 warning (0.00 sec)

In diesem Fall (und wenn Ihre Abfrage eher aus Bereichen als aus Konstanten oder Referenztypen besteht), vermeiden Sie die Verwendung zusammengesetzter Indizes. Es verschwendet nur Ihren Arbeitsspeicher und Puffer und erhöht die Leistungsverschlechterung Ihrer Abfragen.

Präfix-Indizes

Präfix-Indizes sind Indizes, die Spalten enthalten, die als Index referenziert werden, aber nur die für diese Spalte definierte Anfangslänge annehmen, und dieser Teil (oder Präfix-Daten) ist der einzige Teil, der im Puffer gespeichert wird. Präfixindizes können dazu beitragen, Ihre Pufferpoolressourcen und auch Ihren Festplattenspeicher zu verringern, da sie nicht die volle Länge der Spalte einnehmen müssen. Was bedeutet das? Nehmen wir ein Beispiel. Vergleichen wir die Auswirkungen zwischen dem Index in voller Länge und dem Präfixindex.

root[test]#> create index name on users_account(last_name, first_name);

Query OK, 0 rows affected (0.42 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> \! du -hs /var/lib/mysql/test/users_account.*

12K     /var/lib/mysql/test/users_account.frm

36M     /var/lib/mysql/test/users_account.ibd

Wir haben einen zusammengesetzten Index voller Länge erstellt, der insgesamt 36 MiB Tablespace für die Tabelle users_account verbraucht. Lassen Sie es fallen und fügen Sie dann einen Präfixindex hinzu.

root[test]#> drop index name on users_account;

Query OK, 0 rows affected (0.01 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> alter table users_account engine=innodb;

Query OK, 0 rows affected (0.63 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> \! du -hs /var/lib/mysql/test/users_account.*

12K     /var/lib/mysql/test/users_account.frm

24M     /var/lib/mysql/test/users_account.ibd






root[test]#> create index name on users_account(last_name(5), first_name(5));

Query OK, 0 rows affected (0.42 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> \! du -hs /var/lib/mysql/test/users_account.*

12K     /var/lib/mysql/test/users_account.frm

28M     /var/lib/mysql/test/users_account.ibd

Bei Verwendung des Präfixindex hält er nur bis zu 28 MiB und das sind weniger als 8 MiB als bei Verwendung des Index in voller Länge. Das ist großartig zu hören, aber es bedeutet nicht, dass es leistungsfähig ist und das erfüllt, was Sie brauchen.

Wenn Sie sich entscheiden, einen Präfixindex hinzuzufügen, müssen Sie zunächst ermitteln, welche Art von Abfrage für den Datenabruf Sie benötigen. Das Erstellen eines Präfixindex hilft Ihnen, den Pufferpool effizienter zu nutzen, und somit hilft es bei Ihrer Abfrageleistung, aber Sie müssen auch seine Einschränkungen kennen. Vergleichen wir beispielsweise die Leistung bei Verwendung eines Index voller Länge und eines Präfixindex.

Lassen Sie uns einen vollständigen Index mit einem zusammengesetzten Index erstellen,

root[test]#> create index name on users_account(last_name, first_name);

Query OK, 0 rows affected (0.45 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#>  EXPLAIN format=json select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

*************************** 1. row ***************************

EXPLAIN: {

  "query_block": {

    "select_id": 1,

    "cost_info": {

      "query_cost": "1.61"

    },

    "table": {

      "table_name": "users_account",

      "access_type": "ref",

      "possible_keys": [

        "name"

      ],

      "key": "name",

      "used_key_parts": [

        "last_name",

        "first_name"

      ],

      "key_length": "60",

      "ref": [

        "const",

        "const"

      ],

      "rows_examined_per_scan": 3,

      "rows_produced_per_join": 3,

      "filtered": "100.00",

      "using_index": true,

      "cost_info": {

        "read_cost": "1.02",

        "eval_cost": "0.60",

        "prefix_cost": "1.62",

        "data_read_per_join": "1K"

      },

      "used_columns": [

        "last_name",

        "first_name"

      ]

    }

  }

}

1 row in set, 1 warning (0.00 sec)



root[test]#> flush status;

Query OK, 0 rows affected (0.02 sec)



root[test]#> pager cat -> /dev/null; select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

PAGER set to 'cat -> /dev/null'

3 rows in set (0.00 sec)



root[test]#> nopager; show status like 'Handler_read%';

PAGER set to stdout

+-----------------------+-------+

| Variable_name         | Value |

+-----------------------+-------+

| Handler_read_first    | 0 |

| Handler_read_key      | 1 |

| Handler_read_last     | 0 |

| Handler_read_next     | 3 |

| Handler_read_prev     | 0 |

| Handler_read_rnd      | 0 |

| Handler_read_rnd_next | 0     |

+-----------------------+-------+

7 rows in set (0.00 sec)

Das Ergebnis zeigt, dass es tatsächlich einen abdeckenden Index verwendet, d. h. "using_index":true und Indizes ordnungsgemäß verwendet, d. h. Handler_read_key wird erhöht und führt einen Index-Scan durch, wenn Handler_read_next erhöht wird.

Lassen Sie uns jetzt versuchen, den Präfixindex des gleichen Ansatzes zu verwenden,

root[test]#> create index name on users_account(last_name(5), first_name(5));

Query OK, 0 rows affected (0.22 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#>  EXPLAIN format=json select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

*************************** 1. row ***************************

EXPLAIN: {

  "query_block": {

    "select_id": 1,

    "cost_info": {

      "query_cost": "3.60"

    },

    "table": {

      "table_name": "users_account",

      "access_type": "ref",

      "possible_keys": [

        "name"

      ],

      "key": "name",

      "used_key_parts": [

        "last_name",

        "first_name"

      ],

      "key_length": "10",

      "ref": [

        "const",

        "const"

      ],

      "rows_examined_per_scan": 3,

      "rows_produced_per_join": 3,

      "filtered": "100.00",

      "cost_info": {

        "read_cost": "3.00",

        "eval_cost": "0.60",

        "prefix_cost": "3.60",

        "data_read_per_join": "1K"

      },

      "used_columns": [

        "last_name",

        "first_name"

      ],

      "attached_condition": "((`test`.`users_account`.`first_name` = 'Maximus Aleksandre') and (`test`.`users_account`.`last_name` = 'Namuag'))"

    }

  }

}

1 row in set, 1 warning (0.00 sec)



root[test]#> flush status;

Query OK, 0 rows affected (0.01 sec)



root[test]#> pager cat -> /dev/null; select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

PAGER set to 'cat -> /dev/null'

3 rows in set (0.00 sec)



root[test]#> nopager; show status like 'Handler_read%';

PAGER set to stdout

+-----------------------+-------+

| Variable_name         | Value |

+-----------------------+-------+

| Handler_read_first    | 0 |

| Handler_read_key      | 1 |

| Handler_read_last     | 0 |

| Handler_read_next     | 3 |

| Handler_read_prev     | 0 |

| Handler_read_rnd      | 0 |

| Handler_read_rnd_next | 0     |

+-----------------------+-------+

7 rows in set (0.00 sec)

MySQL zeigt, dass es den Index richtig verwendet, aber auffällig ist, dass im Vergleich zu einem Index in voller Länge ein Mehraufwand entsteht. Das ist naheliegend und erklärbar, da der Präfixindex nicht die gesamte Länge der Feldwerte abdeckt. Die Verwendung eines Präfixindex ist weder ein Ersatz noch eine Alternative zur Indizierung in voller Länge. Es kann auch zu schlechten Ergebnissen führen, wenn der Präfixindex unangemessen verwendet wird. Sie müssen also bestimmen, welche Art von Abfrage und Daten Sie abrufen müssen.

Indizes abdecken

Das Abdecken von Indizes erfordert keine spezielle Syntax in MySQL. Ein abdeckender Index in InnoDB bezieht sich auf den Fall, wenn alle in einer Abfrage ausgewählten Felder von einem Index abgedeckt werden. Es muss kein sequenzielles Lesen über die Festplatte erfolgen, um die Daten in der Tabelle zu lesen, sondern es werden nur die Daten im Index verwendet, wodurch die Abfrage erheblich beschleunigt wird. Zum Beispiel unsere frühere Abfrage, d. h. 

select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

Wie bereits erwähnt, ist dies ein abdeckender Index. Wenn Sie beim Speichern Ihrer Daten sehr gut geplante Tabellen und einen ordnungsgemäß erstellten Index haben, versuchen Sie, Ihre Abfragen so zu gestalten, dass sie den abdeckenden Index nutzen, damit Sie vom Ergebnis profitieren. Dies kann Ihnen helfen, die Effizienz Ihrer Abfragen zu maximieren und zu einer großartigen Leistung zu führen.

Nutzen Sie Tools, die Berater oder Überwachung der Abfrageleistung bieten

Organisationen tendieren oft dazu, zuerst auf GitHub zu gehen und nach Open-Source-Software zu suchen, die große Vorteile bieten kann. Für einfache Ratschläge, die Ihnen bei der Optimierung Ihrer Abfragen helfen, können Sie das Percona Toolkit nutzen. Für einen MySQL DBA ist das Percona Toolkit wie ein Schweizer Taschenmesser.

Für Operationen müssen Sie analysieren, wie Sie Ihre Indizes verwenden, Sie können pt-index-usage verwenden.

Pt-query-digest ist ebenfalls verfügbar und kann MySQL-Anfragen aus Protokollen, Prozesslisten und tcpdump analysieren. Tatsächlich ist pt-query-digest das wichtigste Werkzeug, das Sie zum Analysieren und Untersuchen fehlerhafter Abfragen verwenden müssen. Verwenden Sie dieses Tool, um ähnliche Abfragen zusammenzufassen und Berichte über diejenigen zu erstellen, die die meiste Ausführungszeit beanspruchen.

Zum Archivieren alter Aufzeichnungen können Sie pt-archiver verwenden. Untersuchen Sie Ihre Datenbank auf doppelte Indizes und nutzen Sie pt-duplicate-key-checker. Sie können auch pt-deadlock-logger nutzen. Deadlocks sind zwar keine Ursache für eine leistungsschwache und ineffiziente Abfrage, sondern für eine schlechte Implementierung, wirken sich jedoch auf die Ineffizienz der Abfrage aus. Wenn Sie eine Tabellenwartung benötigen und Indizes online hinzufügen müssen, ohne den Datenbankdatenverkehr zu einer bestimmten Tabelle zu beeinträchtigen, können Sie pt-online-schema-change verwenden. Alternativ können Sie gh-ost verwenden, was auch sehr nützlich für Schema-Migrationen ist.

Wenn Sie nach Unternehmensfunktionen suchen, gebündelt mit vielen Funktionen von Abfrageleistung und -überwachung, Alarmen und Warnungen, Dashboards oder Metriken, die Ihnen bei der Optimierung Ihrer Abfragen helfen, und Beratern, ist ClusterControl möglicherweise das richtige Tool dafür Sie. ClusterControl bietet viele Funktionen, die Ihnen Top Queries, Running Queries und Query Outliers anzeigen. Schauen Sie sich diesen Blog MySQL Query Performance Tuning an, der Ihnen zeigt, wie Sie Ihre Abfragen mit ClusterControl auf Augenhöhe überwachen können.

Fazit

Sie sind beim letzten Teil unseres zweiteiligen Blogs angelangt. Wir haben hier die Faktoren behandelt, die zu einer Verschlechterung der Abfrage führen, und wie Sie sie beheben können, um Ihre Datenbankabfragen zu maximieren. Wir haben auch einige Tools geteilt, die Ihnen zugute kommen und bei der Lösung Ihrer Probleme helfen können.