Показаны сообщения с ярлыком mysql. Показать все сообщения
Показаны сообщения с ярлыком mysql. Показать все сообщения

понедельник, 14 сентября 2015 г.

Логирование всех запросов в MySQL 5.6.x

В процессе выяснения для чего magento 2.x требуется пользователь с GRANT ALL вместо GRANT ALL PRIVILEGES столкнулся с тем, что теперь переменная log больше не воспринимается в MySQL 5.6.x. Оказывается теперь верный способ для логирования выглядит так:

# cat > /etc/mysql/conf.d/logs.cnf << _EOF_
[mysqld]
log_output=FILE
general_log=ON
general_log_file=/var/log/mysql/general.log
_EOF_

После внесения изменений нужно перезагрузить сервер:

# service mysql restart

Если вам не подходит перезагрузка, то тоже самое можно сделать без перезагрузки через SET GLOBAL:

mysql> SET GLOBAL log_output=FILE;
mysql> SET GLOBAL general_log_file='/var/log/mysql/general.log';
mysql> SET GLOBAL general_log=ON;

Проверено на Percona Server 5.6.25-73.1.

UPDATE: Как оказалось, magento 2 не требует GRANT ALL на пользователя, вполне достаточно GRANT ALL PRIVILEGES, но разрешение нужно дать на конкретную базу, а не по маске.

понедельник, 1 сентября 2014 г.

Немного подробностей о работе access check в MySQL

Если вы создали в MySQL пользователя someuser@'%' и не можете взять в толк, почему его не пускает с localhost, то вам по этой ссылке.

Более подробно вопрос раскрыт в официальной документации MySQL.

Чтобы продебажить приоритет в котором выдается доступ, нужно выполнить следующий запрос

SELECT Host,User FROM mysql.user ORDER BY Host DESC, User DESC;

В результате вы получите что-то вроде такой выборки

+-------------+-----------------+
| Host        | User            |
+-------------+-----------------+
| mysql55.lan | root            |
| mysql55.lan |                 |
| localhost   | root            |
| localhost   |                 |
| ::1         | root            |
| 127.0.0.1   | root            |
| %           | someuser        |
+-------------+-----------------+

При таком раскладе берется первое совпадение - Host="localhost", User="" (пустое поле User означает любого пользователя) и следовательно до обработки someuser дело не доходит.

пятница, 31 мая 2013 г.

Сломанный collation utf8_general_ci в MySQL 5.1.x

Наступил на забавные грабли при копировании базы с mysql 5.0.51 на mysql 5.1.49. Сделал дамп базы и скопировал на тестовый сервер, но при импорте произошла ошибка

ERROR 1062 (23000) at line 8615879: Duplicate entry 'Voß' for key 'ticketwords'

Пробую найти значение в уже добавленных строках, которое конфликтует со значением 'Voß'

mysql> select * from swticketwords where contents = 'Voß';
+--------------+----------+
| ticketwordid | contents |
+--------------+----------+
|        20080 | Vos      |
+--------------+----------+
1 row in set (0.00 sec)

Ерунда какая-то. Получается, что база считает 'Vos' = 'Voß' и отказывается добавлять значение. Начинаю гуглить и сразу натыкаюсь на репорты в багтрекере MySQL [1] [2]. Если в кратце, то разрабы поломали collation utf8_general_ci (который работал в mysql 5.0.x) и отказываются его чинить в mysql 5.1.x, мотивируя это тем, что фикс сломает еще больше.

Чтобы хоть как-то решить проблему был создан отдельный collation utf8_general_mysql500_ci, который возвращает старое поведение. Я поправил дамп, указав явный collation для таблицы swticketwords, и база успешно импортировалась. Чтобы показать разницу между utf8_general_ci и utf8_general_mysql500_ci я сделал запросы со старым и новым collation.

mysql> select * from swticketwords where contents = 'Voß' collate utf8_general_ci;
+--------------+----------+
| ticketwordid | contents |
+--------------+----------+
|        20080 | Vos      |
|       126378 | Voß      |
+--------------+----------+
2 rows in set (0.00 sec)

На поле contents создан уникальный ключ ticketwords, и двух значений быть не должно.
mysql> select * from swticketwords where contents = 'Voß' collate utf8_general_mysql500_ci;
+--------------+----------+
| ticketwordid | contents |
+--------------+----------+
|       126378 | Voß      |
+--------------+----------+
1 row in set (0.00 sec)

Ссылки:
1) http://bugs.mysql.com/bug.php?id=27877
2) http://bugs.mysql.com/bug.php?id=43593
3) http://dev.mysql.com/doc/refman/5.1/en/charset-unicode-sets.html

среда, 21 ноября 2012 г.

Создание дампа базы MySQL в котором используется отдельный INSERT на каждую строку данных

Вчера в процессе прикручивания автоматического создания зон в связке PowerDNS + PowerAdmin мне понадобилось узнать какие данные добавляет PowerAdmin при создании новой зоны. Сперва полез в код, но быстро бросил эту затею.

Следующей мыслью было сравнить дамп базы powerdns до и после создания зоны в poweradmin. Если вы когда-то просматривали дампы, которые по-умолчанию создает mysqldump, то наверняка видели, что там значения в таблицу вставляются через один вызов INSERT. Что-то вроде INSERT INTO `tablename` VALUES (a1, a2, a3, ..., aN), (b1, b2, b3, ..., bN), ...

В итоге таблица где много записей будет выглядеть не информативно при сравнении дампа до и после создания зоны в poweradmin. Чтобы решить эту проблему нужно либо применить "умный" diff, либо заставить mysqldump формировать вставку каждой строки данных через отдельный INSERT.

После пары экспериментов в опциями из man mysqldump нашлась искомая:

$ mysqldump --skip-extended-insert dbname > dbname.sql

При использовании этой опции дамп выглядит так:

INSERT INTO `tablename` VALUES (a1, a2, a3, ..., aN);
INSERT INTO `tablename` VALUES (b1, b2, b3, ..., bN);
...

После этого в выводе diff отлично видно какие данные были добавлены в БД после создания зоны.

понедельник, 19 ноября 2012 г.

Настройка MySQL master-slave репликации

Настраивать master-slave репликацию в MySQL приходится редко, но постоянно приходится подглядывать мануал. Чтобы свести эту операцию к "одному касанию" я добавил этот пост (подходит для Debian-based дистрибутивов, для остальных подойдет если копировать вдумчиво).

Установка MySQL сервера


Устанавливаем MySQL сервер и клиент

# aptitude install mysql-server mysql-client

Далее я убираю удаленный вход в базу под root, тестовую базу и тестового пользователя. Для автоматизации этих действий существует скрипт mysql_secure_installation, входящий в дистрибутив MySQL сервера.

# mysql_secure_installation

Чтобы каждый раз не вводить пароль при подключении к базе я создаю конфиг для консольного клиента

# touch /root/.my.cnf
# chown root:root /root/.my.cnf
# chmod 0600 /root/.my.cnf
# cat > /root/.my.cnf <<EOF
[client]
host=localhost
user=root
password=your-root-password
EOF

Настройка MySQL master ноды


В Debian-based дистрибутивах есть возможность создать локальный конфиг mysql не изменяя /etc/mysql/my.cnf (чтобы не было конфликта при обновлении пакета). Я пользуюсь этой возможностью

# cat > /etc/mysql/conf.d/master.cnf <<EOF
[mysqld]
server-id = 500
log-bin = mysql-bin
log-bin-index = mysql-bin.index
max_binlog_size = 512M
expire_logs_days = 30
bind-address = 0.0.0.0
EOF

# invoke-rc.d mysql restart

При настройке master ноды нужно уделить внимание выбору значения server-id (оно должно быть уникальным в пределах группы серверов, вовлеченных в репликацию). Опции max_binlog_size и expire_logs_days управляют ротацией бинарного лога.

Далее нужно создать пользователя который будет использоваться slave нодами для репликации.

mysql> CREATE USER 'slave-user'@'slave-host' IDENTIFIED BY 'slave-password';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'slave-user'@'slave-host';
mysql> FLUSH PRIVILEGES;

Далее нужно подготовить слепок баз данных, которые мы будем реплицировать

mysql> FLUSH TABLES WITH READ LOCK;
mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000004 |     3211 |              |                  |
+------------------+----------+--------------+------------------+
1 row in set (0.00 sec)

Сейчас нужно записать имя файла бинарного лога (mysql-bin.000004) и позицию в этом логе (3211). Далее в этой же сессии выполнить

mysql> \!mysqldump --add-drop-database --all-databases > /tmp/all-db.sql
mysql> UNLOCK TABLES;

Файл /tmp/all-db.sql нужно передать на slave ноду.

Настройка MySQL slave ноды


Для работы MySQL сервера в качестве slave ноды нужно внести пару изменений в его дефолтный конфиг.

# cat > /etc/mysql/conf.d/slave.cnf <<EOF
[mysqld]
server-id = 510
relay-log = relay-bin
relay-log-index = relay-bin.index
EOF

# invoke-rc.d mysql restart

Далее нужно импортировать файл /tmp/all-db.sql, который был создан на master ноде

# mysql < /tmp/all-db.sql
# mysqladmin flush-privileges

Затем нужно подключить slave ноду к master серверу и запустить репликацию.

mysql> CHANGE MASTER TO master_host='master-host', master_user='slave-user', master_password='slave-password', master_log_file='mysql-bin.000004', master_log_pos=3211;
mysql> START SLAVE;

Посмотреть статус репликации можно командой SHOW SLAVE STATUS\G. Если все работает правильно, то в полях Slave_IO_Running и Slave_IO_Running должно быть значение Yes.

пятница, 29 июня 2012 г.

Увеличение числа одновременно открытых файлов в MySQL сервере

Сегодня довелось решать проблему с превышением лимита одновременно открытых файлов у MySQL сервера. Ошибка, которая свидетельствует о такой проблеме выглядит так:

MySQL Error: Out of resources when opening file './db_name/table_name.MYD' (Errcode: 24)

Поскольку все коды ошибок я на память не помню, то первым делом заглянул в вывод perror:

$ perror 24
OS error code  24:  Too many open files

Смотрим сколько разрешено открывать файловых дескрипторов пользователю mysql (пользователь, с правами которого работает MySQL сервер).

$ sudo su - mysql -c 'ulimit -n'
1024

Далее смотрим, сколько дескрипторов разрешено в MySQL:

$ mysql -se "show variables like 'open_files_limit'"
Variable_name       Value
open_files_limit    1024

Увеличим лимит в 4 раза, до 4096:

$ echo 'mysql  -  nofile  4096' | sudo tee -a /etc/security/limits.d/mysql.conf
$ sudo su - mysql -c 'ulimit -n'
4096

Если лимит не изменился, то нужно посмотреть где подключен pam_limits.so в конфигурации PAM.

$ grep -r limits /etc/pam.*
/etc/pam.d/sshd:session    required     pam_limits.so
/etc/pam.d/atd:session    required   pam_limits.so
/etc/pam.d/login:session    required   pam_limits.so
/etc/pam.d/sudo:session required pam_limits.so
/etc/pam.d/cron:session    required   pam_limits.so
/etc/pam.d/su:# session    required   pam_limits.so

Для Debian нужно добавить строку session required pam_limits.so в /etc/pam.d/common-session

$ echo 'session required pam_limits.so' | sudo tee -a /etc/pam.d/common-session

Осталось поправить конфиг MySQL, чтобы сервер начал использовать новый лимит. Для этого в my.cnf в секцию [mysqld] добавляем строчку

open_files_limit = 4096

пятница, 4 мая 2012 г.

Пароль админа в MySQL сервере если стоит панель Plesk

Если на сервере стоит панель управления Parallels Plesk, то зачастую владелец сервера понятия не имеет о пароле root в MySQL. Чтобы узнать его самостоятельно логинимся в SSH под root. Затем находим пароль в /etc/psa/.psa.shadow

# cat /etc/psa/.psa.shadow
therootpassword

Имя пользователя будет не root, а admin. Чтобы не вводить его каждый раз руками я рекомендую сохранить его в ~/.my.cnf (доступный на чтение только root)

# touch ~/.my.cnf
# chown root:root ~/.my.cnf
# chmod 600 ~/.my.cnf
# cat > ~/.my.cnf << EOF
[client]
user=admin
password=therootpassword
EOF

После этого mysql и mysqladmin просить пароль перестанут.

UPDATE: На одном из серверов столкнулся с тем, что в /etc/psa/.psa.shadow пароль не в plain text, а в виде хеша. Рядом лежит файлик /etc/psa/README.psa.shadow в котором говорится следующее
Starting from version 10.2, Panel encrypts the Administrator's password before
saving it to /etc/psa/.psa.shadow. For security reasons, only users with root
permissions are able to get the plain password. To obtain the password, such
users should run the following command:

/usr/local/psa/bin/admin --show-password
Выполнив эту команду вы получите пароль для пользователя "root" (в старых версиях был "admin").

вторник, 27 марта 2012 г.

MySQL: backup and partial restore

Одна из методик создания резервных копий баз данных MySQL основывается на использовании снимков LVM. Скрипт, который отвечает за создание резервной копии выглядит примерно так:

#!/bin/bash
MYSQL_VG="VolGroup00"
MYSQL_LV="mysql"
MYSQL_SRC="/mnt/mysql-root"
MYSQL_DST="root@backup.server:/backup/mysql-server"

echo "FLUSH TABLES WITH READ LOCK; \! /sbin/lvcreate -s -n ${MYSQL_LV}-backup -L 1G ${MYSQL_VG}/${MYSQL_LV}" | mysql
echo "UNLOCK TABLES" | mysql
mkdir ${MYSQL_SRC}
mount /dev/${MYSQL_VG}/${MYSQL_LV} ${MYSQL_SRC} -o ro
rsync -avzxHS --delete --numeric-ids ${MYSQL_SRC}/ ${MYSQL_DST}/
umount ${MYSQL_SRC}
rmdir ${MYSQL_SRC}
lvchange -an ${MYSQL_VG}/${MYSQL_LV}-backup
lvremove ${MYSQL_VG}/${MYSQL_LV}-backup

Примечание: примеры ориентированы на Debian Squeeze и в других дистрибутивах могут потребоваться доработки.

В самом начале буферы таблиц в MySQL базах сбрасываются на диск и вводится блокировка операций записи в таблицы. Затем создается снимок LVM тома, на котором расположены базы MySQL. После создания снимка снимается блокировка записи и MySQL продолжает работать в нормальном режиме. Расплатой за это удобство является снижение скорости дисковых операций записи в MySQL в то время, пока активен снимок LVM.

Далее снимок монтируется как обычное блочное устройство и файлы, в которых MySQL хранит базы данных, переносятся в резервную копию любым удобных способом. В примере используется rsync, что позволяет передавать по сети только измененившиеся части файлов. По окончании переноса файлов, снимок LVM размонтируется и удаляется из менеждера томов.

Таким образом делается бэкап без остановки работы сервера. Однако создание резервных копий не самоцель, их нужно также восстанавливать в случае потери данных или необходимости получить доступ к более раннему состоянию баз данных. Самый простой способ восстановить резервную копию, созданную описанных выше способом, это остановить сервер MySQL и скопировать данные обратно.

# service mysql stop
# rsync -avxzHS --delete --numeric-ids root@backup.server:/backup/mysql-server/ /var/lib/mysql/
# service mysql start

Такой способ восстановления подходит, если нужно вернуть все базы в исходное состояние на момент создания резервной копии. Но что делать, если нужно восстановить только одну базу, одну таблицу или только несколько записей из конкретной таблицы? Что если основной сервер не допускает остановки? Конечно можно поднять отдельный MySQL сервер на другом сервере, скопировать туда бэкап и после запуска вытащить из него нужные данные посредством mysqldump. Но что делать, если другого сервера нет или версия MySQL сервера содержит патчи, которые делают резервную копию несовместимой с дистрибутивной версией? Я постараюсь дать одно из возможных решений этой проблемы.

В своем способе я также буду запускать отдельную копию MySQL сервера, но основная идея заключается в том, чтобы не копировать данные из бэкапа, а примонтировать их через сеть. Я буду использовать sshfs для этих целей. Чтобы при запуске второго сервера не изменились данные в бэкапе я применю оверлей на базе AUFS. Одна из его частей будет примонтированным бэкапом, а вторая - хранить все изменения относительно бэкапа. Таким образом я избегаю любых модификаций резервной копии.

# mkdir /mnt/mysql-{backup,datadir,tmp}
# sshfs root@backup.server:/backup/mysql-server /mnt/mysql-backup
# mount -t aufs none /mnt/mysql-datadir -o dirs=/mnt/mysql-tmp:/mnt/mysql-backup=ro
# mysqld_safe --defaults-file=/root/mysql-backup-restore.cnf

/root/mysql-backup-restore.cnf - конфигурация для второго сервера, которую я сделал на базе конфига из дистрибутива.

[mysqld]
user=root
pid-file        = /root/mysql-restore.pid
socket          = /root/mysql-restore.sock
basedir         = /usr
datadir         = /mnt/mysql-datadir
tmpdir          = /tmp
language        = /usr/share/mysql/english
skip-networking
skip-external-locking
key_buffer              = 16M
max_allowed_packet      = 16M
thread_stack            = 192K
thread_cache_size       = 8
myisam-recover         = BACKUP
query_cache_limit       = 1M
query_cache_size        = 16M
expire_logs_days        = 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet      = 16M

[isamchk]
key_buffer              = 16M

!includedir /etc/mysql/conf.d/

После запуска вторая копия MySQL сервера доступна только через сокет /root/mysql-restore.sock, чем позволяет исключить посторонний доступ на время восстановления данных. Чтобы подключиться к запущенному серверу, нужно явно указать сокет:

# mysql -S /root/mysql-restore.sock
# mysqldump -S /root/mysql-restore.sock --add-drop-table dbname table1 table2 > dbrestore.sql

После того, как данные сняты посредством mysqldump нужно остановить вторую копию сервера. Для этого нужно выполнить:

# mysqladmin -S /root/mysql-restore.sock shutdown

Когда дополнительный сервер остановится можно отмонтировать /mnt/mysql-datadir и /mnt/mysql-backup, и удалить директории /mnt/mysql-{backup,datadir,tmp}

Несомненным преимуществом такого подхода является время, которое тратится на частичное восстановление резервной копии. Из недостатков пожалуй отсутствие sshfs и aufs во многих дистрибутивах (для centos я так и не смог найти пакета для добавления поддержки aufs). Возможно его следует заменить на любую другую реализацию union fs, но я это делать не пробовал.