Последний пост про нормальные формы, перевод книги PHP 6 and MySQL 5 for Dynamic Web Sites. Предыдущие три вынесены ссылками в конце поста. Read On…
Нормализация баз данных: вторая нормальная форма
Третий пост, перевод книги PHP 6 and MySQL 5 for Dynamic Web Sites. Предыдущие два: вводный, 1НФ Read On…
Нормализация баз данных: первая нормальная форма
Итак, в предыдущем посте была вводная часть про нормализацию баз данных, ключи и отношения. В этом посте будет рассмотрена первая нормальная форма или 1НФ сокращено. Read On…
Нормализация базы данных
Попала в руки одна замечательная книжка — PHP 6 and MySQL 5 for Dynamic Web Sites , за авторством Larry Ulman. В целом, книга расчитана на новичков — середнячков, но затрагиваются и довольно серьёзные вещи, при чем объясняется весьма доходчивым языком.
Задела глава про нормальные формы. Довольно мудрёную тему автор раскрывает в весьма доходчивой манере. На русском издания я не нашел, поэтому перевел эту часть книги. Обьем статьи довольно большой, поэтому я разобью на несколько постов. В этом будет вводная часть.
Вкратце, что такое нормализация. Большинство современных субд разработаны на основе реляционной алгебры, которая появилась раньше самих реляционных субд под авторством некого доктора Кодда. Он же вывел несколько правил, или форм, по упорядочиванию данных и их отношений. Всего таких форм 6 + две вне конкурса, Бойса-Кодда и доменно-ключевая.
На практике редко нормализуют дальше 3-ей нормальной формы. Поподробнее узнать обо всех нормальных формах и теории по ссылкам внизу поста. Read On…
Вложенные SQL запросы: одновременное добавление одинарных и множественных строк в INSERT
Понадобилось для одной задачи сделать выборку из таблицы А по определенным условиям и вставить ее в таблицу B.
Можно особо не заморачиваться, сделать SELECT в переменную, потом пройтись циклом по INSERT и все.
Но это не очень красивое решение. Очень запросов много + на переменные память расходуется. Проще использовать вложенные запросы, они же nested queries. Но тут возникает проблемка — если вставлять только данные из INSERT, то все ок, но если добавлять еще и какие-нибудь неизменные значения, то выдается Subquery returned more than 1 value.
Чтобы не обьяснять на пальцах — сама таблица B
CREATE TEMPORARY TABLE `B` ( `status` TINYINT(4) NULL DEFAULT NULL , `pair` TEXT NULL DEFAULT NULL , ) ENGINE = MyISAM DEFAULT CHARACTER SET = utf8 COLLATE = utf8_general_ci;
Копируем из таблицы A и вставляем в B так
INSERT INTO `B` (`pair`)
SELECT `pair` FROM `A` ORDER BY RAND() LIMIT 100;
Если поле status имеет неизменное значение, например 123, то просто вот так уже не вставить
INSERT INTO `B` (`status`, `pair`) VALUES (
(SELECT `pair` FROM `A` ORDER BY RAND() LIMIT 100),
123);
Не совсем красивое решение нашел
UPDATE `B` SET `status` = '123' WHERE 1=1;
Но хотелось бы в одну строку все заделать. Отпишитесь плиз в коментах, кто знает, как реализовать.
Проблемы с сериализацией массивов
Иногда бывает надо сохранить массив с данными куда-то, чтобы потом его достать и прочитать. Есть две хорошие функции для этого — serialize и unserialize. И все бы ничего, но вот иногда массив не получается десериализовать назад — возвращается предательское bool(false) вместо заветного массивчика.
Проблем может быть две — первая это включенная директива magic_quotes. Лечится примерно так —
(get_magic_quotes_gpc()) ? stripslashes(unserialize($variable)) : unserialize($variable);
За работу кода не отвечаю, но думаю мысль понятна.
Второй вариант, с которым втух я на несколько часов, это переносы строки. Если массив имеет в себе ячейку, в которой несколько строк, то он нифига не сериализуется взад, а выдаст false. Лечится убиванием \r\n
$str = preg_replace("/\n/", "", $str);
$str = preg_replace("/\r/", "", $str);
Считаем, сколько строк кода в php проекте.
Скрипт для тюнинга MYSQL
Знакомый кинул линк на перловый скриптец, который собирает статистику из конфига и логов и выдает рекомендации по оптимизации бд.
Вот такой вывод:
-------- General Statistics --------------------------------------------------
[--] Skipped version check for MySQLTuner script
[OK] Currently running supported MySQL version 5.0.51a-24+lenny5-log
[!!] Switch to 64-bit OS - MySQL cannot currently use all of your RAM
-------- Storage Engine Statistics -------------------------------------------
[--] Status: +Archive -BDB +Federated -InnoDB -ISAM -NDBCluster
[--] Data in MyISAM tables: 5M (Tables: 260)
[OK] Total fragmented tables: 0
-------- Security Recommendations -------------------------------------------
[OK] All database users have passwords assigned
-------- Performance Metrics -------------------------------------------------
[--] Up for: 1d 12h 14m 59s (4M q [33.837 qps], 39K conn, TX: 822M, RX: 460M)
[--] Reads / Writes: 30% / 70%
[--] Total buffers: 57.0M global + 50.5M per thread
(400 max threads)
[!!] Allocating > 2GB RAM on 32-bit systems can cause system instability
[!!] Maximum possible memory usage: 19.8G (293%
of installed RAM)
[!!] Slow queries: 10%
(443K/4M)
[OK] Highest usage of available connections: 38%
(152/400)
[OK] Key buffer size / total MyISAM indexes:
15.0M/4.7M
[OK] Key buffer hit rate: 99.1% (9M cached
/ 91K reads)
[OK] Query cache efficiency: 75.5% (2M cached /
2M selects)
[OK] Query cache prunes per day: 0
[OK] Sorts requiring temporary tables: 0% (0 temp
sorts / 439K sorts)
[OK] Temporary tables created on disk: 1% (8K
on disk / 444K total)
[OK] Thread cache hit rate: 99% (307 created /
39K connections)
[!!] Table cache hit rate: 0% (5 open /
11K opened)
[OK] Open file limit used: 0%
(11/65K)
[OK] Table locks acquired immediately: 99%
(2M immediate / 2M
locks)
-------- Recommendations -----------------------------------------------------
General recommendations:
Increase table_cache gradually to avoid file descriptor limits
Variables to adjust:
*** MySQL's maximum memory usage is dangerously high ***
*** Add RAM before increasing MySQL buffer variables ***
table_cache (> 32562)
Класс для работы с сервисами сокращения ссылок
Небольшой класс, возвращает сокращенную ссылку. Можно обращаться как напрямую к конкретному сервису, так и выбранному рандомно через Shorteners->any(). Есть возможность указать прокси.
Работает с
- bit.ly
- goo.gl
- is.gd
- tinyurl.com
- dlmn.org
- ndurl.com
- qr.cx
- gatorurl.com
- jmb.tw
- linkee.com
- mtny.mobi
- cli.gs
<?php
error_reporting(1);
class Shorteners {
public $proxy = null;
public function bitly($url) {
$connectURL = 'http://api.bit.ly/v3/shorten?login=masstest&apiKey=R_44403eb439622dd59ba2598255f30824&uri=' . urlencode($url) . '&format=txt';
return $this->curl_get_result($connectURL, NULL);
}
public function googl($url) {
$connectURL = 'http://goo.gl/api/shorten';
$post_fields = "security_token=null&url=" . urlencode($url);
$res = $this->curl_get_result($connectURL, $post_fields);
$obj = json_decode($res);
$ans = (!empty($res)) ? $obj->{'short_url'} : false;
return $ans;
}
public function isgd($url) {
$connectURL = 'http://is.gd/api.php?longurl=' . urlencode($url);
return $this->curl_get_result($connectURL, NULL);
}
public function tinyurl($url) {
$connectURL = 'http://tinyurl.com/api-create.php?url=' . urlencode($url);
return $this->curl_get_result($connectURL, NULL);
}
public function dlmn($url) {
$connectURL = 'http://dlmn.org/submit/?url=' . urlencode($url) . '&ajax=false';
$page = $this->curl_get_result($connectURL, NULL);
preg_match("/<input id=\"dlmn-loc\" value=\"(.*?)\"/si", $page, $out);
if (strlen($out[1]) < 3) {
return false;
} else {
$answer = (empty($out[1])) ? false : "http://" . $out[1];
return $answer;
}
}
public function crum($url) {
$connectURL = 'http://crum.bs/api.php?function=simpleshorten&url=' . urlencode($url);
return $this->curl_get_result($connectURL, NULL);
}
public function ndurl($url) {
$connectURL = 'http://www.ndurl.com/api.generate/?url=' . urlencode($url) . '&type=web';
$res = $this->curl_get_result($connectURL, NULL);
$obj = json_decode(stripslashes($res));
$obj = $obj->data;
$ans = (!empty($res)) ? $obj->{'shortURL'} : false;
return $ans;
}
public function qr($url) {
$connectURL = 'http://qr.cx/api/?longurl=' . urlencode($url);
return $this->curl_get_result($connectURL, NULL);
}
public function gatorurl($url) {
$connectURL = 'http://gatorurl.com/api/rest.php?url=' . urlencode($url);
return $this->curl_get_result($connectURL, NULL);
}
public function jmb($url) {
$connectURL = 'http://jmb.tw/api/create/?newurl=' . urlencode($url);
return $this->curl_get_result($connectURL, NULL);
}
public function linkee($url) {
$connectURL = 'http://api.linkee.com/1.0/shorten?input=' . urlencode($url);
$res = $this->curl_get_result($connectURL, NULL);
$obj = json_decode($res);
$ans = (!empty($res)) ? $obj->{'result'} : false;
return $ans;
}
public function mtny($url) {
$connectURL = 'http://mtny.mobi/api/?url=' . urlencode($url) . '&ismobile=false&type=simple';
return $this->curl_get_result($connectURL, NULL);
}
public function cligs($url) {
$connectURL = 'http://cli.gs/cligs/new';
$post = "URL=" . urlencode($url);
$page = $this->curl_get_result($connectURL, $post);
preg_match("/Your new clig has been created/si", $page, $out);
if (strlen($out[0]) < 3) {
return false;
} else {
preg_match("#http://twitter.com/home\?status=(.*?)\"#si", $page, $out);
$answer = (empty($out[1])) ? false : $out[1];
return $answer;
}
}
public function any($url) {
$func = get_class_methods($this);
$sh = $func[array_rand($func, 1)];
return $this->$sh($url, NULL);
}
private function curl_get_result($url, $postdata) {
$ch = curl_init();
$timeout = 5;
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
if (isset($postdata)) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
}
if ($this->proxy) {
//var_dump( $this->proxy );
curl_setopt($ch, CURLOPT_PROXY, $this->proxy);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, "60");
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
curl_setopt($ch, CURLOPT_TIMEOUT, "60");
} else {
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, "30");
curl_setopt($ch, CURLOPT_TIMEOUT, "30");
}
$data = curl_exec($ch);
curl_close($ch);
return trim($data);
}
}
$test = new Shorteners();
$url = "http://rambler.ru";
$class_methods = get_class_methods('Shorteners');
foreach ($class_methods as $method):
echo $test->$method . "\r\n";
endforeach;
?>
Простой способ проверки процесса на существование
Для простых многопоточных систем очень часто используют запуск в фоновом режиме. Запускают их примерно вот так:
php script.php &
И управление процессом от юзера уходит, отловить такой процесс из другого скрипта уже не получится. А если процесс выполняет какую то одну и ту же задачу с интервалом по крону, и нужно, чтобы при запуска нового процесса старый был убит? Поймать его можно аццкой смесью bash + php:
$killPid = exec("ps ux | awk '/script_name.php/ && !/awk/ {print $2}'");//находим пид процесса
if($killPid)//если есть, киляем
exec ("kill ".$killPid);
exec('php script_name.php &');//запускаем новый