MySQLdb & client encoding

Opublikowane przez Jarosław Zabiełło Tue, 15 Aug 2006 23:28:00 GMT

[vide: English version]

MySQLdb to główna biblioteka dla Pythona obsługująca bazę MySQL. Baza ta (od werseji 4.1 wzwyż) ma wbudowany wygodny mechanizm translacji znaków jakie mają być wyświetlane dla klienta. Oczywiście, należy założyć, że natywnym formatem danych dla bazy to UTF-8. Za pomocą prostej kwerendy (SET NAMES latin2) można wymusić aby wszelkie napisy były wypluwane do klienta w wybranym formacie (tu: iso-8859-2).

Ostatnio, przy okazji pracy z Pylons, przyjrzałem się bardzo ciekawemu projektowi: SQLAlchemy. Wszystko wskazuje na to, że SQLObject odejdzie do lamusa (tym bardziej że jego twórca coś go porzucił i planuje stworzyć nowy SQLObject2). SQLAlchemy jest nie tylko znacznie poteżniejszym ORM, ale ma także znacznie lepszą dokumentację niż SQLObject – ok. 117 stron samego tylko manuala.

Jedyny problem, jakie to typowe, to brak czytelnej opcji translacji polskich znaków. Podobny problem swego czasu znalazłem w Django (wysłałem patcha i to poprawili dodając na sztywno “SET NAMES utf8”) W wypadku SQLAlchemy nie ma opcji do definicji kodowania po stronie klienta. Manual wspomina tylko o ustawieniu kodowania dla bazy, a to nie to samo. Okazuje, się, że rozwiązanie jest bardzo proste. Podczas tworzenia połączenia do bazy wystarczy użyć dodatkowych parametrów. Nie ma potrzeby bawienia się w żadne kwerendy “SET NAMES”. Owe parametry to use_unicode i charset:

import MySQLdb
conn = MySQLdb.connect(
    user='root', 
    passwd='', 
    db='test', 
    use_unicode=False, 
    charset='cp1250')

W powyższym wypadku, MySQL będzie zakładał że klient ma odebrać teksty w formacie stringów cp1250. Zaś w wypadku użycia “use_unicode=True” zwracane będą śliczne obiekty unicodowe! Przykładowy plik konfiguracyjny dla Pylonsa korzystającego z SQLAlchemy (config/init.py) może zatem wyglądać np. tak:

from sqlalchemy import *
import sqlalchemy.pool as pool
import MySQLdb

def getconn():
    return MySQLdb.connect(
        user='root', 
        passwd='', 
        db='test', 
        use_unicode=False, 
        charset='utf8')

db = create_engine(
    'mysql://root:@localhost/test',
    pool=pool.QueuePool(getconn, pool_size=20, max_overflow=40), 
    strategy='threadlocal')
metadata = BoundMetaData(db)

test_table = Table('test', metadata, autoload=True)

class Test(object):  
    def __str__(self):  
        return self.title  

test_mapper = mapper(Test, test_table) 

W powyższym przykładzie, wymusiłem kodowanie utf8 dla klienta oraz połączenie z bazą działać będzie w puli 20-40 wątków. Właśnie tego mi brakuje w Django: pracy wielowątkowej, bo zużywa ona mniej pamięci. W/w model można użyć w kontrolerze Pylons (controllers/home.py) np. tak:

from myproject.lib.base import *
from myproject.models import *

class HomeController(BaseController):
  def index(self):        
      c.rows = select([test_table.c.id, test_table.c.name]).execute()
      return render_response('/home.myt')

Posted in , ,  | Tagi , , ,  | 4 comments

MySQL5 - rehabilitacja

Opublikowane przez Jarosław Zabiełło Tue, 30 May 2006 12:04:00 GMT

Po paru godzinach testów i walki z bazami MySQL 5.0.21 i PostgreSQL 8.1.4, muszę zwrócić trochę honoru bazie MySQL. PostgreSQL to bardzo zaawansowana i potężna baza danych, jednakże, jak pisałem wcześniej, ma jeden, zasadniczy feler, który ją wyklucza z moich zastosowań – kiepską obsługę różnych kodowań w ramach jednej bazy danych. Próba napisania aplikacji wielojęzycznej sortującej i wyszukującej z uwzględnianiem wielu różnych języków jest bardzo trudna. Praktycznie nie ma sensu aby do tego użyć PostgreSQL.

Tutaj pokazuje swoje możliwości MySQL 5. na razie nie mam potwierdzonych informacji o słabej stabilności tak jak to było z wersjami wcześniejszymi. Zaś problem dziwacznego zachowania się warunku LIKE… otóż udało mi się za pomocą prób i błędów połączonych z przeszukiwaniem dokumentacji, ustalić ostateczne rozwiązanie.

Po kolei. Wpierw trzeba wiedzieć, że MySQL 5 (choć jest to już od wersji 4.1) udostępnił obsługę wielojęzyczności na znacznie lepszym poziomie niż inne bazy. Otóż można łatwo zdefiniować niezależne traktowanie tekstu dla każdej tabeli w ramach tej samej bazy. Mało tego, można pójść dalej i zdefiniować niezależne kodowanie dla każdego pola tekstowego oddzielnie.

Daje to niebywałą elastyczność i komfort w obsłudze tekstów międzynarodowych. Można bowiem wymusić aby w tekstach polskich działało sortowanie zgodne z zasadami języka polskiego oraz aby duże i małe polskie ogonki były traktowane tak samo przy wyszukiwaniu. Inne pole może posiadać identyczne reguły dla języka niemieckiego, szwedzkiego, itp. itd. Jedynym założeniem jest aby kodowanie takich tabel i/lub pól było w UTF8. To jest wewnętrzny format w jakim trzeba trzymać dane tekstowe. Nie ma on nic wspólnego z kodowaniem jakie uzyskuje klient. Odpada w ogóle konieczność pisania własnych procedur aby to przekodowywać. Cała robotę za nas “odwali” MySQL. Chcesz w swoich skryptach wypluwać do przeglądarki polskie ogonki w formacie ISO-8859-2? Żaden problem. Wywołaj kwerendę “SET NAMES latin2” i potem wykonuj SELECT

Samo kodowanie znaków to dopiero początek. Chcemy także aby działało sortowanie i wyszukiwanie bez wrażliwości na wielkość liter. Aby to działało należy zadbać o to aby każde pole posiadało odpowiednią definicję tzw. collation. Dla języka polskiego będzie to utf8_polish_ci. Przykładowa definicja tabeli (z dodatkowymi bajerami jakie daje MySQL, np. kompresję indeksów) mogłaby wyglądać np. tak:
CREATE TABLE `artykul` (
  `id` int(11) NOT NULL auto_increment,
  `tytul` varchar(255) collate utf8_polish_ci NOT NULL,
  `tresc` text collate utf8_polish_ci NOT NULL,
  PRIMARY KEY  (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci PACK_KEYS=1 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=COMPRESSED;

MySQL udostepnia także słowo COLLATE do bardzo wygodnej obsługi tekstów międzynarodowych. Lista dostępnych języków jest podana w dokumentacji i jest imponująca.

Gdzie leżał zatem problem z dziwnym zachowaniem się bazy? Po prostu nie było zdefinowanego żadnego collation i baza trochę zwariowała. Na pewno byłoby lepiej, aby w takiej sytuacji zachowywała się bardziej przewidywalnie. Ale wystarczyło dodanie definicji odpowiedniego zestawu języka i wszystko wróciło do normy.

Posted in  | 6 comments

PostgreSQL - inne problemy

Opublikowane przez Jarosław Zabiełło Mon, 29 May 2006 19:00:00 GMT

Zauważyłem, że problem z dziwacznym działaniem kwerend MySQL5 korzystających z LIKE nie występuje pod Linuksem. Być może to po prostu przypadłość kiepsko przygotowanej binarki dla Windowsów…

Najbardziej bolesna sprawa związana z PostgreSQL (dalej: PG) jest zła implementacja wersji międzynarodowych. Tzn. PG ustawia jedną collations dla całej bazy co skutecznie uniemożliwia to, aby w ramach jednej bazy używać różnych tabel z różnymi sortowaniami (wg różnych języków).

Zaś MySQL potrafi ustawić niezależnie collation nawet dla poszczególnych kolumn!

Próbowałem przeszukać listy dyskusyjne na temat PG, ale to, co znalazłem, potwierdza moje obawy: PG ma to źle zrobione. Jeśli chcę mieć efekt sortowania i takiego samego traktowania dużych i małych znaków (istotne przy większości wyszukiwań tekstu) to musiałbym dla każdego języka tworzyć oddzielną bazę. To raczej nie wchodzi w grę.

Posted in  | Tagi  | brak comments

MySQL 5 - strzeż się się tego koszmaru

Opublikowane przez Jarosław Zabiełło Mon, 29 May 2006 07:54:00 GMT

Baza MySQL nigdy nie uchodziła za wzór poprawnej pracy, ale to co ostatnio się z nią dzieje woła o pomstę do nieba… Zainstalowałem sobie najnowszą wersję stabilną MySQL 5 pod win32. Wpierw myslałem że znalazłem jakiś błąd we frameworku Django. Ale krótki czat z innymi programistami i przejrzenie netu, pozbawił mnie złudzeń. Błąd leży w silniku samej bazy. Włączyłem logowanie zapytań i uruchomiłem kwerendę bezpośrednio z poziomu klienta.

Otóż okazuje się, że w MySQL5 kompletnie popsuta jest obsługa warunku “LIKE”. Ilość zwracanych rekordów jest większa niż być powinna. I to nie chyba ma nic wspólnego z tym, czy kodowanie tabel jest w UTF8 czy nie, gdyż nadmierna ilość rekordów jest znajdowana nawet, jak wyszukuje się słowo zawiera tylko znaki ASCII.

Inne “kwiatki” związane z MySQL, o których trzeba sobie jasno powiedzieć, to notoryczne niszczenie plików indeksowych w wypadku zbyt dużego obciążenia bazy. Oczywiście to można łatwo naprawić za pomocą kwerendy REPAIR TABLE tabelka. Ale dopóki nie wykona się tej operacja, tabela nie jest dostępna i aplikacja nam się wywali. Tego typu problemy zauważyłem na MySQL 4.x i 4.1. Nie miałem okazji poddać większym obciążeniom bazę MySQL 5.x, ale nie zdziwiłbym się jakby też z nią były problemy.

Zawsze tyle się mówi w branży, że MySQL to niepoważny projekt amatorski (słaba stabilność i niszczenie swoich tabel pod dużym obciążeniem, koszmarnie wolne tabele transakcyjne innodb, słaba obsługa lockowania – tylko na poziomie tabel a nie wierszy itp, itd) Niszczenie swoich tabel indeksowych mogłem jeszcze zdzierżyć, ale błędna obsługa wyszukiwania tak podstawowej operacji jak LIKE? No way. Całe szczęście, że Django ma dobry ORM i można łatwo zmigrować do PostgreSQL. Chyba nie ma innego wyboru jak powiedzieć: “Goodbye MySQL and welcome PostgreSQL!”

Zobacz c.d. MySQL5 rehabilitacja

Posted in  | Tagi  | 13 comments