यह MySQL में डेटाबेस क्वेरी दक्षता को अधिकतम करने के लिए दो-भाग श्रृंखला ब्लॉग का दूसरा भाग है। आप यहां भाग एक पढ़ सकते हैं।
सिंगल-कॉलम, कम्पोजिट, प्रीफिक्स और कवरिंग इंडेक्स का उपयोग करना
अक्सर उच्च ट्रैफ़िक प्राप्त करने वाली तालिकाओं को ठीक से अनुक्रमित किया जाना चाहिए। न केवल अपनी तालिका को अनुक्रमित करना महत्वपूर्ण है, बल्कि आपको यह निर्धारित करने और विश्लेषण करने की भी आवश्यकता है कि विशिष्ट तालिका के लिए आपको किस प्रकार के प्रश्नों या पुनर्प्राप्ति के प्रकार की आवश्यकता है। यह दृढ़ता से अनुशंसा की जाती है कि तालिका के लिए कौन से अनुक्रमणिका आवश्यक हैं, यह तय करने से पहले आप किसी विशिष्ट तालिका पर किस प्रकार के प्रश्नों या डेटा की पुनर्प्राप्ति की आवश्यकता है, इसका विश्लेषण करें। आइए इस प्रकार की अनुक्रमणिका और आप अपने क्वेरी प्रदर्शन को अधिकतम करने के लिए उनका उपयोग कैसे कर सकते हैं, इस पर चलते हैं।
एकल-स्तंभ अनुक्रमणिका
InnoD टेबल में अधिकतम 64 सेकेंडरी इंडेक्स हो सकते हैं। एक एकल-स्तंभ अनुक्रमणिका (या पूर्ण-स्तंभ अनुक्रमणिका) एक अनुक्रमणिका है जिसे केवल एक विशेष स्तंभ को सौंपा गया है। किसी विशेष कॉलम में एक इंडेक्स बनाना जिसमें अलग-अलग मान हों, एक अच्छा उम्मीदवार है। एक अच्छे इंडेक्स में उच्च कार्डिनैलिटी और आंकड़े होने चाहिए ताकि ऑप्टिमाइज़र सही क्वेरी प्लान चुन सके। अनुक्रमणिका के वितरण को देखने के लिए, आप नीचे दिए गए SHOW INDEXES सिंटैक्स के साथ जाँच कर सकते हैं:
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)
आप टेबल info_schema.index_statistics या mysql.innodb_index_stats के साथ भी निरीक्षण कर सकते हैं।
यौगिक (समग्र) या बहु-भाग अनुक्रमणिका
एक मिश्रित सूचकांक (आमतौर पर एक समग्र सूचकांक कहा जाता है) कई स्तंभों से बना एक बहु-भाग सूचकांक है। MySQL एक विशिष्ट कंपोजिट इंडेक्स के लिए बाध्य 16 कॉलम तक की अनुमति देता है। सीमा से अधिक नीचे की तरह एक त्रुटि देता है:
ERROR 1070 (42000): Too many key parts specified; max 16 parts allowed
एक समग्र अनुक्रमणिका आपके प्रश्नों को बढ़ावा देती है, लेकिन इसके लिए यह आवश्यक है कि आप डेटा कैसे प्राप्त कर रहे हैं, इसकी पूरी समझ होनी चाहिए। उदाहरण के लिए, DDL वाली तालिका...
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
...जिसमें कंपोजिट इंडेक्स `name` होता है। एक बार जब इन कुंजियों को उपयोग किए गए प्रमुख भागों के रूप में संदर्भित किया जाता है, तो समग्र अनुक्रमणिका क्वेरी प्रदर्शन में सुधार करती है। उदाहरण के लिए, निम्नलिखित देखें:
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
इस्तेमाल किए गए_की_पार्ट्स दिखाते हैं कि क्वेरी प्लान ने हमारे कंपोजिट इंडेक्स में शामिल हमारे वांछित कॉलम को पूरी तरह से चुना है।
समग्र अनुक्रमण की भी अपनी सीमाएं हैं। क्वेरी में कुछ शर्तें सभी स्तंभों को कुंजी का हिस्सा नहीं ले सकती हैं।
दस्तावेज़ीकरण कहता है, "जब तक तुलना ऑपरेटर =, <=>, या IS NULL है, तब तक ऑप्टिमाइज़र अंतराल को निर्धारित करने के लिए अतिरिक्त कुंजी भागों का उपयोग करने का प्रयास करता है। यदि ऑपरेटर> है , <,>=, <=, !=, <>, BETWEEN, या LIKE, ऑप्टिमाइज़र इसका उपयोग करता है लेकिन अधिक महत्वपूर्ण भागों पर विचार नहीं करता है। निम्नलिखित अभिव्यक्ति के लिए, ऑप्टिमाइज़र पहली तुलना से =का उपयोग करता है। यह>=का भी उपयोग करता है दूसरी तुलना से लेकिन आगे कोई महत्वपूर्ण भाग नहीं मानता है और अंतराल निर्माण के लिए तीसरी तुलना का उपयोग नहीं करता है…" . मूल रूप से, इसका मतलब यह है कि भले ही आपके पास दो स्तंभों के लिए समग्र अनुक्रमणिका हो, नीचे दी गई एक नमूना क्वेरी में दोनों फ़ील्ड शामिल नहीं हैं:
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)
इस मामले में (और यदि आपकी क्वेरी स्थिर या संदर्भ प्रकारों के बजाय श्रेणियों की अधिक है) तो समग्र अनुक्रमणिका का उपयोग करने से बचें। यह सिर्फ आपकी मेमोरी और बफर को बर्बाद करता है और यह आपके प्रश्नों के प्रदर्शन में गिरावट को बढ़ाता है।
उपसर्ग अनुक्रमणिका
उपसर्ग अनुक्रमणिका वे अनुक्रमणिकाएँ होती हैं जिनमें अनुक्रमणिका के रूप में संदर्भित स्तंभ होते हैं, लेकिन केवल उस स्तंभ के लिए परिभाषित आरंभिक लंबाई होती है, और वह भाग (या उपसर्ग डेटा) बफ़र में संग्रहीत एकमात्र भाग होता है। उपसर्ग अनुक्रमणिका आपके बफर पूल संसाधनों और आपके डिस्क स्थान को कम करने में मदद कर सकती है क्योंकि इसमें कॉलम की पूरी लंबाई लेने की आवश्यकता नहीं होती है। इसका क्या मतलब है? आइए एक उदाहरण लेते हैं। आइए पूर्ण-लंबाई वाले अनुक्रमणिका बनाम उपसर्ग अनुक्रमणिका के बीच प्रभाव की तुलना करें।
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
हमने एक पूर्ण-लंबाई वाली समग्र अनुक्रमणिका बनाई है जो users_account तालिका के लिए कुल 36MiB तालिका स्थान का उपभोग करती है। आइए इसे छोड़ दें और फिर एक उपसर्ग अनुक्रमणिका जोड़ें।
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
उपसर्ग अनुक्रमणिका का उपयोग करते हुए, यह केवल 28MiB तक धारण करता है और यह पूर्ण-लंबाई वाले अनुक्रमणिका का उपयोग करने की तुलना में 8MiB से कम है। यह सुनकर बहुत अच्छा लगा, लेकिन इसका मतलब यह नहीं है कि यह प्रदर्शनकारी है और आपको जो चाहिए वह कार्य करता है।
यदि आप एक उपसर्ग अनुक्रमणिका जोड़ने का निर्णय लेते हैं, तो आपको पहले यह पहचानना होगा कि आपको डेटा पुनर्प्राप्ति के लिए किस प्रकार की क्वेरी की आवश्यकता है। उपसर्ग अनुक्रमणिका बनाने से आपको बफ़र पूल के साथ अधिक दक्षता का उपयोग करने में मदद मिलती है और इसलिए यह आपके क्वेरी प्रदर्शन में मदद करता है लेकिन आपको इसकी सीमा जानने की भी आवश्यकता है। उदाहरण के लिए, आइए पूर्ण-लंबाई अनुक्रमणिका और उपसर्ग अनुक्रमणिका का उपयोग करते समय प्रदर्शन की तुलना करें।
आइए समग्र अनुक्रमणिका का उपयोग करके एक पूर्ण-लंबाई वाली अनुक्रमणिका बनाएं,
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)
परिणाम से पता चलता है कि यह, वास्तव में, एक कवरिंग इंडेक्स का उपयोग कर रहा है, यानी "use_index":सच है और इंडेक्स का ठीक से उपयोग करता है, यानी हैंडलर_रीड_की इंक्रीमेंट किया गया है और हैंडलर_रीड_नेक्स्ट इंक्रीमेंट के रूप में एक इंडेक्स स्कैन करता है।
अब, आइए उसी दृष्टिकोण के उपसर्ग अनुक्रमणिका का उपयोग करने का प्रयास करें,
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 से पता चलता है कि यह इंडेक्स का ठीक से उपयोग करता है लेकिन ध्यान देने योग्य बात यह है कि फुल-लेंथ इंडेक्स की तुलना में लागत ओवरहेड है। यह स्पष्ट और व्याख्या योग्य है, क्योंकि उपसर्ग अनुक्रमणिका फ़ील्ड मानों की पूरी लंबाई को कवर नहीं करती है। उपसर्ग अनुक्रमणिका का उपयोग करना पूर्ण-लंबाई अनुक्रमण का प्रतिस्थापन नहीं है, न ही कोई विकल्प है। उपसर्ग अनुक्रमणिका का अनुपयुक्त उपयोग करने पर यह खराब परिणाम भी उत्पन्न कर सकता है। इसलिए आपको यह निर्धारित करने की आवश्यकता है कि आपको किस प्रकार की क्वेरी और डेटा पुनर्प्राप्त करने की आवश्यकता है।
कवरिंग इंडेक्स
इंडेक्स को कवर करने के लिए MySQL में किसी विशेष सिंटैक्स की आवश्यकता नहीं होती है। InnoDB में एक कवरिंग इंडेक्स उस मामले को संदर्भित करता है जब किसी क्वेरी में चयनित सभी फ़ील्ड एक इंडेक्स द्वारा कवर किए जाते हैं। तालिका में डेटा को पढ़ने के लिए डिस्क पर अनुक्रमिक पढ़ने की आवश्यकता नहीं है, लेकिन केवल इंडेक्स में डेटा का उपयोग करें, क्वेरी को काफी तेज कर दें। उदाहरण के लिए, हमारी पहले की क्वेरी यानी
select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G
जैसा कि पहले उल्लेख किया गया है, एक कवरिंग इंडेक्स है। जब आपके पास अपने डेटा को संग्रहीत करने और इंडेक्स को ठीक से बनाने के लिए एक बहुत अच्छी तरह से नियोजित टेबल हैं, तो जितना संभव हो सके यह बनाने का प्रयास करें कि आपके प्रश्नों को कवरिंग इंडेक्स का लाभ उठाने के लिए डिज़ाइन किया गया है ताकि आपको परिणाम का लाभ मिल सके। इससे आपको अपने प्रश्नों की दक्षता को अधिकतम करने में मदद मिल सकती है और परिणाम एक शानदार प्रदर्शन के लिए हो सकता है।
सलाहकार या क्वेरी प्रदर्शन निगरानी की पेशकश करने वाले लीवरेज टूल
संगठन अक्सर शुरुआत में पहले जीथब पर जाते हैं और ओपन-सोर्स सॉफ़्टवेयर ढूंढते हैं जो महान लाभ प्रदान कर सकते हैं। आपके प्रश्नों को अनुकूलित करने में आपकी सहायता करने वाली सरल सलाह के लिए, आप Percona टूलकिट का लाभ उठा सकते हैं। MySQL DBA के लिए, Percona Toolkit स्विस आर्मी नाइफ की तरह है।
ऑपरेशंस के लिए, आपको यह विश्लेषण करने की आवश्यकता है कि आप अपने इंडेक्स का उपयोग कैसे कर रहे हैं, आप पीटी-इंडेक्स-उपयोग का उपयोग कर सकते हैं।
Pt-query-digest भी उपलब्ध है और यह लॉग, प्रोसेसलिस्ट और tcpdump से MySQL के प्रश्नों का विश्लेषण कर सकता है। वास्तव में, सबसे महत्वपूर्ण उपकरण जो आपको खराब प्रश्नों के विश्लेषण और निरीक्षण के लिए उपयोग करना है, वह है पीटी-क्वेरी-डाइजेस्ट। समान प्रश्नों को एक साथ एकत्रित करने के लिए इस टूल का उपयोग करें और उन पर रिपोर्ट करें जो सबसे अधिक निष्पादन समय का उपभोग करते हैं।
पुराने अभिलेखों को संग्रहीत करने के लिए, आप पीटी-संग्रहकर्ता का उपयोग कर सकते हैं। डुप्लीकेट इंडेक्स के लिए अपने डेटाबेस का निरीक्षण करते हुए, पीटी-डुप्लीकेट-की-चेकर का लाभ उठाएं। आप पीटी-डेडलॉक-लॉगर का भी लाभ उठा सकते हैं। हालांकि गतिरोध एक खराब प्रदर्शन और अक्षम क्वेरी का कारण नहीं है, लेकिन एक खराब कार्यान्वयन है, फिर भी यह क्वेरी अक्षमता को प्रभावित करता है। यदि आपको तालिका रखरखाव की आवश्यकता है और आपको किसी विशेष तालिका में जाने वाले डेटाबेस ट्रैफ़िक को प्रभावित किए बिना ऑनलाइन अनुक्रमणिका जोड़ने की आवश्यकता है, तो आप पीटी-ऑनलाइन-स्कीमा-परिवर्तन का उपयोग कर सकते हैं। वैकल्पिक रूप से, आप gh-ost का उपयोग कर सकते हैं, जो स्कीमा माइग्रेशन के लिए भी बहुत उपयोगी है।
यदि आप एंटरप्राइज़ सुविधाओं की तलाश कर रहे हैं, क्वेरी प्रदर्शन और निगरानी, अलार्म और अलर्ट, डैशबोर्ड या मीट्रिक से बहुत सारी सुविधाओं के साथ बंडल किया गया है जो आपको अपने प्रश्नों और सलाहकारों को अनुकूलित करने में मदद करता है, तो ClusterControl आपके लिए उपकरण हो सकता है तुम। ClusterControl कई सुविधाएँ प्रदान करता है जो आपको शीर्ष क्वेरी, रनिंग क्वेरी और क्वेरी आउटलेयर दिखाती हैं। इस ब्लॉग को चेकआउट करें MySQL क्वेरी प्रदर्शन ट्यूनिंग जो आपका मार्गदर्शन करती है कि आप ClusterControl के साथ अपने प्रश्नों की निगरानी के लिए कैसे समान रहें।
निष्कर्ष
जैसे ही आप हमारे दो-श्रृंखला वाले ब्लॉग के अंतिम भाग पर पहुंचे हैं। हमने यहां उन कारकों को शामिल किया है जो क्वेरी में गिरावट का कारण बनते हैं और आपके डेटाबेस प्रश्नों को अधिकतम करने के लिए इसे कैसे हल किया जाए। हमने कुछ टूल भी साझा किए हैं जो आपको लाभ पहुंचा सकते हैं और आपकी समस्याओं को हल करने में मदद कर सकते हैं।