2015年3月24日 星期二

阿里雲和網站備案實戰

在中國只要開80 port就需要備案,以下以企業網站備案為實例

買網址
http://www.net.cn/ 萬網
在首頁可以搜尋網址,用支付寶(淘寶)登錄,然後用支付寶付費
买域名时需要填的资料:
域名所有者单位名称:*
域名管理联系人:*
所属区域:*
通讯地址:*
邮编:*
联系电话:*
手机:*
电子邮箱:*
企业管理人:
所属行业:
之後要填《网站备案信息真实性核验单》(需負責人簽名蓋公章)


設定網址聯繫人,以便即時提醒通知(如網址快過期了)
登錄 http://www.net.cn/ 萬網後
上方 "产品管理/续费" => 我的域名 => 在你買的網址後面點"管理" => 進入"域名控制台"頁面
裡面有"基本信息"、"域名信息修改"、"域名所有者实名认证" 、"域名证书打印(備案時需要打印出來寄送)" ...等,可以修改聯繫信箱電話、查詢網址实名认证结果

網址綁定IP
在 "域名控制台" 中 點上方的"域名解析" 裡面可以"設置解析"

買虛擬主機
http://www.net.cn/hosting/basic/
基础版
网页空间: 2G
单月流量: 20GB
并发连接数: 150个
价格:288元/年

進入虛擬主機管理控制台
登錄 http://www.net.cn/ 萬網後
上方 "产品管理/续费" => "我的主机" =>  在你買的主機後面點"管理"( 在這個列表頁可以看到我們買的虛擬主機IP ) => 進入"主機管理控制台"頁面
站点信息 => 基础环境设置 => 域名绑定 - 可以綁定網址,但基本上還是要在你"域名控制台"上設定才對
站点信息 => 高级环境设置 => PHP版本设置 - 可以把PHP設成4, 5.2, 5.3,但要"停止站点"後重啟才會生效
站点信息 => 高级环境设置 => PHP.ini设置 - 可以弱弱的設定一點點PHP設定,
数据库管理 => 管理 - 進入phpmyadmin( 版本有點舊 )管理你虛擬主機的資料庫

發票索取
http://help.www.net.cn/knowledge_detail.htm?knowledgeId=5867323

缺點:
無法ssh,不能修改php.ini設定,云虚拟主机仅支持1个站点。

因為Joomla 3 在PHP5.3以下php.ini需要關閉 magic_quotes_gpc這設定,但虛擬主機沒辦法設定,所以買了阿里雲ECS

買阿里雲ECS (類似Linode的VPS,可以ssh)
進入阿里雲網頁 http://www.aliyun.com/ ,可用淘寶(支付寶)帳號登錄
产品 => 弹性计算 => 云服务器ECS http://www.aliyun.com/product/ecs/ 這邊有介紹阿里 雲ECS => 點"立即购买"後可以配置你要買的ECS
我選
包年包月 ( 買10個月送兩個月 ) 的青島主機
CPU: 1核 内存: 1GB 数据盘: 0G 带宽: 1Mbps(不限流量)
"镜像类型"選"市场镜像"然後可以選人家配置好的主機環境(有點類似docker的image)
並在此步驟設定ssh時root的密碼
以下是我選的鏡像:
PHP运行环境(Ubuntu 64位 | PHP5.4 | Apache)
Ubuntu12.04 64位 来源:上海驻云信息科技有限公司
集成软件:PHP5.4.27、Apache2.4.9、MySQL5.5.37 ... etc

進入阿里雲ECS後台
進入阿里雲網頁 http://www.aliyun.com/ => "管理控制台" => 已开通的产品与服务: "云服务器ECS" => "实例" (列表中可以看到你買的ECS的IP,啟動後可以ssh登錄) => "管理"
可以用網頁的ssh ,只是要設網頁的登錄密碼,而且不好用,還是建議用putty連線
停機後可以更改作業系統,如CentOS改Ubuntu (資料會不見)

新增雲主機聯繫人
会员中心 => 会员信息 => 联系人管理

找查阿里雲ECS的MySQL密碼(以我上面選的市場鏡像為例)
重設密碼
$ bash /alidata/init/initPasswd.sh # 重設mysql和ftp密碼
MySQL和FTP密碼放在
/alidata/account.log

備案
http://beian.aliyun.com/ 備案流程圖
1. 登录备案系统 - 
https://beian.gein.cn 阿里雲ICP 代備案管理系統
這邊帳號和淘寶是分開的,需重新申請
注意:這邊必須寫業主的資料,如手機、email..等。如果將來要改資料,還要重新跑一次備案流程。如檢核點拍照、重新郵寄資料...
2. 填写备案信息 - 
企业备案请提前准备好企业证件负责人证件扫描件或照片,例如营业执照组织机构代码证
3. 提交初审 - 
阿里云审核时长为1个工作日结果将以短信和邮件形式通知
4. 邮寄资料和办理拍照 - 
http://help.aliyun.com/knowledge_detail.htm?knowledgeId=5974581 办理拍照流程说明
邮寄资料 - 
a. 网站备案信息真实性核验单 请打印3份,按照样例分别填写并全部邮寄(前面签名盖公章的那份) - 必須打印三份後分別簽名蓋章,不能簽名蓋章後打印三份,否則會被退
b. 域名证书 - 在"域名控制台" 中打印出來一份
c. 邮寄地址 - 略,備案網頁流程上會跟你說
办理拍照 - 前往拍照核验点免费办理拍照
a. 请 網站負責人 本人携带 身份证原件 和 记录备案订单号(你將會知道) 前往核验点(你將會知道)
5. 等待管局审核 - 大概要等兩個禮拜(實測約16天)
6. 备案成功


參考資料:
http://beian.aliyun.com/ 備案流程
http://www.net.cn/ 萬網,阿里雲的,買網址、租虛擬主機
https://beian.gein.cn 阿里雲ICP 代備案管理系統
http://cp.hichina.com/ 萬網 - (虛擬主機)主机管理控制台
http://www.aliyun.com/ 阿里雲 - 租雲服務器ECS ( 可ssh )

2015年3月18日 星期三

XFCE4 無法更改外觀

系統:Arch Linux
桌面:XFCE4

今天重開機進入XFCE4桌面後,發現按鈕變得很醜
發現
應用程式選單 => 設定值 => 外觀 ( Settings Manger -> Appearance ),也無法更改按鈕樣式

上網查了一下,原來是 .config/xfce4/xfconf/xfce4-perchannel-xml/display.xml 這檔案有問題,估計是使用 xrandr 設定雙螢幕解析度時,另外的螢幕抓不到解析度造成的

解法:
.config/xfce4/xfconf/xfce4-perchannel-xml/display.xml 移除掉重新登錄,
如果你移除掉 .config/xfce4/ 整個資料夾,XFCE4桌面會回復最原始設定的樣子

解決後正常的按鈕


參考資料:
https://bbs.archlinux.org/viewtopic.php?id=153432 [Solved] xfce appearance style, icons, and mouse don't change




2015年3月17日 星期二

用PHP實現MySQL Transaction 交易功能

情境:
假設庫存有6隻手機(stock)要賣,總共有8個人要搶購,手機庫存不能小於零,所以只有六個人會買到。把有誰在搶購(action_record)和誰搶購到紀錄(trade_record)到資料表,並更商品新庫存數量。並在交易後執行一些SQL,來確保交易後的SQL可以正常執行。

我在trade資料庫的資料表結構如下,我用下面這三張表做測試:
/*! action_record表是操作紀錄,有連線進來的紀錄都記進去,不受商品庫存影響 */
CREATE TABLE `action_record` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `action` varchar(255) NOT NULL,
  `ms` varchar(255) NOT NULL,
  `new_time` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*! stock表是商品資料表,記錄了庫存量,stock欄位 */
CREATE TABLE `stock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `stock` int(11) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*! trade_record表是交易記錄表,有交易成功才記錄 */
CREATE TABLE `trade_record` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `stock_id` int(11) NOT NULL,
  `action_id` int(11) NOT NULL,
  `new_time` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


如何用一道指令模擬同時搶購的行為?
我們寫一個簡單的script來跑,print.sh:
#!/usr/bin/bash
echo "test:$1 ms:$(($(date +%s%N)/1000000))" >> print.log
取現在的timestamp的millisecond倒到print.log中,並觀察print.log
$ echo $(($(date +%s%N)/1000000))  # 取現在的timestamp
PHP取現在的timestamp的millisecond
echo round(microtime(true) * 1000);

有兩種方法
1. pipe line ( 不採用 )
$ bash print.sh 1 | bash print.sh 2  # 跑兩次
print.log:
test:1 ms:1426572647622
test:2 ms:1426572647624
test:2 ms:1426572652053
test:1 ms:1426572652057

用pipe line執行多次print.sh後,在print.log中我們可以看到,pipe line幾乎是同時執行的,有時候甚至第二個命令會比第一個快一點。而非第一個執行完才跑第二個

1.1. 證明pipe line同時執行的其他方法(robert)
1.1.1 先產生一個大一點的log檔(1G以上)
$ cat access_log.log >> tmp2.log  => 跑多次
$ ls tmp2.log -lh
-rw-rw-r-- 1 bear bear 1.1G Nov  9 11:45 tmp2.log
1.1.2 使用pipe line,並切換screen 用top或ps檢查
$ cat tmp2.log |awk '{print $1}'|sort -n|uniq -c|sort -n
4372760 192.168.x.147
$ top

$ ps aux | egrep  -e '(awk|sort|cat)'
bear     14113 19.0  0.0   7224   688 pts/7    S+   14:32   0:00 cat tmp2.log
bear     14114 75.0  0.0  14896   984 pts/7    S+   14:32   0:00 awk {print $1}
bear     14115 57.0  0.4  15728  8568 pts/7    R+   14:32   0:00 sort -n
bear     14117  0.0  0.0  15724  2024 pts/7    S+   14:32   0:00 sort -n
bear     14119  0.0  0.1   9452  2136 pts/6    S+   14:32   0:00 egrep --color=auto -e (awk|sort|cat)
可以看到在原本tty結果出來之前,top和ps 都列出 pipe line 後面的 cat, awk, sort都已經在執行了

正規式grep: egrep  -e '(awk|sort|cat)'

2. nohup <command> & ( 採用 )
$ nohup bash print.sh 1 & nohup bash print.sh 2 & #跑兩次
print.log:
test:2 ms:1426573615274
test:1 ms:1426573615275
test:1 ms:1426573627275
test:2 ms:1426573627278

與pipe line一樣是同時執行,有時候甚至第二個命令會比第一個快一點。

模擬交易程式 buy.php:
// useage: php buy.php 
$user_id = $argv[1];
$stock_id = 1;
exec("echo \"user_id:$user_id, start_time_ms:$(($(date +%s%N)/1000000))\" >> print.log");

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$db = new mysqli("localhost", "username", "password", "trade");

$action = array('user_id' => $user_id, "stock_id" => $stock_id);
$action_record_start_id = save_action_record($db, $action);

$result = $db->query("SELECT * FROM `stock` WHERE id = 1");
$row = $result->fetch_assoc();

try {
 echo "try!\n";
    // First of all, let's begin a transaction
 $db->autocommit(FALSE); // equal START TRANSACTION or BEGIN

    // A set of queries; if one fails, an exception should be thrown

    $db->query("INSERT INTO `trade_record` (`id`, `user_id`, `stock_id`, `action_id`, `new_time`) VALUES (NULL, '$user_id', '$stock_id', '$action_record_start_id', NOW());");

 $db->query("UPDATE stock SET stock=stock-1 WHERE id=1");

    // If we arrive here, it means that no exception was thrown
    // i.e. no query has failed, and we can commit the transaction
 $db->commit();
} catch (Exception $e) {
    // An exception has been thrown
    // We must rollback the transaction
    echo "Exception!\n";
 $db->rollback();
}

$db->autocommit(TRUE);
$action = array('action_record_start_id' => $action_record_start_id, 'status' => 'END_TRANSACTION');
save_action_record($db, $action);

echo "\n";

function milliseconds() {
    $mt = explode(' ', microtime());
    return $mt[1] * 1000 + round($mt[0] * 1000);
}

function save_action_record($db, $action) {
 $action_json = json_encode($action);
 $ms = milliseconds();
 $sql = "INSERT INTO action_record VALUES (NULL, '$action_json', '$ms', NOW())";
 // echo $sql."\n";
 $db->query($sql);
 return $db->insert_id;
}
只要有執行,就寫入操作記錄(action_record),交易成功便寫入交易記錄(trade_record),並在商品(stock)把庫存減一,交易完成後再寫一次操作記錄,記錄這是哪個操作記錄的結束。

讓mysqli會丟出例外( Exceptions )
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

關於交易的四個特性(ACID)
A - Atomicity (原子性、不可分割):交易內的 SQL 指令,不管在任何情況,都只能是全部執行完成,或全部不執行。若是發生無法全部執行完成的狀況,則會回滾(rollback)到完全沒執行時的狀態。
C - Consistency (一致性):交易完成後,必須維持資料的完整性。所有資料必須符合預設的驗證規則、外鍵限制...等。
I - Isolation (隔離性):多個交易可以獨立、同時執行,不會互相干擾。
D - Durability (持久性):交易完成後,異動結果須完整的保留。

開始進入交易模式
START TRANSACTIONBEGIN 或 SET AUTOCOMMIT=1
結束交易模式
交易完成:使用 COMMIT 儲存所有變動,並結束交易。
交易過程異常:使用 ROLLBACK 回滾,取消交易,還原到未進行交易的狀態。(交易過程斷線亦會ROLLBACK)

更多的議題,如 儲存點 (SAVEPOINT)、不能 ROLLBACK 的指令、會造成自動終止交易並 COMMIT 的指令、查詢目前 AUTOCOMMIT 的設定值、InnoDB的3種加鎖的類型、InnoDB 實現的鎖定模式、不同 SQL Statements 在 InnoDB 的鎖定、讀取可能產生的異常情況、交易的4種隔離層級、設定交易層級、死結 (dead lock) ... 等更進階議題,請看參考資料中 XYZ的筆記本 的連結

使用pipe line 同時搶購
$ php buy.php 1 | php buy.php 2 | php buy.php 3 | php buy.php 4 | php buy.php 5 | php buy.php 6 | php buy.php 7 | php buy.php 8  #1~8號使用者同時搶
結果:
mysql> use trade;
Database changed
mysql> select * from stock;
+----+-------+-------+
| id | name  | stock |
+----+-------+-------+
|  1 | phone |     0 |
+----+-------+-------+
1 row in set (0.00 sec)

mysql> select * from trade_record;
+----+---------+----------+-----------+---------------------+
| id | user_id | stock_id | action_id | new_time            |
+----+---------+----------+-----------+---------------------+
|  1 |       4 |        1 |         1 | 2015-03-17 13:58:52 |
|  2 |       5 |        1 |         2 | 2015-03-17 13:58:53 |
|  3 |       2 |        1 |         5 | 2015-03-17 13:58:53 |
|  4 |       3 |        1 |         6 | 2015-03-17 13:58:53 |
|  5 |       7 |        1 |         8 | 2015-03-17 13:58:53 |
|  7 |       6 |        1 |         4 | 2015-03-17 13:58:53 |
+----+---------+----------+-----------+---------------------+
6 rows in set (0.00 sec)

mysql> select * from action_record;
+----+---------------------------------------------------------+---------------+---------------------+  
| id | action                                                  | ms            | new_time            |  
+----+---------------------------------------------------------+---------------+---------------------+  
|  1 | {"user_id":"4","stock_id":1}                            | 1426571932876 | 2015-03-17 13:58:52 |
|  2 | {"user_id":"5","stock_id":1}                            | 1426571932877 | 2015-03-17 13:58:52 |
|  3 | {"user_id":"1","stock_id":1}                            | 1426571932878 | 2015-03-17 13:58:52 |
|  4 | {"user_id":"6","stock_id":1}                            | 1426571932882 | 2015-03-17 13:58:52 |
|  5 | {"user_id":"2","stock_id":1}                            | 1426571932884 | 2015-03-17 13:58:52 |  
|  6 | {"user_id":"3","stock_id":1}                            | 1426571932886 | 2015-03-17 13:58:52 |  
|  7 | {"user_id":"8","stock_id":1}                            | 1426571932886 | 2015-03-17 13:58:52 |  
|  8 | {"user_id":"7","stock_id":1}                            | 1426571932888 | 2015-03-17 13:58:52 |
|  9 | {"action_record_start_id":1,"status":"END_TRANSACTION"} | 1426571933040 | 2015-03-17 13:58:53 |  
| 10 | {"action_record_start_id":2,"status":"END_TRANSACTION"} | 1426571933072 | 2015-03-17 13:58:53 |  
| 11 | {"action_record_start_id":8,"status":"END_TRANSACTION"} | 1426571933139 | 2015-03-17 13:58:53 |  
| 12 | {"action_record_start_id":5,"status":"END_TRANSACTION"} | 1426571933172 | 2015-03-17 13:58:53 |  
| 13 | {"action_record_start_id":6,"status":"END_TRANSACTION"} | 1426571933239 | 2015-03-17 13:58:53 |  
| 14 | {"action_record_start_id":4,"status":"END_TRANSACTION"} | 1426571933272 | 2015-03-17 13:58:53 |  
| 15 | {"action_record_start_id":7,"status":"END_TRANSACTION"} | 1426571933338 | 2015-03-17 13:58:53 |  
+----+---------------------------------------------------------+---------------+---------------------+  
15 rows in set (0.00 sec)
商品(stock)庫存為0 - 正常
交易記錄(trade_record)有六筆資料 - 正常
交易前的操作記錄(action_record)有8筆資料 - 正常
交易後的操作記錄(action_record)有7筆資料 - 異常

經我反覆測試後,"交易後的操作記錄"會有問題,有時候正常,有時候不足8筆

使用nohup <command> & 同時搶購
$ nohup php buy.php 1 & nohup php buy.php 2 & nohup php buy.php 3 & nohup php buy.php 4 & nohup php buy.php 5 & nohup php buy.php 6 & nohup php buy.php 7 & nohup php buy.php 8 &
經多次測試後 "交易後的操作記錄"(action_record)都有8筆資料 - 正常
其他資料表與pipe line方法相同 - 正常

小結:
同時跑多個進程(process)時,還是用nohup比較靠譜一點

範例程式(含table schema):
https://github.com/kalecgos0616/tools/tree/master/transaction

參考資料:
http://stackoverflow.com/questions/2708237/php-mysql-transactions-examples PHP + MySQL transactions examples
http://xyz.cinc.biz/2013/05/mysql-transaction.html XYZ的筆記本 - MySQL 交易功能 Transaction 整理(較詳細,主要參考這篇)
http://stackoverflow.com/questions/12091971/how-to-start-and-end-transaction-in-mysqli How to start and end transaction in mysqli?
http://blog.longwin.com.tw/2006/03/innodb_transaction_2006/ MySQL InnoDB 的 Transaction 的簡易用法
http://stackoverflow.com/questions/16548528/linux-command-to-get-time-in-milliseconds Linux command to get time in milliseconds
http://stackoverflow.com/questions/3656713/how-to-get-current-time-in-milliseconds-in-php How to get current time in milliseconds in PHP?
http://stackoverflow.com/questions/14578243/turning-query-errors-to-exceptions-in-mysqli Turning query errors to Exceptions in MySQLi
http://oss.org.cn/man/database/mysql/inonodb_zh/8.htm InnoDB 事务模式与锁定

2015年3月11日 星期三

將別的repository的commit合併到svn上

情境:
有兩個同專案的程式,因網路關係,其中一個有一陣子被放生到一個新的git上(因為git沒有網路也能用),現在要把git(程式較新)的commit移回svn上(git分出去後無提交新的commit)
假設svn倉庫在 http://svn.domain/trunk/project/
git倉庫在 ssh://bear@192.168.0.115:/srv/http/git_repo
本機端git svn 操作在git_svn_repo
本機端保留的git本地倉庫在 /your/git_repo/path

解法:
You'll need to add the other repository as a remote, then fetch its changes. From there you see the commit and you can cherry-pick it.

使用git svn 拉回svn的程式
$ git svn clone http://svn.domain/trunk/project/ git_svn_repo
$ cd git_svn_repo
新增git的遠端倉庫,並命名git_repo
$ git remote add git_repo file:///your/git_repo/path #我在本地端另外目錄有pull git倉庫的東西,先用他的commit 去merge
抓回git_repo
$ git fetch git_repo
檢查branch
$ git branch -a
查git倉庫的log
$ git log remotes/git_repo/master
將git倉庫你要合併的commit cherry-pick到master(他將會推上svn)
$ git cherry-pick commit_id
上傳到svn
$ git svn dcommit


將git_svn_repo的更新推到git
$ git remote rm git_repo #因為前面用本機保留的git本地倉庫,所以刪掉重新把remote設成git遠端倉庫
$ git remote add origin ssh://bear@192.168.0.115:/srv/http/git_repo
直接推送會報錯:
$ git push origin master
lman@192.168.0.115's password:
To ssh://bear@192.168.0.115:/srv/http/git_repo
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'ssh://bear@192.168.0.115:/srv/http/git_repo'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
解法:
$ git pull origin master # 這會多一個merge的commit
(編輯修改後)
$ git push origin master
即可將git_svn_repo的修改內容推送到遠端git倉庫

不過上面那段是錯的,原因:這樣git_svn_repo的修改就送不到svn上了



參考資料:
http://stackoverflow.com/questions/5120038/is-it-possible-to-cherry-pick-a-commit-from-another-git-repository

2015年3月6日 星期五

使用 PHP-MySQL, PHP-MySQLi, PDO 連線資料庫

最底層常見PHP連資料庫的方法有:
mysql_connect - 最原始的,大多數教科書使用的
mysqli - i 代表 Improvement ,提更了相對進階的功能,就 Extension 而言,本身也增加了安全性
pdo - (PHP Data Object) 則是提供了一個 Abstraction Layer 來操作資料庫

下面範例讀取trade資料庫的salary資料表,範例資料內容:
mysql> desc salary;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| is_traded   | tinyint(1)   | NO   |     | NULL    |                |
| ps          | varchar(255) | NO   | UNI | NULL    |                |
| update_time | datetime     | NO   |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

mysql> select * from salary;
+----+-----------+--------+---------------------+
| id | is_traded | ps     | update_time         |
+----+-----------+--------+---------------------+
|  1 |         0 | test11 | 2015-03-02 10:08:16 |
|  3 |         1 | test   | 2015-03-02 10:08:45 |
|  6 |         1 | 456456 | 2015-03-04 11:53:57 |
|  7 |         1 | 151656 | 2015-03-04 14:35:40 |
+----+-----------+--------+---------------------+

使用mysql_connect
mysql_connect("localhost", "uaername", "password");
mysql_select_db("trade");

$is_traded = 1;
$result = mysql_query("SELECT * FROM `salary` WHERE is_traded = '$is_traded'");

while ($row = mysql_fetch_array($result,  MYSQL_ASSOC)) {
 echo $row['ps']."\n";
}

mysql_free_result($result);

缺點:
$is_traded 的地方容易被 SQL Injection。必須使用mysql_escape_string() (備註:5.3.0之後棄用) 以及 mysql_real_escape_string(),但這樣敘述會變得複雜且醜陋,而且如果欄位多了,可以想見會是怎樣的情形...

mysql_connect使用mysql_real_escape_string()避免SQL Injection
mysql_connect("localhost", "root", "123123");
mysql_select_db("trade");

$is_traded = 1;
$query = sprintf("SELECT * FROM `salary` WHERE is_traded ='%s'", mysql_real_escape_string($is_traded));
echo "$query\n";
$result = mysql_query($query);

while ($row = mysql_fetch_array($result,  MYSQL_ASSOC)) {
 echo $row['ps']."\n";
}

mysql_free_result($result);

使用mysqli
$mysqli = new mysqli("localhost", "username", "password", "trade");
$is_traded = 1;
$result = $mysqli->query("SELECT * FROM `salary` WHERE is_traded = '$is_traded'");
while ($row = $result->fetch_assoc()) {
 echo "result is \$row['id']:{$row['id']}, \$row['is_traded']:{$row['is_traded']}, \$row['ps']:{$row['ps']}, \$row['update_time']:{$row['update_time']}\n";
}
一樣 $is_traded 的地方容易被 SQL Injection。

使用mysqli避免SQL Injection
$mysqli = new mysqli("localhost", "root", "123123", "trade");
$stmt = $mysqli->prepare(
  "SELECT * FROM `salary` WHERE is_traded =?");
$is_traded = "1'";
$stmt->bind_param( "s", $is_traded);
// "ss' is a format string, each "s" means string
$stmt->execute();

$stmt->bind_result($id, $is_traded, $ps, $update_time);
// then fetch and close the statement
while($stmt->fetch()){
    echo "result is \$id:$id, \$is_traded:$is_traded, \$ps:$ps, \$update_time:$update_time\n";
}
缺點:select * 時必須按照順序用bind_result榜定各欄位因fetch_assoc()不屬於$stmt的函數,所以不能叫他
優點:可以Bind Column。多援 Transaction, Multi Query,提供了 Object oriented style

使用PDO
$dsn = "mysql:host=localhost;dbname=trade";
$dbh = new PDO($dsn, "root", "123123");

$sql = "SELECT * FROM `salary` WHERE is_traded =?";
$sth = $dbh->prepare($sql);

$sth->execute(array(1));

while ($result = $sth->fetch(PDO::FETCH_OBJ)) {
 print_r($result);
}

$dbh = NULL;
優點:
可以回傳欄位名稱的大小

但,實務上我們都用framework的DBI( database interface )來連資料庫,如ActiveRecord

參考資料:
http://blog.roga.tw/2010/06/%E6%B7%BA%E8%AB%87-php-mysql-php-mysqli-pdo-%E7%9A%84%E5%B7%AE%E7%95%B0/

2015年3月4日 星期三

MySQL的INDEX、PRIMARY、UNIQUE、FULLTEXT 索引差異

先來說明MySQL的Index和Key有什麼差異
沒有差異。他們是同義詞

INDEX(or KEY) - 指一般的非唯一索引。所以該索引可以允許有相同值。索引不執行限制你的數據,他們僅用於加速查詢
UNIQUE - 和index相同,差異在於不能允許重複值,可以為空(只能有一個空值)。但根據你的應用程式,這可能造成不快,如果想避免這狀況,你應該在相關欄位不允許NULL
PRIMARY - 與UNIQUE相似,但一個表只有一個主鍵(雖然有些資料庫系統不強制如此)。PRIMARY索引是主要手段去識別表中的資料,不允許NULL值。通常是自動增加(Auto-incremented)的數字
FULLTEXT - 行為與上面不同。僅用於"全文搜索"( 只能用在「CHAR」、「VARCHAR」與「TEXT」型態的欄位 )。不像上面三個使用B-TREEHash表實做。

相同點
上面的索引都能使用多個欄位( SuperKey、超級索引鍵、複合鍵 )
除了FULLTEXT,欄位在排序時效果是顯著的

查indexes設定
mysql> show indexes from [TABLE];

參考資料:
http://stackoverflow.com/questions/707874/differences-between-index-primary-unique-fulltext-in-mysql  Differences between INDEX, PRIMARY, UNIQUE, FULLTEXT in MySQL?
http://stackoverflow.com/questions/1401572/what-are-differences-between-index-v-s-key-in-mysql  What are differences between Index v.s. Key in MySQL

2015年3月3日 星期二

MySQL 中MyISAM 與 InnoDB 的差異


MyISAM與InnoDB這兩個 engine 是在 MySQL 中最常用到的兩個 engine。
MyISAM 是在 MySQL 5.1 之前的 default engine,InnoDB 則是 MySQL 5.5 之後的 default engine。
大多數不換 InnoDB 有幾個原因:
1. InnoDB 佔用的空間比較大 - 延伸閱讀( Mobile01-現在SSD硬碟可以拿來跑資料庫嗎? )
2. InnoDB 不好備份
3. MyISAM 效能比較好(只讀)

MYISAM:
1. MYISAM supports Table-level Locking - MyISAM支援Table-level的鎖定
2. MyISAM designed for need of speed - 速度較快
3. MyISAM does not support foreign keys hence we call MySQL with MYISAM is DBMS - 不支援外鍵
4. MyISAM stores its tables, data and indexes in diskspace using separate three different files. (tablename.FRM, tablename.MYD, tablename.MYI) - 儲存資料表、資料、indexes在硬碟空間的三個不同檔案上( *.FRM, *.MYD, *.MYI )
5. MYISAM not supports transaction. You cannot commit and rollback with MYISAM. Once you issue a command it’s done. - 不支援交易。不能commit和rollback
6. MYISAM supports fulltext search - 支援全文搜索(Innodb要到5.6.4以上才支援)
註:
MySQL 5.6.21,在innodb新增fulltext index後,在/var/lib/mysql/db_name/ 下會產生 FTS_0000000000000780_BEING_DELETED.ibd ... 等類似檔案,刪除fulltext index後要執行 mysql> OPTIMIZE TABLE innodb_table_name; 才會刪掉他們
7. You can use MyISAM, if the table is more static with lots of select and less update and delete. - 選擇MyISAM,如果資料表select很多且update和delete很少

INNODB:
1. InnoDB supports Row-level Locking (multi processing on single table) - 支援Row-level 鎖定
2. InnoDB designed for maximum performance when processing high volume of data - 處理大量資料時效能較好
3. InnoDB support foreign keys hence we call MySQL with InnoDB is RDBMS - 支援外鍵
4. InnoDB stores its tables and indexes in a tablespace - 儲存資料表和indexes在tablespace
5. InnoDB supports transaction. You can commit and rollback with InnoDB - 支援交易,可以commit和rollback

總結:
最大的差別在於"referential integrity"( 引用完整性,foreign key )和"transactions"( 交易,commit and rollback )
Frequent reading, almost no writing( 頻繁的讀,幾乎沒寫 )              => MyISAM
Full-text search in MySQL <= 5.5 ( 當MySQL版本<=5.5需要全文搜索時 ) => MyISAM
其他情況,用Innodb會比較好

正規化
有可能會因為 MyISAM 卡 query 的問題而設計出非正規化的 schema。用 InnoDB 可能可以正規化,用適當的設備成本 (像是適當使用 JOIN) 降低人力維護成本。

有嚴格資料正確性需求的 Query 使用 Transaction
像是金流相關系統,一筆購買紀錄可能要修改好幾個表格。用 MyISAM 時必須對好幾個表格使用 TABLE LOCK (仍然有 atomic 問題),現在可以用 transaction 解決。

檢查表是Innodb還是MyISAM
mysql> SHOW TABLE STATUS FROM `database`; #檢查資料庫
or
mysql> SHOW TABLE STATUS FROM `database` LIKE 'tablename'; #指定資料表

轉換 MyISAM tabe 成 InnoDB
(先備份 - 備份InnoDB只需要備份 /var/lib/mysql 目錄即可)
方法一、使用ALTER TABLE
mysql> ALTER TABLE salary_myisam ENGINE = InnoDB;
轉回MyISAM
mysql> ALTER TABLE salary_myisam ENGINE = MyISAM;
用ALTER TABLE特點:
a、適合所有儲存引擎。
b、轉換過程會耗費大量時間。
c、轉換時候源表會被讀加鎖。
方法二、使用mysqldump,然後修改sql的CREATE TALBE 語句

參考資料:
https://blog.gslin.org/archives/2012/11/24/3034/mysql-%E4%B8%AD%EF%BC%8Cmyisam-%E8%88%87-innodb-%E5%B8%B6%E4%BE%86%E7%9A%84%E5%B7%AE%E7%95%B0/ MySQL 中,MyISAM 與 InnoDB 帶來的差異...[中文]
http://stackoverflow.com/questions/20148/myisam-versus-innodb MyISAM versus InnoDB
http://stackoverflow.com/questions/12614541/whats-the-difference-between-myisam-and-innodb What's the difference between MyISAM and InnoDB?
http://stackoverflow.com/questions/4515490/how-do-i-know-if-a-mysql-table-is-using-myisam-or-innodb-engine 檢查資料表engine是innodb還是myisam
http://fecbob.pixnet.net/blog/post/43281382-mysql%E8%B3%87%E6%96%99%E5%BA%AB%E8%BD%89%E6%8F%9B%E8%A1%A8%E7%9A%84%E5%84%B2%E5%AD%98%E5%BC%95%E6%93%8E%E7%9A%84%E6%96%B9%E6%B3%95 MySQL資料庫轉換表的儲存引擎的方法