Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

PHP tips by a MYSQL DBA


Published on

Talk was given by Shantanu Oak in phpcamp pune

Published in: Technology
  • Be the first to comment

PHP tips by a MYSQL DBA

  1. 1. <ul><ul><li>PHP tips by a MYSQL DBA </li></ul></ul><ul><ul><li>-- Shantanu Oak </li></ul></ul>
  2. 2. Signing query <ul><li>Sign your queries... select * from mytable; SeLeCt * from mytable; </li></ul><ul><li>Or use comments in the query like... select /* query by shantanu login.php file */ name, age from customers </li></ul><ul><li>This helps while watching process-list and slow query logs </li></ul><ul><li>No line breaks in a query but space after , of column name </li></ul>
  3. 3. Output Query <ul><li>echo the query and make it hidden </li></ul><ul><li>Display style none as shown below: </li></ul><ul><li><span style=&quot;display:none&quot;> <?php echo $qrysearch ?> </span> </li></ul><ul><li>Anyone can go to View – Source and find the query </li></ul><ul><li>This will be useful for debugging. Remove the code before taking the code to production server. </li></ul>
  4. 4. MySQL command history <ul><li>[root@databaseserver215 ~]# tail .mysql_history </li></ul><ul><li>show processlist; show slave status; stop slave; SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; START SLAVE; show slave status; show variables like '%innod%'; show slave status; </li></ul>
  5. 5. Mysqld --print-defaults <ul><li>[root@localhost mysql]# mysqld --print-defaults </li></ul><ul><li>--port=3306 --socket=/var/lib/mysql/mysql.sock --skip-external-locking --log-bin=/var/log/mysql/binary/mysql-bin.log --binlog-ignore-db=mysql --binlog-ignore-db=test --server-id=4 --expire_logs_days=15 --max_binlog_size=1024M --report-host= --relay-log=/var/log/mysql/binary/cluster1-relay-bin --sync_binlog=1 --group_concat_max_len=500000 --innodb_data_home_dir= --innodb_data_file_path=ibdata1:2800M;ibdata2:2800M:autoextend --innodb_buffer_pool_size=4G --innodb_additional_mem_pool_size=1M --innodb_log_files_in_group=2 --innodb_log_file_size=1G --innodb_log_buffer_size=8M --innodb_flush_log_at_trx_commit=1 --innodb_lock_wait_timeout=50 --log_slow_queries=/var/log/mysql/mysql-slow.log --long_query_time=2 --skip-name-resolve --max_connections=400 --max_user_connections=1600 --max_connect_errors=50 --wait_timeout=1200 --connect_timeout=5 --interactive_timeout=120 --join_buffer_size=1M --read_buffer_size=4M --read_rnd_buffer_size=16M --table_cache=512 --max_allowed_packet=4M --key_buffer_size=6144M --sort_buffer_size=2M --myisam_sort_buffer_size=64M --thread_cache=128 --thread_concurrency=8 --thread_cache_size=40 --thread_stack=128K --low_priority_updates=1 --query_cache_limit=16M --query_cache_min_res_unit=1 --query_cache_size=500M --query_cache_type=1 --skip-bdb --group_concat_max_len=3072 --ft_min_word_len=1 --ft_stopword_file=/etc/stopword.txt </li></ul>
  6. 6. Use Strict Mode mysql> SET local sql_mode='strict_all_tables'; <ul><li>mysql> SELECT @@local.sql_mode; </li></ul><ul><li>+--------------------------------+ </li></ul><ul><li>| @@local.sql_mode | </li></ul><ul><li>+--------------------------------+ </li></ul><ul><li>| STRICT_ALL_TABLES,NO_ZERO_DATE | </li></ul><ul><li>+--------------------------------+ </li></ul><ul><li>1 row in set (0.00 sec)‏ </li></ul><ul><li>mysql> SELECT @@global.sql_mode; </li></ul><ul><li>+-------------------+ </li></ul><ul><li>| @@global.sql_mode | </li></ul><ul><li>+-------------------+ </li></ul><ul><li>| | </li></ul><ul><li>+-------------------+ </li></ul><ul><li>1 row in set (0.00 sec)‏ </li></ul>
  7. 7. Use Cron <ul><li>If you want to delete rows older than current date use crontab -e command to edit the crontab file. Make sure that the mysql command can be executed at command prompt. </li></ul><ul><li># remove records earlier than current date </li></ul><ul><li>1 0 * * * mysql -h -uroot -e'delete from testdb.test_tb where d < current_date()' </li></ul>
  8. 8. Using Log files <ul><li>MySQL can generate different log files like... </li></ul><ul><li>General, slow, binlog and error </li></ul><ul><li>Apache error log files also contain some valuable information. </li></ul>
  9. 9. Error log <ul><li>grep -n &quot;File&quot; -v dataentry_error_log | tail </li></ul><ul><li>cat -n iro_error_log | tail -300 | more </li></ul>
  10. 10. Binlog I <ul><li>Binlogs are enabled in order to send insert /update queries to the slave. </li></ul><ul><li>But you can enable binlogs even if the server is not part of the replication. </li></ul><ul><li>This log will automatically store all the important statements along with it's date-time and IP addresses. </li></ul>
  11. 11. Binlog II <ul><li>mysqlbinlog --stop-datetime=&quot;2008-05-01 00:00:00&quot; mysql-bin.000005 >> prit1.txt </li></ul><ul><li>grep -i 'PA003111' prit1.txt | sort | uniq > sendprit.txt </li></ul><ul><li>mysqlbinlog –start-datetime=&quot;`date +%Y-%m-%d' '%H:00:00 -d&quot;1 hour ago&quot;`&quot; --stop-datetime=&quot;`date +%Y-%m-%d' '%H:00:00`&quot; mysql-bin.* | awk '/tbl_company_master/,/;/' | replace tbl_company_master new.tbl_company_master | more </li></ul>
  12. 12. Slow-query-log mysqldumpslow <ul><li>You can save the first 1000 expensive queries into a separate file using built-in mysqldumpslow utility. </li></ul><ul><li>mysqldumpslow /var/log/mysql/mysql-slow.log | head -1000 > slowextract.txt </li></ul><ul><li>sort by count instead of time (default) and show actual values of integers and text instead of N and S </li></ul><ul><li>mysqldumpslow mysql-slow-demaster.log -s c -a > extract.txt </li></ul>
  13. 13. Slow-query log - I <ul><li># Time: 080602 11:37:50 </li></ul><ul><li># User@Host: root[root] @ [] </li></ul><ul><li># Query_time: 103 Lock_time: 0 Rows_sent: 82 Rows_examined: 1060213 </li></ul><ul><li>use d_Jd_ClientFB; </li></ul><ul><li>Select dosdate, count(*) as SmsCount, operator as Operator from ClientFeedback_backup where SMSSent = 'Y' and CompanyMobile is not null group by dosDate, operator; </li></ul>
  14. 14. Slow-query log II The explain plan shows as below: id: 1 select_type: SIMPLE table: ClientFeedback_backup type: ref possible_keys: SentIndex,CompanyMobileIndex key: SentIndex key_len: 2 ref: const rows: 434148 Extra: Using where; Using temporary; Using filesort
  15. 15. Slow-query log III <ul><li>The index is being used on 'Sent' column which is of low cardinality. It means only 'Y' and 'N' values are stored in this column and it is as good as using no index at all. Because Index column looks for unique values and it did not find such values in this column. A composite index on dosDate, operator in that order is necessary. </li></ul>
  16. 16. General Log <ul><li>General logs can become very big in a very short time since they have all the selects along with update/ delete </li></ul><ul><li>Worth enabling on test servers where you are testing your PHP code </li></ul>
  17. 17. Low and High Priority <ul><li>The HIGH_PRIORITY hint can be used on SELECT or INSERT statements to let MySQL know that this is a high priority query. This hint will basically allow the query to skip in line. </li></ul><ul><li>The LOW_PRIORITY hint can be used on INSERT and UPDATE statements. If you use the LOW_PRIORITY keyword, execution of the query is delayed until no other clients are reading from the table. This means that you may wait a LONG time, or forever on servers with a heavy read volume. </li></ul><ul><li>insert HIGH PRIORITY into logappointment (emailid, appflag, date1, callerid, tel, mob) values ( ' ', 'Y', now(), '2191361', '22579950', '9415023611'); </li></ul>
  18. 18. sql_big_results <ul><li>The SQL_BIG_RESULT hint can be used with DISTINCT and GROUP BY SELECT statements. It as you might guess, tells MySQL that the result set will be big. According to the MySQL documentation, if invoked </li></ul><ul><li>MySQL directly uses disk-based temporary tables if needed, and prefers sorting to using a temporary table with a key on the GROUP BY elements. </li></ul><ul><li>SeleCT SQL_BUFFER_RESULT SQL_BIG_RESULT t1.contactid, t1.parentid, t1.companyname, t1.createdby, t1.curTime, group_concat(t1.contract_type) as contract_type, t1.promptype, t1.freez, t1.mask, t1.contract_series FROM tbl_company_master t1 WHERE  t1.contract_type <> 'prompt' GROUP BY t1.parentid ORDER BY t1.companyName ASC, t1.parentId ASC </li></ul>
  19. 19. Insert delayed Part I <ul><li>It will return immediately, but it will still wait until other clients have closed the table before executing the statement. </li></ul><ul><li>Note: INSERT DELAYED only works on MyISAM, MEMORY, and ARCHIVE tables. </li></ul><ul><li>You can delay INSERT's from happening until the table is free by using the DELAYED hint in your SQL statement. For example: </li></ul><ul><li>INSERT DELAYED INTO table (col) VALUES ('val'); </li></ul><ul><li>The above SQL statement will return quickly, and it stores the insert statement in a memory queue until the table you are inserting into is free from reads. This means that if there are multiple inserts in the queue they can be written in one block, which is a more optimal use of IO. </li></ul>
  20. 20. Insert Delayed Part II <ul><li>The downside to this is that it is not transactionally safe at all. You don't really know how long its going to take for your INSERT to happen. If the server crashes, or is forcefully shutdown you will loose your INSERTs. So don't use this on any critical information. </li></ul><ul><li>One great use for the DELAYED keyword would be for storing web stats in a database. You don't want the client waiting for the stats to insert, and its not that big of a deal if you loose a few stats (for most people). </li></ul>
  21. 21. Store IP addresses <ul><li>CREATE TABLE Sessions (session_id INT UNSIGNED NOT NULL AUTO_INCREMENT, display_address varchar(15), ip_address INT UNSIGNED NOT NULL, session_data TEXT NOT NULL, PRIMARY KEY (session_id), INDEX (ip_address) ) ENGINE = InnoDB; </li></ul><ul><li>insert into Sessions values (NULL, '', INET_ATON(''), 'some more data'); </li></ul><ul><li>insert into Sessions values (NULL, '', INET_ATON(''), 'data from other IP'); </li></ul><ul><li>select session_id, display_address, ip_address as ip_raw, INET_NTOA(ip_address) as ip, session_data from Sessions WHERE ip_address = INET_ATON('') or ip_address = INET_ATON('') ; </li></ul>
  22. 22. Replication <ul><li>A client that has the SUPER privilege can disable binary logging of its own statements by using a SET SQL_LOG_BIN=0 statement. As a brief example, if I am loading a large table it could be good to disable </li></ul><ul><li>logging before beginning the import. </li></ul><ul><li>mysql> SET SQL_LOG_BIN=0; </li></ul><ul><li>mysql> LOAD DATA INFILE 'honking_big_file' INTO BIG_TABLE; </li></ul>
  23. 23. Normalize your database <ul><li>The first normal form doesn't allow you to store values like this in a single cell. </li></ul><ul><li>/146/,/834/,/3483/,/4043/,/20852/,/221554/,/221561/,/222987/,/223154/,/223539/ </li></ul><ul><li>'Z008677','Z004949','Z008572','Z004951' </li></ul>
  24. 24. Table Types <ul><li>There are 2 important types of tables. </li></ul><ul><li>MyISAM when there are many selects or many inserts or when you need Full text index. </li></ul><ul><li>InnoDB when there are a lot of select, update, inserts and deletes happening simultaneously. This is the only table type that supports transactions and foreign keys. </li></ul><ul><li>Archive table type supports only inserts and selects. No Update/ delete is allowed. Good for log tables. </li></ul><ul><li>Federated tables allows you to connect to remote tables as if they are local. Doesn't work for very big tables and is buggy. </li></ul><ul><li>Memory tables are temporary tables living in memory and are dropped when the server is restarted. </li></ul>
  25. 25. Column Types <ul><li>Do not use float or double to store numbers. Use decimal or integer </li></ul><ul><li>Do not use BLOB to store files, images. Save them in a directory and store the path in the table. </li></ul><ul><li>Avoid 'text' datatype. Use varchar. No need to use varchar(255) since we can now have varchar(3000)‏ </li></ul><ul><li>But it does not mean that you should have varchar(3000) when varchar(100) is enough. Indexes will have issues with that figure. </li></ul><ul><li>Do not use set or enum if you are not comfortable with database concepts. </li></ul>
  26. 26. Learn how to use “load data infile” <ul><li>The method of 'Load data in file' is usually 20 times faster than using INSERT statements. </li></ul>
  27. 27. Load file <ul><li>You can read the file contents from within mysql. This feature is useful to read the file saved on the server and send the contents to the user through PHP. </li></ul><ul><li>[root@irodb2 mysql]# echo '<b> this is bold in xml file </b>' > mytest.xml </li></ul><ul><li>[root@irodb2 mysql]# mysql test </li></ul><ul><li>mysql> select load_file(&quot;/var/log/mysql/mytest.xml&quot;), 'test constant column', bse_code from bsecode limit 1 </li></ul><ul><li>*************************** 1. row *************************** </li></ul><ul><li>load_file(&quot;/var/log/mysql/mytest.txt&quot;): <b> this is bold in xml file </b> </li></ul><ul><li>test constant column: test constant column </li></ul><ul><li>bse_code: 513375 </li></ul>
  28. 28. Balance PHP and MySQL <ul><li>// userID,posts,runningTotal| </li></ul><ul><li>// output running total </li></ul><ul><li>// 2, 23434, 28330| // 6, 3443, 4896| // 1, 422, 1453| // 3, 344, 1031| // 4, 344, 687| // 5, 343, 343| </li></ul><ul><li>echo 'userID,posts,runningTotal|<br>'; </li></ul><ul><li>$q = mysql_query(&quot;select * from `members` order by `posts` DESC&quot;); </li></ul><ul><li>while($a = mysql_fetch_row($q)){ echo &quot;$a[0],$a[1],$total|<br>&quot;; $total = $total - $a[1]; </li></ul><ul><li>} </li></ul>
  29. 29. Use Joins <ul><li>Do not execute the query and take the rows one at a time to compare it's value with another row. Use joins. </li></ul><ul><li>Do not use IN </li></ul><ul><li>Use different joins like inner, left </li></ul>
  30. 30. Update 2 tables in one query <ul><li>You can join 2 tables in a single select. You can update those 2 tables in a single statement as well. </li></ul><ul><li>  select * from packet_date_sent AS pds       INNER JOIN tempAdvSep2007 AS tas         ON pds.enroll_no = tas.enroll_no       INNER JOIN packet_sent AS ps         ON ps.enroll_no = tas.enroll_no            AND ps.material_id = pds.material_id            AND ps.course_id = pds.course_id            AND ps.enroll_date = pds.enroll_date WHERE  pds.date_sent = '2007-09-01'       AND pds.material_id BETWEEN 62 AND 97; </li></ul>
  31. 31. Update 2 tables in one query <ul><li>UPDATE packet_date_sent AS pds </li></ul><ul><li>      INNER JOIN tempAdvSep2007 AS tas ON pds.enroll_no = tas.enroll_no </li></ul><ul><li>      INNER JOIN packet_sent AS ps ON ps.enroll_no = tas.enroll_no AND ps.material_id = pds.material_id AND ps.course_id = pds.course_id AND ps.enroll_date = pds.enroll_date </li></ul><ul><li>SET    pds.sent_mode = '5', pds.postofficecode = 1, pds.system_date = NOW(), pds.branch_id = '99', ps.sent_bit = '1', ps.system_date = NOW(), ps.branch_id = '99' </li></ul><ul><li>WHERE  pds.date_sent = '2007-09-01' AND pds.material_id BETWEEN 62 AND 97; </li></ul>
  32. 32. Execute linux commands <ul><li>You can use quotes to execute Linux command from within PHP </li></ul><ul><li><?php </li></ul><ul><li>echo `mysql -h -uroot -H -e 'set @count:= 0; select @count:= @count + 1 as &quot;sr no&quot;, p,d from test.t' `; </li></ul><ul><li>?> </li></ul>
  33. 33. Full Text search <ul><li>Only MyISAM table types supports this. </li></ul><ul><li>You can use match – against syntax when you want to use this type of search. </li></ul>
  34. 34. SQL_CALC_FOUND_ROWS <ul><li>mysql> select SQL_CALC_FOUND_ROWS name, country from india limit 1G name: Punjab Plains country: IN </li></ul><ul><li>mysql> select found_rows(); +--------------+ | found_rows() | +--------------+ | 38202 | +--------------+ 1 row in set (0.00 sec)‏ </li></ul>
  35. 35. Use MySQL variables mysql> set @count:= 0; Query OK, 0 rows affected (0.00 sec)‏ mysql> select @count:= @count + 1 as myid, p from t; +------+-----+ | myid | p | +------+-----+ | 1 | 20% | | 2 | 20% | | 3 | 30% | | 4 | 30% | | 5 | 50% | | 6 | 50% | +------+-----+ 10 rows in set (0.00 sec)‏
  36. 36. Collation issues <ul><li>SET @@SESSION.collation_connection = 'latin1_general_ci'; </li></ul>
  37. 37. error_reporting(E_ALL); <ul><li>declaring a variable ahead of time, </li></ul><ul><li>referencing a variable that isn’t available in that segment of code, or </li></ul><ul><li>using a define that isn’t set. </li></ul>
  38. 38. Use deterministic functions <ul><li>A function is considered deterministic if it always produces the same result </li></ul><ul><li>Non-deterministic (changing value) functions return different values every time. </li></ul><ul><li>MySQL does not have to process the function and hence faster </li></ul><ul><li>Replication will see the actual value and not now() that will have different time on Master and slave </li></ul>
  39. 39. Save these values in a variable and use that value in the SQL statement <ul><li>mysql> select now(), rand(), curtime(); </li></ul><ul><li>+---------------------+------------------+-----------+ </li></ul><ul><li>| now() | rand() | curtime() | </li></ul><ul><li>+---------------------+------------------+-----------+ </li></ul><ul><li>| 2008-09-17 18:04:26 | 0.38422505500189 | 18:04:26 | </li></ul><ul><li>+---------------------+------------------+-----------+ </li></ul><ul><li>mysql> select now(), rand(), curtime(); </li></ul><ul><li>+---------------------+------------------+-----------+ </li></ul><ul><li>| now() | rand() | curtime() | </li></ul><ul><li>+---------------------+------------------+-----------+ </li></ul><ul><li>| 2008-09-17 18:04:29 | 0.98001360425727 | 18:04:29 | </li></ul><ul><li>+---------------------+------------------+-----------+ </li></ul>
  40. 40. Forget select * mysql> explain select * from tbl_compcatarea a inner join bid_details b on a.contactid=b.contractid; +----+-------------+-------+------+---------------+-----------+---------+--------------------+-------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-----------+---------+--------------------+-------+-------+ | 1 | SIMPLE | b | ALL | ContractID | NULL | NULL | NULL | 24386 | | | 1 | SIMPLE | a | ref | contactid | contactid | 47 | d_jds.b.contractID | 1 | | +----+-------------+-------+------+---------------+-----------+---------+--------------------+-------+-------+ mysql> explain select a.contactid, a.compname from tbl_compcatarea a inner join bid_details b on a.contactid=b.contractid; +----+-------------+-------+-------+---------------+------------+---------+--------------------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+------------+---------+--------------------+-------+-------------+ | 1 | SIMPLE | b | index | ContractID | ContractID | 48 | NULL | 24386 | Using index | | 1 | SIMPLE | a | ref | contactid | contactid | 47 | d_jds.b.contractID | 1 | | +----+-------------+-------+-------+---------------+------------+---------+--------------------+-------+-------------+
  41. 41. Standards and conventions for naming columns, tables and indexes <ul><li>The name of the key is misleading in the table tbl_company_source. The key name is 'ContractID' and the column used for indexing is 'contactID'. I overlooked this problem because of the similarity in the key name and column name. In fact we need two different indexes on ContractID and contactID. </li></ul>