MariaDB
 sql >> Teknologi Basis Data >  >> RDS >> MariaDB

Memaksimalkan Efisiensi Kueri Basis Data untuk MySQL - Bagian Kedua

Ini adalah bagian kedua dari blog seri dua bagian untuk Memaksimalkan Efisiensi Kueri Database Di MySQL. Anda dapat membaca bagian satu di sini.

Menggunakan Single-Column, Composite, Prefix, dan Covering Index

Tabel yang sering menerima lalu lintas tinggi harus diindeks dengan benar. Tidak hanya penting untuk mengindeks tabel Anda, tetapi Anda juga perlu menentukan dan menganalisis jenis kueri atau jenis pengambilan apa yang Anda perlukan untuk tabel tertentu. Sangat disarankan agar Anda menganalisis jenis kueri atau pengambilan data apa yang Anda perlukan pada tabel tertentu sebelum Anda memutuskan indeks apa yang diperlukan untuk tabel tersebut. Mari kita bahas jenis indeks ini dan bagaimana Anda dapat menggunakannya untuk memaksimalkan kinerja kueri Anda.

Indeks Kolom Tunggal

Tabel InnoD dapat berisi maksimal 64 indeks sekunder. Indeks kolom tunggal (atau indeks kolom penuh) adalah indeks yang ditetapkan hanya untuk kolom tertentu. Membuat indeks ke kolom tertentu yang berisi nilai berbeda adalah kandidat yang baik. Indeks yang baik harus memiliki kardinalitas dan statistik yang tinggi sehingga pengoptimal dapat memilih rencana kueri yang tepat. Untuk melihat distribusi indeks, Anda dapat memeriksa dengan sintaks SHOW INDEXES seperti di bawah ini:

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)

Anda dapat memeriksa juga dengan tabel information_schema.index_statistics atau mysql.innodb_index_stats.

Gabungan (Komposit) atau Indeks Multi-Bagian

Indeks gabungan (biasa disebut indeks komposit) adalah indeks multi-bagian yang terdiri dari beberapa kolom. MySQL memungkinkan hingga 16 kolom dibatasi untuk indeks komposit tertentu. Melebihi batas mengembalikan kesalahan seperti di bawah ini:

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

Indeks komposit memberikan dorongan untuk kueri Anda, tetapi Anda harus memiliki pemahaman yang murni tentang cara Anda mengambil data. Misalnya, tabel dengan DDL sebesar...

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

...yang terdiri dari indeks komposit `nama`. Indeks komposit meningkatkan kinerja kueri setelah kunci ini menjadi referensi sebagai bagian kunci yang digunakan. Misalnya, lihat berikut ini:

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

Used_key_parts menunjukkan bahwa rencana kueri telah dengan sempurna memilih kolom yang diinginkan yang tercakup dalam indeks komposit kami.

Pengindeksan komposit juga memiliki keterbatasan. Kondisi tertentu dalam kueri tidak dapat mengambil semua kolom bagian dari kunci.

Dokumentasi mengatakan, "Pengoptimal mencoba menggunakan bagian kunci tambahan untuk menentukan interval selama operator perbandingan =, <=>, atau IS NULL. Jika operatornya> , <,>=, <=, !=, <>, BETWEEN, atau LIKE, pengoptimal menggunakannya tetapi tidak mempertimbangkan bagian penting lagi. Untuk ekspresi berikut, pengoptimal menggunakan =dari perbandingan pertama. Ini juga menggunakan>=dari perbandingan kedua tetapi tidak mempertimbangkan bagian penting lebih lanjut dan tidak menggunakan perbandingan ketiga untuk konstruksi interval..." . Pada dasarnya, ini berarti bahwa terlepas dari Anda memiliki indeks gabungan untuk dua kolom, contoh kueri di bawah ini tidak mencakup kedua bidang:

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)

Dalam kasus ini (dan jika kueri Anda lebih banyak rentang daripada konstan atau tipe referensi), maka hindari menggunakan indeks komposit. Ini hanya membuang memori dan buffer Anda dan meningkatkan penurunan performa kueri Anda.

Indeks Awalan

Indeks awalan adalah indeks yang berisi kolom yang direferensikan sebagai indeks, tetapi hanya mengambil panjang awal yang ditentukan untuk kolom itu, dan bagian itu (atau data awalan) adalah satu-satunya bagian yang disimpan dalam buffer. Indeks awalan dapat membantu mengurangi sumber daya buffer pool Anda dan juga ruang disk Anda karena tidak perlu mengambil panjang penuh kolom. Apa artinya ini? Mari kita ambil contoh. Mari kita bandingkan dampak antara indeks panjang penuh versus indeks awalan.

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

Kami membuat indeks komposit full-length yang menghabiskan total 36MiB tablespace untuk tabel users_account. Mari kita lepaskan lalu tambahkan indeks awalan.

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

Menggunakan indeks awalan, ini hanya menampung hingga 28MiB dan itu kurang dari 8MiB daripada menggunakan indeks full-length. Itu bagus untuk didengar, tetapi itu tidak berarti bahwa itu berkinerja baik dan melayani apa yang Anda butuhkan.

Jika Anda memutuskan untuk menambahkan indeks awalan, Anda harus mengidentifikasi terlebih dahulu jenis kueri untuk pengambilan data yang Anda perlukan. Membuat indeks awalan membantu Anda memanfaatkan lebih banyak efisiensi dengan kumpulan buffer sehingga membantu kinerja kueri Anda, tetapi Anda juga perlu mengetahui batasannya. Misalnya, mari kita bandingkan kinerjanya saat menggunakan indeks full-length dan indeks awalan.

Mari kita buat indeks panjang penuh menggunakan indeks komposit,

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)

Hasilnya mengungkapkan bahwa sebenarnya, menggunakan indeks penutup yaitu "using_index":true dan menggunakan indeks dengan benar, yaitu Handler_read_key bertambah dan melakukan pemindaian indeks saat Handler_read_next bertambah.

Sekarang, mari kita coba menggunakan indeks awalan dengan pendekatan yang sama,

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 mengungkapkan bahwa ia menggunakan indeks dengan benar, tetapi ada biaya overhead dibandingkan dengan indeks full-length. Itu jelas dan dapat dijelaskan, karena indeks awalan tidak mencakup seluruh panjang nilai bidang. Menggunakan indeks awalan bukanlah pengganti, atau alternatif, pengindeksan full-length. Itu juga dapat membuat hasil yang buruk saat menggunakan indeks awalan secara tidak tepat. Jadi, Anda perlu menentukan jenis kueri dan data apa yang perlu diambil.

Meliputi Indeks

Meliputi Indeks tidak memerlukan sintaks khusus di MySQL. Indeks penutup di InnoDB mengacu pada kasus ketika semua bidang yang dipilih dalam kueri dicakup oleh indeks. Tidak perlu melakukan pembacaan berurutan melalui disk untuk membaca data dalam tabel tetapi hanya menggunakan data dalam indeks, secara signifikan mempercepat kueri. Misalnya, kueri kami sebelumnya yaitu 

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

Seperti yang disebutkan sebelumnya, adalah indeks penutup. Ketika Anda memiliki tabel yang sangat terencana saat menyimpan data Anda dan membuat indeks dengan benar, cobalah untuk membuat kueri Anda dirancang untuk memanfaatkan indeks penutup sehingga Anda akan mendapatkan keuntungan dari hasilnya. Ini dapat membantu Anda memaksimalkan efisiensi kueri dan menghasilkan kinerja yang hebat.

Manfaatkan Alat yang Menawarkan Penasihat atau Pemantauan Kinerja Kueri

Organisasi sering kali pada awalnya cenderung menjadi yang pertama di github dan menemukan perangkat lunak sumber terbuka yang dapat menawarkan manfaat besar. Untuk saran sederhana yang membantu Anda mengoptimalkan kueri, Anda dapat memanfaatkan Percona Toolkit. Untuk DBA MySQL, Percona Toolkit seperti pisau tentara swiss.

Untuk operasi, Anda perlu menganalisis bagaimana Anda menggunakan indeks Anda, Anda dapat menggunakan pt-index-usage.

Pt-query-digest juga tersedia dan dapat menganalisis kueri MySQL dari log, daftar proses, dan tcpdump. Faktanya, alat terpenting yang harus Anda gunakan untuk menganalisis dan memeriksa kueri buruk adalah pt-query-digest. Gunakan alat ini untuk menggabungkan kueri serupa dan melaporkan kueri yang menghabiskan waktu eksekusi paling banyak.

Untuk mengarsipkan catatan lama, Anda dapat menggunakan pt-archiver. Memeriksa database Anda untuk indeks duplikat, manfaatkan pt-duplicate-key-checker. Anda mungkin juga memanfaatkan pt-deadlock-logger. Meskipun kebuntuan bukanlah penyebab kueri yang berkinerja buruk dan tidak efisien tetapi implementasi yang buruk, namun hal itu berdampak pada ketidakefisienan kueri. Jika Anda memerlukan pemeliharaan tabel dan mengharuskan Anda untuk menambahkan indeks secara online tanpa mempengaruhi lalu lintas database yang menuju ke tabel tertentu, maka Anda dapat menggunakan pt-online-schema-change. Atau, Anda dapat menggunakan gh-ost, yang juga sangat berguna untuk migrasi skema.

Jika Anda mencari fitur perusahaan, yang dibundel dengan banyak fitur dari kinerja dan pemantauan kueri, alarm dan peringatan, dasbor atau metrik yang membantu Anda mengoptimalkan kueri, dan penasihat, ClusterControl mungkin merupakan alat untuk Anda. ClusterControl menawarkan banyak fitur yang menunjukkan kepada Anda Kueri Teratas, Kueri Berjalan, dan Pencilan Kueri. Lihat blog ini Penyesuaian Kinerja Kueri MySQL yang memandu Anda agar setara dalam memantau kueri Anda dengan ClusterControl.

Kesimpulan

Saat Anda tiba di bagian akhir blog dua seri kami. Kami membahas di sini faktor-faktor yang menyebabkan penurunan kueri dan cara mengatasinya untuk memaksimalkan kueri basis data Anda. Kami juga membagikan beberapa alat yang dapat bermanfaat bagi Anda dan membantu memecahkan masalah Anda.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. 3 Cara Menampilkan Kolasi untuk Koneksi Anda di MariaDB

  2. Pengelompokan Asli ProxySQL dengan Kubernetes

  3. Failover Tingkat Lanjut Menggunakan Kait Skrip Post/pra

  4. Melambung lebih tinggi di cloud dengan MariaDB SkySQL

  5. Cara mengkonfigurasi AppArmor untuk sistem berbasis MySQL (Replikasi MySQL/MariaDB + Galera)