СУБД. Лекция 9

СУБД
Алехин Владислав
Лекция 9

Организационные моменты

Рейтинг СУБД

СУБД Тип Apr 2021 Mar 2021 Apr 2020
1 Oracle Relational DBMS 1274.92 -46.82 -70.51
2 MySQL Relational DBMS 1220.69 -34.14 -47.66
3 Microsoft SQL Server Relational DBMS 1007.97 -7.33 -75.46
4 PostgreSQL Relational DBMS 553.52 +4.23 +43.66
5 MongoDB Document store 469.97 +7.58 +31.54
6 DB2 Relational DBMS 157.78 +1.77 -7.85
7 Redis Key-value store 155.89 +1.74 +11.08
8 Elasticsearch Search engine 152.18 -0.16 +3.27
9 SQLite Relational DBMS 125.06 +2.42 +2.87
10 Microsoft Access Relational DBMS 116.72 -1.41 -5.19

https://db-engines.com/en/ranking

Релизный цикл MySQL

Release General availability Latest version Latest release End of support
5.1 14 November 2008; 12 years ago 5.1.73 2013-12-03Dec 2013
5.5 3 December 2010; 10 years ago 5.5.62 2018-10-22Dec 2018
5.6 5 February 2013; 8 years ago 5.6.51 2021-01-20Feb 2021
5.7 21 October 2015; 5 years ago 5.7.34 2021-04-20Oct 2023
8.0 19 April 2018; 3 years ago 8.0.24 2021-04-20Apr 2026

Сравнение с MySQL

Архитектура MySQL

Архитектура MySQL

Архитектура MySQL

В MySQL различные хранилища объектов реализованы в виде подключаемых модулей.

Это порождает конфликт интересов:

Данная особенность архитектуры MySQL проходит красной нитью через весь функционал.

Подсистемы хранения: MyISAM

Подсистемы хранения: InnoDB

Подсистемы хранения: Memory

Подсистемы хранения: критерии выбора

Подсистемы хранения: надежность

Подсистемы хранения: практические примеры

Подсистемы хранения: за бортом

Не транзакционный DDL

Метаданные дублируются как в ядре MySQL, так и в его хранилище. Возникает проблема с транзакционным обновлении метаданных.

Из-за этого DDL-запросы в MySQL не транзакционны.

То есть, любой DDL-запрос автоматически подтверждает старую транзакцию и начинает новую. Этот факт может сильно усложнять написание скриптов миграции.

Дорогой ALTER TABLE

По той же причине, в MySQL почти любой ALTER TABLE влечет за собой полное пересоздание таблицы (по крайней мере в InnoDB). В том числе это касается таких операций как удаление CONSTRAINT-ов, удаление столбцов и создание NULL-столбцов.

В большинстве СУБД подобные операции требуют только изменения метаданных и выполняются мгновенно.

С MySQ 5.7.4 появился флаг ALGORITHM=INPLACE для модификации таблицы без копирования её содержимого.

Особенности выборки данных

По умолчанию резьтаты полностью извлекаются и сохраняются в памяти. В большинстве случаев это наиболее эффективный способ работы и, благодаря архитектуре MySQL, это проще в реализации. Если вы работаете с наборами результатов, которые имеют большое количество строк и не могут выделить пространство в heap вашей JVM, Можно указать драйверу передавать результаты обратно по одной строке за раз.

Для включения этого функционала нужно создать экземпляр Statement следующим образом:

stmt = conn.createStatement(
    java.sql.ResultSet.TYPE_FORWARD_ONLY,
    java.sql.ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);

Архитектура MySQL

В MySQL хранилище и лог транзакций существуют за пределами "движка" СУБД.

Как следствие, в MySQL вместо журнала транзакций для репликации используется отдельный журнал.

Репликация в MySQL

Режимы репликации:

STATEMENT

Сохраняются непосредственно sql запросы, которые несут информацию об изменениях записей.

ROW

Сохраняется информация о конкретных изменениях определенных записей.

В случае DDL-выражений сохраняются сами запросы.

MIXED

Промежуточный формат, который старается использовать statement, когда возможно, а когда нет — row.

Репликация в MySQL

Особенности планировщика запросов

Особенности планировщика запросов

Гарантированно плохие запросы

SELECT title, description FROM foo
UNION ALL
SELECT title, description FROM bar
ORDER BY title;

SELECT * FROM (
    SELECT title, MIN(description) as desc
    FROM film
    GROUP BY title
) f
WHERE f.title LIKE 'al%';

SELECT ... IN ...

SELECT * FROM sakila.film
WHERE film_id
IN (1,23,25,106,277);

SELECT * FROM sakila.film
WHERE film_id = 1
   OR film_id = 23
   OR film_id = 25
   OR film_id = 106
   OR film_id = 277;
    

DELETE

Стандартный синтаксис

DELETE FROM t1
WHERE
    t1.id IN (SELECT t2.id FROM t2);

Специфичный синтаксис

DELETE t1
FROM
    t1 JOIN t2 ON (t1.id = t2.id);

Что еще плохо?

WARNING vs ERROR

CREATE TABLE data (
  id  INT NOT NULL,
  foo INT NULL,
  bar NUMERIC(4, 2)
);
INSERT INTO data VALUES (1, NULL, 1234.5678);


SELECT * FROM data;
+----+------+-------+
| id | foo  | bar   |
+----+------+-------+
|  1 | NULL | 99.99 |
+----+------+-------+
1 rows in set (0,00 sec)

WARNING vs ERROR

sql_mode = 'STRICT_TRANS_TABLES'

Данная опция включена по-умолчанию с MySQL 5.7.5.

Первый General Availability релиз с данным изменением 5.7.9 вышел 21 октября 2015 года.

WARNING vs ERROR

ALTER TABLE data
  MODIFY COLUMN foo INT NOT NULL;


SELECT * FROM data;
+----+-----+-------+
| id | foo | bar   |
+----+-----+-------+
|  1 |   0 | 99.99 |
+----+-----+-------+
1 rows in set (0,00 sec)

AUTO_INCREMENT

TIMESTAMP vs DATETIME

TIMESTAMP:

DATETIME:

TIMESTAMP

CREATE TABLE k (
  A INT NULL,
  B NUMERIC (4, 2),
  C TIMESTAMP
);


INSERT INTO k (A, B) VALUES (42, 22.7);


SELECT * FROM k;

TIMESTAMP

SELECT * FROM k;
+----+-------+---------------------+
| A  | B     |                     |
+----+-------+---------------------+
| 42 | 22.70 | 2017-04-27 20:27:17 |
+----+-------+---------------------+
1 rows in set (0,00 sec)


UPDATE k SET B = B + 1;


SELECT * FROM k;

TIMESTAMP

CREATE TABLE k (
  A INT NULL,
  B NUMERIC (4, 2),
  C TIMESTAMP,
  D TIMESTAMP
);
Error Code: 1067. Invalid default value for 'D'

TIMESTAMP

CREATE TABLE k (
  A INT NULL,
  B NUMERIC (4, 2),
  C TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  D TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
0 rows updated (0,00 sec)

GROUP BY

SELECT A, B, SUM(C)
FROM K
GROUP BY A;

UPDATE

В MySQL операция UPDATE не соответствует стандарту SQL92.

13.9 <update statement: positioned>
6) The <value expression>s are effectively evaluated before updating the object row. If a <value expression> contains a reference to a column of T, then the reference is to the value of that column in the object row before any value of the object row is updated.

Иллюстрируется запросом:

UPDATE foo SET
    a = b,
    b = a
WHERE id = 42;

lower_case_table_names

В MySQL есть параметр lower_case_table_names, который приводит имена всех таблиц в нижний регистр и по-умолчанию включён под Windows.

В результате, если взять базу, у которой имена таблиц не в нижнем регистре, и развернуть её под Windows, то перенести эту базу обратно под Linux уже не получится.

REPEATABLE READ (PostgreSQL)

# BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
400

# UPDATE accounts SET balance = balance + 100
$ WHERE name = 'Alice';
UPDATE 1

# COMMIT;

# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
500
            
# BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
400







# UPDATE accounts SET balance = balance + 100
$ WHERE name = 'Alice';
ERROR:  could not serialize access
        due to concurrent update
            

REPEATABLE READ (MySQL)

# SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
# BEGIN;
# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
400

# UPDATE accounts SET balance = balance + 100
$ WHERE name = 'Alice';
Query OK, 1 row affected (0.00 sec)

# COMMIT;

# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
500
            
# SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
# BEGIN;
# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
400







# UPDATE accounts SET balance = balance + 100
$ WHERE name = 'Alice';
Query OK, 1 row affected (0.00 sec)

# COMMIT;

# SELECT balance  FROM accounts WHERE name = 'Alice';
balance
---------
600
            

REPEATABLE READ (MySQL)

# SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
# BEGIN;
# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
400

# UPDATE accounts SET balance = balance + 100
$ WHERE name = 'Alice';
Query OK, 1 row affected (0.00 sec)









# COMMIT;

# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
500
            
# SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
# BEGIN;









# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
400

# UPDATE accounts SET balance = balance + 100
$ WHERE name = 'Alice';


Query OK, 1 row affected (0.00 sec)

# COMMIT;

# SELECT balance FROM accounts WHERE name = 'Alice';
balance
---------
600
            

Что хорошо?

Секционирование

CREATE TABLE orders_range (
    customer_surname VARCHAR(30),
    store_id INT,
    salesperson_id INT,
    order_date DATE,
    note VARCHAR(500)
) ENGINE = InnoDB
PARTITION BY RANGE (YEAR (order_date)) (
    PARTITION p_old VALUES LESS THAN(2008),
    PARTITION p_2008 VALUES LESS THAN(2009),
    PARTITION p_2009 VALUES LESS THAN(MAXVALUE)
);

Секционирование

RANGE

По диапазону значений

PARTITION BY RANGE (store_id) (
    PARTITION p0 VALUES LESS THAN (10),
    PARTITION p1 VALUES LESS THAN (20),
    PARTITION p3 VALUES LESS THAN (30)
);

Секционирование

LIST

По точному списку значений

PARTITION BY LIST(store_id) (
    PARTITION pNorth VALUES IN (3,5,6,9,17),
    PARTITION pEast VALUES IN (1,2,10,11,19,20)
);

Секционирование

HASH

По хэшу от какой-либо функции

PARTITION BY HASH(YEAR(hired))
PARTITIONS 10;

KEY

Почти то же самое что и HASH, но по ключу

PARTITION BY KEY(s1)
PARTITIONS 10;

Секционирование

COLLATIONS

MySQL для регистронезависимого сравнения использует COLLATIONS (например: utf8_general_ci):
http://collation-charts.org/mysql60/mysql604.utf8_general_ci.european.html

Проблемы PostgreSQL:

libslave

Библиотека на C++, которая может быть использована в вашем приложении для получения обновлений из MySQL. Libslave не связана на уровне кодов с MySQL-сервером; она собирается и линкуется только с клиентом — libmysqlclient.

Базовые параметры

Кэш MyISAM

key_buffer_size
25 – 50 % от общего объема памяти, зарезервированного для MyISAM кэшей.

key_buffer_1.key_buffer_size = 1G
key_buffer_2.key_buffer_size = 1G

CACHE INDEX t1, t2 IN key_buffer_1;
LOAD INDEX INTO CACHE t1, t2;

Эту SQL-команду можно поместить в файл, выполняемый MySQL на этапе запуска. Имя файла задается с помощью параметра init_file.

В нем может быть несколько SQL-команд, каждая в отдельной строке.

Размер блока ключей key_cache_block_size

Кэш InnoDB

innodb_buffer_pool_size
В отличие от кэша ключей MyISAM, в пуле буферов InnoDB кэшируются не только индексы, там также хранятся сами данные, буфер вставок, блокировки и другие внутренние структуры. В InnoDB пул буферов используется также для реализации отложенных операций записи и позволяет объединить несколько таких процедур, чтобы затем выполнить их последовательно.
Рекомендуется выставить до 80% физической памяти.
innodb_max_dirty_pages_pct
Говорит InnoDB о допустимом количестве «грязных» (модифицированных) страниц в пуле буферов.

Кэш InnoDB

В MySQL есть возможность сохранить состояние кэша, чтобы избежать проблемы с прогревом базы.

SET GLOBAL innodb_buffer_pool_dump_at_shutdown = ON;
SET GLOBAL innodb_buffer_pool_dump_now = ON;
SET GLOBAL innodb_buffer_pool_load_now = ON;

SHOW STATUS LIKE 'Innodb_buffer_pool_dump_status';
SHOW STATUS LIKE 'Innodb_buffer_pool_load_status';

Ввод / вывод в MyISAM

delay_key_write

Определяет, когда будут сбрасываться данные на диск.

OFF
MyISAM сбрасывает измененные блоки из буфера ключей после каждой записи, если только таблица не блокирована командой LOCK TABLES.
ON
Включен режим отложенной записи ключей, но только для таблиц, созданных с параметром DELAY_KEY_WRITE.
ALL
Для всех таблиц типа MyISAM используется отложенная запись ключей.
ALTER TABLE sometable DELAY_KEY_WRITE = 1;

Ввод / вывод в MyISAM

Ввод / вывод в InnoDB

Ввод / вывод в InnoDB

innodb_log_file_size
Общий максимальный размер файла логов транзакций.
innodb_log_files_in_group
Количество файлов в группе.
innodb_log_buffer_size
Размер буфера лога транзакций.

Ввод / вывод в InnoDB

mysql> pager grep sequence
PAGER SET TO 'grep sequence'

mysql> SHOW engine innodb STATUS\G SELECT sleep(60); SHOW engine innodb STATUS\G
Log sequence number 84 3836410803
 1 row IN SET (0.06 sec)
 1 row IN SET (1 min 0.00 sec)
Log sequence number 84 3838334638
 1 row IN SET (0.05 sec)

mysql> SELECT (3838334638 - 3836410803) / 1024 / 1024 AS MB_per_min;

+------------+
| MB_per_min |
+------------+
| 1.83471203 |
+------------+

Размер лога выбираем примерно на 10-60 минут работы сервера.

Ввод / вывод в InnoDB

innodb_flush_log_at_trx_commit

0
Писать буфер в файл журнала и сбрасывать журнал на устройство постоянного хранения (диск) раз в секунду, но ничего не делать в момент фиксации транзакции.
1
Писать буфер в файл журнала и сбрасывать его на устройство постоянного хранения при каждой фиксации транзакции.
2
Писать буфер в файл журнала при каждой фиксации, но не сбрасывать его на устройство постоянного хранения.
Алехин Владислав
E-mail: v.alehin@corp.mail.ru
Спасибо за внимание!