Pro MySQL experts voice in open source phần 5 pot

77 235 0
Pro MySQL experts voice in open source phần 5 pot

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

If you felt that a more efficient join order would be to use the order given in the SELECT statement, you would use the STRAIGHT_JOIN hint, as shown in Listing 7-37. Listing 7-37. Example of the STRAIGHT_JOIN Hint mysql> EXPLAIN -> SELECT * -> FROM Category c -> STRAIGHT_JOIN Product2Category p2c -> STRAIGHT_JOIN Product p -> WHERE c.name LIKE 'Video%' -> AND c.category_id = p2c.category_id -> AND p2c.product_id = p.product_id \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: c type: ALL possible_keys: PRIMARY key: NULL key_len: NULL ref: NULL rows: 14 Extra: Using where *************************** 2. row *************************** id: 1 select_type: SIMPLE table: p2c type: index possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: NULL rows: 8 Extra: Using where; Using index *************************** 3. row *************************** id: 1 select_type: SIMPLE table: p type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: ToyStore.p2c.product_id rows: 1 Extra: 3 rows in set (0.00 sec) CHAPTER 7 ■ ESSENTIAL SQL276 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 276 As you can see, MySQL dutifully follows your desired join order. The access pattern it comes up with, in this case, is suboptimal compared with the original, MySQL-chosen access path. Where in the original EXPLAIN from Listing 7-36, you see MySQL using ref and eq_ref access types for the joins to Product2Category and Category, in the STRAIGHT_JOIN EXPLAIN (Listing 7-37), you see MySQL has reverted to using an index scan on Product2Category and an eq_ref to access Product. In this case, the STRAIGHT_JOIN made things worse. In most cases, MySQL will indeed choose the most optimal pattern for accessing tables in your SELECT statements. However, if you encounter a situation in which you suspect a different order would produce speedier results, you can use this technique to test your theories. ■Caution If you do find a situation in which you suspect changing the join order would speed up a query, make sure that MySQL is using up-to-date statistics on your table before making any changes. After you run a baseline EXPLAIN to see MySQL’s chosen access strategy for your query, run an ANALYZE TABLE against the table, and then check your EXPLAIN again to see if MySQL changed the join order or access strategy. ANALYZE TABLE will update the statistics on key distribution that MySQL uses to decide an access strategy. Remember that running ANALYZE TABLE will place a read lock on your table, so carefully choose when you run this statement on large tables. The USE INDEX and FORCE INDEX Hints You’ve noticed a particularly slow query, and run an EXPLAIN on it. In the EXPLAIN result, you see that for a particular table, MySQL has a choice of more than one index that contain columns on which your WHERE or ON condition depends. It happens that MySQL has chosen to use an index that you suspect is less efficient than another index on the same table. You can use one of two join hints to prod MySQL into action: • The USE INDEX (index_list) hint tells MySQL to consider only the indexes contained in index_list during its evaluation of the table’s access strategy. However, if MySQL determines that a sequential scan of the index or table data (index or ALL access types) will be faster using any of the indexes using a seek operation (eq_ref, ref, ref_or_null, and range access types), it will perform a table scan. • The FORCE INDEX (index_list), on the other hand, tells MySQL not to perform a table scan, 3 and to always use one of the indexes in index_list. The FORCE_INDEX hint is avail- able only in MySQL versions later than 4.0.9. The IGNORE INDEX Hint If you simply want to tell MySQL to not use one or more indexes in its evaluation of the access strategy, you can use the IGNORE INDEX (index_list) hint. MySQL will perform the optimization of joins as normal, but it will not include in the evaluation any indexes listed in index_list. Listing 7-38 shows the results of placing an IGNORE INDEX hint in a SELECT statement. CHAPTER 7 ■ ESSENTIAL SQL 277 3. Technically, FORCE INDEX makes MySQL assign a table scan a very high optimization weight, making the use of a table scan very unlikely. 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 277 Listing 7-38. Example of How the IGNORE INDEX Hint Forces a Different Access Strategy mysql> EXPLAIN -> SELECT p.name, p.unit_price, coi.price -> FROM CustomerOrderItem coi -> INNER JOIN Product p -> ON coi.product_id = p.product_id -> INNER JOIN CustomerOrder co -> ON coi.order_id = co.order_id -> WHERE co.ordered_on = '2004-12-07' \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: co type: ref possible_keys: PRIMARY,ordered_on key: ordered_on key_len: 3 ref: const rows: 1 Extra: Using where; Using index *************************** 2. row *************************** id: 1 select_type: SIMPLE table: coi type: ref possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: ToyStore.co.order_id rows: 1 Extra: *************************** 3. row *************************** id: 1 select_type: SIMPLE table: p type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: ToyStore.coi.product_id rows: 1 Extra: 3 rows in set (0.01 sec) mysql> EXPLAIN -> SELECT p.name, p.unit_price, coi.price -> FROM CustomerOrderItem coi CHAPTER 7 ■ ESSENTIAL SQL278 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 278 -> INNER JOIN Product p -> ON coi.product_id = p.product_id -> INNER JOIN CustomerOrder co IGNORE INDEX (ordered_on) -> ON coi.order_id = co.order_id -> WHERE co.ordered_on = '2004-12-07' \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: co type: ALL possible_keys: PRIMARY key: NULL key_len: NULL ref: NULL rows: 6 Extra: Using where *************************** 2. row *************************** id: 1 select_type: SIMPLE table: coi type: ref possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: ToyStore.co.order_id rows: 1 Extra: *************************** 3. row *************************** id: 1 select_type: SIMPLE table: p type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: ToyStore.coi.product_id rows: 1 Extra: 3 rows in set (0.03 sec) As in the previous example, you see that the resulting query plan was less optimal than without the join hint. Without the IGNORE_INDEX hint, MySQL had a choice between using the PRIMARY key or the index on ordered_on. Of these, it chose to use the ref access strategy—a lookup based on a non-unique index—and used the constant in the WHERE expression to fulfill the reference condition. CHAPTER 7 ■ ESSENTIAL SQL 279 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 279 In contrast, when the IGNORE_INDEX (ordered_on) hint is used, MySQL sees that it has the choice to use the PRIMARY key index (needed for the inner join from CustomerOrderItem to CustomerOrder). However, it decided that a table scan of the data, using a WHERE condition to filter out orders placed on December 7, 2004, would be more efficient in this case. Subqueries and Derived Tables Now we’re going to dive into a newer development in the MySQL arena: the subquery and derived table abilities available in MySQL version 4.1 and later. Subqueries are, simply stated, a SELECT statement within another statement. Subqueries are sometimes called sub-SELECTs, for obvious reasons. Derived tables are a specialized version of a subquery used in the FROM clause of your SELECT statements. As you’ll see, some subqueries can be rewritten as an outer join, but not all of them can be. In fact, there are certain SQL activities in MySQL that are impossible to achieve in a single SQL statement without the use of subqueries. In versions prior to MySQL 4.1, programmers needed to use multiple SELECT statements, possibly storing results in a temporary table or program variable and using that result in their code with another SQL statement. Subqueries As we said, a subquery is simply a SELECT statement embedded inside another SQL statement. As such, like any other SELECT statement, a subquery can return any of the following results: •A single value, called a scalar result •A single-row result—one row, multiple columns of data •A single-column result—one column of data, many rows •A tabular result—many columns of data for many rows The result returned by the subquery dictates the context in which the subquery may be used. Furthermore, the syntax used to represent the subquery varies depending on the returned result. We’ll show numerous examples for each different type of query in the follow- ing sections. Scalar Subqueries When a subquery returns only a single value, it may be used just like any other constant value in your SQL statements. To demonstrate, take a look at the example shown in Listing 7-39. Listing 7-39. Example of a Simple Scalar Subquery mysql> SELECT * -> FROM Product p -> WHERE p.unit_price = (SELECT MAX(unit_price) FROM Product) \G *************************** 1. row *************************** CHAPTER 7 ■ ESSENTIAL SQL280 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 280 product_id: 6 sku: SPT003 name: Tennis Racket description: Fiberglass Tennis Racket weight: 2.15 unit_price: 104.75 1 row in set (0.34 sec) Here, we’ve used this scalar subquery: (SELECT MAX(unit_price) FROM Product) This can return only a single value: the maximum unit price for any product in our catalog. Let’s take a look at the EXPLAIN output, shown in Listing 7-40, to see what MySQL has done. Listing 7-40. EXPLAIN for the Scalar Subquery in Listing 7-39 mysql> EXPLAIN -> SELECT * -> FROM Product p -> WHERE p.unit_price = (SELECT MAX(unit_price) FROM Product) \G *************************** 1. row *************************** id: 1 select_type: PRIMARY table: p type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 10 Extra: Using where *************************** 2. row *************************** id: 2 select_type: SUBQUERY table: Product type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 10 Extra: 2 rows in set (0.00 sec) You see no real surprises here. Since we have no index on the unit_price column, no indexes are deployed. MySQL helpfully notifies us that a subquery was used. CHAPTER 7 ■ ESSENTIAL SQL 281 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 281 The statement in Listing 7-39 may also be written using a simple LIMIT expression with an ORDER BY, as shown in Listing 7-41. We’ve included the EXPLAIN output for you to compare the two query execution plans used. Listing 7-41. Alternate Way of Expressing Listing 7-39 mysql> SELECT * -> FROM Product p -> ORDER BY unit_price DESC -> LIMIT 1 \G *************************** 1. row *************************** product_id: 6 sku: SPT003 name: Tennis Racket description: Fiberglass Tennis Racket weight: 2.15 unit_price: 104.75 1 row in set (0.00 sec) mysql> EXPLAIN -> SELECT * -> FROM Product p -> ORDER BY unit_price DESC -> LIMIT 1 \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: p type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 10 Extra: Using filesort 1 row in set (0.00 sec) You may be wondering why even bother with the subquery if the LIMIT statement is more efficient. There are a number of reasons to consider using a subquery in this situation. First, the LIMIT clause is MySQL-specific, so it is not portable. If this is a concern for you, the sub- query is the better choice. Additionally, many developers feel the subquery is a more natural, structured, and readable way to express the statement. The subquery in Listing 7-39 is only a simple query. For more complex queries, involving two or more tables, a subquery would be required, as Listing 7-42 demonstrates. CHAPTER 7 ■ ESSENTIAL SQL282 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 282 Listing 7-42. Example of a More Complex Scalar Subquery mysql> SELECT p.product_id, p.name, p.weight, p.unit_price -> FROM Product p -> WHERE p.weight = ( -> SELECT MIN(weight) -> FROM CustomerOrderItem -> ); + + + + + | product_id | name | weight | unit_price | + + + + + | 8 | Video Game - Car Racing | 0.25 | 48.99 | | 9 | Video Game - Soccer | 0.25 | 44.99 | | 10 | Video Game - Football | 0.25 | 46.99 | + + + + + 3 rows in set (0.00 sec) Here, because the scalar subquery retrieves data from CustomerOrderItem, not Product, there is no way to rewrite the query using either a LIMIT or a join expression. Let’s take a look at a third example of a scalar subquery, shown in Listing 7-43. Listing 7-43. Another Example of a Scalar Subquery mysql> SELECT -> p.name -> , p.unit_price -> , ( -> SELECT AVG(price) -> FROM CustomerOrderItem -> WHERE product_id = p.product_id -> ) as "avg_sold_price" -> FROM Product p; + + + + | name | unit_price | avg_sold_price | + + + + | Action Figure - Tennis | 12.95 | 12.950000 | | Action Figure - Football | 11.95 | 11.950000 | | Action Figure - Gladiator | 15.95 | 15.950000 | | Soccer Ball | 23.70 | 23.700000 | | Tennis Balls | 4.75 | 4.750000 | | Tennis Racket | 104.75 | 104.750000 | | Doll | 59.99 | 59.990000 | | Video Game - Car Racing | 48.99 | NULL | | Video Game - Soccer | 44.99 | NULL | | Video Game - Football | 46.99 | 46.990000 | + + + + 10 rows in set (0.00 sec) CHAPTER 7 ■ ESSENTIAL SQL 283 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 283 The statement in Listing 7-43 uses a scalar subquery in the SELECT clause of the outer statement to return the average selling price of the product, stored in the CustomerOrderItem table. In the subquery, note that the WHERE expression essentially joins the CustomerOrderItem. product_id with the product_id of the Product table in the outer SELECT statement. For each product in the outer Product table, MySQL is averaging the price column for the product in the CustomerOrderItem table and returning that scalar value into the column aliased as "avg_sold_price". Take special note of the NULL values returned for the “Video Game – Car Racing” and “Video Game – Soccer” products. What does this behavior remind you of? An outer join exhibits the same behavior. Indeed, we can rewrite the SQL in Listing 7-43 as an outer join with a GROUP BY expression, as shown in Listing 7-44. Listing 7-44. Listing 7-43 Rewritten As an Outer Join mysql> SELECT -> p.name -> , p.unit_price -> , AVG(coi.price) AS "avg_sold_price" -> FROM Product p -> LEFT JOIN CustomerOrderItem coi -> ON p.product_id = coi.product_id -> GROUP BY p.name, p.unit_price; + + + + | name | unit_price | avg_sold_price | + + + + | Action Figure - Football | 11.95 | 11.950000 | | Action Figure - Gladiator | 15.95 | 15.950000 | | Action Figure - Tennis | 12.95 | 12.950000 | | Doll | 59.99 | 59.990000 | | Soccer Ball | 23.70 | 23.700000 | | Tennis Balls | 4.75 | 4.750000 | | Tennis Racket | 104.75 | 104.750000 | | Video Game - Car Racing | 48.99 | NULL | | Video Game - Football | 46.99 | 46.990000 | | Video Game - Soccer | 44.99 | NULL | + + + + 10 rows in set (0.11 sec) However, what if we wanted to fulfill this request: “Return a list of each product name, its unit price, and the average unit price of all products tied to the product’s related categories.” As an exercise, see if you can write a single query that fulfills this request. Give up? You cannot use a single SQL statement, because in order to retrieve the average unit price of prod- ucts within related categories, you must average across a set of the Product table. Since you must also GROUP BY all the rows in the Product table, you cannot provide this information in a single SELECT statement with a join. Without subqueries, you would be forced to make two separate SELECT statements: one for all the product IDs, product names, and unit prices, and another for the average unit prices for each product ID in Product2Category that fell in a related category. Then you would need to manually merge the two results programmatically. CHAPTER 7 ■ ESSENTIAL SQL284 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 284 You could do this in your application code, or you might use a temporary table to store the average unit price for all categories, and then perform an outer join of your Product resultset along with your temporary table. With a scalar subquery, however, you can accomplish the same result with a single SELECT statement and subquery. Listing 7-45 shows how you would do this. Listing 7-45. Complex Scalar Subquery Showing Average Category Unit Prices mysql> SELECT -> p.name -> , p.unit_price -> , ( -> SELECT AVG(p2.unit_price) -> FROM Product p2 -> INNER JOIN Product2Category p2c2 -> ON p2.product_id = p2c2.product_id -> WHERE p2c2.category_id = p2c.category_id -> ) AS avg_cat_price -> FROM Product p -> INNER JOIN Product2Category p2c -> ON p.product_id = p2c.product_id -> GROUP BY p.name, p.unit_price; + + + + | name | unit_price | avg_cat_price | + + + + | Action Figure - Football | 11.95 | 12.450000 | | Action Figure - Gladiator | 15.95 | 15.950000 | | Action Figure - Tennis | 12.95 | 12.450000 | | Doll | 59.99 | 59.990000 | | Soccer Ball | 23.70 | 23.700000 | | Tennis Balls | 4.75 | 54.750000 | | Tennis Racket | 104.75 | 54.750000 | | Video Game - Car Racing | 48.99 | 48.990000 | | Video Game - Football | 46.99 | 45.990000 | | Video Game - Soccer | 44.99 | 45.990000 | + + + + 10 rows in set (0.72 sec) Here, we’re joining two copies of the Product and Product2Category tables in order to find the average unit prices for each product and the average unit prices for each product in any related category. This is possible through the scalar subquery, which returns a single averaged value. The key to the SQL is in how the WHERE condition of the subquery is structured. Pay close attention here. We have a condition that states WHERE p2c2.category_id = p2c.category_id. This condition ensures that the average returned by the subquery is across rows in the inner Product table (p2) that have rows in the inner Product2Category (p2c2) table matching any cat- egory tied to the row in the outer Product table (p). If this sounds confusing, take some time to scan through the SQL code carefully, noting how the connection between the outer and inner CHAPTER 7 ■ ESSENTIAL SQL 285 505x_Ch07_FINAL.qxd 6/27/05 3:28 PM Page 285 [...]... NOT EXISTS optimization over a LEFT JOIN … WHERE … IS NULL query In fact, if you look at the EXPLAIN output from Listing 7 -53 , shown in Listing 7 -54 , you see that MySQL has done just that 50 5x_Ch07_FINAL.qxd 6/27/ 05 3:28 PM Page 291 CHAPTER 7 ■ ESSENTIAL SQL Listing 7 -54 EXPLAIN from Listing 7 -53 mysql> EXPLAIN -> SELECT c.name -> FROM Category c -> LEFT JOIN Product2Category p2c -> ON c.category_id... Product table Finally, a WHERE clause filters out the rows in Product where the unit price is less than the minimum sale price of the product This differs from the correlated subquery example, in which a separate lookup query is executed for each row in Product Listing 7 -58 shows the EXPLAIN output from the derived table SQL in Listing 7 -57 Listing 7 -58 EXPLAIN Output of Listing 7 -57 mysql> -> -> ->... match in the outer result (Product table) It would be more efficient to do a single pass to find the minimum sale prices for each unique product, and then join that resultset to the outer query A derived table fulfills this need, as shown in Listing 7 -57 Listing 7 -57 Example of a Derived Table Query mysql> -> -> -> -> -> -> -> SELECT p.name FROM Product p INNER JOIN ( SELECT coi.product_id, MIN(price)... more than once, using the CustomerOrderItem table Notice the GROUP BY and HAVING clause 291 50 5x_Ch07_FINAL.qxd 292 6/27/ 05 3:28 PM Page 292 CHAPTER 7 ■ ESSENTIAL SQL Listing 7 -55 Getting Product IDs Purchased More Than Once mysql> SELECT coi.product_id -> FROM CustomerOrderItem coi -> GROUP BY coi.product_id -> HAVING COUNT(*) > 1; + + | product_id | + + | 5 | + + 1 row in set (0.00 sec)... surprise: ERROR 12 35 (42000): This version of MySQL doesn't yet support \ 'LIMIT & IN/ ALL/ANY/SOME subquery' At the time of this writing, MySQL does not support LIMIT expressions in certain subqueries, including the one in the preceding example Instead, you can use a derived table to get around the problem, as demonstrated in Listing 7-60 Listing 7-60 Using LIMIT with a Derived Table mysql> > -> -> ->... union(City,Zip); Using where 1 row in set (0.00 sec) In Listing 8-4, you see the new index_merge optimization technique available in MySQL 5. 0 The UNION optimization essentially queries both the City and Zip indexes, returning matching records that meet the part of the WHERE expression using the index, and then merges the two resultsets into a single resultset ■ Note Prior to MySQL 5. 0.4, you may see Using union... an inner join on a derived table containing the outer join from Listing 8- 15, referencing the rowID column (join and derived table techniques are detailed in Chapter 7) As expected, the query removes the 1 35 rows from RssEntry corresponding to our orphaned records Listing 8-17 shows a quick repeat of our initial report queries from Listing 8-13, verifying that the referencing summary report contains... p.name FROM Product p INNER JOIN ( SELECT DISTINCT product_id FROM CustomerOrderItem ORDER BY price DESC LIMIT 2 ) as top_price_product ON p.product_id = top_price_product.product_id; 50 5x_Ch07_FINAL.qxd 6/27/ 05 3:28 PM Page 297 CHAPTER 7 ■ ESSENTIAL SQL + -+ | name | + -+ | Tennis Racket | | Doll | + -+ 2 rows in set (0. 05 sec) Summary We’ve certainly covered a lot of ground in this... Connect to database $products = mysql_ query("SELECT product_id FROM Product2Category WHERE category_id = 5" ); if ($products) { $deletes = array(); while ($product = mysql_ fetch_row($products)) { array_push($deletes, $product[0]); } mysql_ query("DELETE FROM Product WHERE product_id IN (" implode("','", $deletes) ")"); mysql_ query("DELETE FROM Product2Category WHERE category_id = 5" ); } ?> Notice that... Extra: 3 rows in set (0.11 sec) As you can tell from Listing 8-6, the optimizer has indeed used both indexes (with a const reference) in order to pull appropriate records from the table The third row set in the EXPLAIN output is simply informing you that the two results from the first and second SELECT statements were combined However, we still have one problem Listing 8 -5 has produced two rows in our resultset . row in Product. Listing 7 -58 shows the EXPLAIN output from the derived table SQL in Listing 7 -57 . Listing 7 -58 . EXPLAIN Output of Listing 7 -57 mysql& gt; EXPLAIN -> SELECT p.name FROM Product. EXPLAIN output from Listing 7 -53 , shown in Listing 7 -54 , you see that MySQL has done just that. CHAPTER 7 ■ ESSENTIAL SQL290 50 5x_Ch07_FINAL.qxd 6/27/ 05 3:28 PM Page 290 Listing 7 -54 . EXPLAIN from. GROUP BY and HAVING clause. CHAPTER 7 ■ ESSENTIAL SQL 291 50 5x_Ch07_FINAL.qxd 6/27/ 05 3:28 PM Page 291 Listing 7 -55 . Getting Product IDs Purchased More Than Once mysql& gt; SELECT coi.product_id ->

Ngày đăng: 08/08/2014, 20:21

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan