Professional Documents
Culture Documents
Jay Pipes
Community Relations Manager, North America
(jay@mysql.com)
Copyright MySQL AB
Overview
Copyright MySQL AB
Benchmarking Concepts
Providesatrackrecordofchanges
Baselineisthestartingpoint
Testingdoneiteratively
Deltasbetweentestsshowdifferencethatthechange(s)made
Stress/Loadtestingofapplicationand/ordatabase
Harnessorframeworkusefultoautomatemanybenchmarktasks
Copyright MySQL AB
Benchmarking Tips
Alwaysgiveyourselfatarget
Recordeverything
Schemadump
my.cnffiles
hardware/osconfigurationfilesasneeded
Isolatetheproblem
Shutdownunnecessaryprograms
Stopnetworktraffictomachine
Disablethequerycache
Changeonethingatatime
Copyright MySQL AB
Benchmarking Toolbox
SysBench
http://sysbench.sourceforge.net/
mysqlslap (5.1+)
http://dev.mysql.com/doc/refman/5.1/en/mysqlslap.html
Apache Bench (ab)
supersmack
http://www.vegan.net/tony/supersmack/
MyBench
http://jeremy.zawodny.com/mysql/mybench/
Copyright MySQL AB
Profiling Concepts
Copyright MySQL AB
Profiling Toolbox
SHOW Commands
SHOWPROCESSLIST|STATUS|INNODBSTATUS
http://dev.mysql.com/show
EXPLAIN
http://dev.mysql.com/explain
MyTop
http://jeremy.zawodny.com/mysql/mytop/
Whole host of Linux power tools
gprof / oprofile
vmstat / ps / top / mpstat / procinfo
apd for PHP developers
http://pecl.php.net/package/apd
Copyright MySQL AB
log_slow_queries=/var/lib/mysql/slowqueries.log
long_query_time=2
log_long_format
Copyright MySQL AB
SETGLOBALSLOW_QUERY_LOG={ON|OFF}
http://dev.mysql.com/doc/refman/5.1/en/logtables.html
Profiling Tips
Copyright MySQL AB
Sources of Problems
Copyright MySQL AB
10
Schema Guidelines
Copyright MySQL AB
11
Ahh,
normalization...
http://thedailywtf.com/forums/thread/75982.aspx
Copyright MySQL AB
12
Schema Tips
Copyright MySQL AB
13
Copyright MySQL AB
CREATETABLEUsers(
user_idINTNOTNULLAUTO_INCREMENT
,emailVARCHAR(80)NOTNULL
,display_nameVARCHAR(50)NOTNULL
,passwordCHAR(41)NOTNULL
,PRIMARYKEY(user_id)
,UNIQUEINDEX(email)
)ENGINE=InnoDB;
CREATETABLEUserExtra(
user_idINTNOTNULL
,first_nameVARCHAR(25)NOTNULL
,last_nameVARCHAR(25)NOTNULL
,addressVARCHAR(80)NOTNULL
,cityVARCHAR(30)NOTNULL
,provinceCHAR(2)NOTNULL
,postcodeCHAR(7)NOTNULL
,interestsTEXTNULL
,bioTEXTNULL
,signatureTEXTNULL
,skillsTEXTNULL
,companyTEXTNULL
,PRIMARYKEY(user_id)
)ENGINE=InnoDB;
14
Copyright MySQL AB
15
Copyright MySQL AB
///etc/my.cnf
[mysql.server]
init_file=/path/to/datadir/init.sql
//init.sql
//Setupthehotcache
SETGLOBAL
hot_cache.key_buffer_size=128K
//Cachethepostcodelookup
CACHEINDEXzip_lookup
TOhot_cache;
//Preloadtheindexsequentially
LOADINDEXINTOCACHEzip_lookup;
//Controlcacheinvalidation
SETGLOBAL
default.key_cache_division_limit=70;
16
CREATETABLEProducts(
product_idINTNOTNULLAUTO_INCREMENT
,nameVARCHAR(80)NOTNULL
,unit_costDECIMAL(7,2)NOTNULL
,descriptionTEXTNULL
,image_pathTEXTNULL
,num_viewsINTUNSIGNEDNOTNULL
,num_in_stockINTUNSIGNEDNOTNULL
,num_on_orderINTUNSIGNEDNOTNULL
,PRIMARYKEY(product_id)
,INDEX(name(20))
)ENGINE=InnoDB;//OrMyISAM
//GettingasimpleCOUNTofproducts
//easyonMyISAM,terribleonInnoDB
SELECTCOUNT(*)
FROMProducts;
Copyright MySQL AB
CREATETABLEProducts(
product_idINTNOTNULLAUTO_INCREMENT
,nameVARCHAR(80)NOTNULL
,unit_costDECIMAL(7,2)NOTNULL
,descriptionTEXTNULL
,image_pathTEXTNULL
,PRIMARYKEY(product_id)
,INDEX(name(20))
)ENGINE=InnoDB;//OrMyISAM
CREATETABLEProductCounts(
product_idINTNOTNULL
,num_viewsINTUNSIGNEDNOTNULL
,num_in_stockINTUNSIGNEDNOTNULL
,num_on_orderINTUNSIGNEDNOTNULL
,PRIMARYKEY(product_id)
)ENGINE=InnoDB;
CREATETABLEProductCountSummary(
total_productsINTUNSIGNEDNOTNULL
)ENGINE=MEMORY;
17
Copyright MySQL AB
18
CREATETABLEProducts2Tags(
record_idINTUNSIGNEDNOTNULLAUTO_INCREMENT
,product_idINTUNSIGNEDNOTNULL
,tag_idINTUNSIGNEDNOTNULL
,PRIMARYKEY(record_id)
,UNIQUEINDEX(product_id,tag_id)
)ENGINE=InnoDB;
Copyright MySQL AB
19
Indexing Guidelines
Copyright MySQL AB
20
Selectivity
Copyright MySQL AB
21
Determining Selectivity
Copyright MySQL AB
22
Determining Selectivity
Copyright MySQL AB
23
CREATETABLETags(
tag_idINTNOTNULLAUTO_INCREMENT
,tag_textVARCHAR(50)NOTNULL
,PRIMARYKEY(tag_id)
)ENGINE=MyISAM;
CREATETABLEProducts(
product_idINTNOTNULLAUTO_INCREMENT
,nameVARCHAR(100)NOTNULL
//manymorefields...
,PRIMARYKEY(product_id)
)ENGINE=MyISAM;
CREATETABLEProducts2Tags(
product_idINTNOTNULL
,tag_idINTNOTNULL
,PRIMARYKEY(product_id,tag_id)
)ENGINE=MyISAM;
Copyright MySQL AB
//Thistopqueryusestheindex
//onProducts2Tags
SELECTp.name
,COUNT(*)astags
FROMProducts2Tagsp2t
INNERJOINProductsp
ONp2t.product_id=p.product_id
GROUPBYp.name;
//Thisonedoesnotbecause
//indexorderprohibitsit
SELECTt.tag_text
,COUNT(*)asproducts
FROMProducts2Tagsp2t
INNERJOINTagst
ONp2t.tag_id=t.tag_id
GROUPBYt.tag_text;
24
CREATETABLETags(
tag_idINTNOTNULLAUTO_INCREMENT
,tag_textVARCHAR(50)NOTNULL
,PRIMARYKEY(tag_id)
)ENGINE=MyISAM;
CREATETABLEProducts(
product_idINTNOTNULLAUTO_INCREMENT
,nameVARCHAR(100)NOTNULL
//manymorefields...
,PRIMARYKEY(product_id)
)ENGINE=MyISAM;
CREATETABLEProducts2Tags(
product_idINTNOTNULL
,tag_idINTNOTNULL
,PRIMARYKEY(product_id,tag_id)
)ENGINE=MyISAM;
Copyright MySQL AB
CREATEINDEXix_tag
ONProducts2Tags(tag_id);
//or...createacoveringindex:
CREATEINDEXix_tag_prod
ONProducts2Tags(tag_id,product_id);
//But,onlyifnotInnoDB...why?
25
Copyright MySQL AB
26
Copyright MySQL AB
27
Coding Guidelines
Copyright MySQL AB
28
//Badpractice
SELECTp.name
,(SELECTMAX(price)
FROMOrderItems
WHEREproduct_id=p.product_id)
ASmax_sold_price
FROMProductsp;
Copyright MySQL AB
//Goodpractice
SELECTp.name
,MAX(oi.price)ASmax_sold_price
FROMProductsp
INNERJOINOrderItemsoi
ONp.product_id=oi.product_id
GROUPBYp.name;
29
//Badperformance
SELECT
c.company
,o.*
FROM
Customersc
INNERJOINOrderso
ONc.customer_id=o.customer_id
WHEREorder_date=(
SELECTMAX(order_date)
FROMOrders
WHEREcustomer=o.customer
)
GROUPBYc.company;
Copyright MySQL AB
//Goodperformance
SELECT
c.company
,o.*
FROM
Customersc
INNERJOIN(
SELECT
customer_id
,MAX(order_date)asmax_order
FROMOrders
GROUPBYcustomer_id
)ASm
ONc.customer_id=m.customer_id
INNERJOINOrderso
ONc.customer_id=o.customer_id
ANDo.order_date=m.max_order
GROUPBYc.company;
30
Demonstration :)
Copyright MySQL AB
31
Copyright MySQL AB
32
//Badidea
SELECT*
FROMOrders
WHERE
TO_DAYS(order_created)
TO_DAYS(CURRENT_DATE())>=7;
Copyright MySQL AB
//Betteridea
SELECT*
FROMOrders
WHERE
order_created>=CURRENT_DATE()INTERVAL7DAY;
33
//Initialschema
CREATETABLECustomers(
customer_idINTNOTNULL
,emailVARCHAR(80)NOTNULL
//morefields
,PRIMARYKEY(customer_id)
,INDEX(email(40))
)ENGINE=InnoDB;
//Badidea,can'tuseindex
//onemailfield
SELECT*
FROMCustomers
WHEREemailLIKE'%.com';
//So,weenablefastsearchingonareversedfield
//valuebyinsertingacalculatedfield
ALTERTABLECustomers
ADDCOLUMNrv_emailVARCHAR(80)NOTNULL;
//Now,weupdatetheexistingtablevalues
UPDATECustomersSETrv_email=REVERSE(email);
//Then,wecreateanindexonthenewfield
CREATEINDEXix_rv_emailONCustomers(rv_email);
//Then,wemakeatriggertokeepourdatainsync
DELIMITER;;
CREATETRIGGERtrg_bi_cust
BEFOREINSERTONCustomers
FOREACHROWBEGIN
SETNEW.rv_email=REVERSE(NEW.email);
END;;
//sametriggerforBEFOREUPDATE...
//ThenSELECTonthenewfield...
WHERErv_emailLIKECONCAT(REVERSE('.com'),'%');
Copyright MySQL AB
34
//Badidea
SELECT*
FROMOrders
WHERE
TO_DAYS(order_created)
TO_DAYS(CURRENT_DATE())>=7;
//Betteridea
SELECT*
FROMOrders
WHERE
order_created>=CURRENT_DATE()
INTERVAL7DAY;
Copyright MySQL AB
//BestideaistofactorouttheCURRENT_DATE
//nondeterministicfunctioninyourapplication
//codeandreplacethefunctionwithaconstant.
//Forinstance,inyourPHPcode,youwould
//simplyinsertdate('Ymd')inthequery
//insteadofCURRENT_DATE()
//Now,querycachecanactuallycachethequery!
SELECTorder_id,order_created,customer_id
FROMOrders
WHEREorder_created>='20060524'INTERVAL7DAY;
35
//Instead,issuethefollowing,whichallows
//MySQLtoquicklyuseindexesinorderto
//grabthedesiredrow
SELECT@row_id:=COUNT(*)FROMAds;
SELECT@row_id:=FLOOR(RAND()*@row_id)+1;
SELECT*FROMAdsWHEREad_id=@row_id;
//Popquiz:whatshouldtheabovelooklike
//iftheAdstableisanInnoDBtable?:)
Copyright MySQL AB
36
Copyright MySQL AB
37
The UPDATEstatement
Use the ONDUPLICATEKEYUPDATE clause of the
INSERT statement for a performance gain
The DELETE statement
Do you really need to delete the row?
Properly segment data that should be deleted using
vertical partitioning so that you can use TRUNCATE
TABLE instead
Or, INSERT the record id into a separate table, then:
DELETEmain_tableFROMmain_table
LEFTJOINdeleted_records
ONmain_table.id=deleted_records.id
WHEREdeleted_records.idISNOTNULL;
Copyright MySQL AB
38
Copyright MySQL AB
39
Copyright MySQL AB
40
myisam_sort_buffer_size
key_buffer_size>> Main
Copyright MySQL AB
41
read_buffer_size>>
Copyright MySQL AB
42
innodb_buffer_pool_size>> Both
pages
Blocks of size 16K
If you have InnoDB-only system, set to 60-80% of
total memory
Examine Innodb_buffer_pool_readsvs
Innodb_buffer_pool_read_requests
innodb_log_file_size
Copyright MySQL AB
43
innodb_log_buffer_size>>
buffer
Set < 16M (recommend 1M to 8M)
innodb_flush_method
Copyright MySQL AB
44
Recommended Resources
http://www.mysqlperformanceblog.com/
Peter Zaitsev's blog Excellent material
Optimizing Linux Performance
Philip Ezolt (HP Press)
http://dev.mysql.com/tech-resources/articles/pro-mysqlch6.pdf
Pro MySQL (Apress) chapter on profiling (EXPLAIN)
Advanced PHP Programming
George Schlossnagle (Developer's Library)
Copyright MySQL AB
45
Final Thoughts
Copyright MySQL AB
46