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

СУБД
Навроцкий Артем
Лекция 9

Рейтинг СУБД

СУБД Тип May 2020 Apr 2020 May 2019
1 Oracle Relational DBMS 1345.44 +0.02 +59.89
2 MySQL Relational DBMS 1282.64 +14.29 +63.67
3 Microsoft SQL Server Relational DBMS 1078.30 -5.12 +6.12
4 PostgreSQL Relational DBMS 514.80 +4.95 +35.91
5 MongoDB Document store 438.99 +0.57 +30.92
6 DB2 Relational DBMS 162.64 -2.99 -11.80
7 Elasticsearch Search engine 149.13 +0.22 +0.51
8 Redis Key-value store 144.17 -1.33 -4.93
9 SQLite Relational DBMS 123.09 +0.84 +0.14
10 Microsoft Access Relational DBMS 119.90 -2.02 -23.88

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

Сравнение с 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 для модификации таблицы без копирования её содержимого.

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

By default, ResultSets are completely retrieved and stored in memory. In most cases this is the most efficient way to operate and, due to the design of the MySQL network protocol, is easier to implement. If you are working with ResultSets that have a large number of rows or large values and cannot allocate heap space in your JVM for the memory required, you can tell the driver to stream the results back one row at a time.

To enable this functionality, create a Statement instance in the following manner:

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

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

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: bozaro@yandex.ru
Спасибо за внимание!