<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Alexander Rodionov IT</title><generator>teletype.in</generator><description><![CDATA[Alexander Rodionov IT]]></description><image><url>https://teletype.in/files/22/28/222865d4-60b3-440e-b759-bb001e453925.jpeg</url><title>Alexander Rodionov IT</title><link>https://blog.devgu.ru/</link></image><link>https://blog.devgu.ru/?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/devguru?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/devguru?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Fri, 17 Apr 2026 10:09:08 GMT</pubDate><lastBuildDate>Fri, 17 Apr 2026 10:09:08 GMT</lastBuildDate><item><guid isPermaLink="true">https://blog.devgu.ru/mysql-count-vs-sum</guid><link>https://blog.devgu.ru/mysql-count-vs-sum?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru</link><comments>https://blog.devgu.ru/mysql-count-vs-sum?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru#comments</comments><dc:creator>devguru</dc:creator><title>MySQL count VS sum</title><pubDate>Thu, 28 Jan 2021 08:12:57 GMT</pubDate><tt:hashtag>mysql</tt:hashtag><tt:hashtag>sql</tt:hashtag><tt:hashtag>оптимизация_запросов</tt:hashtag><description><![CDATA[Небольшое сравнение двух способов посчитать кол-во записей.]]></description><content:encoded><![CDATA[
  <p>Небольшое сравнение двух способов посчитать кол-во записей.</p>
  <p>Табличка:</p>
  <pre>mysql&gt; show create table mobile_stat_device\G
*************************** 1. row ***************************
       Table: mobile_stat_device
Create Table: CREATE TABLE &#x60;mobile_stat_device&#x60; (
  &#x60;id&#x60; int unsigned NOT NULL AUTO_INCREMENT,
  &#x60;deviceUuid&#x60; char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT &#x27;Unique device ID&#x27;,
  &#x60;deviceUuidHash&#x60; int unsigned GENERATED ALWAYS AS (crc32(&#x60;deviceUuid&#x60;)) STORED NOT NULL,
  &#x60;createdAt&#x60; datetime NOT NULL COMMENT &#x27;Inserting date&#x27;,
  PRIMARY KEY (&#x60;id&#x60;),
  UNIQUE KEY &#x60;deviceUuid_udx&#x60; (&#x60;deviceUuidHash&#x60;,&#x60;deviceUuid&#x60;),
  KEY &#x60;deviceUuidHash_idx&#x60; (&#x60;deviceUuidHash&#x60;)
) ENGINE=InnoDB AUTO_INCREMENT=2490316 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.00 sec)
</pre>
  <p>Старый добрый <code>count</code>:</p>
  <pre>mysql&gt; select count(1) from mobile_stat_device\G
*************************** 1. row ***************************
count(1): 2097152
1 row in set (2.20 sec)
</pre>
  <p>Старый добрый <code>sum</code>:</p>
  <pre>mysql&gt; select sum(1) from mobile_stat_device\G
*************************** 1. row ***************************
sum(1): 2097152
1 row in set (0.97 sec)
</pre>
  <p><strong>SUM быстрее COUNT более чем в два раза.</strong></p>
  <p>При том, что планы выполнения совпадают до буквы.</p>
  <pre>mysql&gt; explain format=json select count(1) from mobile_stat_device\G
*************************** 1. row ***************************
EXPLAIN: {
  &quot;query_block&quot;: {
    &quot;select_id&quot;: 1,
    &quot;cost_info&quot;: {
      &quot;query_cost&quot;: &quot;213393.07&quot;
    },
    &quot;table&quot;: {
      &quot;table_name&quot;: &quot;mobile_stat_device&quot;,
      &quot;access_type&quot;: &quot;index&quot;,
      &quot;key&quot;: &quot;deviceUuidHash_idx&quot;,
      &quot;used_key_parts&quot;: [
        &quot;deviceUuidHash&quot;
      ],
      &quot;key_length&quot;: &quot;4&quot;,
      &quot;rows_examined_per_scan&quot;: 2088550,
      &quot;rows_produced_per_join&quot;: 2088550,
      &quot;filtered&quot;: &quot;100.00&quot;,
      &quot;using_index&quot;: true,
      &quot;cost_info&quot;: {
        &quot;read_cost&quot;: &quot;4538.07&quot;,
        &quot;eval_cost&quot;: &quot;208855.00&quot;,
        &quot;prefix_cost&quot;: &quot;213393.07&quot;,
        &quot;data_read_per_join&quot;: &quot;318M&quot;
      }
    }
  }
}
1 row in set, 1 warning (0.00 sec)</pre>
  <pre>mysql&gt; explain format=json select sum(1) from mobile_stat_device\G
*************************** 1. row ***************************
EXPLAIN: {
  &quot;query_block&quot;: {
    &quot;select_id&quot;: 1,
    &quot;cost_info&quot;: {
      &quot;query_cost&quot;: &quot;213393.07&quot;
    },
    &quot;table&quot;: {
      &quot;table_name&quot;: &quot;mobile_stat_device&quot;,
      &quot;access_type&quot;: &quot;index&quot;,
      &quot;key&quot;: &quot;deviceUuidHash_idx&quot;,
      &quot;used_key_parts&quot;: [
        &quot;deviceUuidHash&quot;
      ],
      &quot;key_length&quot;: &quot;4&quot;,
      &quot;rows_examined_per_scan&quot;: 2088550,
      &quot;rows_produced_per_join&quot;: 2088550,
      &quot;filtered&quot;: &quot;100.00&quot;,
      &quot;using_index&quot;: true,
      &quot;cost_info&quot;: {
        &quot;read_cost&quot;: &quot;4538.07&quot;,
        &quot;eval_cost&quot;: &quot;208855.00&quot;,
        &quot;prefix_cost&quot;: &quot;213393.07&quot;,
        &quot;data_read_per_join&quot;: &quot;318M&quot;
      }
    }
  }
}
1 row in set, 1 warning (0.00 sec)</pre>
  <tt-tags>
    <tt-tag name="mysql">#mysql</tt-tag>
    <tt-tag name="sql">#sql</tt-tag>
    <tt-tag name="оптимизация_запросов">#оптимизация_запросов</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.devgu.ru/mysql-auto-increment</guid><link>https://blog.devgu.ru/mysql-auto-increment?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru</link><comments>https://blog.devgu.ru/mysql-auto-increment?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru#comments</comments><dc:creator>devguru</dc:creator><title>AUTO_INCREMENT в MySQL</title><pubDate>Fri, 22 Jan 2021 08:19:58 GMT</pubDate><category>MySQL</category><tt:hashtag>mysql</tt:hashtag><tt:hashtag>оптимизация_запросов</tt:hashtag><description><![CDATA[Казалось бы, что может быть проще, чем автоинкремент в mysql. Но и с ним есть подводные камни и грабли, которые лучше знать и учитывать при проектировании баз данных.]]></description><content:encoded><![CDATA[
  <p>Казалось бы, что может быть проще, чем автоинкремент в mysql. Но и с ним есть подводные камни и грабли, которые лучше знать и учитывать при проектировании баз данных.</p>
  <p>Создадим тестовую табличку:</p>
  <pre>mysql&gt; create table test_ai(
    -&gt;     id int primary key not null auto_increment,
    -&gt;     alias varchar(255) not null,
    -&gt;     unique index (alias)
    -&gt; );
Query OK, 0 rows affected (0.04 sec)</pre>
  <p>Главное, что в ней есть - это уникальный ключ, по полю alias</p>
  <p>Вставим три записи</p>
  <pre>mysql&gt; insert into test_ai(alias) values (&#x27;alias1&#x27;), (&#x27;alias2&#x27;), (&#x27;alias3&#x27;);
Query OK, 3 rows affected (0.00 sec)</pre>
  <p>Посмотрим значение auto_increment в табличке</p>
  <pre>mysql&gt; show create table test_ai\G
*************************** 1. row ***************************
       Table: test_ai
Create Table: CREATE TABLE &#x60;test_ai&#x60; (
  &#x60;id&#x60; int NOT NULL AUTO_INCREMENT,
  &#x60;alias&#x60; varchar(255) NOT NULL,
  PRIMARY KEY (&#x60;id&#x60;),
  UNIQUE KEY &#x60;alias&#x60; (&#x60;alias&#x60;)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci</pre>
  <p>Всё вроде верно. Мы вставили 3 записи и следующее значение id будет равно 4.</p>
  <p><strong>Теперь начинается интересное.</strong> Попробуем выполнить такую же вставку что и предыдущая.</p>
  <pre>mysql&gt; insert into test_ai(alias) values (&#x27;alias1&#x27;), (&#x27;alias2&#x27;), (&#x27;alias3&#x27;);
ERROR 1062 (23000): Duplicate entry &#x27;alias1&#x27; for key &#x27;test_ai.alias&#x27;</pre>
  <p>Ожидаемо, получим ошибку. Посмотрим опять на значение auto_increment</p>
  <pre>mysql&gt; show create table test_ai\G
*************************** 1. row ***************************
       Table: test_ai
Create Table: CREATE TABLE &#x60;test_ai&#x60; (
  &#x60;id&#x60; int NOT NULL AUTO_INCREMENT,
  &#x60;alias&#x60; varchar(255) NOT NULL,
  PRIMARY KEY (&#x60;id&#x60;),
  UNIQUE KEY &#x60;alias&#x60; (&#x60;alias&#x60;)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.01 sec)</pre>
  <p><strong>7!</strong> То есть mysql сначала увеличивает счетчик, а потом вставляет. Ладно, подавим ошибку и еще раз вставим.</p>
  <pre>mysql&gt; insert ignore into test_ai(alias) values (&#x27;alias1&#x27;), (&#x27;alias2&#x27;), (&#x27;alias3&#x27;);
Query OK, 0 rows affected, 3 warnings (0.02 sec)
Records: 3  Duplicates: 3  Warnings: 3</pre>
  <p>И посмотрим.</p>
  <pre>mysql&gt; show create table test_ai\G
*************************** 1. row ***************************
       Table: test_ai
Create Table: CREATE TABLE &#x60;test_ai&#x60; (
  &#x60;id&#x60; int NOT NULL AUTO_INCREMENT,
  &#x60;alias&#x60; varchar(255) NOT NULL,
  PRIMARY KEY (&#x60;id&#x60;),
  UNIQUE KEY &#x60;alias&#x60; (&#x60;alias&#x60;)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)</pre>
  <p><strong>10!</strong> То есть автоинкремент увеличивается в любом случае.</p>
  <p><strong><em>Попробуем replace.</em></strong></p>
  <pre>mysql&gt; replace into test_ai(alias) values (&#x27;alias1&#x27;), (&#x27;alias2&#x27;), (&#x27;alias3&#x27;);
Query OK, 6 rows affected (0.00 sec)
Records: 3  Duplicates: 3  Warnings: 0</pre>
  <pre>mysql&gt; show create table test_ai\G
*************************** 1. row ***************************
       Table: test_ai
Create Table: CREATE TABLE &#x60;test_ai&#x60; (
  &#x60;id&#x60; int NOT NULL AUTO_INCREMENT,
  &#x60;alias&#x60; varchar(255) NOT NULL,
  PRIMARY KEY (&#x60;id&#x60;),
  UNIQUE KEY &#x60;alias&#x60; (&#x60;alias&#x60;)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)</pre>
  <p>Replace с одним валидным значением.</p>
  <pre>mysql&gt; replace into test_ai(alias) values (&#x27;alias1&#x27;), (&#x27;alias2&#x27;), (&#x27;alias3&#x27;), (&#x27;alias4&#x27;);
Query OK, 7 rows affected (0.01 sec)
Records: 4  Duplicates: 3  Warnings: 0</pre>
  <pre>mysql&gt; show create table test_ai\G
*************************** 1. row ***************************
       Table: test_ai
Create Table: CREATE TABLE &#x60;test_ai&#x60; (
  &#x60;id&#x60; int NOT NULL AUTO_INCREMENT,
  &#x60;alias&#x60; varchar(255) NOT NULL,
  PRIMARY KEY (&#x60;id&#x60;),
  UNIQUE KEY &#x60;alias&#x60; (&#x60;alias&#x60;)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)</pre>
  <p>При этом ID поменялись, что логично. Но про replace и его принцип работы, как-нибудь в другой раз.</p>
  <pre>mysql&gt; select * from test_ai
    -&gt; ;
+----+--------+
| id | alias  |
+----+--------+
| 13 | alias1 |
| 14 | alias2 |
| 15 | alias3 |
| 16 | alias4 |
+----+--------+</pre>
  <p>И еще раз сделаем replace.</p>
  <pre>mysql&gt; replace into test_ai(alias) values (&#x27;alias1&#x27;), (&#x27;alias2&#x27;), (&#x27;alias3&#x27;), (&#x27;alias4&#x27;);
Query OK, 8 rows affected (0.02 sec)
Records: 4  Duplicates: 4  Warnings: 0</pre>
  <pre>mysql&gt; select * from test_ai;
+----+--------+
| id | alias  |
+----+--------+
| 17 | alias1 |
| 18 | alias2 |
| 19 | alias3 |
| 20 | alias4 |
+----+--------+</pre>
  <p><strong><em>Попробуем обработать ошибку вставки через &#x27;on duplicate key update&#x27;</em></strong></p>
  <pre>mysql&gt; insert into test_ai(alias) values (&#x27;alias1&#x27;), (&#x27;alias2&#x27;), (&#x27;alias3&#x27;) on duplicate key update id = id;
Query OK, 0 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0
</pre>
  <pre>mysql&gt; show create table test_ai\G
*************************** 1. row ***************************
       Table: test_ai
Create Table: CREATE TABLE &#x60;test_ai&#x60; (
  &#x60;id&#x60; int NOT NULL AUTO_INCREMENT,
  &#x60;alias&#x60; varchar(255) NOT NULL,
  PRIMARY KEY (&#x60;id&#x60;),
  UNIQUE KEY &#x60;alias&#x60; (&#x60;alias&#x60;)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci</pre>
  <p><em><strong>Вывод:</strong> не зависит успешно или неуспешно вставлены данные, в любом случае auto_increment будет ползти вверх. А значит, при достаточных объёмах вставляемых данных (с любым результатом) auto_increment  очень быстро может достичь максимального значения для первичного ключа. Кроме того, необходимо учитывать возможную неравномерность распределения значений первичного ключа, например, в случае если <a href="https://blog.devgu.ru/mysql-offset-optimization" target="_blank">необходимости по нему итерироваться</a>.</em></p>
  <tt-tags>
    <tt-tag name="mysql">#mysql</tt-tag>
    <tt-tag name="оптимизация_запросов">#оптимизация_запросов</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.devgu.ru/mysql-offset-optimization</guid><link>https://blog.devgu.ru/mysql-offset-optimization?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru</link><comments>https://blog.devgu.ru/mysql-offset-optimization?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru#comments</comments><dc:creator>devguru</dc:creator><title>Оптимизация OFFSET в MySQL</title><pubDate>Mon, 11 Jan 2021 10:32:34 GMT</pubDate><category>MySQL</category><tt:hashtag>mysql</tt:hashtag><tt:hashtag>sql</tt:hashtag><tt:hashtag>оптимизация_запросов</tt:hashtag><description><![CDATA[Штука вроде очевидная, но всё же стоит её записать.]]></description><content:encoded><![CDATA[
  <p>Штука вроде очевидная, но всё же стоит её записать.</p>
  <p>В базах данных <code>offset</code> работает так: выбираются первые записи, где кол-во выбираемых записей равно <code>offset+limit</code>, потом записи до <code>offset</code> отбрасываются и остаток уже возвращается в запросе. Покажу на примере.</p>
  <p>Как всегда, любимая табличка <code>mobile_stat_device</code></p>
  <pre>mysql&gt; show create table mobile_stat_device\G
*************************** 1. row ***************************
       Table: mobile_stat_device
Create Table: CREATE TABLE &#x60;mobile_stat_device&#x60; (
  &#x60;id&#x60; int unsigned NOT NULL AUTO_INCREMENT,
  &#x60;deviceUuid&#x60; char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT &#x27;Unique device ID&#x27;,
  &#x60;deviceUuidHash&#x60; int unsigned GENERATED ALWAYS AS (crc32(&#x60;deviceUuid&#x60;)) STORED NOT NULL,
  &#x60;createdAt&#x60; datetime NOT NULL COMMENT &#x27;Inserting date&#x27;,
  PRIMARY KEY (&#x60;id&#x60;),
  UNIQUE KEY &#x60;deviceUuid_udx&#x60; (&#x60;deviceUuidHash&#x60;,&#x60;deviceUuid&#x60;),
  KEY &#x60;deviceUuidHash_idx&#x60; (&#x60;deviceUuidHash&#x60;)
) ENGINE=InnoDB AUTO_INCREMENT=2490316 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci</pre>
  <p>С большим количеством записей</p>
  <pre>mysql&gt; select count(1) from mobile_stat_device;
+----------+
| count(1) |
+----------+
|  2097152 |
+----------+</pre>
  <p>Запрос, который выполняется почти секунду:</p>
  <pre>mysql&gt; select SQL_NO_CACHE * from mobile_stat_device order by id limit 10 offset 2050000;
+---------+--------------------------------------+----------------+---------------------+
| id      | deviceUuid                           | deviceUuidHash | createdAt           |
+---------+--------------------------------------+----------------+---------------------+
| 2377645 | 370f4d5e-519b-11eb-a85f-42010a9c0002 |     1792791173 | 2021-01-08 10:21:03 |
| 2377646 | 370f4d6c-519b-11eb-a85f-42010a9c0002 |     3206227716 | 2021-01-08 10:21:03 |
| 2377647 | 370f4d7b-519b-11eb-a85f-42010a9c0002 |     3766230312 | 2021-01-08 10:21:03 |
| 2377648 | 370f4d89-519b-11eb-a85f-42010a9c0002 |     2525345549 | 2021-01-08 10:21:03 |
| 2377649 | 370f4d98-519b-11eb-a85f-42010a9c0002 |     3387067681 | 2021-01-08 10:21:03 |
| 2377650 | 370f4da6-519b-11eb-a85f-42010a9c0002 |     2785299536 | 2021-01-08 10:21:03 |
| 2377651 | 370f4db5-519b-11eb-a85f-42010a9c0002 |     1202520612 | 2021-01-08 10:21:03 |
| 2377652 | 370f4dc3-519b-11eb-a85f-42010a9c0002 |     3937153461 | 2021-01-08 10:21:03 |
| 2377653 | 370f4dd2-519b-11eb-a85f-42010a9c0002 |     1015628201 | 2021-01-08 10:21:03 |
| 2377654 | 370f4de1-519b-11eb-a85f-42010a9c0002 |     2783037901 | 2021-01-08 10:21:03 |
+---------+--------------------------------------+----------------+---------------------+
10 rows in set, 1 warning (0.50 sec)</pre>
  <p>С очень любопытным <code>explain</code>:</p>
  <pre>mysql&gt; explain format=json select SQL_NO_CACHE * from mobile_stat_device order by id limit 10 offset 2050000\G
*************************** 1. row ***************************
EXPLAIN: {
  &quot;query_block&quot;: {
    &quot;select_id&quot;: 1,
    &quot;cost_info&quot;: {
      &quot;query_cost&quot;: &quot;213695.60&quot;
    },
    &quot;ordering_operation&quot;: {
      &quot;using_filesort&quot;: false,
      &quot;table&quot;: {
        &quot;table_name&quot;: &quot;mobile_stat_device&quot;,
        &quot;access_type&quot;: &quot;index&quot;,
        &quot;key&quot;: &quot;PRIMARY&quot;,
        &quot;used_key_parts&quot;: [
          &quot;id&quot;
        ],
        &quot;key_length&quot;: &quot;4&quot;,
        &quot;rows_examined_per_scan&quot;: 2050010,
        &quot;rows_produced_per_join&quot;: 2088550,
        &quot;filtered&quot;: &quot;100.00&quot;,
        &quot;cost_info&quot;: {
          &quot;read_cost&quot;: &quot;4840.61&quot;,
          &quot;eval_cost&quot;: &quot;208855.00&quot;,
          &quot;prefix_cost&quot;: &quot;213695.61&quot;,
          &quot;data_read_per_join&quot;: &quot;318M&quot;
        },
        &quot;used_columns&quot;: [
          &quot;id&quot;,
          &quot;deviceUuid&quot;,
          &quot;deviceUuidHash&quot;,
          &quot;createdAt&quot;
        ]
      }
    }
  }
}</pre>
  <p>Для того, что бы отдать 680 байт mysql прочитал 318 мегабайт. Кол-во затронутых записей <code>rows_examined_per_scan = 2050010 = 2050000(offset) + 10(limit)</code></p>
  <p>И что самое плохое в этом запросе - с увеличением <code>offset</code> производительность и дальше будет деградировать.</p>
  <h3>Как быть?</h3>
  <p>В данном конкретном случае, можно воспользоваться тем, что id идут по порядку и переписать запрос так</p>
  <pre>mysql&gt; select SQL_NO_CACHE * from mobile_stat_device where id between 2377645 and 2377654 order by id;
+---------+--------------------------------------+----------------+---------------------+
| id      | deviceUuid                           | deviceUuidHash | createdAt           |
+---------+--------------------------------------+----------------+---------------------+
| 2377645 | 370f4d5e-519b-11eb-a85f-42010a9c0002 |     1792791173 | 2021-01-08 10:21:03 |
| 2377646 | 370f4d6c-519b-11eb-a85f-42010a9c0002 |     3206227716 | 2021-01-08 10:21:03 |
| 2377647 | 370f4d7b-519b-11eb-a85f-42010a9c0002 |     3766230312 | 2021-01-08 10:21:03 |
| 2377648 | 370f4d89-519b-11eb-a85f-42010a9c0002 |     2525345549 | 2021-01-08 10:21:03 |
| 2377649 | 370f4d98-519b-11eb-a85f-42010a9c0002 |     3387067681 | 2021-01-08 10:21:03 |
| 2377650 | 370f4da6-519b-11eb-a85f-42010a9c0002 |     2785299536 | 2021-01-08 10:21:03 |
| 2377651 | 370f4db5-519b-11eb-a85f-42010a9c0002 |     1202520612 | 2021-01-08 10:21:03 |
| 2377652 | 370f4dc3-519b-11eb-a85f-42010a9c0002 |     3937153461 | 2021-01-08 10:21:03 |
| 2377653 | 370f4dd2-519b-11eb-a85f-42010a9c0002 |     1015628201 | 2021-01-08 10:21:03 |
| 2377654 | 370f4de1-519b-11eb-a85f-42010a9c0002 |     2783037901 | 2021-01-08 10:21:03 |
+---------+--------------------------------------+----------------+---------------------+
10 rows in set, 1 warning (0.00 sec)
</pre>
  <p>Explain:</p>
  <pre>mysql&gt; explain format=json select SQL_NO_CACHE * from mobile_stat_device where id between 2377645 and 2377654 order by id\G
*************************** 1. row ***************************
EXPLAIN: {
  &quot;query_block&quot;: {
    &quot;select_id&quot;: 1,
    &quot;cost_info&quot;: {
      &quot;query_cost&quot;: &quot;2.57&quot;
    },
    &quot;ordering_operation&quot;: {
      &quot;using_filesort&quot;: false,
      &quot;table&quot;: {
        &quot;table_name&quot;: &quot;mobile_stat_device&quot;,
        &quot;access_type&quot;: &quot;range&quot;,
        &quot;possible_keys&quot;: [
          &quot;PRIMARY&quot;
        ],
        &quot;key&quot;: &quot;PRIMARY&quot;,
        &quot;used_key_parts&quot;: [
          &quot;id&quot;
        ],
        &quot;key_length&quot;: &quot;4&quot;,
        &quot;rows_examined_per_scan&quot;: 10,
        &quot;rows_produced_per_join&quot;: 10,
        &quot;filtered&quot;: &quot;100.00&quot;,
        &quot;cost_info&quot;: {
          &quot;read_cost&quot;: &quot;1.57&quot;,
          &quot;eval_cost&quot;: &quot;1.00&quot;,
          &quot;prefix_cost&quot;: &quot;2.57&quot;,
          &quot;data_read_per_join&quot;: &quot;1K&quot;
        },
        &quot;used_columns&quot;: [
          &quot;id&quot;,
          &quot;deviceUuid&quot;,
          &quot;deviceUuidHash&quot;,
          &quot;createdAt&quot;
        ],
        &quot;attached_condition&quot;: &quot;(&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;id&#x60; between 2377645 and 2377654)&quot;
      }
    }
  }
}</pre>
  <p>Вот, стало значительно веселее.</p>
  <p>Если необходимо пробежаться по всем записям таблички чтобы, например, сделать экспорт, то можно использовать условие <code>id &gt; самого большого id прошлой выборки, но id &lt;= самого большого id прошлой выборки + limit + 1</code>. Условием остановки итераций будет значение <code>id &gt;= максимального значения id</code></p>
  <p>Запросы для отображения по страницам - для чего обычно и используют лимиты и офсеты, тут смысла не имеют из-за большого количества данных. Если же наложить какие-то внешние фильтры, уменьшив там самым кол-во выбираемых данных, то и offset будет работать быстро.</p>
  <tt-tags>
    <tt-tag name="mysql">#mysql</tt-tag>
    <tt-tag name="sql">#sql</tt-tag>
    <tt-tag name="оптимизация_запросов">#оптимизация_запросов</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.devgu.ru/mysql-slow-sql-story</guid><link>https://blog.devgu.ru/mysql-slow-sql-story?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru</link><comments>https://blog.devgu.ru/mysql-slow-sql-story?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru#comments</comments><dc:creator>devguru</dc:creator><title>MySQL. История одного медленного запроса</title><pubDate>Fri, 08 Jan 2021 11:38:07 GMT</pubDate><category>MySQL</category><tt:hashtag>mysql</tt:hashtag><tt:hashtag>sql</tt:hashtag><tt:hashtag>оптимизация_запросов</tt:hashtag><description><![CDATA[SQL запросы иногда бывают не оптимизированы, из-за неправильного понимания механизмов работы]]></description><content:encoded><![CDATA[
  <p>SQL запросы иногда бывают не оптимизированы, из-за неправильного понимания механизмов работы</p>
  <p>Есть вот такая табличка:</p>
  <pre>mysql&gt; show create table mobile_stat_device\G
*************************** 1. row ***************************
       Table: mobile_stat_device
Create Table: CREATE TABLE &#x60;mobile_stat_device&#x60; (
  &#x60;id&#x60; int unsigned NOT NULL AUTO_INCREMENT,
  &#x60;deviceUuid&#x60; char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT &#x27;Unique device ID&#x27;,
  &#x60;deviceUuidHash&#x60; int unsigned GENERATED ALWAYS AS (crc32(&#x60;deviceUuid&#x60;)) STORED NOT NULL,
  &#x60;createdAt&#x60; datetime NOT NULL COMMENT &#x27;Inserting date&#x27;,
  PRIMARY KEY (&#x60;id&#x60;),
  UNIQUE KEY &#x60;deviceUuid_udx&#x60; (&#x60;deviceUuidHash&#x60;,&#x60;deviceUuid&#x60;),
  KEY &#x60;deviceUuidHash_idx&#x60; (&#x60;deviceUuidHash&#x60;)
) ENGINE=InnoDB AUTO_INCREMENT=2490316 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci</pre>
  <p>Табличка с <code>uuid</code> устройств. Для ускорения запросов добавлено поле  <code>deviceUuidHash</code> и создан комбинированный уникальный индекс по полям <code>deviceUuidHash</code> и <code>deviceUuid</code>.</p>
  <p>Кол-во записей в mobile_stat<em>_</em>device чуть больше 2х миллионов:</p>
  <pre>mysql&gt; select count(1) from mobile_stat_device;
+----------+
| count(1) |
+----------+
|  2097152 |
+----------+
</pre>
  <p>Нам необходимо выбрать <code>id</code> для определенных <code>uuid</code> устройств. Мой первый запрос выглядел вот так:</p>
  <pre>mysql&gt; select id, deviceUuid from mobile_stat_device where 
(deviceUuid = &#x27;36fffc8d-519b-11eb-a85f-42010a9c0002&#x27; and deviceUuidHash = crc32(deviceUuid)) or
(deviceUuid = &#x27;36fffd73-519b-11eb-a85f-42010a9c0002&#x27; and deviceUuidHash = crc32(deviceUuid));
+---------+--------------------------------------+
| id      | deviceUuid                           |
+---------+--------------------------------------+
| 2327645 | 36fffc8d-519b-11eb-a85f-42010a9c0002 |
| 2327653 | 36fffd73-519b-11eb-a85f-42010a9c0002 |
+---------+--------------------------------------+</pre>
  <p>Посмотрев на <code>explain</code>, я успокоился - индекс используется, ну и ладно</p>
  <pre>
mysql&gt; explain select id, deviceUuid from mobile_stat_device where (deviceUuid = &#x27;36fffc8d-519b-11eb-a85f-42010a9c0002&#x27; and deviceUuidHash=crc32(deviceUuid)) or (deviceUuid = &#x27;36fffd73-519b-11eb-a85f-42010a9c0002&#x27; and deviceUuidHash = crc32(deviceUuid));
+----+-------------+--------------------+------------+-------+---------------+----------------+---------+------+---------+----------+--------------------------+
| id | select_type | table              | partitions | type  | possible_keys | key            | key_len | ref  | rows    | filtered | Extra                    |
+----+-------------+--------------------+------------+-------+---------------+----------------+---------+------+---------+----------+--------------------------+
|  1 | SIMPLE      | mobile_stat_device | NULL       | index | NULL          | deviceUuid_udx | 148     | NULL | 2088550 |     1.99 | Using where; Using index |
+----+-------------+--------------------+------------+-------+---------------+----------------+---------+------+---------+----------+--------------------------+</pre>
  <p>Я не обратил внимание на количество <code>rows</code> в эксплейне. Точнее, когда данных мало, то и кол-во <code>rows</code> приемлемое. Ошибочность запроса стала понятна только когда нагрузил данными табличку и еще раз проверил скорость запроса.</p>
  <p>Вот тот же <code>explain</code> в более информативном виде.</p>
  <pre>
mysql&gt; explain format=json select id, deviceUuid from mobile_stat_device where (deviceUuid = &#x27;36fffc8d-519b-11eb-a85f-42010a9c0002&#x27; and deviceUuidHash=crc32(deviceUuid)) or (deviceUuid = &#x27;36fffd73-519b-11eb-a85f-42010a9c0002&#x27; and deviceUuidHash = crc32(deviceUuid))\G
*************************** 1. row ***************************
EXPLAIN: {
  &quot;query_block&quot;: {
    &quot;select_id&quot;: 1,
    &quot;cost_info&quot;: {
      &quot;query_cost&quot;: &quot;216917.41&quot;
    },
    &quot;table&quot;: {
      &quot;table_name&quot;: &quot;mobile_stat_device&quot;,
      &quot;access_type&quot;: &quot;index&quot;,
      &quot;key&quot;: &quot;deviceUuid_udx&quot;,
      &quot;used_key_parts&quot;: [
        &quot;deviceUuidHash&quot;,
        &quot;deviceUuid&quot;
      ],
      &quot;key_length&quot;: &quot;148&quot;,
      &quot;rows_examined_per_scan&quot;: 2088550,
      &quot;rows_produced_per_join&quot;: 41562,
      &quot;filtered&quot;: &quot;1.99&quot;,
      &quot;using_index&quot;: true,
      &quot;cost_info&quot;: {
        &quot;read_cost&quot;: &quot;212761.19&quot;,
        &quot;eval_cost&quot;: &quot;4156.21&quot;,
        &quot;prefix_cost&quot;: &quot;216917.41&quot;,
        &quot;data_read_per_join&quot;: &quot;6M&quot;
      },
      &quot;used_columns&quot;: [
        &quot;id&quot;,
        &quot;deviceUuid&quot;,
        &quot;deviceUuidHash&quot;
      ],
      &quot;attached_condition&quot;: &quot;(((&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuid&#x60; = &#x27;36fffc8d-519b-11eb-a85f-42010a9c0002&#x27;) and (&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuidHash&#x60; = crc32(&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuid&#x60;))) or ((&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuid&#x60; = &#x27;36fffd73-519b-11eb-a85f-42010a9c0002&#x27;) and (&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuidHash&#x60; = crc32(&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuid&#x60;))))&quot;
    }
  }
}
</pre>
  <p>О чём это говорит? Запрос пробегает по 2088550 записям и для ответа использует 41562. А так быть не должно. Где-то ошибка.</p>
  <p>Внимательный читатель уже догадался где. Вот в этом условии - <code>and deviceUuidHash = crc32(deviceUuid)</code>. Похоже, MySQL сначала вычисляет значение <code>crc32</code> для всех записей, а потом уже, используя индекс, делается селект.</p>
  <p>Переписанный запрос.</p>
  <pre>mysql&gt; explain format=json select id, deviceUuid from mobile_stat_device
where (deviceUuid = &#x27;36fffc8d-519b-11eb-a85f-42010a9c0002&#x27; and 
deviceUuidHash=crc32(&#x27;36fffc8d-519b-11eb-a85f-42010a9c0002&#x27;)) or 
(deviceUuid = &#x27;36fffd73-519b-11eb-a85f-42010a9c0002&#x27; and 
deviceUuidHash = crc32(&#x27;36fffd73-519b-11eb-a85f-42010a9c0002&#x27;))\G
*************************** 1. row ***************************
EXPLAIN: {
  &quot;query_block&quot;: {
    &quot;select_id&quot;: 1,
    &quot;cost_info&quot;: {
      &quot;query_cost&quot;: &quot;0.85&quot;
    },
    &quot;table&quot;: {
      &quot;table_name&quot;: &quot;mobile_stat_device&quot;,
      &quot;access_type&quot;: &quot;range&quot;,
      &quot;possible_keys&quot;: [
        &quot;deviceUuid_udx&quot;,
        &quot;deviceUuidHash_idx&quot;
      ],
      &quot;key&quot;: &quot;deviceUuid_udx&quot;,
      &quot;used_key_parts&quot;: [
        &quot;deviceUuidHash&quot;,
        &quot;deviceUuid&quot;
      ],
      &quot;key_length&quot;: &quot;148&quot;,
      &quot;rows_examined_per_scan&quot;: 2,
      &quot;rows_produced_per_join&quot;: 2,
      &quot;filtered&quot;: &quot;100.00&quot;,
      &quot;using_index&quot;: true,
      &quot;cost_info&quot;: {
        &quot;read_cost&quot;: &quot;0.65&quot;,
        &quot;eval_cost&quot;: &quot;0.20&quot;,
        &quot;prefix_cost&quot;: &quot;0.85&quot;,
        &quot;data_read_per_join&quot;: &quot;320&quot;
      },
      &quot;used_columns&quot;: [
        &quot;id&quot;,
        &quot;deviceUuid&quot;,
        &quot;deviceUuidHash&quot;
      ],
      &quot;attached_condition&quot;: &quot;(((&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuidHash&#x60; = &lt;cache&gt;(crc32(&#x27;36fffc8d-519b-11eb-a85f-42010a9c0002&#x27;))) and (&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuid&#x60; = &#x27;36fffc8d-519b-11eb-a85f-42010a9c0002&#x27;)) or ((&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuidHash&#x60; = &lt;cache&gt;(crc32(&#x27;36fffd73-519b-11eb-a85f-42010a9c0002&#x27;))) and (&#x60;test&#x60;.&#x60;mobile_stat_device&#x60;.&#x60;deviceUuid&#x60; = &#x27;36fffd73-519b-11eb-a85f-42010a9c0002&#x27;)))&quot;
    }
  }
}</pre>
  <p>Обратите внимание, насколько упал <code>query_cost</code> и насколько меньше записей выбирается.</p>
  <p>Вывода два:</p>
  <ol>
    <li>Быть осторожнее с вычислительными значениями в запросах, если эти значения используются в where.</li>
    <li>Нагружать БД данными и потом уже проверять насколько корректно отрабатывают запросы. </li>
  </ol>
  <tt-tags>
    <tt-tag name="mysql">#mysql</tt-tag>
    <tt-tag name="sql">#sql</tt-tag>
    <tt-tag name="оптимизация_запросов">#оптимизация_запросов</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.devgu.ru/cloudflare-workers</guid><link>https://blog.devgu.ru/cloudflare-workers?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru</link><comments>https://blog.devgu.ru/cloudflare-workers?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru#comments</comments><dc:creator>devguru</dc:creator><title>Cloudflare workers - хорошее решение для быстрого старта.</title><pubDate>Mon, 04 Jan 2021 08:37:36 GMT</pubDate><tt:hashtag>javascript</tt:hashtag><tt:hashtag>cloudflare</tt:hashtag><tt:hashtag>cloudflare_workers</tt:hashtag><description><![CDATA[Вы наверняка знаете, что такое cloudflare, но знаете ли вы, что у них есть очень клёвая услуга для построения бессерверных (serverless) приложений? Воркеры! ]]></description><content:encoded><![CDATA[
  <p>Вы наверняка знаете, что такое <a href="https://www.cloudflare.com/" target="_blank">cloudflare</a>, но знаете ли вы, что у них есть очень клёвая услуга для построения бессерверных (serverless) приложений? <a href="https://developers.cloudflare.com/workers/" target="_blank">Воркеры!</a> </p>
  <p>Услуга нацелена прежде всего на разработчиков мобильных приложений, чтобы без всякого геморроя поднимать бэкенды для своих аппликух. Кроме обработчиков запросов имеется KV-хранилище и даже воркеры, которые работают по расписанию. Отладчик, песочница - всё в наличии. Что еще надо, чтобы поднять простенький бэкэнд?</p>
  <p>А если учесть, что воркер может запускаться на тысячах машин распределенных по миру, выбирая наиболее близкую для клиента локацию, решение мне видится идеальным для быстрого старта. </p>
  <p>И самое главное - вся эта красота бесплатна, если запросов меньше, чем 100 000 в день.</p>
  <p>Однако, данную технологию могут использовать не только фронтендеры, но и все желающие, подключив фантазию. Например, можно <a href="https://medium.com/@TarasPyoneer/how-to-set-up-a-custom-domain-for-your-homepage-in-notion-53fa3d54f848" target="_blank">прикрутить собственный домен для notion</a>.<br /><br />Я же на коленке написал воркер, который отдаёт <a href="https://blog.devgu.ru/tool-github-gist-to-irame-converter" target="_blank">код конвертера</a> и добавил его в статью teletype через iframe. Вот что получилось:</p>
  <figure class="m_column">
    <iframe src="data:text/html;charset=utf-8, <head><base target=&#x27;_blank&#x27; /></head> <body><script src=&#x27;https://gist.github.com/bojik/24e03ea511edd4c41b02a02c2d940454.js&#x27;></script> </body>"></iframe>
    <figcaption>Воркер клаудфлеер для оттдачи кода конвертера</figcaption>
  </figure>
  <tt-tags>
    <tt-tag name="javascript">#javascript</tt-tag>
    <tt-tag name="cloudflare">#cloudflare</tt-tag>
    <tt-tag name="cloudflare_workers">#cloudflare_workers</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.devgu.ru/tool-github-gist-to-irame-converter</guid><link>https://blog.devgu.ru/tool-github-gist-to-irame-converter?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru</link><comments>https://blog.devgu.ru/tool-github-gist-to-irame-converter?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru#comments</comments><dc:creator>devguru</dc:creator><title>Конвертер из GitHub Gist в iframe представление</title><pubDate>Wed, 30 Dec 2020 08:07:36 GMT</pubDate><category>Инструменты</category><tt:hashtag>github_gist</tt:hashtag><tt:hashtag>github</tt:hashtag><tt:hashtag>teletype_in</tt:hashtag><tt:hashtag>tools</tt:hashtag><description><![CDATA[Помучился с гистами и телетайпом и решил упростить себе, а заодно и еще кому-то, жизнь - сделать на коленке конвертер.]]></description><content:encoded><![CDATA[
  <p>Помучился с гистами и телетайпом и решил упростить себе, а заодно и еще кому-то, жизнь - сделать на коленке конвертер.</p>
  <p>Сформированный код необходимо вставить в блок iframe телетайпа и всё должно работать ;-)</p>
  <figure class="m_column">
    <iframe src="https://soft-sound-4002.devguru.workers.dev/"></iframe>
    <figcaption>Converter embed code of github gist to iframe</figcaption>
  </figure>
  <tt-tags>
    <tt-tag name="github_gist">#github_gist</tt-tag>
    <tt-tag name="github">#github</tt-tag>
    <tt-tag name="teletype_in">#teletype_in</tt-tag>
    <tt-tag name="tools">#tools</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://blog.devgu.ru/github-gist-teletype-in</guid><link>https://blog.devgu.ru/github-gist-teletype-in?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru</link><comments>https://blog.devgu.ru/github-gist-teletype-in?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=devguru#comments</comments><dc:creator>devguru</dc:creator><title>Как разместить github gist в постах на платформе Teletype</title><pubDate>Tue, 22 Dec 2020 08:41:13 GMT</pubDate><tt:hashtag>github</tt:hashtag><tt:hashtag>github_gist</tt:hashtag><tt:hashtag>teletype_in</tt:hashtag><tt:hashtag>блоггерское</tt:hashtag><description><![CDATA[<img src="https://teletype.in/files/79/15/7915fea9-1228-4eb8-90c0-904717b9383d.jpeg"></img>Открыть контекст текстового блока, выбрать iframe.]]></description><content:encoded><![CDATA[
  <p>Открыть контекст текстового блока, выбрать iframe.</p>
  <figure class="m_original">
    <img src="https://teletype.in/files/79/15/7915fea9-1228-4eb8-90c0-904717b9383d.jpeg" width="234" />
    <figcaption>В контекстном меню выбать iframe</figcaption>
  </figure>
  <p>Поправить следующий код, указав правильные размеры и embed код на нужный гист. <strong>Не забыть заменить в коде гиста двойные кавычки на одинарные.</strong> </p>
  <p><strong>UPD:</strong> Если руками делать не хочется, милости прошу, <a href="https://blog.devgu.ru/tool-github-gist-to-irame-converter" target="_blank">автоматическое средство</a> </p>
  <p>Разместить код в открывшемся окошке.</p>
  <figure class="m_column">
    <iframe src="data:text/html;charset=utf-8, <head><base target=&#x27;_blank&#x27; /></head> <body><script src=&#x27;https://gist.github.com/Albert-W/e37d1c4fa30c430c37d7b1b1fe9b60d8.js&#x27;></script> </body>"></iframe>
  </figure>
  <tt-tags>
    <tt-tag name="github">#github</tt-tag>
    <tt-tag name="github_gist">#github_gist</tt-tag>
    <tt-tag name="teletype_in">#teletype_in</tt-tag>
    <tt-tag name="блоггерское">#блоггерское</tt-tag>
  </tt-tags>

]]></content:encoded></item></channel></rss>