2014-09-16

Работа с Azure SQL из PHP в Ubuntu

Приведу пример, как это делается в Ubuntu. Установим нужные пакеты

sudo apt-get install php5-cli php5-sybase freetds*

Изменим файл концигурации FreeTDS /etc/freetds/freetds.conf, добавив секцию

[xxxserver.database.windows.net]
    host = xxxserver.database.windows.net
    port = 1433
    tds version = 8.0

Короткий кусочек кода на PHP5

<?php
$server = 'xxxserver';
$dbname = 'xxxdbname';
$user = "xxxuser@{$server}";
$passwd = 'xxxpassword';

$p = new PDO("dblib:host={$server}.database.windows.net;dbname={$dbname}", $user, $passwd);
foreach($p->query('SELECT col FROM table') as $row){
        echo $row['term'];
}
?>

Это очень актуально для Yii-фреймворка.

2014-08-13

Python: how to stop process correctly

Одной из задач, с которой встречаются разработчики многопоточных/многопроцессных приложений, является их корректная остановка. Особенно это касается таких потоков/процессов, в которых осуществляется бесконечная блокирующая операция, типа цикла "while True" или чтения из некоего генератора.

while True:
    # do something
С потоками проще, потому что им можно передать ссылку на объект, который можно проверять при каждой итерации и останавливать при соблюдении условий.

import time
from threading import Thread

def in_thread(con):
    while con[0]:
        # ... doing something
        pass

con = [True]
t = Thread(target=in_thread, args=(con,))
t.start()

time.sleep(3)
# Stop the thread
con[0] = False
t.join()
С процессами несколько иначе, они более изолированные, и нужно использовать объекты синхронизации. Очень удобный способ мне подсказал коллега, с использованием Lock.

import time
from threading import Thread
from multiprocessing import Process, Lock

def in_process(lock):

    def in_thread(con):
        while con[0]:
            pass
            # ... doing something

    con = [True]
    t = Thread(target=in_thread, args=(con,))
    t.daemon = True
    t.start()

    # Lock acquired in parent process, child process waits, but thread already ran!
    lock.acquire()

    # Polite way
    con[0] = False
    t.join()

    # Way for impatient
    # import os
    # os._exit(0)


lock = Lock()
lock.acquire()
Process(target=in_process, args=(lock,)).start()

time.sleep(3)
# ... process in action ...

# Release lock, child process going to be finished
lock.release()

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'

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