Professional Documents
Culture Documents
Jay Pipes
Community Relations Manager, North America
(jay@mysql.com)
➢ Testing done iteratively
➢ Deltas between tests show difference that the change(s) made
● Stress/Load testing of application and/or database
● Harness or framework useful to automate many benchmark tasks
● mysqlslap (5.1+)
➢ http://dev.mysql.com/doc/refman/5.1/en/mysqlslap.html
● MyBench
● http://jeremy.zawodny.com/mysql/mybench/
➢ CPU
➢ I/O (Disk)
➢ Network and OS
● EXPLAIN
➢ http://dev.mysql.com/explain
● MyTop
➢ http://jeremy.zawodny.com/mysql/mytop/
http://thedailywtf.com/forums/thread/75982.aspx
Copyright MySQL AB The World’s Most Popular Open Source Database 12
Schema Tips
● Consider horizontally splitting many-columned tables
(example ahead)
● Consider vertically partitioning many-rowed tables
➢ Merge tables (MyISAM only)
➢ Partitioning (5.1+)
records
● Less frequently queried data doesn't take up memory
● More possibilities for indexing and different storage
engines
➢ Allows targeted multiple MyISAM key caches for hot
CREATE TABLE Products (
product_id INT NOT NULL AUTO_INCREMENT
, name VARCHAR(80) NOT NULL
CREATE TABLE Products (
, unit_cost DECIMAL(7,2) NOT NULL
product_id INT NOT NULL AUTO_INCREMENT
, description TEXT NULL
, name VARCHAR(80) NOT NULL
, image_path TEXT NULL
, unit_cost DECIMAL(7,2) NOT NULL
, PRIMARY KEY (product_id)
, description TEXT NULL
, INDEX (name(20))
, image_path TEXT NULL
) ENGINE=InnoDB; // Or MyISAM
, num_views INT UNSIGNED NOT NULL
, num_in_stock INT UNSIGNED NOT NULL
CREATE TABLE ProductCounts (
, num_on_order INT UNSIGNED NOT NULL
product_id INT NOT NULL
, PRIMARY KEY (product_id)
, num_views INT UNSIGNED NOT NULL
, INDEX (name(20))
, num_in_stock INT UNSIGNED NOT NULL
) ENGINE=InnoDB; // Or MyISAM
, num_on_order INT UNSIGNED NOT NULL
, PRIMARY KEY (product_id)
// Getting a simple COUNT of products
) ENGINE=InnoDB;
// easy on MyISAM, terrible on InnoDB
SELECT COUNT(*)
CREATE TABLE ProductCountSummary (
FROM Products;
total_products INT UNSIGNED NOT NULL
) ENGINE=MEMORY;
CREATE TABLE Products2Tags (
record_id INT UNSIGNED NOT NULL AUTO_INCREMENT
, product_id INT UNSIGNED NOT NULL
, tag_id INT UNSIGNED NOT NULL
, PRIMARY KEY (record_id)
, UNIQUE INDEX (product_id, tag_id)
) ENGINE=InnoDB;
SELECT COUNT(DISTINCT field) / COUNT(*)
FROM my_table;
// or...
SHOW INDEX FROM my_table;
// This top query uses the index
CREATE TABLE Tags (
// on Products2Tags
tag_id INT NOT NULL AUTO_INCREMENT
, tag_text VARCHAR(50) NOT NULL
SELECT p.name
, PRIMARY KEY (tag_id)
, COUNT(*) as tags
) ENGINE=MyISAM;
FROM Products2Tags p2t
INNER JOIN Products p
CREATE TABLE Products (
ON p2t.product_id = p.product_id
product_id INT NOT NULL AUTO_INCREMENT
GROUP BY p.name;
, name VARCHAR(100) NOT NULL
// many more fields...
// This one does not because
, PRIMARY KEY (product_id)
// index order prohibits it
) ENGINE=MyISAM;
SELECT t.tag_text
CREATE TABLE Products2Tags (
, COUNT(*) as products
product_id INT NOT NULL
FROM Products2Tags p2t
, tag_id INT NOT NULL
INNER JOIN Tags t
, PRIMARY KEY (product_id, tag_id)
ON p2t.tag_id = t.tag_id
) ENGINE=MyISAM;
GROUP BY t.tag_text;
CREATE TABLE Tags (
tag_id INT NOT NULL AUTO_INCREMENT
, tag_text VARCHAR(50) NOT NULL
, PRIMARY KEY (tag_id)
) ENGINE=MyISAM;
CREATE INDEX ix_tag
ON Products2Tags (tag_id);
CREATE TABLE Products (
product_id INT NOT NULL AUTO_INCREMENT
// or... create a covering index:
, name VARCHAR(100) NOT NULL
// many more fields...
CREATE INDEX ix_tag_prod
, PRIMARY KEY (product_id)
ON Products2Tags (tag_id, product_id);
) ENGINE=MyISAM;
// But, only if not InnoDB... why?
CREATE TABLE Products2Tags (
product_id INT NOT NULL
, tag_id INT NOT NULL
, PRIMARY KEY (product_id, tag_id)
) ENGINE=MyISAM;
● NOT:
● for each...
(examples ahead)
● Don't try to outthink the optimizer
➢ Sergey, Timour and Igor are really, really smart...
// Good practice
// Bad practice
SELECT p.name
SELECT p.name
, MAX(oi.price) AS max_sold_price
, (SELECT MAX(price)
FROM Products p
FROM OrderItems
INNER JOIN OrderItems oi
WHERE product_id = p.product_id)
ON p.product_id = oi.product_id
AS max_sold_price
GROUP BY p.name;
FROM Products p;
// Good performance
SELECT
c.company
// Bad performance
, o.* FROM
SELECT
Customers c
c.company
INNER JOIN (
, o.* FROM
SELECT
Customers c
customer_id
INNER JOIN Orders o
, MAX(order_date) as max_order
ON c.customer_id = o.customer_id
FROM Orders
WHERE order_date = (
GROUP BY customer_id
SELECT MAX(order_date)
) AS m
FROM Orders
ON c.customer_id = m.customer_id
WHERE customer = o.customer
INNER JOIN Orders o
)
ON c.customer_id = o.customer_id
GROUP BY c.company;
AND o.order_date = m.max_order
GROUP BY c.company;
// Bad idea
// Better idea
SELECT *
SELECT *
FROM Orders
FROM Orders
WHERE
WHERE
TO_DAYS(order_created) –
order_created >= CURRENT_DATE() – INTERVAL 7 DAY;
TO_DAYS(CURRENT_DATE()) >= 7;
// same trigger for BEFORE UPDATE...
// Then SELECT on the new field...
WHERE rv_email LIKE CONCAT(REVERSE('.com'), '%');
// Bad idea
SELECT * // Best idea is to factor out the CURRENT_DATE
FROM Orders // nondeterministic function in your application
WHERE // code and replace the function with a constant.
TO_DAYS(order_created) –
TO_DAYS(CURRENT_DATE()) >= 7; // For instance, in your PHP code, you would
// simply insert date('Ymd') in the query
// Better idea // instead of CURRENT_DATE()
SELECT *
FROM Orders // Now, query cache can actually cache the query!
WHERE SELECT order_id, order_created, customer_id
order_created >= CURRENT_DATE() FROM Orders
– INTERVAL 7 DAY; WHERE order_created >= '20060524' – INTERVAL 7 DAY;
// The query on the left forces MySQL to do a full
// table scan on the entire table. NOT GOOD!
// Instead, issue the following, which allows
// Bad idea... why? // MySQL to quickly use indexes in order to
SELECT * // grab the desired row
FROM Ads
ORDER BY RAND() SELECT @row_id := COUNT(*) FROM Ads;
LIMIT 1 SELECT @row_id := FLOOR(RAND() * @row_id) + 1;
SELECT * FROM Ads WHERE ad_id = @row_id;
// Pop quiz: what should the above look like
// if the Ads table is an InnoDB table? :)
innodb_table SELECT * FROM myisam_table
● Add or drop multiple indexes in one go using ALTER
TABLE vs many CREATE or DROP INDEX statements
DELETE main_table FROM main_table
LEFT JOIN deleted_records
ON main_table.id = deleted_records.id
WHERE deleted_records.id IS NOT NULL;
●
table_cache (InnoDB too...)
● Number of simultaneously open file descriptors
descriptor
● >= 5.1 Split into table_open_cache
● myisam_sort_buffer_size
● Building indexes, set this as high as possible
records?
● Increase within session if you know you will be doing
total memory
● Examine Innodb_buffer_pool_reads vs
Innodb_buffer_pool_read_requests
● Watch for Innodb_buffer_pool_pages_free
approaching 0
● innodb_log_file_size
● Size of the actual log file
● Set to 40-50% of innodb_buffer_pool_size
● innodb_flush_method
● Determines how InnoDB flushes data and logs
● defaults to fsync()
● If getting lots of Innodb_data_pending_fsyncs
● Consider O_DIRECT (Linux only)
● Other ideas
● Get a battery-backed disk controller with a write-
back cache
● Set innodb_flush_log_at_trx_commit=2 (Risky)
✔ http://dev.mysql.com/tech-resources/articles/pro-mysql-
ch6.pdf
➢ Pro MySQL (Apress) chapter on profiling (EXPLAIN)
➢ Subquery optimization
● MySQL Forge
● http://jpipes.com
● jay@mysql.com