<?php
session_start();
# Part : 1 - setting
ini_set('error_reporting', E_ALL);
ini_set('display_errors', 'on');
ini_set('max_execution_time', 300); // 300 seconds = 5 minutes
/*
Updated : 2564-09-03
- code นี้เผยแพร่ที่ http://www.thaiall.com/perlphpasp/source.pl?key=9150
- code ปรับจาก http://www.thaiall.com/perlphpasp/source.pl?key=9144
- ใช้ charset เป็น utf8 กับแฟ้มรหัสต้นฉบับเพื่อการแสดงภาษาที่ถูกต้อง
- ใช้ ini_set เพื่อแสดง error_reporting ในกรณีอยู่ระหว่างทดสอบ code ชุดนี้
- collation หรือ collate คือ กฎเกณฑ์ที่ใช้เปรียบเทียบอักขระ ใช้ในคำสั่ง create table เพื่อภาษาไทย
- สร้าง ฟังก์ชัน doconnect() doquery() dofetch() และ getfld()
- มีการใช้ if แบบลดรูป เช่น echo (true) ? "true" : "false"; หรือ $x = (1 == 1) ? "one" : "two";
- พบ deprecated บน php5 หากใช้ Procedural style
ต้องใช้ mysql_connect() แทน mysqli_connect()
ต้องใช้ mysqli_query() แทน mysql_db_query()
เช่น $r = mysqli_query($conn,$myq); แทน mysql_db_query($db,$myq);
- เพิ่มตัวเลือกการ insert 1000 และหยุดทุก 100 เป็นเวลา 2 วินาที ทำให้ numrows เห็นการเปลี่ยนแปลงได้ จาก process อื่นได้
- การปิด browser ไม่สามารถหยุด process ของ php ที่กำลังประมวลผลได้ จะต้องรอให้แล้วเสร็จ หรือหมดเวลาเอง
- [pass] ตรวจ html code ที่ https://validator.w3.org/#validate_by_input
- [pass] ตรวจ css code ที่ https://jigsaw.w3.org/css-validator/validator
- [pass] ตรวจ php code ที่ https://phpcodechecker.com/
***
การส่งค่าผ่าน url เพื่อประมวลผล
1. action=create_table_innodb
เช่น innodb หากมีข้อมูลน้อย หรือมากถึง 740,000 (ข้อ 16 * 10 ครั้ง) เมื่อ insert 1000 ครั้ง ใช้เวลาประมาณ 45 วินาทีเท่า ๆ กัน
searchlastkeyid ใช้ 0.001 วินาที แม้ค้นพบระเบียนสุดท้าย หรือไม่พบเลย ระยะเวลาก็ใกล้เคียงกัน
2. action=create_table_myisam
เช่น myisam หากมีข้อมูลน้อย หรือมากถึง 740,000 (ข้อ 16 * 10 ครั้ง) เมื่อ insert 1000 ครั้ง ใช้เวลาประมาณ 10 วินาทีเท่า ๆ กัน
searchlastkeyid ใช้ 0.200 วินาที ถ้ามีข้อมูลมาก ก็จะใช้เวลามาก เป็นเชิงเส้นชัดเจน
3. action=drop_table
4. action=add&id=1&ns=1&salary=1&v7=noobj - มีการเชื่อมต่อแบบไม่ใช้ object เช่น mysqli_connect()
5. action=add&id=2&ns=2&salary=2&v7=obj - มีการเชื่อมต่อแบบใช้ object เช่น $conn = new mysqli()
6. action=add&id=3&ns=3&salary=3 - เพิ่มแล้วไปต่อหน้า select เพื่อแสดงข้อมูล
7. action=add&id=1&ns=1&salary=1&v7=noobj&stop=1 - เพิ่มแล้วไม่ทำอะไรต่อ จะ stop ทันที
8. action=add&id=2&ns=2&salary=2&v7=obj&stop=1
9. action=edit:confirm&id=1&ns=กขค&salary=100&v7=noobj
10. action=edit:confirm&id=2&ns=คงจ&salary=1000&v7=obj
11. action=edit:confirm&id=3&ns=ฉชซ&salary=10000
12. action=del:confirm&id=1
13. action=del:confirm&id=2
14. action=del:confirm&id=3
15. action=add&id=1&ns=4&salary=4&stop=1000 - ใช้เวลาสั่ง insert ข้อมูลถึง 30 วินาที โดยนำข้อมูลเข้า 1000 ครั้ง จึงช้ามาก
และจำกัดไม่ให้เกิน 20000 ถ้ากำหนดเกิน จะปรับให้เป็น 20000 อัตโนมัติ แต่ก็ยังช้ามากเช่นเดิม ก่อนทดสอบต้องวางแผนก่อนทดสอบ
16. action=add&id=5&ns=5&salary=5&stop=74000 - query สั่ง insert 74000 ใน 1 คำสั่ง sql และไม่เกิน 1 MB ทำงานเสร็จใน 1 วินาที
17. action=add&id=5&ns=5&salary=5&stop=75000 - query จะ error เพราะ max_allowed_packet ใน my.ini กำหนดเพียง 1 MB
18. action=searchlastkeyid - แสดงระเบียนสุดท้าย
19. action=numrows - แสดงจำนวนระเบียน ณ ปัจจุบัน ถ้าโปรแกรมทำงานอยู่ ต้องเข้าโหมดไม่ระบุตัวตน จะเห็นจำนวนที่เพิ่มขึ้น หาก inser ยังค้างอยู่
*/
# Part : 2 - configuration
# ส่วนกำหนดค่าเริ่มต้นของระบบ
$host = "localhost";
$db = "test";
$tb = "test";
$user = "root"; // รหัสผู้ใช้ ให้สอบถามจากผู้ดูแลระบบ
$password = ""; // รหัสผ่าน ให้สอบถามจากผู้ดูแลระบบ
$create_table_innodb = "create table test (keyid int primary key auto_increment,
id varchar(20), ns varchar(20), salary varchar(20), index (keyid)) default charset=utf8 collate=utf8_unicode_ci engine=innodb;";
$create_table_myisam = "create table test (keyid int,
id varchar(20), ns varchar(20), salary varchar(20)) default charset=utf8 collate=utf8_unicode_ci engine=myisam;";
$drop_table = "drop table test";
$select = '<a href="?">select all</a> | <a href="?action=numrows">check : numrows</a><br/>session_id() = '. session_id();
$mul_rows = 74000; // ไม่ถึง 2 วินาที กำหนดเป็น 75000 ไม่ได้ mysql go away / 74000 records "size of $dall 1035999"
$mulerr_rows = 75000;
/* ใน mysql จำกัดขนาด packet ไม่เกิน 1MB ถ้าเกิดจาก query จะ error
show variables;
show variables like 'max_allowed_packet'; // 1,048,576 bytes
set global max_allowed_packet=16777216; // ทำให้กำหนด 75000 ได้
show variables like 'bulk_insert_buffer_size'; // 8,388,608 bytes
show variables like 'key_buffer_size'; // 16,777,216
*/
if (isset($_REQUEST['action'])) $act = $_REQUEST['action']; else $act = "";
if(isset($_SESSION["engine"])) $engine = $_SESSION["engine"]; else $engine ="-";
if($act == "phpinfo") phpinfo();
# จัดการ session ของ php7
if (isset($_REQUEST["v7"])) {
/* ทดสอบ $_REQUEST["v7"] และเก็บไว้ใน session เมื่อเปลี่ยนเพจ
Procedural style: ค่า false จะใช้ mysqli_query($conn,$myq)
Object oriented style: ค่า obj จะใช้ $conn->query($myq);
*/
$_SESSION["v7"] = false;
if($_REQUEST["v7"] == "obj") $_SESSION["v7"] = true; // จะเชื่อมต่อแบบ Object oriented style
}
# Part : 3 - model
# ส่วนสร้างตาราง
if (strlen($act) > 0 && ($act == "create_table_innodb" || $act == "create_table_myisam")) {
doconnect();
if($act == "create_table_innodb") doquery($create_table_innodb); else doquery($create_table_myisam);
$_SESSION["engine"] = substr($act,13);
footer("$act : completely<br/><a href='?'>back</a>");
}
# ส่วนลบตาราง
if (strlen($act) > 0 && $act == "drop_table") {
doconnect();
doquery($drop_table);
footer("$act : completely<br/><a href='?'>back</a>");
}
# Part : 4 - view
# ส่วนแสดงจำนวนระเบียน
if ($act == "numrows") {
doconnect();
/* พบว่า ถ้า insert 1000 records
หากเรียกทั้ง 2 process คือ insert 1000 และ num_rows พร้อมกัน บน browser เดียว ต้องรอให้เสร็จพร้อมกัน
แต่ถ้าแยก browser เช่น ie , opera , edge , firefox พบว่า ทำงานแยกกันได้ และ num_rows จะแสดงผลได้
หรือใช้ โหมดปกติ (normal mode) กับ โหมดไม่ระบุตัวตน (incognito mode) แยกกัน
หรือใช้ ip ต่างกันบนเครื่องเดียวกัน ก็จะได้ session_id ต่างกันแล้วครับ
จำนวนที่พบ คือ ช่วงหยุด 1 วินาที หรือหยุดที่ทุก ๆ 100 ระเบียน
*/
doquery("select * from test");
$numrows = $r->num_rows;
sleep(2); // ต้องรอให้ session เดียวกัน จบแบบ sequencial ไม่ใช่ parallel หากเปิด 3 browser จะ complete : 1-2 , 3-4 ,5-6 วินาที
footer($numrows ." rows<br/>");
}
# ส่วนแสดงผลหลัก ทั้งปกติ และหลังกดปุ่ม del หรือ edit
if (strlen($act) == 0 || $act == "del" || $act == "edit") {
doconnect();
doquery("select * from test");
$cnt_row = 0;
/* clock : start */
list($u,$s) = preg_split('/ /',microtime());
$gap = $s + $u;
echo "<!DOCTYPE html><html lang=\"th\"><head><title>mysql_test357</title><meta charset=\"utf-8\" />
<link rel=\"stylesheet\" href=\"https://cdn.lazywasabi.net/fonts/ChulabhornLikit/ChulabhornLikitText.css\" >
<style>body {font-family:'Chulabhorn Likit Text';}</style>
</head><body><a href='?action=numrows'>#</a>";
while (dofetch("object")) {
$cnt_row++;
if (isset($_REQUEST['id']) && $_REQUEST['id'] == getfld("object","id"))
$chg = " style='background-color:#f9f9f9";
else
$chg = " readonly style='background-color:#ffffdd";
echo "<form action='?' method=post><table><tr>
<td><input name=id size=5 value='". getfld("object","id") . "' style='background-color:#dddddd' readonly></td>
<td><input name=ns size=40 value='". getfld("object","ns") . "' $chg'></td>
<td><input name=salary size=20 value='". getfld("object","salary") . "' $chg;text-align:right'></td><td>";
if (isset($_REQUEST['id']) && $_REQUEST['id'] == getfld("object","id")) {
echo ($act == "del") ? "<input type=submit name=action value='del:confirm' style='height:40px;background-color:yellow'>" : "";
echo ($act == "edit") ? "<input type=submit name=action value='edit:confirm' style='height:40px;background-color:#aaffaa'>" : "";
} else {
echo "<input type=submit name=action value='del' style='height:26px'> <input type=submit name=action value='edit' style='height:26px'>";
}
echo "</td></tr></table></form>";
}
echo "<form action='?' method=post><table><tr>
<td><input name=id size=5></td>
<td><input name=ns size=40></td>
<td><input name=salary size=20></td>
<td><input type=submit name=action value='add' style='height:26px'></td></tr>
</table>Total = $cnt_row records<br/></form>";
/* clock : stop */
list($u_stop,$s_stop) = preg_split('/ /',microtime());
$gap = $s_stop + $u_stop - $gap;
echo number_format($gap,9) . " seconds<br/>";
if (isset($_SESSION["msg"])) echo "<br/>". $_SESSION["msg"]; // แสดงผลค่านี้ หลังการ Refresh
$_SESSION["msg"] = ""; // เมื่อแสดงผลแล้ว ก็ล้างค่านี้จากตัวแปร msg
footer(null);
}
# Part : 5 - action controller
# ส่วนค้นข้อมูลตาม index key
if ($act == "searchlastkeyid") {
/* clock : start */
list($u,$s) = preg_split('/ /',microtime());
$gap = $s + $u;
/* get last keyid */
doconnect();
doquery("select max(keyid) as kid from test");
if (dofetch("object")) { $kid = getfld("object","kid"); }
if ($engine == "innodb") {
doquery("select keyid from test use index (keyid) where keyid=0". $kid); // ค้นเจอหรือไม่ก็ให้ผลใกล้เคียงกัน ทดสอบเปลี่ยน keyid ได้
} else {
doquery("select keyid from test where keyid=0". $kid);
}
if (dofetch("object")) { echo "last keyid : " .getfld("object","keyid")."<br/>"; }
/* clock : stop */
list($u_stop,$s_stop) = preg_split('/ /',microtime());
$gap = $s_stop + $u_stop - $gap;
echo number_format($gap,9) . " seconds<br/>";
footer(null);
}
# ส่วนเพิ่มข้อมูล
if ($act == "add") {
/* clock : start */
list($u,$s) = preg_split('/ /',microtime());
$gap = $s + $u;
doconnect();
$conn->query("lock tables test low_priority write");
if(isset($_REQUEST['stop'])) $stop = $_REQUEST['stop']; else $stop = 1;
if($stop == $mul_rows || $stop == $mulerr_rows) {
$d1 = "('". $_REQUEST['id'] . "','". $_REQUEST['ns'] . "','". $_REQUEST['salary'] . "')";
$dall = $d1;
for($i = 1;$i < $stop; $i++){
$dall .= ",$d1";
}
echo 'size of $dall : ' . strlen($dall) ." characters<br/>";
doquery("insert into $tb (id, ns , salary) values $dall");
} else {
if($stop > 20000) $stop = 20000; // ป้องกันเครื่องค้าง แต่ขยายได้ ถ้าเครื่อง server รองรับการทำงานจำนวนมากได้
for($i = 1;$i <= $stop; $i++){
doquery("insert into $tb (id, ns , salary) values('". $_REQUEST['id'] . "','". $_REQUEST['ns'] . "','". $_REQUEST['salary'] . "')");
if($i % 100 == 0) {
echo "$i<br/>"; // ถ้าไม่ sleep จะหยุดที่ประมาณ 4000 records เมื่อทดสอบบนเครื่องของผม
$conn->query("unlock tables test");
$conn->close(); // ใช้ phpmyadmin ตรวจจำนวน ที่ถูกนำเข้าแบบ real time ได้
sleep(1); // ทุก 100 รายการ หยุด 1 วินาที การหยุดพักมีผลให้ใช้ num_rows จากต่างเครื่อง browser ได้
doconnect();
$conn->query("lock tables test low_priority write");
}
}
}
$conn->query("unlock tables test");
/* clock : stop */
list($u_stop,$s_stop) = preg_split('/ /',microtime());
$gap = $s_stop + $u_stop - $gap;
echo number_format($gap,9) . " seconds<br/>";
if(isset($_REQUEST['stop'])) die($select); // ถ้ามี stop จะไม่ เขียนผลลัพธ์หลังประมวลผล
if ($r) $_SESSION["msg"] = "insert : completely";
footer("refresh");
}
# ส่วนลบข้อมูล
if ($act == "del:confirm") {
doconnect();
doquery("delete from $tb where id ='". $_REQUEST['id'] . "'");
if ($r) $_SESSION["msg"] = "delete : completely";
footer("refresh");
}
# ส่วนแก้ไขข้อมูล
if ($act == "edit:confirm") {
doconnect();
doquery("update $tb set ns ='". $_REQUEST['ns'] ."', salary ='". $_REQUEST['salary'] ."' where id =". $_REQUEST['id']);
footer("refresh");
}
# Part : 6 - database controller
# รวมฟังก์ชันประมวลผล
function doconnect(){
global $conn,$host,$user,$password,$db;
if ((int)phpversion() >=7) {
// php7
if (isset($_SESSION["v7"]) && $_SESSION["v7"] == false) {
$conn = mysqli_connect($host, $user, $password, $db);
if (!$conn) footer("Connection failed: " . mysqli_connect_error());
} else {
$conn = new mysqli($host, $user, $password, $db);
if ($conn->connect_error) footer("Connection failed: " . $conn->connect_error);
}
} else {
// php5
$conn = mysqli_connect($host, $user, $password, $db); // deprecated mysql_connect($host, $user, $password);
if (!$conn) footer("Connection failed: " . mysqli_connect_error());
}
}
function doquery($myq){
global $r,$conn,$db;
if ((int)phpversion() >=7) {
// php7 = Procedural style || Object oriented style
$r = (isset($_SESSION["v7"]) && $_SESSION["v7"] == false)
? mysqli_query($conn,$myq)
: $conn->query($myq);
if (!$r) footer("Query : Fail<br/>$myq");
} else {
//php5
$r = mysqli_query($conn,$myq); // deprecated : mysql_db_query($db,$myq);
if (!$r) footer("Query : Fail<br/>$myq");
}
}
function dofetch($t) {
global $o, $r; // object, assoc, array มีตัวอย่างที่ http://www.thaiall.com/php/training492.htm
if ((int)phpversion() >=7) {
// php7
if($t == "object") return ($o = $r->fetch_object());
} else {
// php5
if($t == "object") return ($o = mysqli_fetch_object($r)); // deprecated : mysql_fetch_object($r)
}
}
function getfld($t,$fld) {
global $o;
if($t == "object") return ($o->{$fld}); // object style หรือ $o->id
}
# Part : 7 - view : footer
# ส่วนท้าย
function footer($msg){
global $conn;
if($msg == "refresh") {
header("Location: ". $_SERVER["SCRIPT_NAME"]);
if (isset($_SESSION["v7"]) && $_SESSION["v7"] == false)
mysqli_close($conn);
else
$conn->close();
} else {
echo "$msg<br/>version " . (int)phpversion() . " : ";
if ((int)phpversion() >=7) {
echo (isset($_SESSION["v7"]) && $_SESSION["v7"] == false)
? "<a href='?v7=obj'>mysqli object</a> : <span style=\"font-weight:bold;\">mysqli no object</span>"
: "<span style=\"font-weight:bold;\">mysqli object</span> : <a href='?v7=noobj'>mysqli no object</a>";
}
}
footer_menu();
}
function footer_menu(){
global $conn,$r,$select,$mul_rows,$mulerr_rows,$engine;
die ("<br/><span style='font-weight:bold;'>เมนู</span><ol>
<li><a href='?action=create_table_innodb'>?action=create_table_innodb</a></li>
<li><a href='?action=create_table_myisam'>?action=create_table_myisam</a></li>
<li><a href='?action=drop_table'>?action=drop_table</a></li>
<li><a href='?action=add&id=1&ns=1&salary=1&v7=noobj'>?action=add&id=1&ns=1&salary=1&v7=noobj</a></li>
<li><a href='?action=add&id=2&ns=2&salary=2&v7=obj'>?action=add&id=2&ns=2&salary=2&v7=obj</a></li>
<li><a href='?action=add&id=3&ns=3&salary=3'>?action=add&id=3&ns=3&salary=3</a></li>
<li><a href='?action=add&id=1&ns=1&salary=1&v7=noobj&stop=1'>?action=add&id=1&ns=1&salary=1&v7=noobj&stop=1</a></li>
<li><a href='?action=add&id=2&ns=2&salary=2&v7=obj&stop=1'>?action=add&id=2&ns=2&salary=2&v7=obj&stop=1</a></li>
<li><a href='?action=edit:confirm&id=1&ns=กขค&salary=100&v7=noobj'>?action=edit:confirm&id=1&ns=กขค&salary=100&v7=noobj</a></li>
<li><a href='?action=edit:confirm&id=2&ns=คงจ&salary=1000&v7=obj'>?action=edit:confirm&id=1&ns=คงจ&salary=1000&v7=obj</a></li>
<li><a href='?action=edit:confirm&id=3&ns=ฉชซ&salary=10000'>?action=edit:confirm&id=3&ns=ฉชซ&salary=10000</a></li>
<li><a href='?action=del:confirm&id=1'>?action=del:confirm&id=1</a></li>
<li><a href='?action=del:confirm&id=2'>?action=del:confirm&id=2</a></li>
<li><a href='?action=del:confirm&id=3'>?action=del:confirm&id=3</a></li>
<li><a href='?action=add&id=1&ns=4&salary=4&stop=1000'>?action=add&id=1&ns=4&salary=4&stop=1000</a> (innodb:40sec , myisam:10sec+)</li>
<li><a href='?action=add&id=5&ns=5&salary=5&stop=$mul_rows'>?action=add&id=5&ns=5&salary=5&stop=$mul_rows</a> (ต้องแก้เลขในตัวแปร)</li>
<li><a href='?action=add&id=5&ns=5&salary=5&stop=$mulerr_rows'>?action=add&id=5&ns=5&salary=5&stop=$mulerr_rows</a> (75000 error ต้องแก้ my.ini)</li>
<li><a href='?action=searchlastkeyid'>?action=searchlastkeyid</a></li>
<li><a href='?action=numrows'>?action=numrows</a></li>
</ol>$select<br/>MySQL thread : ". $conn->thread_id . "<br/>Engine : ". $engine . "</body></html>"); // $select ถูกใช้เมื่อส่งค่า stop เพื่อเป็น back link
$r->close();
$conn->close();
}
?>
จำนวน : 333 บรรทัด