아주 적은 수의 사용자로 웹사이트를 구축하고 싶습니다. 사용자 수는 몇 명에서 수십 명 이상입니다.
백엔드는 php를 사용하는데, 이제 데이터를 저장하는 방법을 알고 싶습니다.
사용자 수가 매우 적기 때문에 호스트 구성도 매우 낮습니다. mysql을 사용하면 호스트 구성이 너무 낮아 성능에 영향을 미칠 수 있습니다.
파일에 직접 저장하는 것을 고려했지만(데이터의 구조가 비교적 간단하고 json이 처리할 수 있음) 관련된 쿼리(예: mysql의 조인)가 있으면 PHP를 사용해야 합니다. 관련 검색어를 직접 작성하는 것이 조금 번거롭습니다.
더 좋은 방법은 없을까요? 초경량 데이터베이스를 좋아하시나요?
PS: 호스트 구성이 낮고 메모리가 확실히 부족하기 때문에 Redis가 필요하지 않습니다. mysql이나 redis처럼 기적을 만들기 위해 애쓰는 기업은 이런 허술한 환경에서는 제 힘을 발휘할 수 없습니다.
아주 적은 수의 사용자로 웹사이트를 구축하고 싶습니다. 사용자 수는 몇 명에서 수십 명 이상입니다.
백엔드는 php를 사용하는데, 이제 데이터를 저장하는 방법을 알고 싶습니다.
사용자 수가 매우 적기 때문에 호스트 구성도 매우 낮습니다. mysql을 사용하면 호스트 구성이 너무 낮아 성능에 영향을 미칠 수 있습니다.
파일에 직접 저장하는 것을 고려했지만(데이터의 구조가 비교적 간단하고 json이 처리할 수 있음) 관련된 쿼리(예: mysql의 조인)가 있으면 PHP를 사용해야 합니다. 관련 검색어를 직접 작성하는 것이 조금 번거롭습니다.
더 좋은 방법은 없을까요? 초경량 데이터베이스를 좋아하시나요?
PS: 호스트 구성이 낮고 메모리가 확실히 부족하기 때문에 Redis가 필요하지 않습니다. mysql이나 redis처럼 기적을 만들기 위해 애쓰는 기업은 이런 허술한 환경에서는 제 힘을 발휘할 수 없습니다.
SQLite를 사용하거나 단순히 파일 저장소를 사용하는 경우 파일 저장소를 사용하는 경우 데이터 파일은 웹 루트 외부에 배치되어야 합니다.
위의 정답은 sqlite 입니다. 가볍고 서비스도 필요 없고 파일만 있으면 됩니다. sqlite를 사용하기 전에 제가 연구 개발에 참여했던 프로젝트의 클라이언트입니다
여전히 mysql입니다. 파일 읽기 및 쓰기 효율성은 mysql을 사용하는 것보다 낮고, 개발할 때 더욱 어려울 것입니다. 먼저 시도해 보시기 바랍니다. 호스트 구성이 Raspberry Pi보다 낮을까요? 물론 호스트 구성이 이미 낮으면 Windows를 설치할 방법이 없습니다.
ORM을 사용하여 개발하는 경우 초기 단계에서 데이터베이스를 선택하는 것은 고려할 사항이 아닙니다.
SQL 개발을 작성하는 경우 향후 데이터베이스가 변경되면 고려해야 합니다. 작성한 SQL이 호환되지 않을 수 있습니다.
호스트 구성이 아무리 낮아도 생각만큼 과장되지는 않습니다. MySQL과 Postgres는 확실히 실행됩니다.
SQLite를 권장합니다. PHP에는 확장 기능이 포함되어 있지만 기능과 데이터 유형은 MySQL보다 훨씬 간단하지만 일반적으로 사용되는 것들도 괜찮고 성능도 매우 좋습니다.
SQLite를 사용하는 경우 웹사이트의 루트 디렉터리에 데이터 파일을 저장하지 않는 것이 가장 좋습니다. 그렇지 않으면 사람들이 URL을 알고 있으면 해당 파일을 다운로드하게 됩니다.
물론 요청을 거부하도록 Apache/Nginx를 구성할 수 있습니다. 지정된 파일에 접근하기는 하지만 결국에는 숨겨진 위험이 있습니다.
그래서 웹사이트의 루트 디렉터리 외부에 두는 것이 좋습니다.
또한 SQLite를 사용하는 경우 SQL 언어를 작성해야 합니다. PDO 기능 세트를 사용하여 작동합니다.
<code><?php function db() { static $db; if ($db) { return $db; } else { try { $db = new PDO('sqlite:'.$_SERVER['DOCUMENT_ROOT'].'/../data.db3'); } catch (PDOException $e) { echo $e->getMessage(); exit(); } return $db; } } function insert($title = '', $content = '') { global $app; $db = db(); $stmt = $db->prepare('INSERT INTO posts (post_title, post_content) VALUES (?, ?)'); $stmt->bindParam(1, $title, PDO::PARAM_STR); $stmt->bindParam(2, $content, PDO::PARAM_STR); $stmt->execute(); return ($stmt->rowCount() !== 0) ? array(true, 'lastInsertId' => $db->lastInsertId()) : array(false, 'lastInsertId' => $db->lastInsertId()); } function select($id = '') { global $app; $db = db(); if (!empty($id)) { return $db->query('SELECT * FROM posts WHERE id = '.intval($id))->fetchAll(PDO::FETCH_ASSOC); } else { return $db->query('SELECT * FROM posts')->fetchAll(PDO::FETCH_ASSOC); } } function select_v2($id = '') { global $app; $db = db(); if (!empty($id)) { $stmt = $db->prepare('SELECT * FROM posts WHERE id = ?'); $stmt->bindParam(1, $id, PDO::PARAM_INT); } else { $stmt = $db->prepare('SELECT * FROM posts'); } $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_ASSOC); } function update($id, $title = '', $content = '') { global $app; $db = db(); //echo PDO::ATTR_AUTOCOMMIT; //返回0可见PDO默认禁用自动提交事务. //echo $db->getAttribute(PDO::ATTR_AUTOCOMMIT); exit(); //返回1可见MySQL默认会自动提交事务. //SQLite不支持设置PDO::ATTR_AUTOCOMMIT: //SQLite: Uncaught exception 'PDOException' with message 'The auto-commit mode cannot be changed for this driver' //$db->setAttribute(PDO::ATTR_AUTOCOMMIT, false); $db->beginTransaction(); $stmt = $db->prepare('UPDATE posts SET post_title = ?, post_content = ? WHERE id = ?'); $stmt->execute(array($title,$content,$id)); //所有值视作PDO::PARAM_STR处理 //$stmt->execute(array(':title' => $title,':content' => $content,':id' => $id)); //$stmt->bind_param('ssi', $title, $content, $id); //对比mysqli echo 'sleep(3);'."\n"; sleep(3); $db->commit(); //$db->setAttribute(PDO::ATTR_AUTOCOMMIT, true); //commit提交事务后autocommit记得重新设为true return ($stmt->rowCount() !== 0) ? true : false; } function delete($id) { global $app; $db = db(); return ($db->query('DELETE FROM posts WHERE id = '.intval($id))->rowCount() !== 0) ? true : false; } function delete_v2($id) { global $app; $db = db(); $stmt = $db->prepare('DELETE FROM posts WHERE id = ?'); $stmt->bindParam(1, $id, PDO::PARAM_INT); $stmt->execute(); return ($stmt->rowCount() !== 0) ? true : false; } header('Content-Type: text/plain; charset=utf-8'); $sqlite = "CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY, post_title VARCHAR(255) NOT NULL, post_content TEXT NOT NULL )"; db()->query('DROP TABLE IF EXISTS posts;') or exit(); db()->query($sqlite) or exit(); //并发时,SQLite在insert时因为库文件被其他请求锁住而导致阻塞 echo "var_export(insert('标题1', '内容1'));\n"; var_export(insert('标题1', '内容1')); echo "\n\n"; echo "var_export(insert('标题2', '内容2'));\n"; var_export(insert('标题2', '内容2')); echo "\n\n"; echo "var_export(select());\n"; var_export(select()); echo "\n\n"; echo "var_export(update(2, '标题2_更新','内容2_更新'));\n"; var_export(update(2, '标题2_更新','内容2_更新')); echo "\n\n"; echo "var_export(select(2));\n"; var_export(select(2)); echo "\n\n"; echo "var_export(delete(2));\n"; var_export(delete(2)); echo "\n\n"; echo "var_export(select());\n"; var_export(select()); echo "\n\n";</code>
그런 경험이 있으신지 궁금합니다. PHP는 실제로 배열 지향 프로그래밍 언어입니다.
실제로 PHP 배열을 파일로 직접 내보내 데이터를 저장하는 것을 고려해 볼 수 있습니다.
<code><?php header('Content-Type: text/plain; charset=utf-8'); $file = __DIR__.'/data.php'; //数据文件,别人直接URL访问也下载不了 if(!file_exists($file)) { file_put_contents($file, '<?php return array();'); //file_put_contents($file, ''); } $fp = fopen($file, 'r+'); //读写方式打开,将文件指针指向文件头 if(flock($fp, LOCK_EX)) { //阻塞到获取排它锁 //锁定数据文件后再进行读写 $arr = require $file; //$arr = unserialize(file_get_contents($file)); $arr[] = date('Y-m-d H:i:s'); ftruncate($fp, 0); //清空文件 fwrite($fp, '<?php return '.var_export($arr, true).';'); //fwrite($fp, serialize($arr)); fflush($fp); //在释放锁之前刷新输出 //sleep(10); //睡眠10秒,在此期间其他工作进程的请求将被阻塞 flock($fp, LOCK_UN); //释放锁定 echo file_get_contents($file)."\n"; } fclose($fp);</code>
주석에서는 PHP 세션처럼 직렬화된 저장소를 직렬화/직렬 해제하는 방법도 제공합니다.
직렬화/직렬 해제 성능이 var_export/require보다 훨씬 낫다는 점은 언급할 가치가 있습니다.
하지만 var_export/ require의 장점 다른 사람이 직접 접근하는 것을 두려워하지 않고 루트 디렉터리에 파일을 넣을 수 있어 가독성이 더 높다는 점입니다.