Terlalu sering, kita melihat kueri SQL kompleks yang ditulis dengan buruk berjalan melawan tabel database. Kueri semacam itu mungkin membutuhkan waktu yang sangat singkat atau sangat lama untuk dieksekusi, tetapi kueri tersebut menghabiskan banyak CPU dan sumber daya lainnya. Namun demikian, dalam banyak kasus, kueri kompleks memberikan informasi berharga kepada aplikasi/orang. Oleh karena itu, ini membawa aset yang berguna di semua jenis aplikasi.
Kompleksitas kueri
Mari kita lihat lebih dekat pada kueri yang bermasalah. Banyak dari mereka yang kompleks. Itu mungkin karena beberapa alasan:
- Tipe data yang dipilih untuk data;
- Organisasi dan penyimpanan data dalam database;
- Transformasi dan penggabungan data dalam kueri untuk mengambil kumpulan hasil yang diinginkan.
Anda perlu memikirkan ketiga faktor utama ini dengan benar dan menerapkannya dengan benar untuk membuat kueri berkinerja optimal.
Namun, ini mungkin menjadi tugas yang hampir mustahil bagi Pengembang Basis Data dan DBA. Misalnya, akan sangat sulit untuk menambahkan fungsionalitas baru ke sistem Legacy yang ada. Kasus yang sangat rumit adalah ketika Anda perlu mengekstrak dan mengubah data dari sistem lama sehingga Anda dapat membandingkannya dengan data yang dihasilkan oleh sistem atau fungsionalitas baru. Anda harus mencapainya tanpa memengaruhi fungsionalitas aplikasi lawas.
Kueri semacam itu mungkin melibatkan gabungan kompleks, seperti berikut:
- Kombinasi substring dan/atau penggabungan beberapa kolom data;
- Fungsi skalar bawaan;
- UDF yang disesuaikan;
- Kombinasi apa pun dari perbandingan klausa WHERE dan kondisi penelusuran.
Kueri, seperti yang dijelaskan sebelumnya, biasanya memiliki jalur akses yang kompleks. Yang lebih buruk, mereka mungkin memiliki banyak pemindaian tabel dan/atau pemindaian indeks penuh dengan kombinasi seperti GABUNG atau pencarian yang terjadi.
Transformasi dan manipulasi data dalam kueri
Kami perlu menunjukkan bahwa semua data yang disimpan secara terus-menerus dalam tabel database memerlukan transformasi dan/atau manipulasi di beberapa titik ketika kami meminta data tersebut dari tabel. Transformasi dapat berkisar dari transformasi sederhana hingga transformasi yang sangat kompleks. Bergantung pada kerumitannya, transformasi dapat menghabiskan banyak CPU dan sumber daya.
Dalam kebanyakan kasus, transformasi yang dilakukan dalam JOIN terjadi setelah data dibaca dan dipindahkan ke tempdb database (SQL Server) atau file kerja database / temp-tablespaces seperti pada sistem basis data lainnya.
Karena data dalam file kerja tidak dapat diindeks , waktu yang dibutuhkan untuk mengeksekusi transformasi gabungan dan JOIN meningkat secara eksponensial. Data yang diambil menjadi lebih besar. Dengan demikian, kueri yang dihasilkan berkembang menjadi hambatan kinerja melalui pertumbuhan data tambahan.
Jadi, bagaimana Pengembang Basis Data atau DBA dapat mengatasi hambatan kinerja tersebut dengan cepat dan juga menyediakan lebih banyak waktu untuk mereka sendiri untuk merekayasa ulang dan menulis ulang kueri untuk kinerja yang optimal?
Ada dua cara untuk memecahkan masalah persisten seperti itu secara efektif. Salah satunya adalah dengan menggunakan kolom virtual dan/atau indeks fungsional.
Indeks dan kueri fungsional
Biasanya, Anda membuat indeks pada kolom yang menunjukkan kumpulan kolom/nilai unik dalam satu baris (indeks unik atau kunci utama) atau mewakili kumpulan kolom/nilai yang sedang atau mungkin digunakan dalam kondisi pencarian klausa WHERE dari kueri.
Jika Anda tidak memiliki indeks seperti itu, dan Anda telah mengembangkan kueri kompleks seperti yang dijelaskan sebelumnya, Anda akan melihat hal berikut:
- Pengurangan tingkat kinerja saat menggunakan penjelasan kueri dan melihat pemindaian tabel atau pemindaian indeks penuh
- Penggunaan CPU dan sumber daya yang sangat tinggi yang disebabkan oleh kueri;
- Waktu eksekusi yang lama.
Basis data kontemporer biasanya mengatasi masalah ini dengan memungkinkan Anda membuat fungsional atau berbasis fungsi index, seperti yang disebutkan dalam SQLServer, Oracle, dan MySQL (v 8.x). Atau, bisa juga Indeks di berbasis ekspresi/ekspresi indeks, seperti dalam database lain (PostgreSQL dan Db2).
Misalkan Anda memiliki kolom Tanggal_Pembelian dari tipe data TIMESTAMP atau DATETIME di Pesanan . Anda tabel, dan kolom itu telah diindeks. Kami mulai menanyakan Pesanan tabel dengan klausa WHERE:
SELECT ...
FROM Order
WHERE DATE(Purchase_Date) = '03.12.2020'
Transaksi ini akan menyebabkan pemindaian seluruh indeks. Namun, jika kolom belum diindeks, Anda mendapatkan pemindaian tabel.
Setelah memindai seluruh indeks, indeks itu berpindah ke tempdb / workfile (seluruh tabel jika Anda mendapatkan pemindaian tabel ) sebelum mencocokkan nilai 03.12.2020 .
Karena tabel Pesanan besar menggunakan banyak CPU dan sumber daya, Anda harus membuat indeks fungsional yang memiliki ekspresi DATE (Purchase_Date ) sebagai salah satu kolom indeks dan ditampilkan di bawah ini:
CREATE ix_DatePurchased on sales.Order(Date(Purchase_Date) desc, ... )
Dengan demikian, Anda membuat predikat yang cocok TANGGAL (Tanggal_Pembelian) =‘03.12.2020’ dapat diindeks. Jadi, alih-alih memindahkan indeks atau tabel ke tempdb / workfile sebelum mencocokkan nilainya, kami membuat indeks hanya diakses sebagian dan/atau dipindai. Ini menghasilkan penggunaan CPU dan sumber daya yang lebih rendah.
Lihat contoh lain. Ada Pelanggan tabel dengan kolom nama_depan, nama_belakang . Kolom tersebut diindeks seperti:
CREATE INDEX ix_custname on Customer(first_name asc, last_name asc),
Selain itu, Anda memiliki tampilan yang menggabungkan kolom-kolom ini ke dalam nama_pelanggan kolom:
CREATE view v_CustomerInfo( customer_name, .... ) as
select first_name ||' '|| last_name as customer_name,.....
from Customer
where ...
Anda memiliki kueri dari sistem eCommerce yang mencari nama lengkap pelanggan:
select c.*
from v_CustomerInfo c
where c.customer_name = 'John Smith'
....
Sekali lagi, kueri ini akan menghasilkan pemindaian indeks penuh. Dalam skenario terburuk, ini akan menjadi pemindaian tabel penuh yang memindahkan semua data dari indeks atau tabel ke file kerja sebelum penggabungan nama_pertama dan nama_belakang kolom dan mencocokkan nilai 'John Smith'.
Kasus lain adalah membuat indeks fungsional seperti yang ditunjukkan di bawah ini:
CREATE ix_fullcustname on sales.Customer( first_name ||' '|| last_name desc, ... )
Dengan cara ini, Anda dapat membuat penggabungan dalam kueri tampilan menjadi predikat yang dapat diindeks. Alih-alih pemindaian indeks penuh atau pemindaian tabel, Anda memiliki pemindaian indeks parsial. Eksekusi kueri seperti itu menghasilkan CPU dan penggunaan sumber daya yang lebih rendah, tidak termasuk pekerjaan di file kerja dan dengan demikian memastikan waktu eksekusi yang lebih cepat.
Kolom dan kueri virtual (dibuat)
Kolom yang dihasilkan (kolom virtual atau kolom yang dihitung) adalah kolom yang menyimpan data yang dihasilkan dengan cepat. Data tidak dapat secara eksplisit diatur ke nilai tertentu. Ini merujuk pada data di kolom lain yang dikueri, disisipkan, atau diperbarui dalam kueri DML.
Pembuatan nilai kolom tersebut otomatis berdasarkan ekspresi. Ekspresi ini mungkin menghasilkan:
- Urutan nilai bilangan bulat;
- Nilai berdasarkan nilai kolom lain dalam tabel;
- Ini mungkin menghasilkan nilai dengan memanggil fungsi bawaan atau fungsi yang ditentukan pengguna (UDF).
Sama pentingnya untuk dicatat bahwa di beberapa database (SQLServer, Oracle, PostgreSQL, MySQL, dan MariaDB) kolom ini dapat dikonfigurasi untuk menyimpan data secara terus-menerus dengan eksekusi pernyataan INSERT dan UPDATE, atau mengeksekusi ekspresi kolom yang mendasarinya dengan cepat. jika kita menanyakan tabel dan kolom yang menghemat ruang penyimpanan.
Namun, ketika ekspresi rumit, seperti logika kompleks dalam fungsi UDF, penghematan waktu eksekusi, sumber daya, dan biaya kueri CPU mungkin tidak sebanyak yang diharapkan.
Dengan demikian, kita dapat mengonfigurasi kolom sehingga akan terus-menerus menyimpan hasil ekspresi dalam pernyataan INSERT atau UPDATE. Kemudian, kami membuat indeks reguler pada kolom itu. Dengan cara ini, kami akan menghemat CPU, penggunaan sumber daya, dan waktu eksekusi kueri. Sekali lagi, mungkin ada sedikit peningkatan dalam kinerja INSERT dan UPDATE, tergantung pada kompleksitas ekspresi.
Mari kita lihat sebuah contoh. Kami mendeklarasikan tabel dan membuat indeks sebagai berikut:
CREATE TABLE Customer as (
customerID Int GENERATED ALWAYS AS IDENTITY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
customer_name as (first_name ||' '|| last_name) PERSISTED
...
);
CREATE ix_fullcustname on sales.Customer( customer_name desc, ... )
Dengan cara ini, kami memindahkan logika penggabungan dari tampilan dalam contoh sebelumnya ke dalam tabel dan menyimpan data secara terus-menerus. Kami mengambil data menggunakan pemindaian yang cocok pada indeks biasa. Ini adalah hasil terbaik di sini.
Dengan menambahkan kolom yang dihasilkan ke tabel dan membuat indeks reguler pada kolom itu, kita dapat memindahkan logika transformasi ke tingkat tabel. Di sini, kami terus-menerus menyimpan data yang diubah dalam pernyataan penyisipan atau pembaruan yang jika tidak, akan diubah dalam kueri. Pemindaian JOIN dan INDEX akan jauh lebih sederhana dan lebih cepat.
Indeks fungsional, kolom yang dihasilkan, dan JSON
Aplikasi web dan seluler global menggunakan struktur data ringan seperti JSON untuk memindahkan data dari web/perangkat seluler ke database dan sebaliknya. Jejak kecil struktur data JSON membuat transfer data melalui jaringan menjadi cepat dan mudah. Sangat mudah untuk mengompresi JSON ke ukuran yang sangat kecil dibandingkan dengan struktur lain, yaitu XML. Ini dapat mengungguli struktur dalam penguraian waktu proses.
Karena meningkatnya penggunaan struktur data JSON, database relasional memiliki format penyimpanan JSON baik sebagai tipe data BLOB atau tipe data CLOB. Kedua jenis ini membuat data dalam kolom tersebut tidak dapat diindeks sebagaimana adanya.
Untuk alasan ini, vendor database memperkenalkan fungsi JSON untuk membuat kueri dan memodifikasi objek JSON, karena Anda dapat dengan mudah mengintegrasikan fungsi ini ke dalam kueri SQL atau perintah DML lainnya. Namun, kueri ini bergantung pada kompleksitas objek JSON. Mereka sangat memakan CPU dan sumber daya, karena objek BLOB dan CLOB perlu dipindahkan ke memori, atau, lebih buruk lagi, ke file kerja sebelum melakukan query dan/atau manipulasi.
Asumsikan bahwa kita memiliki Pelanggan tabel dengan Detail pelanggan data disimpan sebagai objek JSON dalam kolom bernama CustomerDetail . Kami menyiapkan kueri tabel seperti di bawah ini:
SELECT CustomerID,
JSON_VALUE(CustomerDetail, '$.customer.Name') AS Name,
JSON_VALUE(CustomerDetail, '$.customer.Surname') AS Surname,
JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') AS PostCode,
JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 1"') + ' '
+ JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 2"') AS Address,
JSON_QUERY(CustomerDetail, '$.customer.address.Country') AS Country
FROM Customer
WHERE ISJSON(CustomerDetail) > 0
AND JSON_VALUE(CustomerDetail, '$.customer.address.Country') = 'Iceland'
AND JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') IN (101,102,110,210,220)
AND Status = 'Active'
ORDER BY JSON_VALUE(CustomerDetail, '$.customer.address.PostCode')
Dalam contoh ini, kami menanyakan data untuk pelanggan yang tinggal di beberapa bagian Wilayah Ibu Kota di Islandia. Semua Aktif data harus diambil ke dalam file kerja sebelum menerapkan predikat pencarian. Namun, pengambilan akan mengakibatkan penggunaan CPU dan resource yang terlalu besar.
Oleh karena itu, ada prosedur yang efektif untuk membuat kueri JSON berjalan lebih cepat. Ini melibatkan pemanfaatan fungsionalitas melalui kolom yang dihasilkan, seperti yang dijelaskan sebelumnya.
Kami mencapai peningkatan kinerja dengan menambahkan kolom yang dihasilkan. Kolom yang dihasilkan akan menelusuri dokumen JSON untuk data spesifik yang direpresentasikan dalam kolom menggunakan fungsi JSON dan menyimpan nilainya ke dalam kolom.
Kami dapat mengindeks dan mengkueri kolom yang dihasilkan ini menggunakan SQL biasa di mana kondisi pencarian klausa. Oleh karena itu, pencarian data tertentu dalam objek JSON menjadi sangat cepat.
Kami menambahkan dua kolom yang dihasilkan – Negara dan Kode Pos :
ALTER TABLE Customer
ADD Country as JSON_VALUE(CustomerDetail,'$.customer.address.Country');
ALTER TABLE Customer
ADD PostCode as JSON_VALUE(CustomerDetail,'$.customer.address.PostCode');
CREATE INDEX ix_CountryPostCode on Country(Country asc,PostCode asc);
Juga, kami membuat indeks komposit pada kolom tertentu. Sekarang, kita dapat mengubah kueri ke contoh yang ditampilkan di bawah ini:
SELECT CustomerID,
JSON_VALUE(CustomerDetail, '$.customer.customer.Name') AS Name,
JSON_VALUE(CustomerDetail, '$.customer.customer.Surname') AS Surname,
JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') AS PostCode,
JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 1"') + ' '
+ JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 2"') AS Address,
JSON_QUERY(CustomerDetail, '$.customer.address.Country') AS Country
FROM Customer
WHERE ISJSON(CustomerDetail) > 0
AND Country = 'Iceland'
AND PostCode IN (101,102,110,210,220)
AND Status = 'Active'
ORDER BY JSON_VALUE(CustomerDetail, '$.customer.address.PostCode')
Ini membatasi pengambilan data untuk Pelanggan Aktif hanya di beberapa bagian Wilayah Ibu Kota Islandia. Cara ini lebih cepat dan efisien dari query sebelumnya.
Kesimpulan
Secara keseluruhan, dengan menerapkan kolom virtual atau indeks fungsional ke tabel yang menyebabkan kesulitan (CPU, dan kueri yang membutuhkan banyak sumber daya), kami dapat menghilangkan masalah dengan cukup cepat.
Kolom virtual dan indeks fungsional dapat membantu kueri objek JSON kompleks yang disimpan dalam tabel relasional biasa. Namun, kita perlu menilai masalah dengan hati-hati sebelumnya dan membuat perubahan yang diperlukan.
Dalam beberapa kasus, jika struktur data kueri dan/atau JSON sangat kompleks, sebagian penggunaan CPU dan sumber daya dapat beralih dari kueri ke proses INSERT / UPDATE. Ini memberi kami penghematan CPU dan sumber daya secara keseluruhan lebih sedikit dari yang diharapkan. Jika Anda mengalami masalah serupa, desain ulang tabel dan kueri yang lebih menyeluruh mungkin tidak dapat dihindari.