MySQL Tips
As of January 2008, the Pipelines will no longer publish monthly tips. However, we welcome and will post
user-submitted tips on this page. Please send your tips to webmaster@quest-pipelines.com.
Our archive of monthly tips will be maintained, so feel free to browse them below.
Previous Tips of the Month
2007 Tips:
| January | Connecting to MySQL From Python |
| February | Triggers and Mutating Table Errors in MySQL |
| March | MySQL Error Control Changes |
| April | Testing Queries in MySQL |
| May | Using the EXPLAIN Command |
| June | Connecting MySQL to ASP Pages |
| July | Tips for MySQL Using InnoDB Table Engine |
| August | Resolving MySQL Error 106 |
| September | SQL Helper Function |
| October | Best Practices for Securing Tables, Triggers and Privileges |
| November | Sequence Number Differences Between Oracle and MySQL |
| December | Queries - Group the Resultset with GROUP BY |
2006 Tips
2005 Tips
2004 Tips
January's Tip of the Month
Connecting to MySQL From Python
By Edmon Begoli, Reprinted from IT Toolbox Blogs
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50701
This Python routine demonstrates how to connect to MySQL from Python. I use it for semantic web applications.
Prerequisites:
- Python installation
- MySQLdb library - you can find it at: http://sourceforge.net/projects/mysql-python
- I also use Eclipse as an IDE with pydev plug in
Routine: connects to the local MySQL on the default port 3306. For other ports you would need to specify the port="XXXX" in the connection string.
For testing purposes it select all locations from the onco schema, and
prints first two columns:
#!/usr/bin/python
# e begoli, python connector for mysql
# import MySQL module
import MySQLdb
# connect
db = MySQLdb.connect(host="localhost", user="appuser", passwd="",
db="onco")
# create a database cursor
cursor = db.cursor()
# execute SQL select statement
cursor.execute("SELECT * FROM LOCATION")
# get the number of rows in the resultset
numrows = int(cursor.rowcount)
# get and display one row at a time
for x in range(0,numrows):
row = cursor.fetchone()
print row[0], "-->", row[1]
February's Tip of the Month
Triggers and Mutating Table Errors in MySQL
By Scott Noyes, Reprinted from SearchOpenSource.com
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50702
Since triggers are still in the early stages of development for MySQL, users
may find the number of limitations restrictive.
What are the differences between statement triggers and row triggers?
Statement triggers "fire" once for each statement no matter how many rows that statement affects. Row triggers act for each row affected. A statement like "UPDATE myTable SET myField = 1" would fire a statement trigger only once. That same statement would cause a row trigger to fire as many times as there are rows in the table. MySQL currently only supports row triggers.
What are mutating table errors?
Mutating table errors occur when a trigger attempts to access a "mutating" table, like the table from which the trigger has been called. In MySQL, triggers are not initiated by cascading foreign keys, and they cannot modify the table from which they are called, so neither of those issues can cause a mutating table error. However, foreign keys appear to operate before 'AFTER' triggers, so it is possible to create a trigger that will cause a statement to fail.
Which supports the most triggers, Microsoft SQL Server, Oracle or MySQL? How should that affect my decision in regards to choosing a database?
Triggers are fairly new to MySQL and are, therefore, still in an early stage of development. There are a number of limitations you might find too restrictive if you rely heavily on triggers for your application. All three databases support row triggers before and after insert, update and delete statements. Oracle and Microsoft SQL Server also offer statement triggers.
MySQL allows, at most, one trigger per table per event time and type -- a table can only have one BEFORE INSERT trigger, for example. Both Orable and SQL Server permit multiple triggers of each type on a table. The order in which they execute may not be defined.
MySQL requires each of the six possible triggers to be defined separately. If you want to perform the exact same action on INSERT or on DELETE, you must define two separate triggers. Oracle and SQL Server allow trigger definitions to include multiple event types, such as BEFORE INSERT,
and DELETE.
MySQL does not permit dynamic SQL in a trigger, or in a stored procedure called by a trigger. There is no such restriction in Oracle.
Both MySQL and Oracle attempt to prevent mutating table errors where, for example, a trigger attempts to modify the table which called it. MySQL and Oracle try to stop this by preventing such triggers from being created. It is still possible to create such errors, using cascading foreign keys or by other means. SQL Server allows for recursive triggers, and so must be coded carefully to prevent endless recursion.
How does dual licensing affect the use of MySQL in enterprise IT environments? Is there anything I need to be aware of?
MySQL Community edition is free to use, whether you are a home user or a billion-dollar corporation. You can store your data in it, serve Web pages from it and build reports that rely on it, all without paying a dime. However, if you plan to sell a product that requires MySQL to function, or a product with built-in functionality to use MySQL, you must buy a commercial license. Of course, if you want official support, you'll need to pay for that too.
March's Tip of the Month
MySQL Error Control Changes
By Peter Zaitsev, Reprinted from Planet MySQL
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50703
In MySQL 5.0, error control was mostly improved. For example, strict mode was added to change
the infamous MySQL behavior of cutting overly large strings and numbers and allowing you to use
dates such as February 31st.
In one case, however, there was a reversal - in regards to storage engine initialization.
Previously, if you started MySQL and the Innodb storage engine failed to initialize (i.e. you
resized the log file but forgot to delete old ones) the MySQL Server simply would not start. In
MySQL 5.0, however, it will continue loading and simply have the Innodb storage engine disabled.
This can cause numerous problems especially if you got used to the old behavior and do not check
MySQL logs but simply check that it is started. It especially hurts if you have only some tables
in Innodb so you might not notice that part of your application does not function. Also monitoring
often monitors that MySQL is up and running and will not query that all tables are accessible, thus not
catching such errors. If MySQL is started with Innodb tables disabled, accesses to Innodb tables
will simply result in errors.
The other way I've seen this beating up people is performing Innodb conversion. By default MySQL
substitutes storage engines so if Innodb is disabled MyISAM is used, so I've seen people thinking
they have converted things to Innodb while they really did not because it was substituted back to
MyISAM.
April's Tip of the Month
Testing Queries in MySQL
By Peter J. Farrell, Reprinted from Shuttle.Space
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50704
Let's say you want to test your query, but don't actually want it to change your data? Then use
the LIMIT clause available in MySQL and set it O.
UPDATE admins SET
l_name = 'Farrellll'
WHERE l_name = 'Farrell'
LIMIT 0
The above code would run -- so if there were any problems with it it would fail, but it wouldn't
actually update anything because I've told MySQL to limit the total number of affected records to
0. I learned this little trick from the LIMIT optimization page in the MySQL documentation.
Thought I would blog it since I never heard using the LIMIT clause in this way and I think it
might very useful for some people.
May's Tip of the Month
Using the EXPLAIN Command
By Justin Silverton, Reprinted from http://www.whenpenguinsattack.com
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50705
The explain command can tell you which indexes are used with the specified query and many
other pieces of useful information that can help you choose a better index or query.
Example of usage:
explain select * from table
Explanation of row output:
- table - The name of the table.
- type - The join type, of which there are several.
- possible_keys - This column indicates which indexes MySQL could use to find the rows in this table. If the result is NULL, no indexes would help with this query. You should then take a look at your table structure and see whether there are any indexes that you could create that would increase the performance of this query.
- key - The key actually used in this query, or NULL if no index was used.
- key_len - The length of the key used, if any.
- ref - Any columns used with the key to retrieve a result.
- rows - The number of rows MySQL must examine to execute the query.
- extra - Additional information regarding how MySQL will execute the query. There are several options, such as Using index (an index was used) and Where (a WHERE clause was used).
June's Tip of the Month
Connecting MySQL to ASP Pages
By Scott Noyes, Reprinted from SearchOpenSource.com
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50706
You can connect ASP pages to MySQL using ODBC. Download the ODBC drivers. Once you have MyODBC
configured, you can connect to MySQL with or without DSN. Using DSN:
<%
set conn = Server.CreateObject("ADODB.Connection")
conn.Open "DSN=mysql_dsn"
%>
Without DSN:
<%
set conn = createobject("ADODB.Connection")
conn.open = "DRIVER={MySQL ODBC 3.51 Driver};" & _
"SERVER=yourHostName;" & _
"DATABASE=yourDBName;" & _
"UID=yourUserName;PWD=yourPassword; OPTION=35;"
%>
Check for values
for the OPTION parameter.
July's Tip of the Month
Tips for MySQL Using InnoDB Table Engine
By Peter J. Farrell, Reprinted from Shuttle.Space
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50707
I was doing some research on MySQL found a great video from Google EngEDU in which Jay Pipes from MySQL gives a great Best Practices presentation. Here are a couple of small things I didn't know about MySQL using the InnoDB table engine.
Redundant Indexes
InnoDB automatically "appends" the primary key value on all indexes that are not the primary key index. For example, if you have an index for the product_name field and product_id (primary key), InnoDB internally "appends" the primary key. So essentially, you end up with an index that looks a bit like this:
- product_name
- product_id
- product_id (used internally by the indexing components)
As you can see, in InnoDB this is wasteful as a column is repeated in your index. This is something you don't see unless you know that InnoDB does this internally.
Don't Use SELECT COUNT(*)
Due to the implementation of the page files used for transaction safe tables, counting rows means you have to table scan the entire table. This in unlike MyISAM
tables where it can just use the index. Instead of using COUNT(*) for getting
that metric, Jay recommends you use a counter table that you update by
incrementing by 1 for all inserts and decrease by 1 if you delete a row. You
could even use a trigger for this.
Use Joins Instead of Sub-Queries
Not that I use sub-queries often, but they are costly operations. For instance, you if you wanted to get the MAX() selling prices for all your products, you could do this with a sub-query. However, if you
have 1000 products you will perform 10000 sub-queries + the 1 query to get the
product data. Use a join instead.
Turn the Query Cache
If you have a read intensive applications, turn on the query cache for MySQL. This isn't an InnoDB thing only, but by default the caching pool is 0MB (thus off).
innodb_buffer_pool_size
50-80% of the total memory of your box should be devoted to the InnoDB buffer. I love the quote from Jay, "MySQL just loves to use RAM. It eats it up just like PacMan. RAM is cheap, don't ask your boss...just throw another stick of RAM in that baby." However, innodb_buffer_pool_size != key_buffer_size. Even if you don't use MyISAM table engine, don't set the key_buffer_size to 0 because the internal DB that MySQL uses is implemented in MyISAM. If that DB isn't happy, your server isn't going to be happy.
August's Tip of the Month
Resolving MySQL Error 106
Reprinted from http://www.goitexpert.com
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50708
MySQL database is an excellent option for storing data of any sort and size. However, it does
have its little quirks and bugs that seem to pop up here and there. One of them is the infamous
MySQL Error 106. Many database administrators have had this error occur for them, but few knew
how to fix it or what causes it. It is a Windows-Only error.
This error usually occurs if you are upgrading one version of MySQL to the next one up, (i.e.
MySQL 4.1 to MySQL 5.0). The reason for it is that the two MySQL services are conflicting with one
another and the newer one is not able to start itself up. One option is to completely remove all of
the instances. However, this option is only viable if you have no important data in your database.
But an easier option would be to follow the instructions outlined below.
Step 1: Under your installation path for MySQL or the place where you told it to
store data, open the file called mysql.err. This file can also be named youmachinename.err,
depending on the installation options and the version of MySQL you had installed. Here is the
path to this file on my system:
C:\Program Files\MySQL\MySQL Server 5.0\data
The log file should have a message similar to the following one:
070501 9:45:44 InnoDB: Starting shutdown…
070501 9:45:44 InnoDB: Shutdown completed; log sequence number 0 2235
070501 9:45:44 [Note] C:\Program Files\MySQL\MySQL Server 5.0\bin\mysqld-nt:
Shutdown complete
This error message identifies this specific problem. Follow the instructions for the next step
in order to remedy this error.
Step 2: An easy step to perform, just remove two files, ib_logfile0 and ib_logfile1,
which are located in the same folder.
Step 3: Restart MySQL Service.
The above steps should have fixed the Error 106.
September's Tip of the Month
SQL Helper Function
Reprinted from http://www.thescripts.com
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50709
Here is a class that I call a server because it stores the connection and cursor, handles
errors and sends info (last query, etc) to its master's write() command. called with sys.stdout
as the master, my dbServer prints to stdout. (The code is python.)
from MySQLdb import *
from time import time
class DBServer:
def __init__(self, master):
self.master = master
def Login(self, servername, username, password, database=""):
try:
self.dbconnect = connect(host=servername, user=username, passwd=password, db=database)
except (DatabaseError, OperationalError):
self.dbconnect = None
self.master.write('Couldn\'t connect to the database named %s on host %s'
%(repr(database), repr(servername)))
return
self.dbcursor = self.dbconnect.cursor()
self.Execute('SET autocommit=1')
return self.dbconnect
def DBError(self):
"""Remove the current message from the cursor
and display it."""
try:
(error, message) = self.dbcursor.messages.pop()
except AttributeError:
(error, message) = self.dbconnect.messages.pop()
self.master.write('%s #%d: %s' %(str(error).split('.')[-1],
message[0], message[1]))
def Execute(self, query):
try:
cursor = self.dbcursor
now = time()
cursor.execute(query)
nRows = cursor.rowcount
self.master.write(query)
self.master.write("%d rows affected: %.2f sec." %(nRows, time() - now))
except (DatabaseError, OperationalError):
self.DBError()
return
return cursor
def DBExists(self, database):
"""Return True if database exists"""
resultset = self.Execute("show databases").fetchall()
## print resultset
return (database,) in resultset
def close(self):
self.dbconnect.close()
October's Tip of the Month
Best Practices for Securing Tables, Triggers and Privileges
By MiMi Yeh, Reprinted from http://searchenterpriselinux.techtarget.com
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50710
Although there is no way to limit table access on a per-use basis in MySQL, you can use stored procedures to limit user access to data. This is one of the many security workarounds that Scott Noyes, SearchEnterpriseLinux.com's MySQL expert, suggests, in addition to explaining the differences between user and local variables and preventing privilege pass-alongs by users.
Noyes is a MySQL Core-, Developer- and DBA-certified engineer. He is also the senior Web applications developer for Bookit.com.
How can you prevent users from passing on privileges granted to them on certain tables?
Scott Noyes: When issuing the GRANT statement, you have the option of appending WITH GRANT OPTION to the end. If that option is set, then users will be allowed to issue their own GRANT statements to other users. If you do not include that option, any attempt to issue a grant statement will be met with an access denied error.
Is there a way to limit access a table via a per-use basis in MySQL using the GRANT statement?
Noyes: The GRANT statement provides the following access limitations on a user:
- The number of queries per hour
- The number of updates per hour
- The number of connections to the server per hour
- The number of simultaneous connections to the server
There is no built-in method to limit the number of times a user may access a particular table outside of the per hour limits.
You can use stored procedures to limit user access to data. Construct a stored procedure that logs user access to the table (referencing the CURRENT_USER() function as needed), and exits early if the table has already been accessed. Give your users access only to the stored procedure, and not directly to the table itself.
What is the difference between user variables and local variables?
Noyes: Local variables refer to variables declared within a stored routine. Their scope is limited to the BEGIN and END block in which they are declared.
User variables are named with the @ symbol, and have global scope within the connection (you can set them inside a stored procedure, and still access them after the procedure has finished, for example).
How do you create a trigger for my USERS table that means if one user is removed, that user is also taken out of the USERS_DIR table?
Noyes: If you use the InnoDB storage engine, there is no need for a trigger. You can achieve the same effect using a foreign key with an appropriate cascading action:
ALTER TABLE users_dir ADD FOREIGN KEY (userId) REFERENCES users (userId) ON DELETE CASCADE;
If you use MyISAM or one of the other engines that does not yet enforce foreign key constraints, you can use a trigger:
CREATE TRIGGER users_AD AFTER DELETE ON users FOR EACH ROW DELETE FROM
users_dir WHERE userId = OLD.userId;
Note that the foreign key is defined on the users_dir table, but the trigger is defined on the users table.
The examples shown here for both foreign keys and triggers only address behavior if the user is deleted. You will need to decide what action to take if the user's id is updated to a different value instead.
Where should you put a NOT EXIST subquery, named as a column, in a statement with several joins ahead of it?
Noyes: Aliases defined in the select list, whether aliases of simple columns, results of expressions or subqueries, should not appear in the where clause. To use a subquery in the where clause, you should redefine it.
Using NOT EXISTS is one way to find rows which appear in one table but do not appear in another:
SELECT
t1.*
FROM
t1
WHERE
NOT EXISTS (SELECT * FROM t2 WHERE t1.id = t2.id)
Such correlated subqueries can often be rewritten as joins:
SELECT
t1.*
FROM
t1
LEFT JOIN t2 ON (t1.id = t2.id)
WHERE
t2.id IS NULL;
Test both approaches on your data. A different index or table size can make one approach faster than the other in different situations. Of course, if you're on a hosted server that still hasn't upgrade from 3.23 or 4.0, subqueries aren't available anyway, so you'll have to use the join form.
November's Tip of the Month
Sequence Number Differences Between Oracle and MySQL
By Nigel McFarlane, Reprinted from http://searchenterpriselinux.techtarget.com
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50711
If you're working with relational data, the day will soon come when you need to generate unique
key values for your data, otherwise known as sequence numbers. In this tip I'll contrast the
Oracle way with the MySQL way.
Since sequence numbers aren't part of the entry-level conformance of the SQL92 standard, their
use creates portability problems for your SQL scripts straight away.
Way back when, there were no sequence number features in any relational database. In those
days, if you wanted to generate unique numbers (like invoice numbers), you had to store them
separately in a whole table, and increment them yourself:
CREATE TABLE my_sequence (id INT NOT NULL);
INSERT INTO my_sequence VALUES (0);
UPDATE my_sequence SET id = id + 1;
INSERT
INTO my_table
SELECT id, "some data"
FROM my_sequence;
That approach was rather clunky to say the least, but at least it is standard SQL.
In modern Oracle databases, all you need to do is create a sequence object, and then select new
numbers from it:
CREATE SEQUENCE my_sequence;
INSERT
INTO my_table
SELECT my_sequence.nextval, "some data"
FROM dual;
The special table DUAL contains exactly one row and one column and is used mostly to report
back a single piece of data - in this case derived from a sequence object. In all my life I've
never been able to understand why a dummy table with one row and one column is called DUAL. Doesn't
DUAL mean two-part or two-use? What's dual about a single row? Oh well.
By comparison, MySQL has no DUAL table. Instead, it allows you to put an extra detail on any
numeric column of any table, provided it's an indexed key column. Not only can the column be
NULLable, it can also be AUTO_INCREMENTable. Here's an example:
CREATE TABLE my_table (
id INT NOT NULL AUTO_INCREMENT,
data CHAR(30),
PRIMARY_KEY(id)
);
INSERT INTO my_table (null, "some data");
The INSERT statement looks highly suspect. After all, doesn't a primary key normally require a
concrete value? True, and in this case MySQL calculates that for you. By passing in NULL to the
column with the AUTO_INCREMENT property, you're telling MySQL to use the next free number not
yet used for that column. If this INSERT statement were to be applied three times, each time the
ID value would be one larger.
For the first three rows inserted, that number would merely be 1, then 2, then 3.
The fact that the sequence number is tied to a specific table in MySQL is a little restrictive,
but there are various techniques that can be used to work around it. For example, MySQL provides a
LAST_INSERT_ID() that can be used to dig the generated number out for other purposes.
December's Tip of the Month
Queries - Group the Resultset with GROUP BY
Excerpt from Knowledge Xpert for MySQL
BOOKMARK: http://www.quest-pipelines.com/tiptrack.asp?id=50712
The principal reason for returning data in groups as well as sorting it is to apply an aggregating function, which provides information about each group rather than about individual rows. To explain this, consider an example table called child_products:
mysql> SELECT * FROM child_products;
+-----+--------------+------+-------+
| id | name | age | price |
+-----+--------------+------+-------+
| 101 | Toy train | 3-5 | 12.99 |
| 102 | Racing car | 3-5 | 9.99 |
| 103 | Spinning top | 3-5 | 7.50 |
| 104 | Teddy bear | 0-2 | 12.50 |
| 105 | Kitchen | 2-3 | 24.99 |
+-----+--------------+------+-------+
Each item has a unique id number, a name, the age range of its intended customers, and a price. The GROUP BY clause can be used to group items by age, like this:
mysql> SELECT * FROM child_products GROUP BY age;
+-----+------------+------+-------+
| id | name | age | price |
+-----+------------+------+-------+
| 104 | Teddy bear | 0-2 | 12.50 |
| 105 | Kitchen | 2-3 | 24.99 |
| 101 | Toy train | 3-5 | 12.99 |
+-----+------------+------+-------+
But this is not very useful! It has made the age column distinct, but the row it chooses to display with each distinct age range has effectively been chosen at random. Using GROUP BY in this way and retrieving several columns is seldom helpful.
Now consider this:
mysql> SELECT age FROM child_products GROUP BY age;
+------+
| age |
+------+
| 0-2 |
| 2-3 |
| 3-5 |
+------+
This time products are grouped by age, but only age has been displayed in the result. In other words, the resultset contains only group level data, and not the misleading row-level data from within each group. To sort the groups in descending order:
mysql> SELECT age FROM child_products GROUP BY age DESC;
+------+
| age |
+------+
| 3-5 |
| 2-3 |
| 0-2 |
+------+
The most common use of GROUP BY is in conjunction with aggregating functions. These perform some operation on the values retrieved within each group (even though the individual values do not appear in the resultset).
SUM() and COUNT() are commonly used aggregating functions, which add up items by group and count the number of items in each group, respectively.
Here's an example of GROUP BY with SUM(). It adds up the total cost of items in each age range:
mysql> SELECT age, SUM(price) FROM child_products GROUP BY age;
+------+------------+
| age | SUM(price) |
+------+------------+
| 0-2 | 12.50 |
| 2-3 | 24.99 |
| 3-5 | 30.48 |
+------+------------+
COUNT() can be used in much the same way:
mysql> SELECT age, COUNT(*) FROM child_products GROUP BY age;
+------+----------+
| age | COUNT(*) |
+------+----------+
| 0-2 | 1 |
| 2-3 | 1 |
| 3-5 | 3 |
+------+----------+
The asterisk in COUNT(*) tells the query to look for any row that exists and count it.
You can name a column, COUNT(price) for example, which would only count items where price is not NULL or empty. * is used here just in case any product has no price; this query would still count it.
As well as SUM() and COUNT(), the expressions that may be used with GROUP BY include the following:
- AVG() Return the mean, or average, value of data in the group
- MAX() The maximum value
- MIN() The minimum value
- STD() The standard deviation
Caution: Don't name columns in your GROUP BY clause that are also used by your aggregating function. It defeats the object of grouping!
Here's a slightly more complex example, combining the averaging function AVG(), GROUP BY, and ORDER BY, and using an alias:
mysql> SELECT age, AVG(price) AS avg_p FROM child_products
-> GROUP BY age
-> ORDER BY avg_p;
+------+-----------+
| age | avg_p |
+------+-----------+
| 3-5 | 10.160000 |
| 0-2 | 12.500000 |
| 2-3 | 24.990000 |
+------+-----------+
This query finds the average price of items in each age group and then sorts by the average price (the alias avg_p).
For questions
or comments on this site: webmaster@quest-pipelines.com
All content Copyright
© Quest Software, Inc. All rights reserved.
|