C語言PHP擴展高性能數據庫ORM框架ycdb(1) : SQL生成器

下一章:C語言PHP擴展高性能數據庫ORM框架ycdb(2) : 構建穩定的數據庫/緩存連接池

目錄:

  • 介紹
  • 安裝環境
  • 創建測試表
  • 在linux中編譯ycdb
  • Start ycdatabase
  • 初始化ycdb連接
  • 原生SQL執行
  • 錯誤處理
  • Where 語句
  • Select 語句
  • Insert 語句
  • Replace 語句
  • Update 語句
  • Delete 語句
  • 完整例句
  • 數據庫事務
  • 數據緩存
  • PHP數據庫連接池
  • Redis 連接池方案

 

介紹

       源碼github地址: https://github.com/caohao-php/ycdatabase各位幫忙點個贊

  • 快速 - ycdb是一個爲PHP擴展寫的純C語言寫的mysql數據庫ORM擴展,衆所周知,數據庫ORM是一個非常耗時的操作,尤其對於解釋性語言如PHP,而且對於一個項目來說,ORM大多數情況能佔到項目很大的一個比例,所以這裏我將MySQL的ORM操作用C語言實現,利用C語言的性能,提升ORM的性能。
  • 安全 - ycdb 能通過參數綁定的方式解決SQL注入的問題,而且預綁定能夠避免數據庫每次都編譯SQL,性能更好。
  • 強大 - 便捷的調用方式,強大的功能支持
  • 簡單 - 使用和學習非常簡單,界面友好。
  • 數據緩存 - ycdb支持數據緩存,你可以採用redis作爲介質來緩存數據庫的數據,但是記得在update、insert、delete 操作涉及到與緩存數據相關的數據修改時,需要刪除您的緩存,以保證數據一致性
  • 連接池 - ycdb通過一種特殊的方式來建立一個穩定的與MySQL之間的連接池,性能至少能提升30%,按照 PHP 的運行機制,長連接在建立之後只能寄居在工作進程之上,也就是說有多少個工作進程,就有多少個長連接,打個比方,我們有 10 臺 PHP 服務器,每臺啓動 1000 個 PHP-FPM 工作進程,它們連接同一個 MySQL 實例,那麼此 MySQL 實例上最多將存在 10000 個長連接,數量完全失控了!而且PHP的連接池心跳機制不完善

安裝環境

  • PHP 7.0 +
  • 需要安裝PDO擴展

創建測試表

CREATE TABLE `user_info_test` (
  `uid` int(11) NOT NULL COMMENT 'userid' AUTO_INCREMENT,
  `username` varchar(64) NOT NULL COMMENT 'username',
  `sexuality` varchar(8) DEFAULT 'male' COMMENT 'sexuality:male - 男性  female - 女性',
  `age` int(11) DEFAULT 0 COMMENT 'age',
  `height` double(11,2) DEFAULT 0 COMMENT 'height of a person, 身高',
  `bool_flag` int(11) DEFAULT 1 COMMENT 'flag',
  `remark` varchar(11) DEFAULT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='userinfo';

在Linux編譯擴展

//// /path/to 是你的 PHP7 安裝路徑
$ cd ~/ycdatabase/ycdatabase_extension
$ /path/to/phpize
$ chmod +x ./configure
$ ./configure --with-php-config=/path/to/php-config
$ make 
$ make install

Start ycdb

  • new ycdb()
$db_conf = array("host" => "127.0.0.1", 
                 "username" => "root", 
                 "password" => "test123123", 
                 "dbname" => "userinfo", 
                 "port" => '3306', 
                 "option" => array(
                        PDO::ATTR_CASE => PDO::CASE_NATURAL,
                        PDO::ATTR_TIMEOUT => 2));

$ycdb = new ycdb($db_conf);

我們通過上面代碼創建ycdatabase對象(ycdb),db_conf是數據庫配置,包含host,username,password,dbname,port等信息,還包含option參數,這個參數是pdo的設置參數,具體您可以參考網站 http://php.net/manual/zh/pdo.setattribute.php , 例如上面代碼中的PDO::ATTR_TIMEOUT是連接超時時間(秒),PDO::ATTR_CASE是強制列名爲指定的大小寫。

初始化ycdb連接

  • we need to init pdo connection before we use ycdatabase.
try{
    $ycdb->initialize();
} catch (PDOException $e) {
    echo "find PDOException when initialize\n";
    var_dump($e);
    exit;
}

原生SQL查詢

我們可以通過exec函數直接執行sql語句,如果語句爲插入語句,會返回 insert_id,如果表中沒有AUTO_INCREMENT 列,insert_id 爲0, 其他返回值爲執行結果影響行數,以及query函數執行select語句,如果 $ret = -1 則說明 sql 執行出錯,我們可以通過 $ycdb->errorCode,$ycdb->errorInfo() 分別返回錯誤代碼、錯誤描述。

  • insert data
$insert_id = $ycdb->exec("insert into user_info_test(username, sexuality, age, height) 
                    values('smallhow', 'male', 29, 180)");
if($insert_id == -1) {
    $code = $ycdb->errorCode();
    $info = $ycdb->errorInfo();
    echo "code:" . $code . "\n";
    echo "info:" . $info[2] . "\n";
} else {
    echo $insert_id;
}
  • update data

$ret = $ycdb->exec("update user_info_test set remark='test' where height>=180");
echo $ret; //ret is 3
  • select data
$ret = $ycdb->query("select * from user_info_test where bool_flag=1");
echo json_encode($ret);

Error Info

可以通過 errorCode() 和errorInfo() 函數獲取錯誤碼和錯誤信息

$code = $ycdb->errorCode();
$info = $ycdb->errorInfo();

Where 語句

  • Basic usage 基本用法
$ycdb->select("user_info_test", "username", ["sexuality" => "male"]);
// WHERE sexuality = 'male'

$ycdb->select("user_info_test", "username", ["age" => 29]);  // WHERE age = 29

$ycdb->select("user_info_test", "username", ["age[>]" => 29]); // WHERE age > 29

$ycdb->select("user_info_test", "username", ["age[>=]" => 29]); // WHERE age >= 29

$ycdb->select("user_info_test", "username", ["age[!]" => 29]); // WHERE age != 29

$ycdb->select("user_info_test", "username", ["age[<>]" => [28, 29]]); // WHERE age  BETWEEN 28 AND 29

$ycdb->select("user_info_test", "username", ["age[><]" => [28, 29]]); // WHERE age NOT BETWEEN 28 AND 29

//you can use array
$data = $ycdb->select("user_info_test", "*", [
  "OR" =>[
    "uid" => [2, 3, 4, 7, 9],
    "username" => ["Tom", "Red", "carlo"]]
]);
// WHERE uid in (2, 3, 4, 7, 9) OR username in ('Tom', 'Red', 'carlo')

//Multiple conditional query
$data = $ycdb->select("user_info_test", "*", [
    "uid[!]" => 10,
    "username[!]" => "James",
    "height[!]" => [165, 168, 172],
    "bool_flag" => true,
    "remark[!]" => null
]);
// WHERE 
// uid != 10 AND 
// username != "James" AND 
// height NOT IN ( 165, 168, 172) AND 
// bool_flag = 1 AND 
// remark IS NOT NULL
  • 條件搜索

You can use "AND" or "OR" to make up very complex SQL statements.

$data = $ycdb->select("user_info_test", "*", [
  "OR" => [
    "uid[>]" => 3,
    "age[<>]" => [28, 29],
    "sexuality" => "female"
  ]
]);
// WHERE uid > 3 OR age BETWEEN 29 AND 29 OR sexuality = 'female'

$data = $ycdb->select("user_info_test", "*", [
  "AND" => [
    "OR" => [
      "age" => 29,
      "sexuality" => "female"
    ],
    "height" => 177
  ]
]);
// WHERE (age = 29 OR sexuality='female') AND height = 177


//注意: 第一個 OR 被覆蓋了,所以這個寫法是錯誤的
$data = $ycdb->select("user_info_test", "*", [
  "AND" => [
    "OR" => [
      "age" => 29,
      "sexuality" => "female"
    ],
    "OR" => [
      "uid[!]" => 3,
      "height[>=]" => 170
    ],
  ]
]);
// [X] SELECT * FROM user_info_test WHERE (uid != 3 OR height >= 170)


//我們可以用 # + 註釋 來區分兩個不同的 OR
$data = $ycdb->select("user_info_test", "*", [
  "AND" => [
    "OR #1" => [
      "age" => 29,
      "sexuality" => "female"
    ],
    "OR #2" => [
      "uid[!]" => 3,
      "height[>=]" => 170
    ],
  ]
]);
// [√] SELECT * FROM user_info_test WHERE (age = 29 OR sexuality = 'female') AND (uid != 3 OR height >= 170)
  • 模糊匹配 Like

LIKE USAGE [~].

$data = $ycdb->select("user_info_test", "*", [ "username[~]" => "%ide%" ]);
// WHERE username LIKE '%ide%'

$data = $ycdb->select("user_info_test", "*", [
  "username[~]" => ["%ide%", "Jam%", "%ace"]
]);
// WHERE username LIKE '%ide%' OR username LIKE 'Jam%' OR username LIKE '%ace'

$data = $ycdb->select("user_info_test", "*", [ "username[!~]" => "%ide%" ]);
// WHERE username NOT LIKE '%ide%'
  • 通配符的使用
$ycdb->select("user_info_test", "*", [ "username[~]" => "Londo_" ]); // London, Londox, Londos...

$ycdb->select("user_info_test", "id", [ "username[~]" => "[BCR]at" ]); // Bat, Cat, Rat

$ycdb->select("user_info_test", "id", [	"username[~]" => "[!BCR]at" ]); // Eat, Fat, Hat...
  • 排序和limit
$data = $ycdb->select("user_info_test", "*", [
  'sexuality' => 'male',
  'ORDER' => [
    "age",
    "height" => "DESC",
    "uid" => "ASC"
  ],
  'LIMIT' => '100',  //Get the first 100 of rows (overwritten by next LIMIT)
  'LIMIT' => [20, 100]  //Started from the top 20 rows, and get the next 100
]);
//SELECT * FROM `user_info_test` WHERE `sexuality` = 'male' ORDER BY `age`, `height` DESC, `uid` ASC LIMIT 100 OFFSET 20
  • GROUP And HAVING
$ycdb->select("user_info_test", "sexuality,age,height", [
  'GROUP' => 'sexuality',
 
  // GROUP by array of values
  'GROUP' => [
    'sexuality',
    'age',
    'height'
  ],
 
  // Must have to use it with GROUP together
  'HAVING' => [
    'age[>]' => 30
  ]
]);
//SELECT uid FROM `user_info_test` GROUP BY sexuality,age,height HAVING `age` > 30

Select 語句

  • usage
select($table, $columns, $where)

table [string]

table name

columns [string/array]

Columns to be queried.

where (optional) [array]

The conditions of the query.

select($table, $join, $columns, $where)

table [string]

table name

join [array]

Multi-table query, can be ignored if not used.

columns [string/array]

Columns to be queried.

where (optional) [array]

The conditions of the query.

return [array]

如果返回 -1 則失敗,否則返回結果數組

 

  • example

你可以使用*來匹配所有字段, 但如果你指名字段名可以很好的提高性能.

$datas = $ycdb->select("user_info_test", [
  "uid",
  "username"
], [
  "age[>]" => 31
]);

// $datas = array(
//  [0] => array(
//      "uid" => 6,
//      "username" => "Aiden"
//  ),
//  [1] => array(
//      "uid" => 11,
//      "username" => "smallhow"
//  )
// )

// Select all columns
$datas = $ycdb->select("user_info_test", "*");

// Select a column
$datas = $ycdb->select("user_info_test", "username");
 
// $datas = array(
//  [0] => "lucky",
//  [1] => "Tom",
//  [2] => "Red"
// )
  • Table join

多表查詢SQL較爲複雜,使用ycdb可以輕鬆的解決它

// [>] == RIGH JOIN
// [<] == LEFT JOIN
// [<>] == FULL JOIN
// [><] == INNER JOIN

$ycdb->select("user_info_test",
[ // Table Join Info
  "[>]account" => ["uid" => "userid"], // RIGHT JOIN `account` ON `user_info_test`.`uid`= `account`.`userid`
 
  // This is a shortcut to declare the relativity if the row name are the same in both table.
  "[>]album" => "uid", //RIGHT JOIN `album` USING (`uid`) 
  
  // Like above, there are two row or more are the same in both table.
  "[<]detail" => ["uid", "age"], // LEFT JOIN `detail` USING (`uid`,`age`)
 
  // You have to assign the table with alias.
  "[<]address(addr_alias)" => ["uid" => "userid"], //LEFT JOIN `address` AS `addr_alias` ON `user_info_test`.`uid`=`addr_alias`.`userid`
 
  // You can refer the previous joined table by adding the table name before the column.
  "[<>]album" => ["account.userid" => "userid"], //FULL JOIN `album` ON  `account`.`userid` = `album`.`userid`
 
  // Multiple condition
  "[><]account" => [
    "uid" => "userid",
    "album.userid" => "userid"
  ]
], [ // columns
  "user_info_test.uid",
  "user_info_test.age",
  "addr_alias.country",
  "addr_alias.city"
], [ // where condition
  "user_info_test.uid[>]" => 3,
  "ORDER" => ["user_info_test.uid" => "DESC"],
  "LIMIT" => '50'
]);


// SELECT 
//   user_info_test.uid,
//   user_info_test.age,
//   addr_alias.country,
//   addr_alias.city 
// FROM `user_info_test` 
// RIGHT JOIN `account` ON `user_info_test`.`uid`= `account`.`userid`  
// RIGHT JOIN `album` USING (`uid`) 
// LEFT JOIN `detail` USING (`uid`,`age`) 
// LEFT JOIN `address` AS `addr_alias` ON `user_info_test`.`uid`=`addr_alias`.`userid` 
// FULL JOIN `album` ON  `account`.`userid` = `album`.`userid` 
// INNER JOIN `account` ON `user_info_test`.`uid`= `account`.`userid` 
//   AND `album`.`userid` = `account`.`userid`  
// WHERE `user_info_test`.`uid` > 3 
// ORDER BY  `user_info_test`.`uid` DESC 
// LIMIT 50
  • alias

你可以使用別名,以防止字段衝突

$data = $ycdb->select("user_info_test(uinfo)", [
  "[<]account(A)" => "userid",
], [
  "uinfo.uid(uid)",
  "A.userid"
]);

// SELECT uinfo.uid AS `uid`, A.userid 
// FROM `user_info_test` AS `uinfo` 
// LEFT JOIN `account` AS `A` USING (`userid`)

Insert 語句

insert($table, $data)

table [string]

table name

data [array]

insert data

return [int]

如果返回 -1 則失敗,否則返回插入insert_id,如果表中沒有AUTO_INCREMENT 列,insert_id 爲0

Replace 語句

replace($table, $data)

table [string]

table name

data [array]

replace data

return [int]

如果返回 -1 則失敗,否則返回插入insert_id

$data = array('username' => 'smallhow','sexuality' => 'male','age' => 35, 'height' => '168');
$insert_id = $ycdb->replace("user_info_test", $data);
if($insert_id == -1) {
	$code = $ycdb->errorCode();
	$info = $ycdb->errorInfo();
	echo "code:" . $code . "\n";
	echo "info:" . $info[2] . "\n";
} else {
    echo $insert_id;
}

Update 語句

update($table, $data, $where)

table [string]

table name

data [array]

update data

where (optional) [array]

where condition [可選]

return [int]

如果返回 -1 則失敗,否則返回更新記錄數

$data = array('height' => 182,'age' => 33);
$where = array('username' => 'smallhow');
$ret = $ycdb->update("user_info_test", $data, $where);

Delete 語句

delete($table, $where)

table [string]

table name

where (optional) [array]

where condition [可選]

return [int]

如果返回 -1 則失敗,否則返回刪除記錄數

$where = array('username' => 'smallhow');
$ret = $ycdb->delete("user_info_test", $where);

完整例句

/************** the complete example **********************
SELECT  `name` AS `a`, `avatar` AS `b`, `age` 
FROM `table_a` AS `a` 
RIGHT JOIN `AAAA` AS `a1` USING (`id`)  
LEFT JOIN `BBBB` USING (`E1`,`E2`,`E3`)  
RIGHT JOIN `CCCC` AS `c1` ON `a`.`GG`=`c1`.`HH` AND `II`.`KK` =`c1`.`LL`  
WHERE `user`.`email` NOT IN ('[email protected]', '[email protected]', '[email protected]') AND 
`user`.`uid` < 11111 AND `uid` >= 222 AND 
`uid` IS NOT NULL AND 
`count` NOT IN (36, 57, 89) AND 
`id` != 1 AND `int_num` != 3 AND `double_num` != 3.76 AND 
`AA` LIKE '%saa%' AND `BB` NOT LIKE '%sbb' AND 
(`CC` LIKE '11%' OR `CC` LIKE '22_' OR `CC` LIKE '33%') AND 
(`DD` NOT LIKE '%44%' OR `DD` NOT LIKE '55%' OR `DD` NOT LIKE '66%') AND 
(`EE` LIKE '%E11' AND `EE` LIKE 'E22') AND 
(`FF` LIKE '%F33' OR `FF` LIKE 'F44') AND 
(`GG` NOT LIKE '%G55' AND `GG` NOT LIKE 'G66') AND 
(`HH` NOT LIKE 'H77' OR `HH` NOT LIKE 'H88') AND 
(`II` BETWEEN 1 AND 12) AND NOT 
(`LL` BETWEEN 1 AND 12)  AND (
  (`user_name` IS NULL OR `email` = '[email protected]') AND 
  (`user_name` = 'bar' OR `email` = '[email protected]')
) AND (`user_name` != 'foo' OR `promoted` != 1) 
GROUP BY type,age,gender 
HAVING `uid`.`num` > 111 AND `type` > 'smart' AND 
	`id` != 0 AND `god3` != 9.86 AND `uid` IS NOT NULL 
	AND `AA` LIKE 'SSA%' AND (`CC` LIKE '11%' OR `CC` LIKE '22%' OR `CC` LIKE '33%')
ORDER BY  `user`.`score` , `user`.`uid` ASC, `time` DESC 
LIMIT 33
*/

$table = "table_a(a)";

$join = [
	"[>]AAAA(a1)" => "id",
	"[<]BBBB" => ["E1", "E2", "E3"],
	"[>]CCCC(c1)" => [ "GG" => "HH", "II.KK" => "LL"]
];

$columns = ["name(a)", "avatar(b)", "age"];

$where =  [
	"user.email[!]" => ["[email protected]", "[email protected]", "[email protected]"],
	"user.uid[<]" => 11111,
	"uid[>=]" => 222,
	"uid[!]" => null,
	"count[!]" => [36, 57, 89],
	"id[!]" => true,
	"int_num[!]" => 3,
	"double_num[!]" => 3.76,
	"AA[~]" => "%saa%",
	"BB[!~]" => "%sbb",
	"CC[~]" => ["11%", "22_", "33%"],
	"DD[!~]" => ["%44%", "55%", "66%"],
	"EE[~]" => ["AND" => ["%E11", "E22"]],
	"FF[~]" => ["OR" => ["%F33", "F44"]],
	"GG[!~]" => ["AND" => ["%G55", "G66"]],
	"HH[!~]" => ["OR" => ["H77", "H88"]],
	"II[<>]" => ["1", "12"],
	"LL[><]" => ["1", "12"],
	"AND #1" => [
		"OR #1" => [
			"user_name" => null,
			"email" => "[email protected]",
		],
		"OR #2" => [
			"user_name" => "bar",
			"email" => "[email protected]"
		]
	],
	"OR" => [
		"user_name[!]" => "foo",
		"promoted[!]" => true
	],
	'GROUP' => 'userid',
	'GROUP' => ['type', 'age', 'gender'],
	'HAVING' => [
		"uid.num[>]" => 111,
		"type[>]" => "smart",
		"id[!]" => false,
		"god3[!]" => 9.86,
		"uid[!]" => null,
		"AA[~]" => "SSA%",
		"CC[~]" => ["11%", "22%", "%33"],
	],
	'ORDER' => [
		"user.score",
		"user.uid" => "ASC",
		"time" => "DESC",
	],
	"LIMIT" => '33',
];

$ycdb->select($table, $join, $columns, $where);

 

數據庫事務

$ycdb->begin();

$ret1 = $ycdb->exec("insert into user_info_test(username, sexuality, age, height) values('smallhow', 'male', 29, 180)");
$ret2 = $ycdb->exec("insert into user_info_test(username, sexuality, age, height) values('jesson', 'female', 28, 175)");

if($ret1 == -1 || $ret2 == -1 ) {
  $ycdb->rollback();
} else {
  $ycdb->commit()
}

 

數據緩存

我們可以以 redis,或者其他任何支持 set/get/del/expire 這4種方法的緩存系統作爲介質,存儲 database 返回的數據,如果不指定到期時間,默認存儲到期時間爲5分鐘,當我們指定了緩存,如果對數據有update/delete/insert等更新操作,我們最好是傳入相同的緩存key,以便ycdb能夠清理緩存來保持數據的一致性

//用 redis 作爲數據緩存介質
$redis = new Redis();
$redis->connect('/home/redis/pid/redis.sock');

$option = array("host" => "127.0.0.1", 
                "username" => "test", 
                "password" => "test", 
                "dbname" => "test", 
                "port" => '3306',
                "cache" => $redis,  //cache instance
                'option' => array(
                    PDO::ATTR_CASE => PDO::CASE_NATURAL,
                    PDO::ATTR_TIMEOUT => 2));


$ycdb = new ycdb($option);

try{
  $ycdb->initialize();
} catch (PDOException $e) {
  echo "find PDOException when initialize\n";
  exit;
}

// 保存從數據庫查詢的29歲用戶的數據到緩存
$age = 29;
$cache_key = 'pre_cache_key_' . $age;

$data = $ycdb->select("user_info_test", "*", [
	'age' => $age,
	'CACHE' => ['key' => $cache_key, 'expire' => 600]   //cache key an expire time (seconds)
]);

echo $redis->get($cache_key) . "\n";

// 如果修改,甚至刪除29歲相關的用戶數據的時候,一定記得清理緩存,以保證數據一致性
// it's best to enter the cache key to clean up the cache to keep the data consistent.
$ycdb->update("user_info_test", ['remark' => '29-year-old'], [
  'age' => $age,
  'CACHE' => ['key' => $cache_key]  //cache key
]);

echo $redis->get($cache_key) . "\n";

//刪除相關數據的時候,也需要刪除緩存
$ycdb->delete("user_info_test", [
  'age' => $age,
  'CACHE' => ['key' => $cache_key]  //cache key
]);

echo $redis->get($cache_key) . "\n";

//插入跟緩存有關的數據的時候,比如下面,插入了一個29歲的新用戶,也需要刪除緩存
$insert_data = array();
$insert_data['username'] = 'test';
$insert_data['sexuality'] = 'male';
$insert_data['age'] = 29;
$insert_data['height'] = 176;

$insert_id = $ycdb->insert("user_info_test", $insert_data, ['key' => $cache_key]);

echo $redis->get($cache_key) . "\n";

下一章:開源輕量級PHP數據庫ORM框架ycdb(2) : 構建穩定的數據庫/緩存連接池

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章