Equality Range Optimization(eq_range_index_dive_limit system variable)

Bu mövzu şəxsi araşdırmalarımdan biridir ki, `Equality RANGE` scan zamanı 5.6 və 5.5 versiyaları arasında performance fərqinə baxacıq.
İlk öncə  Equality RANGE nədir? 2 növ bizə tanış olan query təsəvvür edək:

col_name IN(val1, …, valN)
col_name = val1 OR … OR col_name = valN

Yuxarıdakı sorğular o zaman TRUE olur ki, col_name val-ların hər hansı birinə bərabər olsun. Dolayısı ilə bu müqayisə, bərabərliyi yoxlayır və məhz  buna görə `Bərabərlik Aralığı`=`Equality RANGE` adlanır.
Teoriyası biraz aydın olmasa belə praktik olaraq izah edəcik bunu.
İlk öncə MySQL-imizə baxaq. MySQL 5.6.12 və MySQL 5.5.33-dir Qısa olaraq 5.6 və 5.5 olaraq qeyd edəcik.
Birinci olaraq 5.5 versiyada bu tip sorğuların necə çalışdığına baxacıq:

mysql [localhost] {msandbox} (mysqlspp) > select @@version;
+-----------+
| @@version |
+-----------+
| 5.5.33    |
+-----------+
1 row in set (0.00 sec)

Cədvəlmiz isə hər 2 tərəfdə eynidir və aşağıdakı strukturdadır:

mysql [localhost] {msandbox} (mysqlspp) > show create table sales;
+--------+------------------------------------------------------------------------------------------------------------------------------------------------+
| sales | CREATE TABLE `sales` (
`SALES_ID` int(8) NOT NULL AUTO_INCREMENT,
`CUSTOMER_ID` decimal(8,0) NOT NULL,
`PRODUCT_ID` decimal(8,0) NOT NULL,
`SALE_DATE` datetime NOT NULL,
`QUANTITY` decimal(8,0) NOT NULL,
`SALE_VALUE` decimal(8,0) NOT NULL,
`DEPARTMENT_ID` decimal(8,0) DEFAULT '0',
`SALES_REP_ID` decimal(8,0) DEFAULT '0',
`GST_FLAG` decimal(8,0) DEFAULT NULL,
`sale_status` char(1) DEFAULT NULL,
`FREE_SHIPPING` char(1) DEFAULT '',
`DISCOUNT` decimal(8,0) unsigned DEFAULT '0',
PRIMARY KEY (`SALES_ID`),
KEY `sales_cust_idx` (`CUSTOMER_ID`),
KEY `PRODUCT_ID` (`PRODUCT_ID`,`CUSTOMER_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2500004 DEFAULT CHARSET=latin1 |
+--------+------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

Toplam row sayı 2500003-dir:

mysql [localhost] {msandbox} (mysqlspp) > select count(*) from sales;
+----------+
| count(*) |
+----------+
|  2500003 |
+----------+
1 row in set (14.88 sec)

Gördüyümüz kimi, cədvəldə 2500003 row var. Biraz da cədvəlimizi araşdıraq. Cədvəl statusumuza baxdıqda görürük ki, MySQL 5.5 cədvəli full scan etdikdə təqribi row sayını 2500273 hesablayır:

Bundan əlavə cədvəl index-lərinin cardinality-sini öyrənək. Bizim üçün əsas PRODUCT_ID index-idir( KEY `PRODUCT_ID`):

cardinality=200, burdan belə nəticə çıxır ki product_id təqribən 200 unique scan-la tapıla bilər.
Bu rəqəm təqribi rəqəmdir və bunu başqa yolla da tapa bilərik:

mysql [localhost] {msandbox} (mysqlspp) > select count(product_id) from sales;
+-------------------+
| count(product_id) |
+-------------------+
|           2500003 |
+-------------------+
1 row in set (0.32 sec)

mysql [localhost] {msandbox} (mysqlspp) > select count(distinct product_id) from sales;
+----------------------------+
| count(distinct product_id) |
+----------------------------+
|                        150 |
+----------------------------+
1 row in set (0.00 sec)

Burdan belə nəticə çıxardırıq ki, MySQL 5.5-də full table scan zamanı 2500273 row examine olunacaq! bundan əlavə product_id-nin seçkinliyi(cardinality) 200-dür.

MySQL 5.6-da isə bu qiymətlər aşağıdakı kimidir:
Table status:

Index cardinality:

Burdan belə nəticə çıxardırıq ki, MySQL 5.6-də full table scan zamanı 2351755 row examine olunacaq!(optimizer belə qərara gəlib təbii ki bu nisbi rəqəmdir) bundan əlavə product_id-nin seçkinliyi(cardinality) 286-dır.

Hər 2 versiya MySQL-də cədvəl və index-lər haqqında məlumatı əldə etdikdən sonra mövzumuzu izah etməyə başlaya bilərik. sınaqdan keçirəcəyimiz sorğu:
select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9,10)

MySQL 5.5 üçün:

mysql [localhost] {msandbox} (mysqlspp) > select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9,10);
+----------+
| count(*) |
+----------+
|   166659 |
+----------+
1 row in set (0.06 sec)

MySQL 5.6 üçün:

mysql> select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9,10);
+----------+
| count(*) |
+----------+
|   166659 |
+----------+
1 row in set (0.05 sec)

1-dən 10-a qədər range üçün QEP MySQL 5.5-də:

mysql [localhost] {msandbox} (mysqlspp) > explain select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9,10)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales
type: range
possible_keys: PRODUCT_ID
key: PRODUCT_ID
key_len: 4
ref: NULL
rows: 332662
Extra: Using where; Using index
1 row in set (0.00 sec)

MySQL 5.6-da:

mysql> explain select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9,10)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales
type: range
possible_keys: PRODUCT_ID
key: PRODUCT_ID
key_len: 4
ref: NULL
rows: 82220
Extra: Using where; Using index
1 row in set (0.00 sec)

Bura diqqətlə nəzər yetirsək görərik ki, 5.5 versiya MySQL-də <strong>332662</strong> row scan olunur, 5.6 versiyada isə row scan sayı 82220-dir. Böyük fərq göz önündədir.

Query-imizə əlavələrlə yenidən çalışdıraq.
5.5 versiyada:

mysql [localhost] {msandbox} (mysqlspp) > explain select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9,10,11,12)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales
type: range
possible_keys: PRODUCT_ID
key: PRODUCT_ID
key_len: 4
ref: NULL
rows: 398890
Extra: Using where; Using index
1 row in set (0.01 sec)

5.6 versiyada:

mysql> explain select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9,10,11,12)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales
type: range
possible_keys: PRODUCT_ID
key: PRODUCT_ID
key_len: 4
ref: NULL
rows: 98664
Extra: Using where; Using index
1 row in set (0.00 sec)

398890 və 98664 yenidən fərq göz önündədir.
Lakin maraqlısı odur ki, product_id-ni 10-dan aşağı range-də müqayisə etdikdə nəticələr bərabər olur:

5.5 versiyada:

mysql [localhost] {msandbox} (mysqlspp) > explain select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales
type: range
possible_keys: PRODUCT_ID
key: PRODUCT_ID
key_len: 4
ref: NULL
rows: 300112
Extra: Using where; Using index
1 row in set (0.00 sec)

5.6 versiyada:

mysql> explain select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales
type: range
possible_keys: PRODUCT_ID
key: PRODUCT_ID
key_len: 4
ref: NULL
rows: 300112
Extra: Using where; Using index
1 row in set (0.00 sec)

Gördüyümüz kimi hər 2 halda 300112 rows examine olunacaq!
Ümumiləşdirmə aparaq, 5.6 versiyada 10-dan yuxarı range zamanı performance qazancı yaşanır lakin 10-dan aşağı range zamanı heç bir fərq baş vermir.
Buna səbəb 5.6 versiyada ilk dəfə olaraq təqdim olunmuş eq_range_index_dive_limit sistem dəyişənidir. 5.5-də bu dəyişən yoxdur:

mysql [localhost] {msandbox} (mysqlspp) > select @@eq_range_index_dive_limit;
ERROR 1193 (HY000): Unknown system variable 'eq_range_index_dive_limit'

5.6-da isə bu dəyişənin qiyməti default olaraq 10-dur:

mysql> select @@eq_range_index_dive_limit;
+-----------------------------+
| @@eq_range_index_dive_limit |
+-----------------------------+
|                          10 |
+-----------------------------+
1 row in set (0.00 sec)

Yuxarıdakı ümumiləşdirmədə göstərdiyimiz 10-nun səbəbi məhz bu dəyişənin default qiymətidir. Sınaq üçün bu dəyişənin qiymətini 9 edək və query-mizi 1-dən 9-a qədər range-lə run edək:

mysql> set @@eq_range_index_dive_limit=9;
Query OK, 0 rows affected (0.00 sec)

mysql> explain select count(*) from sales force index(product_id) where product_id in (1,2,3,4,5,6,7,8,9)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sales
type: range
possible_keys: PRODUCT_ID
key: PRODUCT_ID
key_len: 4
ref: NULL
rows: 73998
Extra: Using where; Using index
1 row in set (0.00 sec)

Daha əvvəl row sayı 300112 idi, indi isə 73998 oldu. Göründüyü kimi Equality Range Optimization məqsədimizə çatmış olduq. Ən sonda eq_range_index_dive_limit dəyişəninin təsir etdiyi row sayının hesablanması alqoritmini də diqqətinizə çatdırım. Əgər xatırlayırsınızsa table status-dan  row sayı bərabər idi 2351755-a. İndex cardinality isə 286-ya.
Hesablanma: 2351755/286*9 (range sayı)
Cavab:

mysql> select 2351755/286*9;
+---------------+
| 2351755/286*9 |
+---------------+
|    74006.2762 |
+---------------+
1 row in set (0.03 sec)

<strong>Həqiqətən də 74006.2762 təqribən bərabərdir 73998-a.</strong>
Nəticə olaraq onu deyə bilərik ki, MySQL 5.6 özü ilə bərabər bir çox yeniliklər gətirmişdir. bu yeniliklər əsasə etibarı ilə 3 qrupa bölünür:
1. Replication( GTİD based and crash-safe)
2. İnnoDB (performance and safety optimization)
3. MySQL query optimizer improvements

Bu tip yeniliklərdən istifadə edə bilməniz üçün 5.6 versiyaya upgrade etməniz məsləhətdir.

Təşəkkürlər 😉

Səs: +10. Bəyənilsin Zəifdir

Şərh yazın