เปิดเผยปัญหาฐานข้อมูลทั่วไปห้าประการที่เกิดขึ้นในแอปพลิเคชัน PHP รวมถึงการออกแบบสคีมาฐานข้อมูล การเข้าถึงฐานข้อมูล และรหัสตรรกะทางธุรกิจที่ใช้ฐานข้อมูล รวมถึงวิธีแก้ไขปัญหา
หากมีวิธีเดียวในการใช้ฐานข้อมูลที่ถูกต้อง...
มีหลายวิธีที่คุณสามารถสร้างการออกแบบฐานข้อมูล การเข้าถึงฐานข้อมูล และโค้ดตรรกะธุรกิจ PHP บนฐานข้อมูลได้ แต่มักจะจบลงด้วยความผิดพลาด บทความนี้จะอธิบายปัญหาทั่วไปห้าประการที่เกิดขึ้นในการออกแบบฐานข้อมูลและโค้ด PHP ที่เข้าถึงฐานข้อมูล และวิธีการแก้ไขเมื่อคุณพบปัญหาเหล่านั้น
ปัญหาที่ 1: การใช้ MySQL โดยตรง
ปัญหาที่พบบ่อยคือโค้ด PHP รุ่นเก่าใช้ฟังก์ชัน mysql_ เพื่อเข้าถึงฐานข้อมูลโดยตรง รายการที่ 1 แสดงวิธีการเข้าถึงฐานข้อมูลโดยตรง
รายการ 1. Access/get.php
<?php
ฟังก์ชั่น get_user_id( $name )
-
$db = mysql_connect( 'localhost', 'root', 'รหัสผ่าน' );
mysql_select_db( 'users' );
$res = mysql_query( "SELECT id FROM users WHERE login='".$name."'" );
ในขณะที่( $row = mysql_fetch_array( $res ) ) { $id = $row[0];
กลับ $id;
}
var_dump( get_user_id( 'แจ็ค' ) );
?>
โปรดทราบว่าฟังก์ชัน mysql_connect ใช้เพื่อเข้าถึงฐานข้อมูล นอกจากนี้ ให้สังเกตแบบสอบถาม ซึ่งใช้การต่อสตริงเพื่อเพิ่มพารามิเตอร์ $name ให้กับแบบสอบถาม
มีสองทางเลือกที่ดีสำหรับเทคโนโลยีนี้: โมดูล PEAR DB และคลาส PHP Data Objects (PDO) ทั้งสองมีนามธรรมจากการเลือกฐานข้อมูลเฉพาะ เป็นผลให้โค้ดของคุณสามารถทำงานได้บน IBM® DB2®, MySQL, PostgreSQL หรือฐานข้อมูลอื่น ๆ ที่คุณต้องการเชื่อมต่อโดยไม่ต้องปรับแต่งมากนัก
ค่าอื่นของการใช้โมดูล PEAR DB และเลเยอร์นามธรรม PDO คือคุณสามารถใช้ตัวดำเนินการ ? ในคำสั่ง SQL การทำเช่นนี้ทำให้ SQL ง่ายต่อการบำรุงรักษาและปกป้องแอปพลิเคชันของคุณจากการโจมตีแบบแทรก SQL
รหัสทางเลือกที่ใช้ PEAR DB แสดงอยู่ด้านล่าง
รายการ 2. Access/get_good.php
<?php
need_once("DB.php");
ฟังก์ชั่น get_user_id( $name )
-
$dsn = 'mysql://root:password@localhost/users';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( 'เลือก id จากผู้ใช้ WHERE เข้าสู่ระบบ=?',array( $name ) ) ;
$id = โมฆะ;
ในขณะที่( $res->fetchInto( $row ) ) { $id = $row[0]; }
กลับ $id;
}
var_dump( get_user_id( 'แจ็ค' ) );
?>
โปรดทราบว่าการใช้งาน MySQL โดยตรงทั้งหมดได้ถูกยกเลิกแล้ว ยกเว้นสตริงการเชื่อมต่อฐานข้อมูลใน $dsn นอกจากนี้ เรายังใช้ตัวแปร $name ใน SQL ผ่านตัวดำเนินการ ? จากนั้นข้อมูลแบบสอบถามจะถูกส่งผ่านอาร์เรย์ที่ส่วนท้ายของเมธอด query()
ปัญหาที่ 2: การไม่ใช้คุณสมบัติการเพิ่มอัตโนมัติ
เช่นเดียวกับฐานข้อมูลสมัยใหม่ส่วนใหญ่ MySQL มีความสามารถในการสร้างตัวระบุเฉพาะที่เพิ่มขึ้นอัตโนมัติตามแต่ละบันทึก นอกเหนือจากนั้น เราจะยังคงเห็นโค้ดที่รันคำสั่ง SELECT เพื่อค้นหา id ที่ใหญ่ที่สุด จากนั้นเพิ่ม id นั้นทีละ 1 และค้นหาระเบียนใหม่ รายการ 3 แสดงตัวอย่างรูปแบบที่ไม่ดี
รายการ 3.
ตาราง DROP ของ Badid.sql หากมีผู้ใช้อยู่;
สร้างผู้ใช้ตาราง (
รหัสปานกลาง,
ข้อความเข้าสู่ระบบ,
ข้อความรหัสผ่าน
แทรกค่าผู้ใช้ (1, 'แจ็ค', 'ผ่าน')
;
แทรกค่าผู้ใช้ ( 2, 'joan', 'pass' );
INSERT INTO users VALUES (1, 'jane', 'pass' );
ที่นี่ฟิลด์ id จะถูกระบุเป็นจำนวนเต็ม ดังนั้น แม้ว่ามันควรจะไม่ซ้ำกัน แต่เราสามารถเพิ่มค่าใดๆ ก็ได้ ดังที่แสดงในคำสั่ง INSERT หลายคำสั่งที่อยู่ถัดจากคำสั่ง CREATE รายการที่ 4 แสดงโค้ด PHP เพื่อเพิ่มผู้ใช้ในสคีมาประเภทนี้
รายการ 4. Add_user.php
<?php
need_once("DB.php");
ฟังก์ชั่น add_user( $name, $pass )
-
$rows = array();
$dsn = 'mysql://root:password@localhost/bad_badid';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "เลือกสูงสุด (id) จากผู้ใช้" );
$id = โมฆะ;
ในขณะที่( $res->fetchInto( $row ) ) { $id = $row[0]; } $
id += 1;
$sth = $db->prepare( "INSERT INTO users VALUES(?,?,?) " );
$db->execute( $sth, array( $id, $name, $pass ) );
กลับ $id;
}
$id = add_user( 'jerry', 'pass' );
var_dump( $id );
?>
โค้ดใน add_user.php ดำเนินการค้นหาก่อนเพื่อค้นหาค่าสูงสุดของ id จากนั้นไฟล์จะรันคำสั่ง INSERT โดยมีค่า id เพิ่มขึ้น 1 รหัสนี้จะล้มเหลวในสภาวะการแข่งขันบนเซิร์ฟเวอร์ที่มีการโหลดจำนวนมาก แถมยังไม่มีประสิทธิภาพอีกด้วย
แล้วทางเลือกคืออะไร? ใช้คุณสมบัติการเพิ่มอัตโนมัติใน MySQL เพื่อสร้าง ID เฉพาะสำหรับแต่ละส่วนแทรกโดยอัตโนมัติ สคีมาที่อัปเดตมีลักษณะเช่นนี้
รายการ 5. Goodid.php
DROP TABLE หากมีผู้ใช้อยู่;
สร้างผู้ใช้ตาราง (
รหัส MEDIUMINT ไม่เป็นโมฆะ AUTO_INCREMENT
ข้อความเข้าสู่ระบบไม่เป็นโมฆะ
ข้อความรหัสผ่านไม่เป็นโมฆะ
คีย์หลัก (รหัส)
INSERT
INTO ผู้ใช้ค่า ( null, 'jack', 'pass' );
INSERT INTO users VALUES (null, 'joan', 'pass' );
INSERT INTO users VALUES ( null, 'jane', 'pass' );
เราได้เพิ่มแฟล็ก NOT NULL เพื่อระบุว่าฟิลด์จะต้องไม่เป็นโมฆะ นอกจากนี้ เรายังเพิ่มแฟล็ก AUTO_INCREMENT เพื่อระบุว่าฟิลด์นั้นกำลังเพิ่มขึ้นโดยอัตโนมัติ และแฟล็ก PRIMARY KEY เพื่อระบุว่าฟิลด์นั้นเป็นรหัส การเปลี่ยนแปลงเหล่านี้ทำให้สิ่งต่างๆ เร็วขึ้น รายการ 6 แสดงโค้ด PHP ที่อัปเดตเพื่อแทรกผู้ใช้ลงในตาราง
รายการ 6. Add_user_good.php
<?php
need_once("DB.php");
ฟังก์ชั่น add_user( $name, $pass )
-
$dsn = 'mysql://root:password@localhost/good_genid';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$sth = $db->prepare( "INSERT INTO users VALUES(null,?,?)" );
$db->execute( $sth, array( $name, $pass ) );
$res = $db->query( "SELECT Last_insert_id()" );
$id = โมฆะ;
ในขณะที่( $res->fetchInto( $row ) ) { $id = $row[0]; }
กลับ $id;
}
$id = add_user( 'jerry', 'pass' );
var_dump( $id );
?>
ตอนนี้แทนที่จะรับค่า ID สูงสุด ฉันจะใช้คำสั่ง INSERT โดยตรงเพื่อแทรกข้อมูล จากนั้นใช้คำสั่ง SELECT เพื่อดึง ID ของบันทึกที่แทรกครั้งล่าสุด โค้ดนั้นง่ายกว่าและมีประสิทธิภาพมากกว่าเวอร์ชันดั้งเดิมและรูปแบบที่เกี่ยวข้องมาก
คำถามที่ 3: การใช้หลายฐานข้อมูล
ในบางครั้ง เราจะเห็นแอปพลิเคชันที่แต่ละตารางอยู่ในฐานข้อมูลที่แยกจากกัน ซึ่งถือว่าสมเหตุสมผลในฐานข้อมูลที่มีขนาดใหญ่มาก แต่สำหรับแอปพลิเคชันทั่วไป การแบ่งพาร์ติชันระดับนี้ไม่จำเป็น นอกจากนี้ การสืบค้นเชิงสัมพันธ์ไม่สามารถดำเนินการข้ามฐานข้อมูลได้ ซึ่งนำความคิดทั้งหมดไปใช้ฐานข้อมูลเชิงสัมพันธ์ ไม่ต้องพูดถึงว่าการจัดการตารางข้ามหลายฐานข้อมูลจะยากกว่า แล้วฐานข้อมูลหลาย ๆ อันควรมีลักษณะอย่างไร? ขั้นแรก คุณต้องมีข้อมูลบางอย่าง รายการ 7 แสดงข้อมูลดังกล่าวแบ่งออกเป็นสี่ไฟล์
รายการ 7. ไฟล์ฐานข้อมูล
Files.sql:
สร้างไฟล์ตาราง (
รหัสปานกลาง,
user_id MEDIUMINT,
ชื่อข้อความ
ข้อความเส้นทาง
);
Load_files.sql:
แทรกค่าลงในไฟล์ ( 1, 1, 'test1.jpg', 'files/test1.jpg' );
แทรกค่าลงในไฟล์ (2, 1, 'test2.jpg', 'files/test2.jpg' );
Users.sql:
วางตารางหากมีผู้ใช้อยู่
สร้างผู้ใช้ตาราง (
รหัสปานกลาง,
ข้อความเข้าสู่ระบบ,
ข้อความรหัสผ่าน
);
Load_users.sql:
แทรกเข้าไปในค่าผู้ใช้ ( 1, 'แจ็ค', 'ผ่าน');
INSERT INTO users VALUES (2, 'jon', 'pass' );
ในเวอร์ชันหลายฐานข้อมูลของไฟล์เหล่านี้ คุณควรโหลดคำสั่ง SQL ลงในฐานข้อมูลเดียว จากนั้นโหลดคำสั่ง SQL ของผู้ใช้ลงในฐานข้อมูลอื่น รหัส PHP ที่ใช้ในการสืบค้นฐานข้อมูลสำหรับไฟล์ที่เกี่ยวข้องกับผู้ใช้เฉพาะแสดงอยู่ด้านล่าง
รายการ 8. Getfiles.php
<?php
need_once("DB.php");
ฟังก์ชั่น get_user( $name )
-
$dsn = 'mysql://root:password@localhost/bad_multi1';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "เลือก id จากผู้ใช้ WHERE เข้าสู่ระบบ=?",array( $name ) ) ;
$uid = โมฆะ;
ในขณะที่( $res->fetchInto( $row ) ) { $uid = $row[0]
;
}
ฟังก์ชัน get_files( $name )
-
$uid = get_user( $name );
$rows = array();
$dsn = 'mysql://root:password@localhost/bad_multi2';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "เลือก * จากไฟล์ WHERE user_id=?",array( $uid ) ) ;
ในขณะที่( $res->fetchInto( $row ) ) { $rows[] = $row; }
กลับ $แถว;
}
$files = get_files( 'แจ็ค' );
var_dump( $files );
?>
ฟังก์ชัน get_user เชื่อมต่อกับฐานข้อมูลที่มีตารางผู้ใช้และดึง ID ของผู้ใช้ที่กำหนด ฟังก์ชัน get_files เชื่อมต่อกับตารางไฟล์และดึงแถวไฟล์ที่เกี่ยวข้องกับผู้ใช้ที่กำหนด
วิธีที่ดีกว่าในการทำสิ่งเหล่านี้ทั้งหมดคือการโหลดข้อมูลลงในฐานข้อมูล จากนั้นดำเนินการค้นหา ดังตัวอย่างด้านล่าง
รายการ 9. Getfiles_good.php
<?php
need_once("DB.php");
ฟังก์ชั่น get_files( $name )
-
$rows = array();
$dsn = 'mysql://root:password@localhost/good_multi';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query("SELECT files.* จากผู้ใช้ ไฟล์ WHERE
users.login=? และ users.id=files.user_id",
อาร์เรย์( $ชื่อ ) );
ในขณะที่( $res->fetchInto( $row ) ) { $rows[] = $row; }
กลับ $rows;
}
$files = get_files( 'แจ็ค' );
var_dump( $files );
?>
โค้ดไม่เพียงแต่สั้นกว่า แต่ยังเข้าใจง่ายกว่าและมีประสิทธิภาพอีกด้วย แทนที่จะดำเนินการสองแบบสอบถาม เราจะดำเนินการหนึ่งแบบสอบถาม
แม้ว่าคำถามนี้อาจฟังดูลึกซึ้ง แต่ในทางปฏิบัติเรามักจะสรุปว่าตารางทั้งหมดควรอยู่ในฐานข้อมูลเดียวกัน เว้นแต่จะมีเหตุผลที่น่าสนใจมาก
คำถามที่ 4: การไม่ใช้ความสัมพันธ์
ฐานข้อมูลเชิงสัมพันธ์แตกต่างจากภาษาการเขียนโปรแกรมตรงที่ไม่มีประเภทอาร์เรย์ แต่จะใช้ความสัมพันธ์ระหว่างตารางเพื่อสร้างโครงสร้างแบบหนึ่งต่อกลุ่มระหว่างออบเจ็กต์ ซึ่งมีผลเหมือนกับอาร์เรย์ ปัญหาหนึ่งที่ฉันพบในแอปพลิเคชันคือวิศวกรพยายามใช้ฐานข้อมูลเหมือนกับภาษาการเขียนโปรแกรม โดยการสร้างอาร์เรย์โดยใช้สตริงข้อความที่มีตัวระบุที่คั่นด้วยเครื่องหมายจุลภาค ดูรูปแบบด้านล่าง
รายการ 10.
ตารางวาง Bad.sql หากมีไฟล์อยู่
สร้างไฟล์ตาราง (
รหัสปานกลาง,
ชื่อข้อความ
ข้อความเส้นทาง
);
วางตารางหากมีผู้ใช้อยู่;
สร้างผู้ใช้ตาราง (
รหัสปานกลาง,
ข้อความเข้าสู่ระบบ,
ข้อความรหัสผ่าน,
ข้อความของไฟล์
แทรก
ค่าลงในไฟล์ ( 1, 'test1.jpg', 'media/test1.jpg' );
แทรกค่าลงในไฟล์ (2, 'test1.jpg', 'media/test1.jpg' );
INSERT INTO users VALUES (1, 'jack', 'pass', '1,2' );
ผู้ใช้ในระบบสามารถมีได้หลายไฟล์ ในภาษาการเขียนโปรแกรม ควรใช้อาร์เรย์เพื่อแสดงไฟล์ที่เกี่ยวข้องกับผู้ใช้ ในตัวอย่างนี้ โปรแกรมเมอร์เลือกที่จะสร้างฟิลด์ไฟล์ที่มีรายการรหัสไฟล์ที่คั่นด้วยเครื่องหมายจุลภาค ในการรับรายการไฟล์ทั้งหมดสำหรับผู้ใช้รายใดรายหนึ่ง โปรแกรมเมอร์จะต้องอ่านแถวจากตารางผู้ใช้ก่อน จากนั้นจึงแยกวิเคราะห์ข้อความของไฟล์ และรันคำสั่ง SELECT แยกกันสำหรับแต่ละไฟล์ รหัสแสดงอยู่ด้านล่าง
รายการ 11. Get.php
<?php
need_once("DB.php");
ฟังก์ชั่น get_files( $name )
-
$dsn = 'mysql://root:password@localhost/bad_norel';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( "เลือกไฟล์จากผู้ใช้ WHERE เข้าสู่ระบบ=?",array( $name ) ) ;
$ไฟล์ = โมฆะ;
ในขณะที่( $res->fetchInto( $row ) ) { $files = $row[0]; }
$rows = array();
foreach( split( ',',$files ) as $file )
-
$res = $db->query( "SELECT * FROM files WHERE id=?",
อาร์เรย์( $ไฟล์ ) );
ในขณะที่( $res->fetchInto( $row ) ) { $rows[] = $row;
}
ส่งคืน $rows;
}
$files = get_files( 'แจ็ค' );
var_dump( $files );
?>
เทคโนโลยีช้า ดูแลรักษายาก และใช้งานฐานข้อมูลได้ไม่ดี ทางออกเดียวคือออกแบบสคีมาใหม่เพื่อแปลงกลับเป็นรูปแบบเชิงสัมพันธ์แบบดั้งเดิมดังที่แสดงด้านล่าง
รายการ 12. Good.sql
DROP TABLE หากมีไฟล์อยู่
สร้างไฟล์ตาราง (
รหัสปานกลาง,
user_id MEDIUMINT,
ชื่อข้อความ
ข้อความเส้นทาง
);
วางตารางหากมีผู้ใช้อยู่;
สร้างผู้ใช้ตาราง (
รหัสปานกลาง,
ข้อความเข้าสู่ระบบ,
ข้อความรหัสผ่าน
แทรกค่าผู้ใช้ (1, 'แจ็ค', 'ผ่าน')
;
แทรกค่าลงในไฟล์ ( 1, 1, 'test1.jpg', 'media/test1.jpg' );
INSERT INTO files VALUES (2, 1, 'test1.jpg', 'media/test1.jpg' )
ที่นี่ แต่ละไฟล์เกี่ยวข้องกับผู้ใช้ในตารางไฟล์ผ่านฟังก์ชัน user_id สิ่งนี้อาจขัดแย้งกับใครก็ตามที่คิดว่าไฟล์หลายไฟล์เป็นอาร์เรย์ แน่นอนว่าอาร์เรย์ไม่ได้อ้างอิงถึงอ็อบเจ็กต์ที่มีอยู่—อันที่จริงแล้ว ในทางกลับกันด้วย แต่ในฐานข้อมูลเชิงสัมพันธ์ นั่นคือวิธีการทำงาน และการสืบค้นทำได้เร็วและง่ายกว่ามากด้วยเหตุนี้ รายการ 13 แสดงโค้ด PHP ที่เกี่ยวข้อง
รายการ 13. Get_good.php
<?php
need_once("DB.php");
ฟังก์ชั่น get_files( $name )
-
$dsn = 'mysql://root:password@localhost/good_rel';
$db =& DB::Connect( $dsn, array() );
ถ้า (PEAR::isError($db)) { die($db->getMessage()); }
$rows = array();
$res = $db->query("SELECT files.* FROM users,files WHERE users.login=?
และ users.id=files.user_id",array( $name ) );
ในขณะที่( $res->fetchInto( $row ) ) { $rows[] = $row; }
กลับ $แถว;
}
$files = get_files( 'แจ็ค' );
var_dump( $files );
?>
ที่นี่ เราทำการสืบค้นไปยังฐานข้อมูลเพื่อให้ได้แถวทั้งหมด รหัสไม่ซับซ้อนและใช้ฐานข้อมูลตามที่ตั้งใจไว้
คำถามที่ 5: รูปแบบ n+1
ฉันไม่สามารถบอกคุณได้ว่ากี่ครั้งแล้วที่ฉันได้เห็นแอปพลิเคชันขนาดใหญ่ โดยที่โค้ดดึงข้อมูลเอนทิตีบางส่วนก่อน (เช่น ลูกค้า) จากนั้นกลับไปกลับมาเพื่อดึงข้อมูลทีละตัวเพื่อให้ได้แต่ละเอนทิตี รายละเอียดของกิจการ เราเรียกโหมดนี้ว่าโหมด n+1 เนื่องจากแบบสอบถามถูกดำเนินการหลายครั้ง โดยแบบสอบถามหนึ่งจะดึงรายการของเอนทิตีทั้งหมด จากนั้นจึงดำเนินการหนึ่งแบบสอบถามสำหรับแต่ละเอนทิตี n รายการ นี่ไม่ใช่ปัญหาเมื่อ n=10 แต่แล้ว n=100 หรือ n=1000 ล่ะ? แล้วจะมีความไร้ประสิทธิภาพอย่างแน่นอน รายการ 14 แสดงตัวอย่างรูปแบบนี้
รายการ 14. Schema.sql
DROP TABLE หากมีผู้เขียนอยู่;
สร้างผู้เขียนตาราง (
รหัส MEDIUMINT ไม่เป็นโมฆะ AUTO_INCREMENT
ชื่อข้อความไม่เป็นโมฆะ
คีย์หลัก (รหัส)
);
วางตารางหากมีหนังสืออยู่;
สร้างหนังสือตาราง (
รหัส MEDIUMINT ไม่เป็นโมฆะ AUTO_INCREMENT
author_id MEDIUMINT ไม่เป็นโมฆะ
ชื่อข้อความไม่เป็นโมฆะ
คีย์หลัก (รหัส)
แทรก
ลงในค่านิยมของผู้เขียน ( null, 'Jack Herrington');
INSERT INTO authors VALUES ( null, 'Dave Thomas' );
INSERT INTO หนังสือ VALUES ( null, 1, 'Code Generation in Action' );
แทรกลงในค่าหนังสือ ( null, 1, 'Podcasting Hacks');
แทรกลงในค่าหนังสือ ( null, 1, 'PHP Hacks');
แทรกลงในค่าหนังสือ ( null, 2, 'Pragmatic Programmer');
แทรกลงในค่าหนังสือ ( null, 2, 'Ruby on Rails');
INSERT INTO books VALUES ( null, 2, 'Programming Ruby' );
รูปแบบมีความน่าเชื่อถือและไม่มีข้อผิดพลาด ปัญหาอยู่ที่รหัสที่เข้าถึงฐานข้อมูลเพื่อค้นหาหนังสือทั้งหมดโดยผู้แต่งที่กำหนด ดังที่แสดงด้านล่าง
รายการ 15. Get.php
<?php
need_once('DB.php');
$dsn = 'mysql://root:password@localhost/good_books';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
ฟังก์ชัน get_author_id( $name )
-
global $db;
$res = $db->query( "เลือก id จากผู้เขียน WHERE name=?",array( $name ) );
$id = โมฆะ;
ในขณะที่( $res->fetchInto( $row ) ) { $id = $row[0]; }
ส่งคืน $id;
}
ฟังก์ชั่น get_books( $id )
-
global $db;
$res = $db->query( "เลือก id จากหนังสือ WHERE author_id=?",array( $id ) );
$ids = อาร์เรย์();
ในขณะที่( $res->fetchInto( $row ) ) { $ids []= $row[0];
ส่งคืน $ids;
}
ฟังก์ชั่น get_book( $id )
-
global $db;
$res = $db->query( "SELECT * จากหนังสือ WHERE id=?", array( $id ) );
ในขณะที่( $res->fetchInto( $row ) ) { return $row;
กลับเป็นโมฆะ;
}
$author_id = get_author_id( 'แจ็ค เฮอร์ริงตัน' );
$books = get_books( $author_id );
foreach( $books เป็น $book_id ) {
$book = get_book( $book_id );
var_dump($หนังสือ);
-
?>
หากคุณดูโค้ดด้านล่าง คุณอาจคิดว่า "เฮ้ นี่มันชัดเจนและเรียบง่ายจริงๆ" ขั้นแรก ให้รับรหัสผู้แต่ง จากนั้นรับรายชื่อหนังสือ จากนั้นจึงรับข้อมูลเกี่ยวกับหนังสือแต่ละเล่ม ใช่มันชัดเจนและเรียบง่าย แต่มีประสิทธิภาพหรือไม่? คำตอบคือไม่ ดูว่ามีการดำเนินการค้นหากี่รายการเพื่อดึงหนังสือของ Jack Herrington หนึ่งครั้งเพื่อรับรหัส อีกครั้งเพื่อรับรายชื่อหนังสือ จากนั้นดำเนินการค้นหาต่อเล่ม หนังสือสามเล่มต้องมีคำถามห้าข้อ!
วิธีแก้ไขคือใช้ฟังก์ชันเพื่อดำเนินการค้นหาจำนวนมากดังที่แสดงด้านล่าง
รายการ 16. Get_good.php
<?php
need_once('DB.php');
$dsn = 'mysql://root:password@localhost/good_books';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
ฟังก์ชัน get_books( $name )
-
global $db;
$res = $db->query("SELECT books.* FROM authors,books WHERE books.author_id=authors.id AND authors.name=?",
อาร์เรย์( $ชื่อ ) );
$แถว = อาร์เรย์();
ในขณะที่( $res->fetchInto( $row ) ) { $rows []= $row;
กลับ $แถว;
}
$books = get_books( 'แจ็ค เฮอร์ริงตัน' );
var_dump( $หนังสือ );
?>
การเรียกข้อมูลรายการตอนนี้ต้องใช้การสืบค้นแบบรวดเร็วเพียงข้อเดียว ซึ่งหมายความว่าฉันมักจะต้องมีวิธีการเหล่านี้หลายวิธีและมีพารามิเตอร์ต่างกัน แต่ไม่มีทางเลือกจริงๆ หากคุณต้องการมีแอปพลิเคชัน PHP ที่ปรับขนาดได้ คุณต้องใช้ฐานข้อมูลอย่างมีประสิทธิภาพ ซึ่งหมายถึงการสืบค้นที่ชาญฉลาดยิ่งขึ้น
ปัญหาในตัวอย่างนี้ก็คือมันชัดเจนเกินไปนิดหน่อย โดยทั่วไปแล้ว ปัญหา n+1 หรือ n*n ประเภทนี้มีความละเอียดอ่อนกว่ามาก และจะปรากฏขึ้นเฉพาะเมื่อผู้ดูแลระบบฐานข้อมูลเรียกใช้ Query Profiler บนระบบเมื่อระบบมีปัญหาด้านประสิทธิภาพ
ฐานข้อมูล
เป็นเครื่องมือที่มีประสิทธิภาพ และเช่นเดียวกับเครื่องมือที่มีประสิทธิภาพอื่นๆ คุณสามารถนำไปใช้ในทางที่ผิดได้หากคุณไม่ทราบวิธีใช้งานอย่างถูกต้อง เคล็ดลับในการระบุและแก้ไขปัญหาเหล่านี้คือการทำความเข้าใจเทคโนโลยีพื้นฐานให้ดียิ่งขึ้น ฉันได้ยินมานานแล้วว่านักเขียนตรรกะทางธุรกิจบ่นว่าพวกเขาไม่ต้องการเข้าใจฐานข้อมูลหรือโค้ด SQL พวกเขาใช้ฐานข้อมูลเป็นวัตถุและสงสัยว่าเหตุใดประสิทธิภาพจึงต่ำมาก
พวกเขาล้มเหลวในการตระหนักว่าความเข้าใจ SQL นั้นสำคัญเพียงใดในการเปลี่ยนฐานข้อมูลจากความจำเป็นที่ยากลำบากให้เป็นพันธมิตรที่ทรงพลัง หากคุณใช้ฐานข้อมูลทุกวันแต่ไม่คุ้นเคยกับ SQL โปรดอ่าน The Art of SQL หนังสือเล่มนี้เป็นหนังสือที่เขียนได้ดีและใช้งานได้จริง ซึ่งสามารถแนะนำคุณเกี่ยวกับความเข้าใจพื้นฐานเกี่ยวกับฐานข้อมูลได้