2014-07-07

MySQL: UNIQUE KEY + unicode

В прошлой заметке я писал про хранение 4-байтных символов в MySQL и упомянул UTF8MB4, как расширенный вариант UTF8. Оно решает ту задачу, и можно было бы объявить про универсальный и лучший на свете collation, но сегодня я столкнулся с некоторым исключением, которое внесло ложечку дегтя в мои впечатления.

При создании уникального индекса на некое поле с юникодными данными получил ошибку о дублирующихся данных. А ведь их там нет. Проверка показала, что для UTF8MB4 строки "ame", "âme", "Amè", "AMÉ", "ÁME", "Ãme" и "ÂMÈ" одинаковые. Это говорит и SELECT.

Так вот, интересно то, что установив collation в UTF8_BIN я получил нужный результат. Понятное дело, что в этом случае сравнение идет побайтово и всё такое. Фокус в том, что для UTF8MB4_BIN такого эффекта не получаем! Разработчики схитрили или упустили этот момент? Кто знает..

2014-07-01

MySQL: хранение 4-байтных символов юникода

По умолчанию в полях VARCHAR при установленном collation UTF8 не могут храниться строки с символами юникода, состоящими из более чем 3 байт. А такие символы существуют, хоть и не очень часто встречаются. Например "\xf0\x9f\x99\x8e".

Решение в том, чтобы использовать модернизированный collation, UTF8MB4. Просто установите такой на нужной таблице и можно работать. Возможно перед вставкой стоит выполнить команду "SET NAMES UTF8MB4"

P.S. Для поисковиков: "Warning: Incorrect string value"

2014-06-25

MSSQL: псевдопартиционирование

Допустим, у нас есть (а у нас есть) таблица огромных размеров, с огромными индексами и вообще всё у неё огромное. Скажем, это записи из твиттера. Из этой таблицы часто делаются выборки по полю DATETIME. И, хотя на этом поле есть индекс, и несомненно есть Primary Key (вобщем все как надо), доступ к данным осуществляется дольше, чем хотелось бы: для использования индекс загружается в оперативную память, а он, как вы помните, огромный.

Здравые размышления приводят к выводу - нужно табличку поделить, чтобы индексы стали поменьше и помещались в память без проблем. В тоже время шардить табличку не хочется, периодически она нужна целиком. Ну и расширяться пока возможностей нет.

Вариантом временного решения может стать VIEW. Мы разделим таблицу на несколько поменьше, которые будут содержать данные в некоторых заранее известных границах. Создадим VIEW, в котором укажем на эти пределы:

CREATE VIEW table AS
SELECT * FROM table1 WHERE created_at BETWEEN '2014-02-01 00:00:00.000' AND '2014-03-01 00:00:00.000'
UNION ALL
SELECT * FROM table2 WHERE created_at BETWEEN '2014-03-01 00:00:00.000' AND '2014-04-01 00:00:00.000'

Фокус в том, что если запросить данные с указанием условия, присутствующего во VIEW, таблица, не удовлетворяющая этому условию затронута не будет вовсе. Например:

SELECT * FROM table WHERE created_at > '2014-03-01 00:00:00.000'

заденет только вторую таблицу, соответственно поднимет только ее индекс. Как-то так.

2014-06-20

MongoDB: шардинг по быстрому

Шардинг, как известно, позволяет раскидать данные по нескольким серверам БД, распределяя их согласно некоторым условиям. В MongoDB это бесплатно, так сказать all inclusive, к тому же невероятно просто.

Запустим несколько шардов, например на разных портах (или серверах, если есть). Я это делаю в нескольких терминалах, чтобы видеть, что происходит. Но можно добавить ключ --fork для отвязки от терминала.

$ mongod --dbpath db1 --port 27001 --shardsvr
$ mongod --dbpath db2 --port 27002 --shardsvr
$ mongod --dbpath db3 --port 27003 --shardsvr

Запустим шард-сервер и назначим ему один из шардов (на порту 27001) в помощь - там он будет хранить информацию о том, куда что записал:

$ mongos --configdb 127.0.0.1:27001

Цепляемся к шард-серверу, подключаем шарды, указываем что шардить и по какому ключу:

$ mongo
> use admin
> db.runCommand({addshard: "127.0.0.1:27002"})
> db.runCommand({addshard: "127.0.0.1:27003"})
> db.runCommand({enablesharding: "social"})
> db.runCommand({shardcollection: "social.tweets", key: {id:1}})

MongoDB: репликация по быстрому

Вдохновленный этой краткой заметкой решил сделать себе мемо в блоге, дополнив некоторыми замечаниями. Итак, репликация в несколько строк:
Создаем каталоги для данных:

mkdir -p db1 db2 db3

В терминалах запустим несколько реплик в одном наборе "myset", на своих портах и со своими каталогами для данных:

$ mongod --port 27001 --dbpath db1 --replSet myset
$ mongod --port 27002 --dbpath db2 --replSet myset
$ mongod --port 27003 --dbpath db3 --replSet myset

Подключаемся к одной из реплик, делаем ее "мастером", подключаем к ней остальные реплики и проверяем:

$ mongo --port 27001
> rs.initiate()
> rs.add("myhost:27002")
> rs.add("myhost:27003")
> use test
> db.test.insert({id:1})
> db.test.find()

где "myhost" - имя хоста, которое можно увидеть в выводе команды rs.status()

Примечательно и прекрасно, что реплики могут находиться на разных машинах, увеличивая бутылочное горлышко I/O. В своих экспериментах я размещал реплики на PC с Windows 7 на борту, на Macbook и на Ubuntu в VirtualBox.

Важно, чтобы машина с репликой могла однозначно интерпретировать хост "мастера" в IP. На макбуке мне для этого пришлось добавить запись в /etc/hosts:

myhost 192.168.10.130