Mysql int vs varchar sebagai kunci utama (InnoDB Storage Engine?

13

Saya membangun aplikasi web (sistem manajemen proyek) dan saya bertanya-tanya tentang ini ketika datang ke kinerja.

Saya memiliki tabel Issues dan di dalamnya terdapat 12 kunci asing yang terhubung ke berbagai tabel lainnya. dari mereka, 8 dari mereka saya akan perlu bergabung untuk mendapatkan judul dari tabel lain agar catatan masuk akal dalam aplikasi web tetapi kemudian berarti melakukan 8 bergabung yang tampaknya sangat berlebihan terutama karena saya hanya menarik 1 bidang untuk masing-masing bergabung.

Sekarang saya juga telah diberitahu untuk menggunakan kunci primer penambahan otomatis (kecuali jika sharding adalah masalah dalam hal mana saya harus menggunakan GUID) untuk alasan permanen, tetapi seberapa burukkah menggunakan performa varchar (panjang max 32)? Maksud saya sebagian besar tabel ini mungkin tidak akan memiliki banyak catatan (kebanyakan dari mereka harus di bawah 20). Juga jika saya menggunakan judul sebagai kunci utama, saya tidak perlu melakukan bergabung dengan 95% dari waktu jadi untuk 95% dari sql, saya bahkan akan terjadi hit kinerja (saya pikir). Satu-satunya downside yang dapat saya pikirkan adalah yang saya miliki adalah saya akan memiliki penggunaan ruang disk yang lebih tinggi (tapi turun satu hari adalah hal yang sangat besar).

Alasan saya menggunakan tabel pencarian untuk banyak hal ini alih-alih enum adalah karena saya perlu semua nilai ini dapat dikonfigurasi oleh pengguna akhir melalui aplikasi itu sendiri.

Apa kerugian menggunakan varchar sebagai kunci utama untuk tabel yang tidak terkecuali memiliki banyak catatan?

PEMBARUAN - Beberapa Tes

Jadi saya memutuskan untuk melakukan beberapa tes dasar tentang hal ini. Saya memiliki 100000 catatan dan ini adalah pertanyaan dasar:

Pangkalan VARCHAR FK Query

SELECT i.id, i.key, i.title, i.reporterUserUsername, i.assignedUserUsername, i.projectTitle, 
i.ProjectComponentTitle, i.affectedProjectVersionTitle, i.originalFixedProjectVersionTitle, 
i.fixedProjectVersionTitle, i.durationEstimate, i.storyPoints, i.dueDate, 
i.issueSecurityLevelId, i.creatorUserUsername, i.createdTimestamp, 
i.updatedTimestamp, i.issueTypeId, i.issueStatusId
FROM ProjectManagement.Issues i

Basis INT FK Query

SELECT i.id, i.key, i.title, ru.username as reporterUserUsername, 
au.username as assignedUserUsername, p.title as projectTitle, 
pc.title as ProjectComponentTitle, pva.title as affectedProjectVersionTitle, 
pvo.title as originalFixedProjectVersionTitle, pvf.title as fixedProjectVersionTitle, 
i.durationEstimate, i.storyPoints, i.dueDate, isl.title as issueSecurityLevelId, 
cu.username as creatorUserUsername, i.createdTimestamp, i.updatedTimestamp, 
it.title as issueTypeId, is.title as issueStatusId
FROM ProjectManagement2.Issues i
INNER JOIN ProjectManagement2.IssueTypes `it` ON it.id = i.issueTypeId
INNER JOIN ProjectManagement2.IssueStatuses `is` ON is.id = i.issueStatusId
INNER JOIN ProjectManagement2.Users `ru` ON ru.id = i.reporterUserId
INNER JOIN ProjectManagement2.Users `au` ON au.id = i.assignedUserId
INNER JOIN ProjectManagement2.Users `cu` ON cu.id = i.creatorUserId
INNER JOIN ProjectManagement2.Projects `p` ON p.id = i.projectId
INNER JOIN ProjectManagement2.`ProjectComponents` `pc` ON pc.id = i.projectComponentId
INNER JOIN ProjectManagement2.ProjectVersions `pva` ON pva.id = i.affectedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvo` ON pvo.id = i.originalFixedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvf` ON pvf.id = i.fixedProjectVersionId
INNER JOIN ProjectManagement2.IssueSecurityLevels isl ON isl.id = i.issueSecurityLevelId

Saya juga menjalankan kueri ini dengan tambahan berikut:

  • Pilih item tertentu (di mana i.key = 43298)
  • Kelompokkan oleh i.id
  • Dipesan oleh (it.title untuk int FK, i.issueTypeId untuk varchar FK)
  • Batas (50000, 100)
  • Kelompokkan dan batasi bersama
  • Kelompokkan, pesan, dan batasi bersama

Hasil untuk ini di mana:

JENIS QUERY: WAKTU VARCHAR FK / WAKTU INT FK


Basis kueri: ~ 4ms / ~ 52ms

Pilih item tertentu: ~ 140ms / ~ 250ms

Kelompokkan oleh i.id: ~ 4ms / ~ 2.8sec

Dipesan oleh: ~ 231ms / ~ 2sec

Batas: ~ 67ms / ~ 343ms

Kelompokkan dan batasi bersama: ~ 504ms / ~ 2sec

Kelompokkan, pesan, dan batasi bersama: ~ 504ms / ~2.3sec

Sekarang saya tidak tahu konfigurasi apa yang bisa saya buat untuk membuat yang satu atau yang lain (atau keduanya) lebih cepat tetapi sepertinya VARCHAR FK melihat lebih cepat dalam permintaan data (kadang-kadang jauh lebih cepat).

Saya kira saya harus memilih apakah peningkatan kecepatan itu sepadan dengan data tambahan / ukuran indeks.

ryanzec
sumber
Pengujian Anda menunjukkan sesuatu. Saya juga akan menguji dengan berbagai pengaturan InnoDB (buffer pools, dll.) Karena pengaturan default MySQL tidak benar-benar dioptimalkan untuk InnoDB.
ypercubeᵀᴹ
Anda juga harus menguji Sisipkan / Perbarui / Hapus kinerja karena ini juga dapat dipengaruhi oleh ukuran indeks. Kunci satu berkerumun dari setiap tabel InnoDB biasanya PK dan kolom (PK) ini juga termasuk dalam setiap indeks lainnya. Ini mungkin salah satu kelemahan besar dari PK besar di InnoDB dan banyak indeks di atas meja (tapi 32 byte agak sedang, tidak besar, jadi mungkin tidak menjadi masalah).
ypercubeᵀᴹ
Anda juga harus menguji dengan tabel yang lebih besar (dalam kisaran katakanlah 10-100M baris, atau lebih besar), jika Anda mengharapkan tabel Anda dapat tumbuh lebih tinggi dari 100K (yang tidak terlalu besar).
ypercubeᵀᴹ
@ ypercube Jadi saya meningkatkan data menjadi 2 juta dan pernyataan pilih untuk int FK semakin lambat secara eksponensial di mana kunci asing varchar tetap cukup stabil. Suatu pemikiran bahwa varchar sepadan dengan harga dalam persyaratan disk / memori untuk mendapatkan dalam kueri pemilihan (yang akan menjadi kritis pada tabel khusus ini dan beberapa lainnya).
ryanzec
Periksa juga pengaturan db (dan khususnya InnoDB) Anda, sebelum membuat kesimpulan. Dengan tabel referensi kecil, saya tidak akan mengharapkan peningkatan eksponensial
ypercubeᵀᴹ

Jawaban:

9

Saya mengikuti aturan berikut untuk kunci utama:

a) Seharusnya tidak memiliki arti bisnis - mereka harus benar-benar independen dari aplikasi yang Anda kembangkan, oleh karena itu saya mencari bilangan bulat yang dihasilkan secara otomatis. Namun jika Anda memerlukan kolom tambahan untuk menjadi unik maka buatlah indeks unik untuk mendukungnya

b) Seharusnya tampil dalam gabungan - bergabung dengan varchars vs integer adalah sekitar 2x hingga 3x lebih lambat seiring panjang kunci primer tumbuh, jadi Anda ingin memiliki kunci sebagai integer. Karena semua sistem komputer adalah biner, saya curiga karena stringnya diubah menjadi biner kemudian dibandingkan dengan yang lain yang sangat lambat

c) Gunakan tipe data sekecil mungkin - jika Anda mengharapkan tabel Anda memiliki sangat sedikit kolom yang mengatakan 52 negara bagian AS, maka gunakan tipe terkecil yang mungkin mungkin CHAR (2) untuk kode 2 digit, tapi saya masih akan pergi untuk tinyint (128) untuk kolom vs int besar yang bisa mencapai 2billion

Anda juga akan menghadapi tantangan dengan mengalirkan perubahan Anda dari kunci utama ke tabel lain jika misalnya perubahan nama proyek (yang tidak biasa)

Gunakan bilangan bulat otomatis berurutan untuk kunci utama Anda dan dapatkan efisiensi inbuilt yang disediakan sistem database dengan dukungan untuk perubahan di masa depan

Stephen Senkomago Musoke
sumber
1
String tidak diubah menjadi biner; mereka disimpan dalam biner sejak awal. Bagaimana lagi mereka disimpan? Mungkin Anda memikirkan operasi untuk memungkinkan perbandingan case-insensitive?
Jon of All Trades
6

Dalam tes Anda, Anda tidak membandingkan perbedaan kinerja kunci varchar vs int, melainkan biaya beberapa gabungan. Tidak mengherankan bahwa kueri 1 tabel lebih cepat daripada bergabung dengan banyak tabel.
Satu kelemahan kunci utama varchar adalah meningkatnya ukuran indeks seperti yang ditunjukkan atxdba . Bahkan jika tabel pencarian Anda tidak memiliki indeks lain kecuali PK (yang sangat tidak mungkin, tetapi mungkin), setiap tabel yang merujuk pencarian akan memiliki indeks pada kolom ini.
Hal buruk lainnya tentang kunci primer alami, adalah nilainya dapat berubah yang menyebabkan banyak pembaruan berjenjang. Tidak semua RDMS, misalnya Oracle, bahkan membiarkan Anda memilikinyaon update cascade. Secara umum, mengubah nilai kunci utama dianggap sebagai praktik yang sangat buruk. Saya tidak ingin mengatakan bahwa kunci primer alami selalu jahat; jika nilai pencarian kecil dan tidak pernah berubah saya pikir mungkin dapat diterima.

Salah satu opsi yang mungkin ingin Anda pertimbangkan adalah menerapkan tampilan terwujud. Mysql tidak mendukungnya secara langsung, tetapi Anda dapat mencapai fungsionalitas yang diinginkan dengan pemicu pada tabel yang mendasarinya. Jadi Anda akan memiliki satu tabel yang memiliki semua yang Anda butuhkan untuk ditampilkan. Juga, jika kinerja dapat diterima, jangan berjuang dengan masalah yang tidak ada saat ini.

a1ex07
sumber
3

Kelemahan terbesar adalah pengulangan PK. Anda menunjukkan peningkatan penggunaan ruang disk tetapi untuk menjadi jelas ukuran indeks yang meningkat adalah masalah Anda yang lebih besar. Karena innodb adalah indeks berkerumun, setiap indeks sekunder secara internal menyimpan salinan PK yang digunakan untuk akhirnya menemukan catatan yang cocok.

Anda mengatakan tabel diharapkan "kecil" (20 baris memang sangat kecil). Jika Anda memiliki cukup RAM untuk mengatur Innodb_buffer_pool_size sama dengan

select sum(data_length+index_length) from information_schema.tables where engine='innodb';

Kemudian lakukan itu dan Anda mungkin akan duduk cantik. Sebagai aturan umum meskipun Anda ingin meninggalkan setidaknya 30% - 40% dari total memori sistem untuk overhead mysql lain dan dis cache. Dan itu dengan asumsi itu adalah server DB khusus. Jika Anda memiliki hal-hal lain yang berjalan pada sistem Anda juga harus mempertimbangkan persyaratan mereka.

atxdba
sumber
1

Selain jawaban @atxdba - yang menjelaskan mengapa menggunakan numerik akan lebih baik untuk ruang disk, saya ingin menambahkan dua poin:

  1. Jika tabel Masalah Anda berbasis VARCHAR FK, dan katakanlah Anda memiliki 20 VARCHAR kecil (32) FK, catatan Anda bisa mencapai panjang 20x32bytes, sedangkan seperti yang Anda sebutkan tabel lainnya adalah tabel pencarian, sehingga INT FK bisa menjadi TINYINT FK yang membuat untuk 20 bidang catatan 20 byte. Saya tahu beberapa ratus catatan itu tidak akan banyak berubah, tetapi ketika Anda akan mendapatkan beberapa juta saya kira Anda akan menghargai menghemat ruang

  2. Untuk masalah kecepatan, saya akan mempertimbangkan untuk menggunakan indeks penutup, karena sepertinya untuk permintaan ini Anda tidak mengambil banyak data dari tabel pencarian yang akan saya gunakan untuk mencakup indeks dan melakukan sekali lagi tes yang disediakan dengan VARCHAR FK / W / COVERING INDEKS DAN reguler INT FK.

Semoga ini bisa membantu,

Spredzy
sumber