{"id":73210,"date":"2014-03-12T16:04:04","date_gmt":"2014-03-12T16:04:04","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/uncategorized\/execution-plans-part-1-finding-plans\/"},"modified":"2021-07-14T13:07:35","modified_gmt":"2021-07-14T13:07:35","slug":"execution-plans-part-1-finding-plans","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/execution-plans-part-1-finding-plans\/","title":{"rendered":"Execution Plans: Part 1 Finding plans"},"content":{"rendered":"<p>If you have to tackle performance problems in an Oracle database one of the most important skills you need to acquire is the ability to interpret execution plans, and in this series of articles I\u2019ll be telling you how to do just that.<\/p>\n<p>We\u2019ll start with a couple of articles that look at some of the ways we can access execution plans, checking which pieces of information can be found in each of the different sources for execution plans and reviewing which method is best suited in which circumstances.<\/p>\n<p>Once we\u2019ve done that we\u2019ll move on to the basics of interpreting simple execution plans, and introduce the one rule that can be used to interpret most execution plans \u2013 provided the rule is used with a little care. At the same time we\u2019ll highlight the point that the plan we get may be very different from the plan we expect thanks to the optimizer\u2019s ability to transform our SQL.<\/p>\n<p>Inevitably there will be exceptions to the rule, so after a couple of articles on the simpler plans we\u2019ll move on to more complex plans, including updates, deletes, subquery factoring, and scalar subqueries in the select list. From there we can move on to distributed queries, the impact of partitioned objects on plans, and finally have a few words about parallel execution.<\/p>\n<p>We won\u2019t be able to cover all the operations and options that might appear in execution plans \u2013 but we should be able to cover enough material to handle most of the plans you\u2019re likely to see on a typical Oracle system.<\/p>\n<h2>What is an execution plan?<\/h2>\n<p>When we write an SQL statement we may have some idea of what we think Oracle will do to generate the result. An execution plan is the mechanism Oracle uses to show how our SQL can be turned into a series of execution steps to produce the required result. Whether the plan is a prediction made before the query is executed, or is demonstrably the actual plan used at run-time, the approach we take to interpret the plan is the same. Depending when and how we acquired the plan, though, the level of detail (and even the degree of confidence that we\u2019re looking at the right plan) varies. Consider, for example, this query:<\/p>\n<pre>select\r\n\tt1.v1, t2.v1\r\nfrom\r\n\tt1, t2\r\nwhere\r\n\tt1.n2 =       :b1\r\nand\tt2.id =       t1.id\r\nand\tt2.n2 between :b2 and :b3\r\n;<\/pre>\n<p>It\u2019s a simple two-table join; there\u2019s a single column join condition, and there are two \u201csingle-table\u201d predicates eliminating data. When we examine this query we want to be able to answer the following questions \u2013 which aren\u2019t necessarily independent of each other<\/p>\n<ul>\n<li>Which table will Oracle access first?<\/li>\n<li>How will Oracle access that table, by index or tablescan?<\/li>\n<li>How much data will it find?<\/li>\n<li>Which table will Oracle access next, and how? (The \u201cwhich\u201d is easy in this case)<\/li>\n<li>What join mechanism will Oracle use to join the two tables?<\/li>\n<li>How much data will be generated through the effects of the join predicates?<\/li>\n<li>How much data will be discarded by predicates that have to be applied after the join?<\/li>\n<li>Is there any significant difference between Oracle\u2019s predictions and what actually happens (or happened)?<\/li>\n<li>Can we quickly identify why any such differences appeared, and whether they matter?<\/li>\n<li>Does Oracle\u2019s strategy match our expectations?<\/li>\n<li>Can we see why Oracle didn\u2019t pick the strategy we thought was a good one?<\/li>\n<\/ul>\n<p>In this case we might assume that the Oracle should pick a tiny number of rows from t1, taking advantage of an index on the n2 column to find them; then do a nested loop join into t2 using the primary key index that we have on t2 to find out whether or not there is a row in t2 that matches each row found in t1; finally discarding most of the resulting rows because they fail the range test in the last line of our SQL.<\/p>\n<p>So here are a couple of execution plans for the query, introducing a couple of the issues we have to consider before we even start working on any interpretation. These plans came from an instance of 11.2.0.4.<\/p>\n<h3>Using a \u201cpredictive\u201d method:<\/h3>\n<pre>---------------------------------------------------------------------------------------\r\n| Id\u00a0 | Operation\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | Name\u00a0 | Rows\u00a0 | Bytes | Cost (%CPU)| Time\u00a0\u00a0\u00a0\u00a0 |\r\n---------------------------------------------------------------------------------------\r\n|\u00a0\u00a0 0 | SELECT STATEMENT\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0 32 |\u00a0\u00a0 832 |\u00a0\u00a0\u00a0 46\u00a0\u00a0 (3)| 00:00:01 |\r\n|*\u00a0 1 |\u00a0 FILTER\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\r\n|*\u00a0 2 |\u00a0\u00a0 HASH JOIN\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0 32 |\u00a0\u00a0 832 |\u00a0\u00a0\u00a0 46\u00a0\u00a0 (3)| 00:00:01 |\r\n|*\u00a0 3 |\u00a0\u00a0\u00a0 TABLE ACCESS FULL\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | T1\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0 32 |\u00a0\u00a0 416 |\u00a0\u00a0\u00a0 24\u00a0\u00a0 (5)| 00:00:01 |\r\n|\u00a0\u00a0 4 |\u00a0\u00a0\u00a0 TABLE ACCESS BY INDEX ROWID| T2\u00a0\u00a0\u00a0 |\u00a0\u00a0 500 |\u00a0 6500 |\u00a0\u00a0\u00a0 22\u00a0\u00a0 (0)| 00:00:01 |\r\n|*\u00a0 5 |\u00a0\u00a0\u00a0\u00a0 INDEX RANGE SCAN\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | T2_N2 |\u00a0\u00a0\u00a0 45 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0 2\u00a0\u00a0 (0)| 00:00:01 |\r\n---------------------------------------------------------------------------------------\r\nPredicate Information (identified by operation id):\r\n---------------------------------------------------\r\n1 - filter(TO_NUMBER(:B2)&lt;=TO_NUMBER(:B3))\r\n2 - access(\"T2\".\"ID\"=\"T1\".\"ID\")\r\n3 - filter(\"T1\".\"N2\"=TO_NUMBER(:B1))\r\n5 - access(\"T2\".\"N2\"&gt;=TO_NUMBER(:B2) AND \"T2\".\"N2\"&lt;=TO_NUMBER(:B3))<\/pre>\n<h3>Using a \u201creactive\u201d method<\/h3>\n<pre>----------------------------------------------------------------------------------------\r\n| Id\u00a0 | Operation\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | Name\u00a0 | Rows\u00a0 | Bytes | Cost (%CPU)| Time\u00a0\u00a0\u00a0\u00a0 |\r\n----------------------------------------------------------------------------------------\r\n|\u00a0\u00a0 0 | SELECT STATEMENT\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0 33 (100)|\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0|\r\n|*\u00a0 1 |\u00a0 FILTER\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\r\n|\u00a0\u00a0 2 |\u00a0\u00a0 NESTED LOOPS\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0 17 |\u00a0\u00a0 442 |\u00a0\u00a0\u00a0 33\u00a0\u00a0 (0)| 00:00:01 |\r\n|\u00a0\u00a0 3 |\u00a0\u00a0\u00a0 NESTED LOOPS\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0 17 |\u00a0\u00a0 442 |\u00a0\u00a0\u00a0 33\u00a0\u00a0 (0)| 00:00:01 |\r\n|\u00a0\u00a0 4 |\u00a0\u00a0\u00a0\u00a0 TABLE ACCESS BY INDEX ROWID| T1\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0 17 |\u00a0\u00a0 221 |\u00a0\u00a0\u00a0 16\u00a0\u00a0 (0)| 00:00:01 |\r\n|*\u00a0 5 |\u00a0\u00a0\u00a0\u00a0\u00a0 INDEX RANGE SCAN\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | T1_N2 |\u00a0\u00a0\u00a0 17 |\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0 1\u00a0\u00a0 (0)| 00:00:01 |\r\n|*\u00a0 6 |\u00a0\u00a0\u00a0\u00a0 INDEX UNIQUE SCAN\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 | T2_PK |\u00a0\u00a0\u00a0\u00a0 1 |\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0|\u00a0\u00a0\u00a0\u00a0 0\u00a0\u00a0 (0)|\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\r\n|*\u00a0 7 |\u00a0\u00a0\u00a0 TABLE ACCESS BY INDEX ROWID | T2\u00a0\u00a0\u00a0 |\u00a0\u00a0\u00a0\u00a0 1 |\u00a0\u00a0\u00a0 13 |\u00a0\u00a0\u00a0\u00a0 1\u00a0\u00a0 (0)| 00:00:01 |\r\n----------------------------------------------------------------------------------------\r\nPredicate Information (identified by operation id):\r\n---------------------------------------------------\r\n1 - filter(:B3&gt;=:B2)\r\n5 - access(\"T1\".\"N2\"=:B1)\r\n6 - access(\"T2\".\"ID\"=\"T1\".\"ID\")\r\n7 - filter((\"T2\".\"N2\"&gt;=:B2 AND \"T2\".\"N2\"&lt;=:B3))<\/pre>\n<p>The plans are obviously different \u2013 even though I produced them from the same session one right after the other. The main differences show up in two places; first in the \u201cbody\u201d of the plan, where one plan has six lines and the other has eight; then in the Predicate Information, where one plan shows explicit coercion (e.g. to_number(:B3)) of all our bind variables and the other shows no coercion at all. If we\u2019re going to depend on execution plans to help us produce efficient systems we need to know why such contradictions can appear, and learn how to identify how far we can trust the execution plans that Oracle gives us. So let\u2019s look at some of the commoner options for acquiring execution plans, and the limitations of each option.<\/p>\n<h2>Acquiring Execution plans<\/h2>\n<p>Although there are various graphical tools that will produce execution plans at the touch of a button I\u2019m going to stick with methods for using command-line SQL to produce plans, mainly taking advantage of Oracle\u2019s dbms_xplan package. Whatever tools you use, the information you can get is fairly similar \u2013 but if you want to share plans with members of the various fora, list servers, and groups on the Internet you get the best public response if you stick with a well-known format, and dbms_xplan is the de facto standard for Oracle.<\/p>\n<h3>Explain plan<\/h3>\n<p>From an SQL*Plus session:<\/p>\n<pre>explain plan for {your select statement}\r\nselect * from table(dbms_xplan.display);<\/pre>\n<p>This is the simplest bare-bones method for getting a \u201cprediction\u201d of the plan that Oracle would use if you were to execute your statement; and it\u2019s how I got the first of my two plans above. There are several problems with this strategy \u2013 two of them showed up, with different levels of visibility, in my plan.<\/p>\n<p>First, if your query contains bind variables (as mine did) the \u201cexplain plan\u201d feature has no idea of the data types of those bind variables, it assumes they are of type character \u2013 that\u2019s why I got all those to_number() coercions in the Predicate Information \u2013 and the resulting coercions can make a huge difference to the shape of the plan since they may make it impossible for the optimizer to consider some of the indexes that would otherwise be available.<\/p>\n<p>Secondly, Oracle has used \u201cbind-variable peeking\u201d for many years to check for actual values of incoming bind variables when it first optimizes a statement. But \u201cexplain plan\u201d doesn\u2019t even try; it has no idea of actual values, so it uses a few basic rules to estimate the selectivity of predicates involving bind variables. Some of the rules are sensible some are just guesses \u2013 typically using 1% or 5% as the selectivity. Poor estimates of selectivity lead to poor estimates of cardinality (rows) and bad plans.<\/p>\n<p style=\"padding-left: 30px;\"><i>Side note: You might notice in the first plan an example of a strange contradiction that guesses can product: in lines 4 and 5. The index range scan predicts 45 rowids will be found, but the table access predicts 500 rows will be return \u2013 clearly not possible if you\u2019re only going to have 45 rowids to find those rows. There is a guess relating to range-based predicates on indexes that uses a minimum selectivity of 0.45%, while the equivalent but contradictory guess for tables is 5%.<\/i><\/p>\n<p>There are a few more details about \u201cexplain plan\u201d that you might use occasionally. The call has a few extra options; the manuals show it as:<\/p>\n<pre>explain plan\r\nset statement_id = \u2018{string}\u2019\r\ninto <a href=\"mailto:%7Bschema%7D.%7Btable%7D@%7Bdb_link%7D\">{schema}.{table}@{db_link}<\/a>\r\nfor {statement};<\/pre>\n<p>By default the statement_id is null, and the target table is called plan_table (which, in modern versions of Oracle is a public synonym for the global temporary table sys.plan_table$). In line with the table and statement_id, the call to dbms_xplan has a couple of parameters that make it possible for you to explain several statements and then report only the ones you want, the function declaration is:<\/p>\n<pre>dbms_xplan.display({plan_table},{statement_id},{format options},{filter option})<\/pre>\n<p>By leaving the first two parameters null (or explicitly supplying NULL) you will be selecting the most recent statement you have explained into PLAN_TABLE. As far as format options are concerned, there are many more pieces of information written to the plan table which you can choose to display \u2013 and we\u2019ll be looking at some of those later on in the series. The filter option allows you to limit the rows you return from the plan_table \u2013 but I\u2019ve never needed to use it on a production system.<\/p>\n<h3>Autotrace<\/h3>\n<p>There is a special variant of \u201cexplain plan\u201d built into SQL*Plus; this is the autotrace option which you can enable with the \u201cset\u201d command:<\/p>\n<pre>set autotrace on\r\nset autotrace traceonly\r\nset autotrace traceonly explain\r\nset autotrace traceonly statistics\r\nset autotrace off<\/pre>\n<p>When autotrace is enabled SQL*Plus can report the execution plan and execution stats of any statement you execute. You can limit the SQL*Plus output to just the plan, just the stats, both plan and stats, and you can choose to suppress the normal output from the statement (with the traceonly option). For example, had I set autotrace to \u201ctraceonly statistics\u201d and then run my original statement I would have seen only the following output:<\/p>\n<pre>1 row selected.\r\n\r\nStatistics\r\n----------------------------------------------------------\r\n   1\u00a0 recursive calls\r\n   0\u00a0 db block gets\r\n   36\u00a0 consistent gets\r\n   0\u00a0 physical reads\r\n   0\u00a0 redo size\r\n   471\u00a0 bytes sent via SQL*Net to client \r\n   415\u00a0 bytes received via SQL*Net from client\r\n   2\u00a0 SQL*Net roundtrips to\/from client\r\n   0\u00a0 sorts (memory)\r\n   0\u00a0 sorts (disk)\r\n   1\u00a0 rows processed<\/pre>\n<p>This can be convenient if you want to get an idea of how much work a query will have to do without actually displaying the result set, or saving it to disk, on the client machine \u2013 but it\u2019s not something I\u2019ve needed to do often.<\/p>\n<p>There is a little trap with autotrace \u2013 it\u2019s not telling you the actual execution plan, it\u2019s simply doing an \u201cexplain plan\u201d and calling \u201cdbms_xplan.display\u201d in the background; moreover, if you \u201cset autotrace traceonly explain\u201d and issue a <b><i>select<\/i><\/b> statement then, since you\u2019ve said you don\u2019t want to see the actual query output, SQL*Plus doesn\u2019t even run the statement. However, after you\u2019ve been checking plans for many select statements, it\u2019s easy to forget that if you execute an insert, update, delete or merge statement the statement WILL run \u2013 fortunately reporting the number of rows inserted, updated, deleted or merge so that you get a reminder to rollback.<\/p>\n<h3>dbms_xplan.display_cursor()<\/h3>\n<p>The only other option I want to mention in this article is the function that allows you to pull actual execution plans from memory after a query has run. Its definition is:<\/p>\n<pre>dbms_xplan.display_cursor({sql_id},{child_number},{format options})<\/pre>\n<p>In its simplest form (running from SQL*Plus with no parameters) it returns the execution plan for the last SQL statement you\u2019ve executed. For various reasons you may find that when you do this you see a report about being unable to fetch the plan \u2013 this can indicate an odd timing effect with cursor invalidation, but the commonest reason is that you haven\u2019t \u201cset serveroutput off\u201d, and the missing plan is for the call to dbms_output that SQL*Plus has injected after the statement you\u2019ve just run, in which case you will see the following:<\/p>\n<pre>SQL&gt; select * from table(dbms_xplan.display_cursor);\r\n\r\nPLAN_TABLE_OUTPUT\r\n-------------------------------------------------------------------------------------\r\nSQL_ID\u00a0 b3s1x9zqrvzvc, child number 0\r\n\r\nBEGIN DBMS_OUTPUT.ENABLE(1000000); END;\r\n\r\nNOTE: cannot fetch plan for SQL_ID: b3s1x9zqrvzvc, CHILD_NUMBER: 0\r\n      Please verify value of SQL_ID and CHILD_NUMBER;\r\n      It could also be that the plan is no longer in cursor cache (check v$sql_plan)\r\n\r\n8 rows selected.<\/pre>\n<p>I used a call to dbms_xplan.display_cursor() to get the second plan of the two plans above. It\u2019s what Oracle actually did for the specific values for the bind variables I used, with the current object statistics and optimizer environment for my session. It is still, however, reporting the optimizer estimates of how many rows each step of the plan will return \u2013 not the actual numbers of rows found at runtime \u2013 we\u2019ll come to that topic in the next article.<\/p>\n<p>There\u2019s plenty more to say about dbms_xplan.display_cursor() and the ways in which you can use it; but I\u2019ll stop at this point with just one warning. Although it shows you the actual execution plan generated (usually) with the actual bind values you supplied when you ran the query, this doesn\u2019t guarantee that the plan you see is the plan that last appeared on the production system, or the plan that will appear in the future on the production system.<\/p>\n<p>There are many reasons why you can be fooled by execution plans if you\u2019re not looking at exactly what the end-user did at the moment they did it, on the live system; the reasons include things like:<\/p>\n<ul>\n<li>Timing and choice of actual bind values<\/li>\n<li>Optimizer environment and object statistics<\/li>\n<li>Name resolution<\/li>\n<\/ul>\n<p>A call to <b><i>dbms_xplan.display_cursor()<\/i><\/b> is generally less likely to mislead than a call to <b><i>explain plan<\/i><\/b> followed by a call to <b><i>dbms_xplan.display()<\/i><\/b> but it still requires some intelligent thought if you want to be sure that you aren\u2019t looking at the wrong execution plan.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this article we\u2019ve seen how easy it is to get reports of execution plans, but noted that plans come in two \u201cflavours\u201d \u2013 predictive, and actual. We\u2019ve also noted that the presence of bind variables in queries means that the \u201cpredictive\u201d method is likely to give you a misleading idea of what the final execution plan will be.<\/p>\n<p>There\u2019s a general, and reasonably accurate, belief that running the query and checking the actual execution plan afterwards is more likely to give you the plan that would appear at run-time on the production system; however this is still dependent on your working environment being <b>sufficiently similar<\/b> to the end-users\u2019 run-time environment.<\/p>\n<p>We noted that the \u201cvolume\u201d information (rows, bytes) from the execution plan is still predictive even when we report the actual execution plan \u2013 but in the next session we\u2019ll see how we can get the actual volume figures as well, and how this helps us to recognize why the optimizer may have chosen a plan that doesn\u2019t match our expectation.<\/p>\n<p>&nbsp;<\/p>\n<p>All finished? Head on over to <a title=\"Execution Plans Part 2: Things to see\" href=\"https:\/\/allthingsoracle.com\/execution-plans-part-2-things-to-see\/\">Part 2: Things to see<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you have to tackle performance problems in an Oracle database one of the most important skills you need to acquire is the ability to interpret execution plans, and in this series of articles I\u2019ll be telling you how to do just that. We\u2019ll start with a couple of articles that look at some of the ways we can access&hellip;<\/p>\n","protected":false},"author":101205,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143533],"tags":[4783],"coauthors":[],"class_list":["post-73210","post","type-post","status-publish","format-standard","hentry","category-oracle-databases","tag-execution-plans"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/73210","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/101205"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=73210"}],"version-history":[{"count":1,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/73210\/revisions"}],"predecessor-version":[{"id":91695,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/73210\/revisions\/91695"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=73210"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=73210"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=73210"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=73210"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}