├── .gitignore ├── code ├── queries.sql └── schema.ddl └── slides ├── css └── sql-performance-explained-impress.css ├── img ├── Logo CartoDB Positive large 2.png ├── Logo_square_postgis.png ├── democracy-overrated.gif ├── f1.1-index-leaf-nodes.png ├── f1.2-b-tree-structure.png ├── f1.3-b-tree-traversal.png ├── f2.2-index-range.png ├── f2.3-index-range.png ├── f3.2-scalability-by-data-volume.png ├── f5.1-index-based-heap-table.png ├── f5.2-secondary-index-iot.png ├── f6.5-database-order-features.png ├── f7.2-offset-method.png ├── f7.3-seek-method.png ├── f7.4-paging-performance.png ├── idiomcoach.png ├── nacho.png ├── poll.png ├── postgresql-banner.png ├── postgresql-logo.png ├── sql-performance-explained-cover.png └── use-the-index-luke.png ├── index.html └── js └── impress.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /code/queries.sql: -------------------------------------------------------------------------------- 1 | explain analyze SELECT first_name, last_name 2 | FROM employees WHERE employee_id = 123; 3 | 4 | explain analyze SELECT first_name, last_name 5 | FROM employees WHERE employee_id = 111 and subsidiary_id = 333; 6 | 7 | explain analyze SELECT first_name, last_name 8 | FROM employees WHERE subsidiary_id = 333; 9 | 10 | explain analyze SELECT first_name, last_name 11 | FROM employees WHERE upper(last_name) = 'LN 38' 12 | -------------------------------------------------------------------------------- /code/schema.ddl: -------------------------------------------------------------------------------- 1 | \c postgres 2 | 3 | drop database sqlperf; 4 | create database sqlperf; 5 | 6 | \c sqlperf; 7 | 8 | drop table if exists employees; 9 | CREATE TABLE employees ( 10 | employee_id integer not null PRIMARY key, 11 | subsidiary_id integer not null, 12 | first_name text, 13 | last_name text, 14 | date_of_birth DATE NOT NULL, 15 | phone_number character varying(1000) NOT NULL, 16 | enabled boolean default true 17 | ); 18 | 19 | create unique index on employees (subsidiary_id, employee_id); 20 | 21 | --create index employees_last_name on employees(last_name); 22 | 23 | insert into employees (employee_id, subsidiary_id, first_name, last_name, date_of_birth, phone_number) 24 | select i, i % 100, 'FN ' || i, 'LN ' || i, date '01/01/1950' + i * interval '4 HOUR', 'PN' || i from generate_series(1, 100000) a(i); 25 | 26 | update employees set enabled = false where employee_id % 100 = 0; 27 | 28 | -- create index employees_upper_last_name on employees(upper(last_name)); 29 | 30 | -- create index employees_date on employees(date_of_birth); 31 | 32 | -- create index employees_date on employees(date_of_birth, subsidiary_id); 33 | 34 | -- create index employees_date on employees(subsidiary_id, date_of_birth); 35 | 36 | CREATE TABLE sales ( 37 | employee_id integer not null, 38 | subsidiary_id integer not null, 39 | sale_id bigint not null, 40 | amount integer, 41 | sale_date DATE NOT NULL 42 | ); 43 | 44 | alter table sales add primary key (employee_id, subsidiary_id, sale_id); 45 | 46 | alter table sales add constraint employee_sales foreign key (employee_id, subsidiary_id) references employees (employee_id, subsidiary_id); 47 | 48 | insert into sales (employee_id, subsidiary_id, sale_id, amount, sale_date) 49 | select (i / 10) + 1, ((i / 10) + 1) % 100, i, i + i, date '01/06/2016' - i * interval '10 minutes' from generate_series(1, 900000) a(i); 50 | 51 | -- create index employees_date on employees(date_of_birth, subsidiary_id); 52 | 53 | -- create index sales_date on sales(sale_date); 54 | -------------------------------------------------------------------------------- /slides/css/sql-performance-explained-impress.css: -------------------------------------------------------------------------------- 1 | /* Obviously inspired in impress-demo.css */ 2 | 3 | html, body, div, span, applet, object, iframe, 4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 5 | a, abbr, acronym, address, big, cite, code, 6 | del, dfn, em, img, ins, kbd, q, s, samp, 7 | small, strike, strong, sub, sup, tt, var, 8 | b, u, i, center, 9 | dl, dt, dd, ol, ul, li, 10 | fieldset, form, label, legend, 11 | table, caption, tbody, tfoot, thead, tr, th, td, 12 | article, aside, canvas, details, embed, 13 | figure, figcaption, footer, header, hgroup, 14 | menu, nav, output, ruby, section, summary, 15 | time, mark, audio, video { 16 | margin: 0; 17 | padding: 0; 18 | border: 0; 19 | font-size: 100%; 20 | font: inherit; 21 | vertical-align: baseline; 22 | } 23 | 24 | /* HTML5 display-role reset for older browsers */ 25 | article, aside, details, figcaption, figure, 26 | footer, header, hgroup, menu, nav, section { 27 | display: block; 28 | } 29 | body { 30 | line-height: 1.5; 31 | } 32 | ol, ul { 33 | padding: inherit; 34 | line-height: 2; 35 | margin: inherit; 36 | } 37 | ul ul, ul ol, ol ol { 38 | margin-left: 40px; 39 | } 40 | blockquote, q { 41 | quotes: none; 42 | } 43 | blockquote:before, blockquote:after, 44 | q:before, q:after { 45 | content: ''; 46 | content: none; 47 | } 48 | 49 | blockquote p::before, blockquote p::after { 50 | content: '"'; 51 | } 52 | 53 | table { 54 | border-collapse: collapse; 55 | border-spacing: 0; 56 | } 57 | 58 | code { 59 | font-size: 80%; 60 | } 61 | 62 | .compressed code { 63 | font-size: 60%; 64 | } 65 | 66 | pre.code, code { 67 | text-align: left; 68 | } 69 | 70 | pre code.sql.hljs { 71 | padding-top: 0; 72 | padding-bottom: 0; 73 | line-height: 1.3; 74 | } 75 | 76 | pre + pre { 77 | margin-top: 10px; 78 | } 79 | 80 | .explanation { 81 | position: absolute; 82 | margin: 10%; 83 | padding: 2% 3%; 84 | top: 0; 85 | bottom: 0; 86 | left: 0; 87 | right: 0; 88 | background: rgba(255, 255, 0, 0.9); 89 | font-size: 140%; 90 | } 91 | 92 | ul.conclusion, .explanation { 93 | border-radius: 20px; 94 | } 95 | 96 | ul.alternatives li { 97 | list-style-type: lower-alpha;; 98 | } 99 | 100 | .source { 101 | display: inline-block; 102 | position: absolute; 103 | bottom: 20px; 104 | right: 20px; 105 | font-size: 60%; 106 | } 107 | 108 | .source::before { 109 | content: "["; 110 | } 111 | 112 | .source::after { 113 | content: "]"; 114 | } 115 | 116 | code .hljs-keyword { 117 | font-weight: normal; 118 | } 119 | 120 | code strong, code .hljs-keyword { 121 | color: #111; 122 | } 123 | 124 | body { 125 | font-family: 'Overlock', Helvetica, Arial, sans-serif; 126 | } 127 | 128 | .explanation { 129 | font-family: "Open Sans Condensed", sans-serif; 130 | } 131 | 132 | h1, h2, h3 { 133 | font-family: 'Overlock SC', Helvetica, Arial, sans-serif; 134 | } 135 | 136 | pre.code, code { 137 | font-family: 'Fira Mono', monospace; 138 | } 139 | 140 | body { 141 | color: black; 142 | min-height: 740px; 143 | 144 | /* 145 | background: rgb(215, 215, 215); 146 | background: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 500, from(rgb(240, 240, 240)), to(rgb(190, 190, 190))); 147 | background: -webkit-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 148 | background: -moz-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 149 | background: -ms-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 150 | background: -o-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 151 | background: radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 152 | */ 153 | background-color: white; 154 | } 155 | 156 | b, strong, dt { font-weight: bold } 157 | i, em { font-style: italic } 158 | 159 | dd::before { 160 | content: '-> ' 161 | } 162 | 163 | a { 164 | color: inherit; 165 | text-decoration: none; 166 | 167 | text-decoration: underline; 168 | 169 | -webkit-transition: 0.5s; 170 | -moz-transition: 0.5s; 171 | -ms-transition: 0.5s; 172 | -o-transition: 0.5s; 173 | transition: 0.5s; 174 | } 175 | 176 | a:hover, 177 | a:focus { 178 | background: rgba(255,255,255,.2); 179 | } 180 | 181 | h1 { 182 | white-space: nowrap; 183 | } 184 | 185 | p { 186 | text-align: left; 187 | } 188 | 189 | .step p { 190 | margin-top: 10px; 191 | margin-bottom: 40px; 192 | } 193 | 194 | .fallback-message { 195 | line-height: 1.3; 196 | 197 | width: 780px; 198 | padding: 10px 10px 0; 199 | margin: 20px auto; 200 | 201 | border: 1px solid #E4C652; 202 | border-radius: 10px; 203 | background: #EEDC94; 204 | } 205 | 206 | .fallback-message p { 207 | margin-bottom: 10px; 208 | } 209 | 210 | .impress-supported .fallback-message { 211 | display: none; 212 | } 213 | 214 | .step { 215 | position: relative; 216 | padding: 40px; 217 | margin: 20px auto; 218 | width: 1000px; 219 | height: 750px; 220 | 221 | -webkit-box-sizing: border-box; 222 | -moz-box-sizing: border-box; 223 | -ms-box-sizing: border-box; 224 | -o-box-sizing: border-box; 225 | box-sizing: border-box; 226 | 227 | font-size: 30px; 228 | line-height: 1.5; 229 | 230 | text-align: center; 231 | } 232 | 233 | .step ul, .step li, .step dl, .step dt, .step dd { 234 | text-align: left; 235 | } 236 | 237 | .step img { 238 | max-width: 100%; 239 | max-height: 600px; 240 | } 241 | 242 | .step.section h1 { 243 | font-size: 300%; 244 | } 245 | 246 | .step.description h1, .step.question h1, .step.answer h1 { 247 | padding-top: 0; 248 | padding-right: 40px; 249 | padding-left: 40px; 250 | 251 | margin-bottom: 10px; 252 | 253 | text-align: left; 254 | color: #444; 255 | } 256 | 257 | .step.section h1, .step.section h2, #intro h1, #intro h2 { 258 | text-shadow: 0px 13px 17px rgba(150, 150, 150, 0.8); 259 | } 260 | 261 | .step.description h1::before, .step.question h1::before, .step.answer h1::before { 262 | content: '· · '; 263 | } 264 | 265 | .step.description h1::after, .step.question h1::after, .step.answer h1::after { 266 | content: ' · · · · · · · · · · · · · · · · · · · · · · ·'; 267 | } 268 | 269 | .step.description.notes:not(.active), 270 | .step.question.notes:not(.active), 271 | .step.answer.notes:not(.active) { 272 | opacity: 0; 273 | } 274 | 275 | .wtf h1, .wtfit { 276 | color: red; 277 | } 278 | 279 | ul.conclusion { 280 | background: #111; 281 | color: white; 282 | padding-top: 0; 283 | padding-bottom: 0; 284 | } 285 | 286 | ul.conclusion li.good, ul.conclusion li.neutral, ul.conclusion li.bad { 287 | list-style: none; 288 | } 289 | 290 | ul.conclusion li.good::before { 291 | content: '👍 '; 292 | } 293 | 294 | ul.conclusion li.neutral::before { 295 | content: '≃ '; 296 | } 297 | 298 | ul.conclusion li.bad::before { 299 | content: '👎 '; 300 | } 301 | 302 | ul.conclusion li.bad .wtf { 303 | color: red; 304 | } 305 | 306 | #summary ol { 307 | margin-left: 100px; 308 | } 309 | 310 | .impress-enabled .step.answer { 311 | opacity: 0; 312 | } 313 | 314 | .impress-enabled .step { 315 | margin: 0; 316 | opacity: 0.1; 317 | 318 | -webkit-transition: opacity 1s; 319 | -moz-transition: opacity 1s; 320 | -ms-transition: opacity 1s; 321 | -o-transition: opacity 1s; 322 | transition: opacity 1s; 323 | } 324 | 325 | .impress-enabled .step.active { opacity: 1 } 326 | 327 | .impress-enabled .step.substep.active { opacity: 1 } 328 | 329 | .impress-enabled .step.substep:not(.active){ opacity: 0 } 330 | 331 | .slide { 332 | display: block; 333 | 334 | padding: 40px 60px; 335 | 336 | background-color: white; 337 | border: 1px solid rgba(0, 0, 0, .3); 338 | border-radius: 10px; 339 | box-shadow: 0 2px 6px rgba(0, 0, 0, .1); 340 | 341 | font-size: 40px; 342 | line-height: 36px; 343 | letter-spacing: -1px; 344 | } 345 | 346 | .slide q { 347 | display: block; 348 | font-size: 50px; 349 | line-height: 72px; 350 | 351 | margin-top: 100px; 352 | } 353 | 354 | .slide q strong { 355 | white-space: nowrap; 356 | } 357 | 358 | .footnote { 359 | position: absolute; 360 | bottom: 40px; 361 | font-size: 70%; 362 | font-style: italic; 363 | color: black; 364 | } 365 | 366 | .figure-caption::before { 367 | content: 'Figure: ' 368 | } 369 | 370 | .figure-caption { 371 | text-align: left; 372 | font-size: 90%; 373 | font-style: italic; 374 | } 375 | 376 | .full-image-background { 377 | padding: 0; 378 | background-size: auto 100%; 379 | background-repeat: no-repeat; 380 | } 381 | 382 | .full-image-background .content-wrapper { 383 | width: 100%; 384 | height: 100%; 385 | } 386 | 387 | .dark-image-background .content-wrapper { 388 | background: rgba(0, 0, 0, .5); 389 | } 390 | 391 | .dark-image-background .content-wrapper, .dark-image-background .content-wrapper .footnote { 392 | color: white; 393 | } 394 | 395 | .light-image-background .content-wrapper { 396 | background: rgba(255, 255, 255, .2); 397 | } 398 | 399 | .full-image-background .content { 400 | padding: 40px 60px; 401 | } 402 | 403 | 404 | .splash h1, .end h1 { 405 | font-size: 400%; 406 | text-align: center; 407 | -webkit-transform: rotate(-10deg); 408 | -moz-transform: rotate(-10deg); 409 | -ms-transform: rotate(-10deg); 410 | -o-transform: rotate(-10deg); 411 | transform: rotate(-10deg); 412 | } 413 | 414 | .highlight { 415 | display: inline-block; 416 | -webkit-transition: 0.1s; 417 | -moz-transition: 0.1s; 418 | -ms-transition: 0.1s; 419 | -o-transition: 0.1s; 420 | transition: 0.1s; 421 | 422 | -webkit-transition-delay: 2s; 423 | -moz-transition-delay: 2s; 424 | -ms-transition-delay: 2s; 425 | -o-transition-delay: 2s; 426 | transition-delay: 2s; 427 | 428 | text-shadow: 0px 0px 17px rgba(234, 150, 150, 1); 429 | } 430 | 431 | .present .highlight { 432 | -webkit-transform: rotate(-10deg); 433 | -moz-transform: rotate(-10deg); 434 | -ms-transform: rotate(-10deg); 435 | -o-transform: rotate(-10deg); 436 | transform: rotate(-10deg); 437 | } 438 | 439 | #intro { 440 | background-size: 100% 100%; 441 | background-color: white; 442 | padding: 40px; 443 | } 444 | 445 | #intro .metadata { 446 | background-color: rgba(255, 255, 255, 0.5); 447 | background-size: 30%; 448 | height: 100%; 449 | line-height: 2; 450 | border-radius: 0.2em; 451 | } 452 | 453 | #intro h1, #intro h2 { 454 | text-align: center; 455 | } 456 | 457 | #intro h1 { 458 | padding: 80px 40px 20px 40px; 459 | font-size: 130%; 460 | } 461 | 462 | #intro h2 { 463 | margin-bottom: 280px; 464 | } 465 | 466 | #intro .author { 467 | line-height: 1.5; 468 | text-align: right; 469 | font-size: 50%; 470 | margin-right: 40px; 471 | background: url('../img/idiomcoach.png') no-repeat 40px 0; 472 | background-size: 300px; 473 | } 474 | 475 | #intro .author a { 476 | display: block; 477 | } 478 | 479 | #author.juanignaciosl { 480 | background: url('../img/nacho.png') no-repeat 100% 40px; 481 | background-size: 350px; 482 | padding-right: 350px; 483 | } 484 | 485 | #author h1 { 486 | font-size: 80%; 487 | } 488 | 489 | #author .metadata { 490 | font-size: 60%; 491 | margin-bottom: 80px; 492 | } 493 | 494 | #author dl { 495 | font-size: 80%; 496 | } 497 | 498 | #author dl dt { 499 | font-weight: bold; 500 | float: left; 501 | clear: left; 502 | margin-right: 20px; 503 | margin-bottom: 10px; 504 | } 505 | 506 | #author dl dd { 507 | float: left; 508 | } 509 | 510 | .not-important { 511 | font-size: 70%; 512 | color: #666666; 513 | } 514 | 515 | .book-cover { 516 | max-height: 100%; 517 | } 518 | 519 | #overview:not(.active) { display: none } 520 | 521 | .impress-on-overview .step { 522 | opacity: 1; 523 | cursor: pointer; 524 | } 525 | 526 | 527 | /* 528 | Now, when we have all the steps styled let's give users a hint how to navigate 529 | around the presentation. 530 | 531 | The best way to do this would be to use JavaScript, show a delayed hint for a 532 | first time users, then hide it and store a status in cookie or localStorage... 533 | 534 | But I wanted to have some CSS fun and avoid additional scripting... 535 | 536 | Let me explain it first, so maybe the transition magic will be more readable 537 | when you read the code. 538 | 539 | First of all I wanted the hint to appear only when user is idle for a while. 540 | You can't detect the 'idle' state in CSS, but I delayed a appearing of the 541 | hint by 5s using transition-delay. 542 | 543 | You also can't detect in CSS if the user is a first-time visitor, so I had to 544 | make an assumption that I'll only show the hint on the first step. And when 545 | the step is changed hide the hint, because I can assume that user already 546 | knows how to navigate. 547 | 548 | To summarize it - hint is shown when the user is on the first step for longer 549 | than 5 seconds. 550 | 551 | The other problem I had was caused by the fact that I wanted the hint to fade 552 | in and out. It can be easily achieved by transitioning the opacity property. 553 | But that also meant that the hint was always on the screen, even if totally 554 | transparent. It covered part of the screen and you couldn't correctly clicked 555 | through it. 556 | Unfortunately you cannot transition between display `block` and `none` in pure 557 | CSS, so I needed a way to not only fade out the hint but also move it out of 558 | the screen. 559 | 560 | I solved this problem by positioning the hint below the bottom of the screen 561 | with CSS transform and moving it up to show it. But I also didn't want this move 562 | to be visible. I wanted the hint only to fade in and out visually, so I delayed 563 | the fade in transition, so it starts when the hint is already in its correct 564 | position on the screen. 565 | 566 | I know, it sounds complicated ... maybe it would be easier with the code? 567 | */ 568 | 569 | .hint { 570 | /* 571 | We hide the hint until presentation is started and from browsers not supporting 572 | impress.js, as they will have a linear scrollable view ... 573 | */ 574 | display: none; 575 | 576 | /* 577 | ... and give it some fixed position and nice styles. 578 | */ 579 | position: fixed; 580 | left: 0; 581 | right: 0; 582 | bottom: 200px; 583 | 584 | background: rgba(0,0,0,0.5); 585 | color: #EEE; 586 | text-align: center; 587 | 588 | font-size: 50px; 589 | padding: 20px; 590 | 591 | z-index: 100; 592 | 593 | /* 594 | By default we don't want the hint to be visible, so we make it transparent ... 595 | */ 596 | opacity: 0; 597 | 598 | /* 599 | ... and position it below the bottom of the screen (relative to it's fixed position) 600 | */ 601 | -webkit-transform: translateY(400px); 602 | -moz-transform: translateY(400px); 603 | -ms-transform: translateY(400px); 604 | -o-transform: translateY(400px); 605 | transform: translateY(400px); 606 | 607 | /* 608 | Now let's imagine that the hint is visible and we want to fade it out and move out 609 | of the screen. 610 | 611 | So we define the transition on the opacity property with 1s duration and another 612 | transition on transform property delayed by 1s so it will happen after the fade out 613 | on opacity finished. 614 | 615 | This way user will not see the hint moving down. 616 | */ 617 | -webkit-transition: opacity 1s, -webkit-transform 0.5s 1s; 618 | -moz-transition: opacity 1s, -moz-transform 0.5s 1s; 619 | -ms-transition: opacity 1s, -ms-transform 0.5s 1s; 620 | -o-transition: opacity 1s, -o-transform 0.5s 1s; 621 | transition: opacity 1s, transform 0.5s 1s; 622 | } 623 | 624 | /* 625 | Now we 'enable' the hint when presentation is initialized ... 626 | */ 627 | .impress-enabled .hint { display: block } 628 | 629 | /* 630 | ... and we will show it when the first step (with id 'bored') is active. 631 | */ 632 | .impress-on-bored .hint { 633 | /* 634 | We remove the transparency and position the hint in its default fixed 635 | position. 636 | */ 637 | opacity: 1; 638 | 639 | -webkit-transform: translateY(0px); 640 | -moz-transform: translateY(0px); 641 | -ms-transform: translateY(0px); 642 | -o-transform: translateY(0px); 643 | transform: translateY(0px); 644 | 645 | /* 646 | Now for fade in transition we have the oposite situation from the one 647 | above. 648 | 649 | First after 4.5s delay we animate the transform property to move the hint 650 | into its correct position and after that we fade it in with opacity 651 | transition. 652 | */ 653 | -webkit-transition: opacity 1s 5s, -webkit-transform 0.5s 4.5s; 654 | -moz-transition: opacity 1s 5s, -moz-transform 0.5s 4.5s; 655 | -ms-transition: opacity 1s 5s, -ms-transform 0.5s 4.5s; 656 | -o-transition: opacity 1s 5s, -o-transform 0.5s 4.5s; 657 | transition: opacity 1s 5s, transform 0.5s 4.5s; 658 | } 659 | 660 | /* 661 | And as the last thing there is a workaround for quite strange bug. 662 | It happens a lot in Chrome. I don't remember if I've seen it in Firefox. 663 | 664 | Sometimes the element positioned in 3D (especially when it's moved back 665 | along Z axis) is not clickable, because it falls 'behind' the
666 | element. 667 | 668 | To prevent this, I decided to make non clickable by setting 669 | pointer-events property to `none` value. 670 | Value if this property is inherited, so to make everything else clickable 671 | I bring it back on the #impress element. 672 | 673 | If you want to know more about `pointer-events` here are some docs: 674 | https://developer.mozilla.org/en/CSS/pointer-events 675 | 676 | There is one very important thing to notice about this workaround - it makes 677 | everything 'unclickable' except what's in #impress element. 678 | 679 | So use it wisely ... or don't use at all. 680 | */ 681 | .impress-enabled { pointer-events: none } 682 | .impress-enabled #impress { pointer-events: auto } 683 | 684 | /* 685 | There is one funny thing I just realized. 686 | 687 | Thanks to this workaround above everything except #impress element is invisible 688 | for click events. That means that the hint element is also not clickable. 689 | So basically all of this transforms and delayed transitions trickery was probably 690 | not needed at all... 691 | 692 | But it was fun to learn about it, wasn't it? 693 | */ 694 | 695 | /* 696 | That's all I have for you in this file. 697 | Thanks for reading. I hope you enjoyed it at least as much as I enjoyed writing it 698 | for you. 699 | */ 700 | -------------------------------------------------------------------------------- /slides/img/Logo CartoDB Positive large 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/Logo CartoDB Positive large 2.png -------------------------------------------------------------------------------- /slides/img/Logo_square_postgis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/Logo_square_postgis.png -------------------------------------------------------------------------------- /slides/img/democracy-overrated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/democracy-overrated.gif -------------------------------------------------------------------------------- /slides/img/f1.1-index-leaf-nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f1.1-index-leaf-nodes.png -------------------------------------------------------------------------------- /slides/img/f1.2-b-tree-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f1.2-b-tree-structure.png -------------------------------------------------------------------------------- /slides/img/f1.3-b-tree-traversal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f1.3-b-tree-traversal.png -------------------------------------------------------------------------------- /slides/img/f2.2-index-range.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f2.2-index-range.png -------------------------------------------------------------------------------- /slides/img/f2.3-index-range.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f2.3-index-range.png -------------------------------------------------------------------------------- /slides/img/f3.2-scalability-by-data-volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f3.2-scalability-by-data-volume.png -------------------------------------------------------------------------------- /slides/img/f5.1-index-based-heap-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f5.1-index-based-heap-table.png -------------------------------------------------------------------------------- /slides/img/f5.2-secondary-index-iot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f5.2-secondary-index-iot.png -------------------------------------------------------------------------------- /slides/img/f6.5-database-order-features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f6.5-database-order-features.png -------------------------------------------------------------------------------- /slides/img/f7.2-offset-method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f7.2-offset-method.png -------------------------------------------------------------------------------- /slides/img/f7.3-seek-method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f7.3-seek-method.png -------------------------------------------------------------------------------- /slides/img/f7.4-paging-performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/f7.4-paging-performance.png -------------------------------------------------------------------------------- /slides/img/idiomcoach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/idiomcoach.png -------------------------------------------------------------------------------- /slides/img/nacho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/nacho.png -------------------------------------------------------------------------------- /slides/img/poll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/poll.png -------------------------------------------------------------------------------- /slides/img/postgresql-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/postgresql-banner.png -------------------------------------------------------------------------------- /slides/img/postgresql-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/postgresql-logo.png -------------------------------------------------------------------------------- /slides/img/sql-performance-explained-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/sql-performance-explained-cover.png -------------------------------------------------------------------------------- /slides/img/use-the-index-luke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanignaciosl/sql-performance-explained/bf7a957e26bd7e85980c79cfadda8baf32aca43f/slides/img/use-the-index-luke.png -------------------------------------------------------------------------------- /slides/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |Slides: juanignaciosl.github.io/sql-performance-explained/slides
94 |
196 | CREATE TABLE employees (
197 | employee_id integer not null PRIMARY key,
198 | subsidiary_id integer not null,
199 | first_name text,
200 | last_name text,
201 | date_of_birth DATE NOT NULL,
202 | phone_number character varying(1000) NOT NULL,
203 | enabled boolean default true
204 | );
205 |
206 | create unique index on employees (employee_id, subsidiary_id);
207 |
208 | create index employees_last_name on employees(last_name);
209 |
210 |
215 | Table "public.employees"
216 | Column | Type | Modifiers
217 | ---------------+-------------------------+--------------
218 | employee_id | integer | not null
219 | subsidiary_id | integer | not null
220 | first_name | text |
221 | last_name | text |
222 | date_of_birth | date | not null
223 | phone_number | character varying(1000) | not null
224 | enabled | boolean | default true
225 | Indexes:
226 | "employees_pkey" PRIMARY KEY, btree (employee_id)
227 | "enabled_employees" btree (last_name) WHERE enabled = true
228 | "employees_last_name" btree (last_name)
229 |
230 |
235 | explain analyze SELECT first_name, last_name
236 | FROM employees WHERE employee_id = 123;
237 |
238 |
243 | explain analyze SELECT first_name, last_name
244 | FROM employees WHERE employee_id = 123;
245 |
246 |
247 | Index Scan using employees_pkey on employees
248 | (cost=0.29..8.31 rows=1 width=16)
249 | (actual time=0.009..0.010 rows=1 loops=1)
250 | Index Cond: (employee_id = 123)
251 | Planning time: 0.310 ms
252 | Execution time: 0.051 ms
253 |
254 |
259 | explain analyze SELECT first_name, last_name
260 | FROM employees WHERE employee_id = 111
261 | and subsidiary_id = 333;
262 |
263 |
268 | explain analyze SELECT first_name, last_name
269 | FROM employees WHERE employee_id = 111
270 | and subsidiary_id = 333;
271 |
272 |
273 | Index Scan using employees_employee_id_subsidiary_id_idx
274 | on employees (cost=0.29..8.31 rows=1 width=16)
275 | (actual time=0.023..0.025 rows=1 loops=1)
276 | Index Cond: ((employee_id = 111) AND (subsidiary_id = 333))
277 | Planning time: 0.130 ms
278 | Execution time: 0.056 ms
279 |
280 |
285 | explain analyze SELECT first_name, last_name
286 | FROM employees WHERE subsidiary_id = 333;
287 |
288 |
293 | explain analyze SELECT first_name, last_name
294 | FROM employees WHERE subsidiary_id = 333;
295 |
296 |
297 | Index Scan using employees_employee_id_subsidiary_id_idx
298 | on employees (cost=0.29..1858.30 rows=1 width=16)
299 | (actual time=0.034..3.498 rows=1 loops=1)
300 | Index Cond: (subsidiary_id = 333)
301 | Planning time: 0.101 ms
302 | Execution time: 3.527 ms
303 |
304 |
309 | explain analyze SELECT first_name, last_name
310 | FROM employees WHERE subsidiary_id = 333;
311 |
312 |
313 | Index Scan using employees_employee_id_subsidiary_id_idx
314 | on employees (cost=0.29..1858.30 rows=1 width=16)
315 | (actual time=0.034..3.498 rows=1 loops=1)
316 | Index Cond: (subsidiary_id = 333)
317 | Planning time: 0.101 ms
318 | Execution time: 3.527 ms
319 |
320 | 321 |325 |A multicolumn B-tree index can be used with query conditions that involve any subset of the index's columns, 322 | but the index is most efficient when there are constraints on the leading (leftmost) columns.
323 | PostgreSQL manual 324 |
330 | select * from employees where date_of_birth > TO_DATE($1, 'YYYY-MM-DD')
331 | and date_of_birth < TO_DATE($2, 'YYYY-MM-DD') and subsidiary_id = $3;
332 |
333 |
334 | create index employees_date on
335 | employees(date_of_birth, subsidiary_id);
336 |
337 |
338 | create index employees_date on
339 | employees(subsidiary_id, date_of_birth);
340 |
341 |
356 | Bitmap Heap Scan on employees
357 | (cost=14.54..22.28 rows=2 width=592) (actual time=0.069..0.071 rows=4 loops=1)
358 | Recheck Cond: ((date_of_birth > '1082-01-01'::date)
359 | AND (date_of_birth < '1083-01-01'::date) AND (subsidiary_id = 50))
360 | Heap Blocks: exact=3
361 | -> Bitmap Index Scan on employees_date (cost=0.00..14.54 rows=2 width=0)
362 | (actual time=0.064..0.064 rows=4 loops=1)
363 | Index Cond: ((date_of_birth > '1082-01-01'::date)
364 | AND (date_of_birth < '1083-01-01'::date) AND (subsidiary_id = 50))
365 | Planning time: 0.365 ms
366 | Execution time: 0.099 ms
367 |
368 |
369 | Bitmap Heap Scan on employees
370 | (cost=4.34..19.58 rows=4 width=35) (actual time=0.045..0.051 rows=4 loops=1)
371 | Recheck Cond: ((subsidiary_id = 50) AND (date_of_birth > '1082-01-01'::date)
372 | AND (date_of_birth < '1083-01-01'::date))
373 | Heap Blocks: exact=3
374 | -> Bitmap Index Scan on employees_date (cost=0.00..4.34 rows=4 width=0)
375 | (actual time=0.037..0.037 rows=4 loops=1)
376 | Index Cond: ((subsidiary_id = 50) AND (date_of_birth > '1082-01-01'::date)
377 | AND (date_of_birth < '1083-01-01'::date))
378 | Planning time: 0.481 ms
379 | Execution time: 0.084 ms
380 |
381 |
386 | Bitmap Heap Scan on employees
387 | (cost=14.54..22.28 rows=2 width=592) (actual time=0.069..0.071 rows=4 loops=1)
388 | Recheck Cond: ((date_of_birth > '1082-01-01'::date)
389 | AND (date_of_birth < '1083-01-01'::date) AND (subsidiary_id = 50))
390 | Heap Blocks: exact=3
391 | -> Bitmap Index Scan on employees_date (cost=0.00..14.54 rows=2 width=0)
392 | (actual time=0.064..0.064 rows=4 loops=1)
393 | Index Cond: ((date_of_birth > '1082-01-01'::date)
394 | AND (date_of_birth < '1083-01-01'::date) AND (subsidiary_id = 50))
395 | Planning time: 0.365 ms
396 | Execution time: 0.099 ms
397 |
398 |
399 | Bitmap Heap Scan on employees
400 | (cost=4.34..19.58 rows=4 width=35) (actual time=0.045..0.051 rows=4 loops=1)
401 | Recheck Cond: ((subsidiary_id = 50) AND (date_of_birth > '1082-01-01'::date)
402 | AND (date_of_birth < '1083-01-01'::date))
403 | Heap Blocks: exact=3
404 | -> Bitmap Index Scan on employees_date (cost=0.00..4.34 rows=4 width=0)
405 | (actual time=0.037..0.037 rows=4 loops=1)
406 | Index Cond: ((subsidiary_id = 50) AND (date_of_birth > '1082-01-01'::date)
407 | AND (date_of_birth < '1083-01-01'::date))
408 | Planning time: 0.481 ms
409 | Execution time: 0.084 ms
410 |
411 | 412 | The child plan node visits an index to find the locations 413 | of rows matching the index condition, and then the upper plan node actually fetches those rows from the table 414 | itself. Fetching rows separately is much more expensive than reading them sequentially, but because not all the 415 | pages of the table have to be visited, this is still cheaper than a sequential scan. 416 | PostgreSQL manual 417 |418 |
423 | explain analyze SELECT first_name, last_name
424 | FROM employees WHERE last_name = 'LN 38'
425 |
426 |
427 | Index Scan using employees_last_name on employees
428 | (cost=0.42..8.44 rows=1 width=16)
429 | (actual time=0.049..0.049 rows=1 loops=1)
430 | Index Cond: (last_name = 'LN 38'::text)
431 | Planning time: 0.286 ms
432 | Execution time: 0.082 ms
433 |
434 |
439 | explain analyze SELECT first_name, last_name
440 | FROM employees WHERE upper(last_name) = 'LN 38'
441 |
442 |
443 | Seq Scan on employees
444 | (cost=0.00..2334.00 rows=500 width=16)
445 | (actual time=0.048..46.647 rows=1 loops=1)
446 | Filter: (upper(last_name) = 'LN 38'::text)
447 | Rows Removed by Filter: 99999
448 | Planning time: 0.092 ms
449 | Execution time: 46.672 ms
450 |
451 |
456 | create index employees_upper_last_name
457 | on employees(upper(last_name));
458 |
459 |
464 | explain analyze SELECT first_name, last_name
465 | FROM employees WHERE upper(last_name) = 'LN 38'
466 |
467 |
468 | Bitmap Heap Scan on employees
469 | (cost=12.29..775.05 rows=500 width=16)
470 | (actual time=0.070..0.070 rows=1 loops=1)
471 | Recheck Cond: (upper(last_name) = 'LN 38'::text)
472 | Heap Blocks: exact=1
473 | -> Bitmap Index Scan on employees_upper_last_name
474 | (cost=0.00..12.17 rows=500 width=0)
475 | (actual time=0.065..0.065 rows=1 loops=1)
476 | Index Cond: (upper(last_name) = 'LN 38'::text)
477 | Planning time: 0.360 ms
478 | Execution time: 0.091 ms
479 |
480 |
485 | explain analyze select * from employees where employee_id = 5;
486 |
487 |
488 | explain analyze select * from employees where employee_id + 1 = 6;
489 |
490 |
495 | explain analyze select * from employees where employee_id = 5;
496 |
497 |
498 | Index Scan using employees_pkey on employees (cost=0.29..8.31 rows=1 width=36) (actual time=0.014..0.015 rows=1 loops=1)
499 | Index Cond: (employee_id = 5)
500 | Planning time: 0.075 ms
501 | Execution time: 0.034 ms
502 |
503 |
504 | explain analyze select * from employees where employee_id + 1 = 6;
505 |
506 |
507 | Seq Scan on employees (cost=0.00..2434.00 rows=500 width=36) (actual time=0.014..15.385 rows=1 loops=1)
508 | Filter: ((employee_id + 1) = 6)
509 | Rows Removed by Filter: 99999
510 | Planning time: 0.066 ms
511 | Execution time: 15.407 ms
512 |
513 |
519 | create index enabled_employees on employees(last_name)
520 | where enabled = 't';
521 |
522 |
527 | int subsidiary_id;
528 | PreparedStatement command = connection.prepareStatement(
529 | "select first_name from employees where subsidiary_id = ?"
530 | );
531 | command.setInt(1, subsidiary_id);
532 |
533 | explain plan
explain plan
681 | SELECT first_name, last_name, subsidiary_id, phone_number
682 | FROM employees
683 | WHERE subsidiary_id = ?
684 | AND UPPER(last_name) LIKE '%INA%';
685 |
686 |
687 | --------------------------------------------------------------
688 | |Id | Operation | Name | Rows | Cost |
689 | --------------------------------------------------------------
690 | | 0 | SELECT STATEMENT | | 17 | 230 |
691 | |*1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 17 | 230 |
692 | |*2 | INDEX RANGE SCAN | EMPLOYEE_PK| 333 | 2 |
693 | --------------------------------------------------------------
694 | Predicate Information (identified by operation id):
695 | ---------------------------------------------------
696 | 1 - filter(UPPER("LAST_NAME") LIKE '%INA%')
697 | 2 - access("SUBSIDIARY_ID"=TO_NUMBER(:A))
698 |
699 |
704 | CREATE INDEX empsubupnam ON employees
705 | (subsidiary_id, UPPER(last_name));
706 |
707 |
712 | CREATE INDEX empsubupnam ON employees
713 | (subsidiary_id, UPPER(last_name));
714 |
715 |
716 | --------------------------------------------------------------
717 | |Id | Operation | Name | Rows | Cost |
718 | --------------------------------------------------------------
719 | | 0 | SELECT STATEMENT | | 17 | 20 |
720 | | 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 17 | 20 |
721 | |*2 | INDEX RANGE SCAN | EMPSUBUPNAM| 17 | 3 |
722 | --------------------------------------------------------------
723 | Predicate Information (identified by operation id):
724 | ---------------------------------------------------
725 | 2 - access("SUBSIDIARY_ID"=TO_NUMBER(:A))
726 | filter(UPPER("LAST_NAME") LIKE '%INA%')
727 |
728 |
740 | explain select subsidiary_id, last_name
741 | from employees where subsidiary_id < 5;
742 |
743 |
744 | Bitmap Heap Scan on employees
745 | (cost=100.20..1089.58 rows=5150 width=12)
746 | (actual time=1.102..3.429 rows=5000 loops=1)
747 | Recheck Cond: (subsidiary_id < 5)
748 | Heap Blocks: exact=925
749 | -> Bitmap Index Scan on employees_subsidiary_id_employee_id_idx
750 | (cost=0.00..98.92 rows=5150 width=0)
751 | (actual time=0.853..0.853 rows=5000 loops=1)
752 | Index Cond: (subsidiary_id < 5)
753 | Planning time: 0.178 ms
754 | Execution time: 3.867 ms
755 |
756 |
761 | CREATE INDEX empsubupnam ON employees
762 | (subsidiary_id, last_name);
763 |
764 |
769 | CREATE INDEX empsubupnam ON employees
770 | (subsidiary_id, last_name);
771 |
772 |
773 | explain select subsidiary_id, last_name
774 | from employees where subsidiary_id < 5;
775 |
776 |
777 | Index Only Scan using empsubupnam on employees
778 | (cost=0.42..170.54 rows=5150 width=12)
779 | (actual time=0.109..0.912 rows=5000 loops=1)
780 | Index Cond: (subsidiary_id < 5)
781 | Heap Fetches: 0
782 | Planning time: 0.091 ms
783 | Execution time: 1.244 ms
784 |
785 |
839 | explain analyze select * from sales order by sale_date;
840 |
841 |
842 | Sort (cost=122200.04..124450.04 rows=900000 width=24)
843 | (actual time=810.872..1007.881 rows=900000 loops=1)
844 | Sort Key: sale_date
845 | Sort Method: external merge Disk: 29912kB
846 | -> Seq Scan on sales
847 | (cost=0.00..14733.00 rows=900000 width=24)
848 | (actual time=0.006..113.583 rows=900000 loops=1)
849 | Planning time: 0.112 ms
850 | Execution time: 1074.673 ms
851 |
852 |
857 | create index sales_date on sales(sale_date);
858 |
859 |
860 | explain analyze select * from sales order by sale_date;
861 |
862 |
867 | create index sales_date on sales(sale_date);
868 |
869 |
870 | explain analyze select * from sales order by sale_date;
871 |
872 |
873 | Index Scan using sales_date on sales
874 | (cost=0.42..29120.43 rows=900000 width=24)
875 | (actual time=0.024..247.604 rows=900000 loops=1)
876 | Planning time: 0.118 ms
877 | Execution time: 315.568 ms
878 |
879 | TL;DR: cost is not always an accurate measure
906 |For a query that requires scanning a large fraction of the table, an explicit sort is likely to be faster than 907 | using an index because it requires less disk I/O due to following a sequential access pattern
908 |Cost can increase because clustering factor of the new index is worse
909 |
930 | select tablename, attname, correlation from pg_stats where
931 | schemaname = 'public';
932 |
933 |
934 | tablename | attname | correlation
935 | -----------+---------------+-------------
936 | employees | date_of_birth | 0.976956
937 | employees | phone_number | 0.801827
938 | employees | enabled | 0.935313
939 | sales | employee_id | 1
940 | sales | subsidiary_id | 0.0180426
941 | sales | sale_id | 1
942 | sales | amount | 1
943 | sales | sale_date | -1
944 | employees | employee_id | 0.976954
945 | employees | subsidiary_id | -0.022466
946 | employees | first_name | 0.801827
947 | employees | last_name | 0.801827
948 |
949 |
1007 | LIMIT { count | ALL }
1008 | OFFSET start
1009 |
1010 |
1011 | SQL:2008
1012 |
1013 | OFFSET start { ROW | ROWS }
1014 | FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY
1015 |
1016 |
1021 | SELECT * FROM sales ORDER BY sale_date DESC OFFSET 10
1022 | FETCH NEXT 10 ROWS ONLY;
1023 |
1024 |
1031 | SELECT * FROM sales WHERE sale_date < ?
1032 | ORDER BY sale_date DESC FETCH FIRST 10 ROWS ONLY;
1033 |
1034 |
1151 | PREPARE stmt(int) AS SELECT $1;
1152 |
1153 |
1158 | PREPARE stmt(int) AS SELECT $1;
1159 |
1160 |
1161 | EXPLAIN EXECUTE stmt(1);
1162 |
1163 |
1168 | PREPARE stmt(int) AS SELECT $1;
1169 |
1170 |
1171 | EXPLAIN EXECUTE stmt(1);
1172 |
1173 |
1174 | Result (cost=0.00..0.01 rows=1 width=0)
1175 |
1176 |
1185 | PREPARE stmt(int) AS SELECT $1;
1186 |
1187 |
1188 | EXPLAIN EXECUTE stmt(1);
1189 |
1190 |
1191 | Result (cost=0.00..0.01 rows=1 width=0)
1192 |
1193 |
1198 | DEALLOCATE stmt;
1199 |
1200 |
1205 | EXPLAIN ANALYZE EXECUTE stmt(1);
1206 |
1207 | Careful, It actually runs the query!
1208 |
1209 | EXPLAIN ANALYZE UPDATE EMPLOYEES
1210 | SET LAST_NAME = 'x' WHERE EMPLOYEE_ID = 1;
1211 |
1212 |
1213 | Update on employees (cost=0.29..8.31 rows=1 width=34)
1214 | (actual time=0.261..0.261 rows=0 loops=1)
1215 | -> Index Scan using employees_pkey on employees
1216 | (cost=0.29..8.31 rows=1 width=34)
1217 | (actual time=0.136..0.138 rows=1 loops=1)
1218 | Index Cond: (employee_id = 1)
1219 | Planning time: 0.095 ms
1220 | Execution time: 0.297 ms
1221 |
1222 | PostgreSQL execution plans do not show index access and filter predicates separately—both show up as “Index Cond”. That means the execution plan must be compared to the index definition to differentiate access predicates from index filter predicates.
1272 |
1277 | CREATE TABLE scale_data (
1278 | section NUMERIC NOT NULL,
1279 | id1 NUMERIC NOT NULL,
1280 | id2 NUMERIC NOT NULL
1281 | );
1282 | CREATE INDEX scale_data_key ON scale_data(section, id1);
1283 |
1284 |
1289 | PREPARE stmt(int) AS SELECT count(*)
1290 | FROM scale_data
1291 | WHERE section = 1
1292 | AND id2 = $1; EXPLAIN EXECUTE stmt(1);
1293 |
1294 |
1295 | Aggregate (cost=529346.31..529346.32 rows=1 width=0)
1296 | Output: count(*)
1297 | -> Index Scan using scale_data_key on scale_data
1298 | (cost=0.00..529338.83 rows=2989 width=0)
1299 | Index Cond: (scale_data.section = 1::numeric)
1300 | Filter: (scale_data.id2 = ($1)::numeric)
1301 |
1302 |
1307 | CREATE INDEX scale_slow
1308 | ON scale_data (section, id1, id2);
1309 |
1310 |
1311 | Aggregate (cost=14215.98..14215.99 rows=1 width=0)
1312 | Output: count(*)
1313 | -> Index Scan using scale_slow on scale_data
1314 | (cost=0.00..14208.51 rows=2989 width=0)
1315 | Index Cond: (section = 1::numeric AND id2 = ($1)::numeric)
1316 |
1317 | Slides & content available soon at juanignaciosl.github.io
1322 |Slides & content available soon at juanignaciosl.github.io
1327 |Use a spacebar or arrow keys to navigate
1333 |