Bagaimana saya bisa mempercepat "tampilkan kolom" di MySQL?

7

Aplikasi saya tergantung pada menjalankan "tampilkan kolom" untuk tabel tertentu. Dibutuhkan sekitar 60 ms untuk dijalankan, sedangkan semua kueri kami yang lain menggunakan ms. Meminta information_schemasecara langsung bahkan lebih lambat.

Basis data berisi sekitar 250 basis data, dengan 100 hingga 200 tabel per basis data (total sekitar 20 ribu tabel).

  • Bagaimana saya bisa mengetahui mengapa operasi ini sangat lambat?
  • Apakah mungkin ada beberapa pengaturan yang dapat saya ubah untuk membuatnya berjalan lebih cepat, atau untuk menyimpannya di sisi SQL?

(Aplikasi ini melakukan sekitar 14 pertanyaan seperti itu per memuat halaman - Saya sangat menyadari bahwa kode warisan ini perlu dibersihkan, tetapi mencari opsi yang mungkin saat saya bekerja pada perbaikan jangka panjang.)

Mpen
sumber
1
Karena minat, dalam skenario apa 60m terlalu lambat untuk memeriksa kolom tabel? Ini bukan sesuatu yang harus Anda lakukan setiap permintaan
1
Apa maksudmu menunjukkan kolom? Mengaitkan nama kolom dari sebuah tabel atau mencetak seluruh kolom? Jika itu namanya .. mengapa Anda tidak mengambilnya sekali saja dan menyimpannya di aplikasi Anda ... jika itu tidak mungkin mengapa Anda tidak membuat tabel lain yang menampung semua kolom berdasarkan tabel?
@Jitsu: Tidak, itu bukan sesuatu yang harus kita lakukan, tapi, memang begitu. Kode Warisan. Sampai saya punya waktu untuk membersihkannya dan melakukannya dengan benar, saya ingin melihat apakah saya bisa mempercepatnya. Saya memiliki sekitar 14 yang menjalankan setiap pemuatan halaman.
@FlorinStingaciu: Ya, nama kolom. Menempatkan mereka di tabel lain mungkin mempercepat, tetapi akan keluar dari sinkronisasi, yang mengalahkan seluruh tujuan meminta tabel secara langsung.
1
@Mat: Bukan ide yang buruk. Memilih untuk bermigrasi ke dba.

Jawaban:

12

MySQL menghitung ulang statistik tabel untuk operasi tertentu yang mengakses INFORMATION_SCHEMAtabel ( SHOW COLUMNShanya alias yang mudah untuk query INFORMATION_SCHEMA.COLUMNS). Tetapkan innodb_stats_on_metadata menjadi false, yang akan mencegah penghitungan ulang ini terjadi ketika Anda meminta metadata dari tabel.

SET GLOBAL innodb_stats_on_metadata=0;

dan tambahkan yang berikut ke my.cnf

[mysqld]
innodb_stats_on_metadata = 0
Aaron Brown
sumber
Saya seharusnya menyebutkan bahwa saya sebenarnya menggunakan MyISAM. Sudah mencoba pengaturan ini, tetapi tidak menghasilkan manfaat apa pun.
mpen
Sudahkah Anda mempertimbangkan ALTER TABLE foo ENGINE = InnoDB? :) Apakah ada alasan bagus untuk menggunakan MyISAM?
Aaron Brown
Alasan warisan sebagian besar, saya pikir. Saya khawatir apa yang akan terjadi jika saya mencobanya; tidak yakin semua FK akan berbaris. Saya akan memikirkannya lagi.
mpen
@AaronBrown +1 untuk jawaban ini karena siapa pun yang menghadapi situasi ini dengan database all-InnoDB memerlukan informasi ini.
RolandoMySQLDBA
1
+1 untuk diletakkan di [mysqld]sana. Mungkin jelas bagi banyak orang bahwa pengaturan ini akan dilakukan di bawah mysqld, tetapi mungkin tidak jelas bagi mereka yang akan menanyakan pertanyaan ini. By the way, ini dipercepat SELECT COUNT(*)di salah satu information_schemameja saya menjadi 6 detik dari lebih dari satu menit. Masih lambat, tetapi peningkatan besar.
Buttle Butkus
3

Saya sarankan Anda membuat database yang memiliki INFORMATION_SCHEMAtabel (atau hanya yang Anda butuhkan) sebagai duplikat. Indeks mereka dengan tepat dan Anda akan mendapatkan keuntungan kinerja.

Masalah sinkronisasi antara database ini dan INFORMATION_SCHEMAitu rumit.

Anda bisa memiliki prosedur yang menyinkronkan tabel ini setiap jam atau setiap 5 menit (seberapa sering struktur tabel berubah?).

Gagasan lain adalah menggunakan MySQL Proxy untuk menangkap ALTER TABLEpernyataan apa pun (dan CREATEdan DROPdan CREATE INDEXdan pernyataan apa pun lainnya yang mengubah informasi yang Anda butuhkan) dan kemudian menyinkronkan skema informasi yang direplikasi setelah pernyataan ini berhasil.


Jika Anda hanya memerlukan nama kolom dan bukan informasi lain, seperti tipe data, panjang, atau indeks yang tersedia, Anda mungkin dapat mengganti penggunaan SHOW COLUMNSdengan kueri (cepat) yang menghasilkan 1 baris saja, dengan LIMIT 1atau tidak sama sekali, dengan salah satu LIMIT 0atau:

SELECT * FROM TableName WHERE FALSE ;

Terlepas dari saran umum yang menentang penggunaan SELECT *, ini mungkin merupakan kasus yang sah di mana tidak ada hal lain yang berguna. (yang lainnya kecuali *, dapat menyebabkan kesalahan!)

ypercubeᵀᴹ
sumber
2

Dalam kasus khusus ini, saya pikir INFORMATION_SCHEMAini adalah herring merah. Dari tes SHOW COLUMNSkinerja saya sendiri , innodb_stats_on_metadatavariabel tampaknya tidak membuat perbedaan pada tabel MyISAM atau InnoDB.

Namun, dari manual MySQL 5.0 ...

Beberapa kondisi mencegah penggunaan tabel sementara di-memori, dalam hal ini server menggunakan tabel di-disk sebagai gantinya:

[...]

  • The SHOW COLUMNSdan The DESCRIBEpernyataan menggunakan BLOBsebagai jenis untuk beberapa kolom, sehingga tabel sementara yang digunakan untuk hasil adalah meja on-disk.

Ini tampaknya telah dihapus dari manual pada MySQL 5.5, tetapi masih berlaku untuk versi itu ...

mysql> SHOW VARIABLES LIKE 'version';
+---------------+-------------------------+
| Variable_name | Value                   |
+---------------+-------------------------+
| version       | 5.5.41-0ubuntu0.14.04.1 |
+---------------+-------------------------+
1 row in set (0.00 sec)

mysql> SHOW STATUS LIKE 'Created_tmp_disk_tables';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 0     |
+-------------------------+-------+
1 row in set (0.00 sec)

mysql> SHOW COLUMNS FROM mysql.user;
[...snip...]
42 rows in set (0.00 sec)

mysql> SHOW STATUS LIKE 'Created_tmp_disk_tables';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 1     |
+-------------------------+-------+
1 row in set (0.00 sec)

Informasi bidang yang dikembalikan dengan set hasil kueri berisi informasi yang sama dengan yang dikembalikan oleh SHOW COLUMNS, sehingga SELECT * FROM my_table LIMIT 0harus mencapai hal yang sama tanpa membuat tabel sementara di-disk per kueri.

Contoh cepat untuk hanya mengambil nama bidang dalam PHP ...

$mysql = new mysqli('localhost', 'root', '', 'my_database');
$field_names = array();
$result = $mysql->query("SELECT * FROM my_table LIMIT 0");
$fields = $result->fetch_fields();
foreach ($fields as $fields)
{
    $field_names[] = $field->name;
}
var_dump($field_names);

Mengambil info bidang dengan cara ini sedikit lebih canggung untuk diterjemahkan. Anda harus berkonsultasi dengan deskripsi MYSQL_FIELDstruktur yang mendasarinya untuk mengeluarkan tipe data dan flag, tetapi itu berjalan sekitar 7 kali lebih cepat pada sistem saya.

Aya
sumber
1

Saya suka saran pertama dalam jawaban @ yerpcube (+1), tapi saya ingin mengusulkan sesuatu

  • buat instance database lain pada port 3307
  • mysqldump database produksi ke File Teks SQL menggunakan opsi berikut:
    • --no-data
    • --routines
    • --triggers
    • --all-databasesatau --databasesdiikuti oleh daftar basis data yang Anda inginkan
  • Muat file SQL Text ke dalam port 3307 MySQL Instance

Dengan demikian, Anda mysqldump akan terlihat sebagai berikut:

mysqldump --no-data --routines --triggers --all-databases > ImportFile.sql

Itu dia. Ke depan, yang perlu Anda lakukan adalah menyambung ke instance database port 3307 ini dan menjalankan kueri terkait skema apa pun dengan isi hati Anda. Jika Anda tahu ada tabel di database produksi yang berubah, cukup masukkan mysqldump skema dari produksi dan muat ulang lagi ke port 3307 misalnya.

PERINGATAN: Jika Anda menginstal instance mysql pada mesin yang sama dengan produksi, pastikan Anda terhubung ke instance tersebut menggunakan

mysql -u... -p... -h127.0.0.1 -P3307 < ImportFile.sql

Jika Anda mengeksekusi

mysql -u... -p... -P3307 < ImportFile.sql

Itu akan selang produksi. Jadi, HATI-HATI !!!!

Alternatifnya adalah dengan hanya menggunakan server DB yang terpisah.

RolandoMySQLDBA
sumber