|
453 | 453 | "cell_type": "markdown", |
454 | 454 | "id": "cell-best-practices", |
455 | 455 | "metadata": {}, |
456 | | - "source": [ |
457 | | - "## Best Practices\n", |
458 | | - "\n", |
459 | | - "### Keep Transactions Short\n", |
460 | | - "\n", |
461 | | - "Long-running transactions can:\n", |
462 | | - "- Lock database resources, blocking other users\n", |
463 | | - "- Increase the risk of deadlocks\n", |
464 | | - "- Consume server memory for maintaining transaction state\n", |
465 | | - "\n", |
466 | | - "```python\n", |
467 | | - "# Good: Short, focused transaction\n", |
468 | | - "with conn.transaction:\n", |
469 | | - " Table.insert(prepared_data)\n", |
470 | | - "\n", |
471 | | - "# Bad: Long transaction with computation inside\n", |
472 | | - "with conn.transaction:\n", |
473 | | - " data = fetch_from_external_api() # Could take minutes!\n", |
474 | | - " processed = heavy_computation(data) # Even longer!\n", |
475 | | - " Table.insert(processed)\n", |
476 | | - "```\n", |
477 | | - "\n", |
478 | | - "**Better approach:** Do computation outside the transaction, then insert:\n", |
479 | | - "\n", |
480 | | - "```python\n", |
481 | | - "# Prepare data outside transaction\n", |
482 | | - "data = fetch_from_external_api()\n", |
483 | | - "processed = heavy_computation(data)\n", |
484 | | - "\n", |
485 | | - "# Quick transaction for database operations only\n", |
486 | | - "with conn.transaction:\n", |
487 | | - " Table.insert(processed)\n", |
488 | | - "```\n", |
489 | | - "\n", |
490 | | - "### Avoid User Interaction Inside Transactions\n", |
491 | | - "\n", |
492 | | - "Never wait for user input while holding a transaction open:\n", |
493 | | - "\n", |
494 | | - "```python\n", |
495 | | - "# Bad: User could take coffee break!\n", |
496 | | - "with conn.transaction:\n", |
497 | | - " Table.insert(data)\n", |
498 | | - " confirm = input(\"Confirm? (y/n)\") # DON'T DO THIS\n", |
499 | | - " if confirm != 'y':\n", |
500 | | - " raise Exception(\"Cancelled\")\n", |
501 | | - "```\n", |
502 | | - "\n", |
503 | | - "### Handle Deadlocks Gracefully\n", |
504 | | - "\n", |
505 | | - "When multiple transactions compete for the same resources, deadlocks can occur.\n", |
506 | | - "MySQL automatically detects and resolves deadlocks by rolling back one transaction.\n", |
507 | | - "\n", |
508 | | - "```python\n", |
509 | | - "import time\n", |
510 | | - "\n", |
511 | | - "def insert_with_retry(data, max_retries=3):\n", |
512 | | - " \"\"\"Insert data with deadlock retry logic.\"\"\"\n", |
513 | | - " for attempt in range(max_retries):\n", |
514 | | - " try:\n", |
515 | | - " with dj.conn().transaction:\n", |
516 | | - " Table.insert(data)\n", |
517 | | - " return # Success\n", |
518 | | - " except Exception as e:\n", |
519 | | - " if \"Deadlock\" in str(e) and attempt < max_retries - 1:\n", |
520 | | - " time.sleep(0.1 * (2 ** attempt)) # Exponential backoff\n", |
521 | | - " continue\n", |
522 | | - " raise\n", |
523 | | - "```\n", |
524 | | - "\n", |
525 | | - "### Use Transactions for Related Inserts\n", |
526 | | - "\n", |
527 | | - "When inserting data that spans multiple tables and must be consistent:\n", |
528 | | - "\n", |
529 | | - "```python\n", |
530 | | - "# Insert related data atomically\n", |
531 | | - "with dj.conn().transaction:\n", |
532 | | - " Subject.insert1(subject_data)\n", |
533 | | - " Session.insert(session_data) # References Subject\n", |
534 | | - " Recording.insert(recording_data) # References Session\n", |
535 | | - "```" |
536 | | - ] |
| 456 | + "source": "## Best Practices\n\n### When to Use Explicit Transactions\n\nA single `insert()` or `update1()` call is already atomic—it either fully succeeds or fully fails.\nExplicit transactions using `conn.transaction` are only needed when **multiple operations** must succeed or fail together:\n\n```python\n# No need for explicit transaction - single insert is already atomic\nTable.insert(data)\n\n# Explicit transaction needed - multiple operations must be atomic\nwith dj.conn().transaction:\n Table1.insert(data1)\n Table2.insert(data2)\n Table3.update1(data3)\n```\n\n### Transactions and Long Computations\n\nWhen computations take a long time, there's a tension between keeping transactions short (to avoid blocking) and maintaining data integrity (ensuring inputs haven't changed).\n\nDataJoint's `make()` method handles this through a three-part pattern that refetches and verifies input data inside the transaction.\nSee {doc}`055-make` for details on implementing safe computations.\n\n### Avoid User Interaction Inside Transactions\n\nNever wait for user input while holding a transaction open:\n\n```python\n# Bad: User could take coffee break!\nwith conn.transaction:\n Table.insert(data)\n confirm = input(\"Confirm? (y/n)\") # DON'T DO THIS\n if confirm != 'y':\n raise Exception(\"Cancelled\")\n```\n\n### Handle Deadlocks Gracefully\n\nWhen multiple transactions compete for the same resources, deadlocks can occur.\nMySQL automatically detects and resolves deadlocks by rolling back one transaction.\n\n```python\nimport time\n\ndef safe_operation(max_retries=3):\n \"\"\"Execute operations with deadlock retry logic.\"\"\"\n for attempt in range(max_retries):\n try:\n with dj.conn().transaction:\n Table1.insert(data1)\n Table2.insert(data2)\n return # Success\n except Exception as e:\n if \"Deadlock\" in str(e) and attempt < max_retries - 1:\n time.sleep(0.1 * (2 ** attempt)) # Exponential backoff\n continue\n raise\n```" |
537 | 457 | }, |
538 | 458 | { |
539 | 459 | "cell_type": "markdown", |
540 | 460 | "id": "cell-summary", |
541 | 461 | "metadata": {}, |
542 | | - "source": [ |
543 | | - "## Summary\n", |
544 | | - "\n", |
545 | | - "Transactions are fundamental to maintaining data integrity in DataJoint:\n", |
546 | | - "\n", |
547 | | - "| Feature | Description |\n", |
548 | | - "|---------|-------------|\n", |
549 | | - "| `conn.transaction` | Context manager for explicit transactions |\n", |
550 | | - "| Automatic rollback | On exception, all changes are undone |\n", |
551 | | - "| `make()` wrapping | Computed table computations are automatically transactional |\n", |
552 | | - "| Master-part atomicity | Insert master and parts together safely |\n", |
553 | | - "\n", |
554 | | - "**Key takeaways:**\n", |
555 | | - "1. Use transactions when multiple operations must succeed or fail together\n", |
556 | | - "2. Keep transactions short to avoid blocking other users\n", |
557 | | - "3. DataJoint's `populate()` automatically handles transactions for you\n", |
558 | | - "4. Master-part relationships provide built-in transactional semantics" |
559 | | - ] |
| 462 | + "source": "## Summary\n\nTransactions are fundamental to maintaining data integrity in DataJoint:\n\n| Feature | Description |\n|---------|-------------|\n| Single operations | Already atomic—no explicit transaction needed |\n| `conn.transaction` | Context manager for grouping multiple operations |\n| Automatic rollback | On exception, all changes are undone |\n| `make()` wrapping | Computed table computations are automatically transactional |\n| Master-part atomicity | Insert master and parts together safely |\n\n**Key takeaways:**\n1. Use explicit transactions only when multiple operations must succeed or fail together\n2. Single inserts and updates are already atomic\n3. DataJoint's `populate()` automatically handles transactions for you\n4. Master-part relationships provide built-in transactional semantics\n5. For long computations, see {doc}`055-make` for safe patterns" |
560 | 463 | } |
561 | 464 | ], |
562 | 465 | "metadata": { |
|
0 commit comments