Learn more on MySQL with our screencast MySQL on the Command Line.
A database is a fundamental component for most web applications. If you’re using PHP, you’re probably using MySQL–an integral part of the LAMP stack.
PHP is relatively easy and most new developers can write functional code within a few hours. However, building a solid, dependable database takes time and expertise. Here are ten of the worst MySQL mistakes I’ve made (some apply to any language/database)…
1. Using MyISAM rather than InnoDB
MySQL has a number of database engines, but you’re most likely to encounter MyISAM and InnoDB.
MyISAM is used by default. However, unless you’re creating a very simple or experimental database, it’s almost certainly the wrong choice! MyISAM doesn’t support foreign key constraints or transactions, which are essential for data integrity. In addition, the whole table is locked whenever a record is inserted or updated; this causes a detrimental effect on performance as usage grows.
MyISAM is used by default. However, unless you’re creating a very simple or experimental database, it’s almost certainly the wrong choice! MyISAM doesn’t support foreign key constraints or transactions, which are essential for data integrity. In addition, the whole table is locked whenever a record is inserted or updated; this causes a detrimental effect on performance as usage grows.
The solution is simple: use InnoDB.
2. Using PHP’s mysql functions
PHP has provided MySQL library functions since day one (or near as makes no difference). Many applications rely on mysql_connect, mysql_query, mysql_fetch_assoc, etc. but the PHP manual states:
If you are using MySQL versions 4.1.3 or later it is strongly recommended that you use the mysqli extension instead.
mysqli, or the MySQL improved extension, has several advantages:
- an (optional) object-oriented interface
- prepared statements (which help prevent SQL-injection attacks and increase performance)
- multiple statements and transaction support
Alternatively, you should consider PDO if you want to support multiple databases.
3. Not sanitizing user input
This should probably be #1: never trust user input. Validate every string using server-side PHP — don’t rely on JavaScript. The simplest SQL injection attacks depend on code such as:
$username = $_POST["name"];
$password = $_POST["password"];
$sql = "SELECT userid FROM usertable WHERE username='$username' AND password='$password';";
// run query...
This can be cracked by entering “
admin'; --” in the username field. The SQL string will equate to:
SELECT userid FROM usertable WHERE username='admin';
The devious cracker can log in as “admin”; they need not know the password because it’s commented out of the SQL.
4. Not using UTF-8
Those of us in the US, UK, and Australia rarely consider languages other than English. We happily complete our masterpiece only to find it cannot be used elsewhere.
UTF-8 solves many internationalization issues. Although it won’t be properly supported in PHP until version 6.0, there’s little to prevent you setting MySQL character sets to UTF-8.
5. Favoring PHP over SQL
When you’re new to MySQL, it’s tempting to solve problems in the language you know. That can lead to unnecessary and slower code. For example, rather than using MySQL’s native AVG() function, you use a PHP loop to calculate an average by summing all values in a record-set.
Watch out also for SQL queries within PHP loops. Normally, it’s more effective to run a query then loop through the results.
In general, utilize the strengths of your database when analyzing data. A little SQL knowledge goes a long way.
6. Not optimizing your queries
99% of PHP performance problems will be caused by the database, and a single bad SQL query can play havoc with your web application. MySQL’s EXPLAIN statement, the Query Profiler, and many other tools can help you find that rogue SELECT.
7. Using the wrong data types
MySQL offers a range of numeric, string, and time data types. If you’re storing a date, use a DATE or DATETIME field. Using an INTEGER or STRING can make SQL queries more complicated, if not impossible.
It’s often tempting to invent your own data formats; for example, storing serialized PHP objects in string. Database management may be easier, but MySQL will become a dumb data store and it may lead to problems later.
8. Using * in SELECT queries
Never use * to return all columns in a table–it’s lazy. You should only extract the data you need. Even if you require every field, your tables will inevitably change.
9. Under- or over-indexing
As a general rule of thumb, indexes should be applied to any column named in the WHERE clause of a SELECT query.
For example, assume we have a usertable with a numeric ID (the primary key) and an email address. During log on, MySQL must locate the correct ID by searching for an email. With an index, MySQL can use a fast search algorithm to locate the email almost instantly. Without an index, MySQL must check every record in sequence until the address is found.
It’s tempting to add indexes to every column, however, they are regenerated during every table INSERT or UPDATE. That can hit performance; only add indexes when necessary.
10. Forgetting to back up
It may be rare, but databases fail. Hard disks can stop. Servers can explode. Web hosts can go bankrupt. Losing your MySQL data is catastrophic, so ensure you have automated backups or replication in place.
11. Bonus mistake: not considering other databases!
MySQL may be the most widely used database for PHP developers, but it’s not the only option. PostgreSQL and Firebird are its closest competitors; both are open source and not controlled by a corporation. Microsoft provide SQL Server Express and Oracle supply 10g Express; both are free versions of the bigger enterprise editions. Even SQLite may be a viable alternative for smaller or embedded applications.
Have I missed your worst MySQL mistakes?
Learn more on MySQL with our screencast MySQL on the Command Line.
Fuente: https://www.sitepoint.com/mysql-mistakes-php-developers/
User Comments
<?php
mysql_connect("localhost","root","");$result = mysql_query("SHOW TABLE STATUS FROM test;");
while($array = mysql_fetch_array($result)) {$total = $array[Data_length]+$array[Index_length];
echo '
Table: '.$array[Name].'<br />
Data Size: '.$array[Data_length].'<br />
Index Size: '.$array[Index_length].'<br />
Total Size: '.$total.'<br />
Total Rows: '.$array[Rows].'<br />
Average Size Per Row: '.$array[Avg_row_length].'<br /><br />
';
}?>
This script is a hack. Feel free to improve and post.
For some reason, I can't get it to format nicely, either.
#!/opt/gnu/bin/perl -w
use strict;
use Getopt::Long;
my @options;
# Get output immediately. It won't hurt performance.
use FileHandle;
autoflush STDERR;
autoflush STDOUT;
my $pw;
push(@options, "password=s", \$pw);
my $host = "localhost";
push(@options, "host=s", \$host);
die "Couldn't parse options" if !GetOptions(@options);
die "Must give -password\n" if !defined($pw);
my $cmd = mysql_cmd("show databases");
open(CMD, $cmd) or die "Couldn't $cmd: $!\n";
my @databases;
my $header = <CMD>;
while ( <CMD> ) {
s/[\r\n]$//g;
#print "$_\n";
push (@databases, $_);
}
close(CMD);
#print "@databases";
my %colmap = ( 'Data_length' => 6,
'Index_length' => 8,
'Engine' => 1,
'Comment' => 17 );
my %size;
my %total_size;
my %engine_map;
my $inno_db_free;
foreach my $db (@databases) {
print STDERR ".";
$cmd = mysql_cmd("use $db; show table status");
open(CMD, $cmd) or die "Couldn't $cmd: $!\n";
my $header = <CMD>;
my $total_size = 0;
if (defined($header)) {
$header =~ s/[\r\n]$//g;
my @head = split("\t", $header);
foreach my $col (keys %colmap) {
die "$db: Expected '$col', found '" . $head[$colmap{$col}] . "'"
if $head[$colmap{$col}] ne $col;
}
while (<CMD>) {
my @data = split("\t");
my ($data_length, $index_length) = @data[6,8];
my ($engine, $comment) = @data[1,17];
$engine_map{$engine}++;
$size{$db}{$engine} += $data_length + $index_length;
$total_size{$db} += $data_length + $index_length;
if ( $comment =~ /InnoDB free: (\d+) kB/ ) {
die "Found two different inno DB free sized.\n"
if defined($inno_db_free) && $inno_db_free != $1;
$inno_db_free = $1;
}
}
close(CMD);
}
}
print STDERR "\n";
print "NOTE: All numbers are in megabytes (M).\n";
printf("Inno DB free: %.1f\n", $inno_db_free / 1024)
if defined($inno_db_free);
printf("%-30s ", "database");
foreach my $engine (sort keys(%engine_map)) {
printf "%7s ", $engine;
}
printf "%8s", "total";
print "\n";
foreach my $db (sort {$total_size{$b} <=> $total_size{$a}} keys %total_size) {
printf("%-30s ", $db);
foreach my $engine (sort keys(%engine_map)) {
my $size= $size{$db}{$engine};
$size = 0 if !defined($size);
printf("%7.1f ", $size / 1024 / 1024);
}
printf("%8.1f\n", $total_size{$db} / 1024 / 1024);
}
sub mysql_cmd {
my $mysql_cmd = shift;
return "mysql -uroot -h$host -p$pw -e '$mysql_cmd'|";
}
Example output:
% ./db-space.pl -p ...
....................
NOTE: All numbers are in megabytes (M).
Inno DB free: 10755.0
database HEAP InnoDB MyISAM total
tldan 0.0 339.1 720.3 1059.4
ml3test7 0.0 1010.8 0.0 1010.8
ml3test6 0.0 930.4 0.0 930.4
test 0.0 655.4 0.0 655.4
blarg4 0.0 39.5 0.0 39.5
The way the foreign key info is stored in the Comment field can be a pain to parse. Here's a snippet of PHP code that shows how to do this.
<?php//DB connection already established$res = mysql_query("SHOW TABLE STATUS LIKE 'MY_TABLE'");$row = mysql_fetch_assoc($res);mysql_free_result($res);$commentArr = preg_split('/; */', $row['Comment']);$foreignKeyArr = array(); //<-- We want to fill this.foreach($commentArr as $comment) {
//Only work on InnoDB foreign key info.
if(preg_match(
'/\(`(.*)`\) REFER `(.*)\/(.*)`\(`(.*)`\)/',
$comment,
$matchArr)) {
$primaryKeyFieldArr = preg_split('/` `/', matchArr[1]);
$foreignKeyDatabase = $matchArr[2];
$foreignKeyTable = $matchArr[3];
$foreignKeyFieldArr = preg_split('/` `/', $matchArr[4]);
for($i = 0; $i < count($primaryKeyFieldArr); $i++) {
$foreignKeyArr[ $primaryKeyFieldArr[$i] ] = array(
'db' => $foreignKeyDatabase,
'table' => $foreignKeyTable,
'field' => $foreignKeyFieldArr[$i]);
}
}?>
Now $foreignKeyArr holds a list of fields from MY_TABLE
that have a foreign key constraint. If MY_FK is a foreign
key referencing YOUR_ID in YOUR_TABLE, you will get:
$foreignKeyArr['MY_FK']['db'] == 'THIS_DATABASE'
$foreignKeyArr['MY_FK']['table'] == 'YOUR_TABLE'
$foreignKeyArr['MY_FK']['field'] == 'YOUR_ID'
I know you Perl guys will balk at this - but the JDBC METADATA can give you this information quite easily.
maybe there is something similar for Perl DBI?
Yes!
http://search.cpan.org/src/TIMB/DBI_AdvancedTalk_2004/sld086.htm
mysqlshow -u <user> --password=<password> --status <dbname> | ruby -e 'puts STDIN.readlines[4..-2].inject(0) {|s,e| s += e.split("|")[7].to_i}'
<?phpprint('<table cols="6"><th>Table</th><th>Data Size</th><th>Index Size</th><th>Total size</th><th>Total Rows</th><th>Avg. Size per Row</th>');mysql_connect("localhost","my_user","password");$result = mysql_query("SHOW TABLE STATUS FROM test;");
while($array = mysql_fetch_array($result)) {$total = $array[Data_length]+$array[Index_length];
if ( $array[Data_length] > 0 ) {
print('<tr><td align="center">');
print(' ' . $array[Name] . '<br /></td><td align="center">');
if ( $array[Data_length] < 1024 ) {
echo ' '.$array[Data_length].'</td><td align="center">';
} elseif ( ($array[Data_length] > 1024) && ($array[Data_length] < 1048576 ) ) {
printf('%.0fK',($array[Data_length] / 1024) );
print('</td><td align="center">');
} elseif ( $array[Data_length] >= 1048576 ) {
printf('%.2fMB',($array[Data_length] / 1048576) );
print('</td><td align="center">');
}
if ( $array[Index_length] < 1024 ) {
echo ' '.$array[Index_length].'<br /></td><td align="center">';
} elseif ( ($array[Index_length] > 1024) && ($array[Index_length] < 1048576 ) ) {
printf('%.0fK',($array[Index_length] / 1024) );
print('<br /></td><td align="center">');
} elseif ( $array[Index_length] >= 1048576 ) {
printf('%.2fMB',($array[Index_length] / 1048576) );
print('<br /></td><td align="center">');
}
if ( $total < 1024 ) {
echo ' '.$total.'<br /></td><td align="center">';
} elseif ( ($total > 1024) && ($total < 1048576 ) ) {
printf('%.0fK',($total / 1024) );
print('<br /></td><td align="center">');
} elseif ( $total >= 1048576 ) {
printf('%.2fMB',($total / 1048576) );
print('<br /></td><td align="center">');
}
echo '
'.$array[Rows].'</td><td align="center">
'.$array[Avg_row_length].'</td></tr>
';
}
}?>
I tried your script BUT got a problem with "SHOW TABLE STATUS". It only returns the first foreign key (in MySql 5.0.27).
Instead of it, I'm using "SHOW CREATE TABLE" and this regular expression:
'/FOREIGN KEY \(`(.*)`\) REFERENCES `(.*)` \(`(.*)`\)/'
I'm assuming that all the references are located in the same database.
http://en.latindevelopers.com/ivancp/2012/a-better-show-table-status/
That stored procedure returns a result like this: